var $ = (id)=> { return document.getElementById(id); } var WEB = { async api(path,data) { if(!path) return {}; var auth_id = LS.get("auth_id"); data = data||{}; if(!data.input) data.input = {} if(auth_id) data.input.auth_id = auth_id; var headers = new Headers({ "Content-Type": "application/json" }); var request = JSON.stringify(data) var options = { method: 'POST', body: (request), headers: headers } var res = await fetch(path,options); var result = {}; var error = null; var res_text = await res.text(); var test = UTIL.isJson(res_text); if(!test.valid) { result = {}; result.error = error result.text = res_text; } else { result = test?.output; var error = result?.error_code; if(error) { LS.remove("auth"); if(MAIN) { MAIN.processClear(); } } } return result?.data||{}; }, } MODEL = {}; LOADED = {}; var UTIL= { genHash(length) { length = length || 10; var chars = "0123456789abcdefghijklmnopqrstuvwxyz"; var res = ''; for (var i = 0; i < length; i++) { res += chars.charAt(Math.random()*chars.length-1); } return res; }, getLength(input) { if(!input) return 0; switch(typeof input) { case "object": return Object.keys(input).length; case "array": case "string": return input.length; case "number": return input; } }, loop(array) { if(!array) return []; switch(typeof array) { case "object": return Object.values(array); case "array": return items; case "number": return input; } }, loop2(array,callback) { if(!array) return 0; switch(typeof array) { case "object": return Object.keys(array).forEach((key)=>callback(array[key],key)); case "array": return array.forEach(callback); case "number": return Array.from({ length: array }, (_, i) => callback(i)); } }, millis() { return new Date().getTime() }, getTimeString(date) { // if(typeof date != "date") date = new Date(); var d = date; var hours = ("0" + d.getHours()).slice(-2); var mins = ("0" + d.getMinutes()).slice(-2); var secs = ("0" + d.getSeconds()).slice(-2); return hours + ":" + mins + ":" +secs; }, getDaysOfMonth(date) { var cur_date = new Date(); if(typeof date == "date") cur_date = date; var date_ = new Date(cur_date.getFullYear(),cur_date.getMonth()+1,0); return date_.getDate(); }, getDateTimeString(date) { return this.getDateString(date)+" "+this.getTimeString(date); }, getDateString(date) { const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; return (date || new Date()).toLocaleDateString('en-CA', options); }, getDayOfWeek(date) { if(typeof date != "date") date = new Date(); return date.toLocaleString("en-US",{weekday:'long'}) }, filterObj(obj,...keys) { if(!obj || UTIL.Empty(obj)) return {}; var clone = UTIL.deepCopy(obj); var output = {}; if(keys) { for(var key in keys) { var obj_key = keys[key]; output[obj_key] = clone[obj_key] } } return output; }, deepCopy(inObj) { var outObj, value, key; if(!this.isObject(inObj))return inObj; outObj = Array.isArray(inObj) ? [] : {} UTIL.loop2(inObj,(v,k)=>outObj[k] = UTIL.deepCopy(v)) return outObj }, Empty(obj) { if(!obj)return true; if(obj instanceof Blob ) return obj.size===0; if(typeof obj === "object" && Object.keys(obj).length === 0) return true; if(typeof obj === "array" && obj.length === 0) return true; return false; }, getKeys(obj) { if(!obj)return []; if(typeof obj === "object") return Object.keys(obj); if(typeof obj === "array") return obj.keys(); }, deepMerge(target, source) { let output = Object.assign({}, target); if (UTIL.isObject(target) && UTIL.isObject(source)) { Object.keys(source).forEach(key => { if (UTIL.isObject(source[key])) { if (!(key in target)) Object.assign(output, { [key]: source[key] }); else output[key] = UTIL.deepMerge(target[key], source[key]); } else { var val = source[key]; if(val!=null) { Object.assign(output, { [key]: val}); } else delete output[key]; } }); } return output; }, hasKeys(obj,keys) { var res = false; if(obj) { keys = keys?keys:[] res = true; for(var k in keys) { var key = keys[k]; if(!obj[key]) res = false; } } return res; }, isNumber(evt) { evt = (evt) ? evt : window.event; if(!evt)return true var charCode = (evt.which) ? evt.which : evt.keyCode; return this.isNum(charCode); }, isNum(charCode) { if ((charCode > 31 && (charCode < 48 || charCode > 57)) && charCode !== 46) { evt.preventDefault(); } else { return true; } }, isEqual(a, b) { if(UTIL.Empty(a) && UTIL.Empty(b))return true; if((this.isObject(a) && this.isObject(b)) || (this.isArray(a) && this.isArray(b))) { if(this.getLength(a) != this.getLength(b)) return false; for(var key in a) { var res = this.isEqual(a[key],b[key]); if(!res) return false; } return true; } else return a==b return false; }, isObject(obj) { return obj && obj.constructor === Object; }, isArray(arr) { return arr && arr.constructor === Array; }, isFunc(struct) { return (struct && typeof struct == "function"); }, async fetchText (path) { var result =""; if(path) { var res = await fetch(path); if(res.ok) result = (res.ok)? await res.text() : ""; } return result; }, showFile(blob) { console.log(blob); var newBlob = new Blob([blob], {type: "application/pdf"}) var data = URL.createObjectURL(newBlob) window.open(data) }, replaceAll(formula,key,value) { value = value||0; return formula.split(key).join(value); }, async fetchJson(url) { var options = {}; options.mode = "cors"; var result = await fetch(this.host+url,options); if (result?.headers.get("Content-Type") == "application/json") { return await result.json(); } return null; }, csvToJSon(csv) { if(!csv) { console.warn("csv is null or empty"); return {}; } var lines = csv.split("\n"); var res = []; if (lines && lines.length > 0) { var headers = lines[0].split(","); for (var i = 1; i < lines.length; i++) { var obj = {}; var currentline = lines[i].split(","); for (var j = 0; j < headers.length; j++) { var key = headers[j].replace(/['"]+/g, '') var value = currentline[j].replace(/['"]+/g, '') obj[key] = value; } res.push(obj); } } return res; }, jsonToCSV(array) { if(!UTIL.isArray(array)) { var new_array = []; if(UTIL.isObject(array)) { var new_array = []; var cols = null; for(var k in array) { var row = array[k]; if(!cols) { cols = []; for(var r_k in row) { cols.push(r_k); } new_array.push(cols); } var data_row = []; for(var c_k in cols) { var col = cols[c_k]; var cell = row[col]; data_row.push(cell||""); } new_array.push(data_row); } } array = new_array; } var str = ''; for (var k in array) { var row = array[k] str+= row.join(); str+= "\n" } return str; }, exportCSV(name,data) { var date = new Date().setTime(); var filename = name||'export_'+date+'.csv'; var charset = "utf-8"; var blob = new Blob([data], {type: "text/csv;charset="+ charset + ";"}); if (window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveBlob(blob, filename); } else { var downloadLink = window.document.createElement("a"); downloadLink.setAttribute('href', window.URL.createObjectURL(blob)); downloadLink.setAttribute('download', filename); downloadLink.click(); } }, generateRandomHash(length = 10) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; }, decodeHash(hash) { result = {}; if(!hash) hash = location.hash; if(hash) { var query = hash.substr(1) if(query) { try { var query = decodeURIComponent(query); var query_obj = query.split("&"); if(query_obj.length>0) { var props = {}; for(var kv of query_obj) { var entry = kv.split("="); if(entry[0]=="comp") result.comp = entry[1] ; else props[entry[0]] = entry[1] } result.props = props; } } catch (error) { console.log("ERROR",error); } } } return result; }, toNavHash(data) { var hash = "#"; if(UTIL.isObject(data)) { var json = JSON.stringify(data); // hash += btoa(json); hash+=json } return hash }, toCurrency(val,currency) { currency = currency||"R" return currency + (val.toFixed(2)); }, isBetween(val,a,b) { if(typeof val == "number" && typeof a == "number" && typeof b == "number") { return val>=a && val<=b; } return false; }, sum(input) { var total = 0; if (toString.call(input) !== "[object Array]") return total; for(var i=0;i max ? max : value; } return 0 }, isJson(text) { var res = {}; try { res.text = text; res.output = JSON.parse(text); } catch (e) { res.valid = false; res.error = e } res.valid = true; return res; }, capitalize(s) { return s && s.length>0 && s[0].toUpperCase() + s.slice(1); }, sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, includeJs(jsFilePath) { var script = document.createElement("script"); script.type = "text/javascript"; script.src = jsFilePath; script.async = false; document.body.appendChild(script); }, toRand(val) { return "R"+ Number(val||0).toFixed(2) }, download(data, fileName, type="text/plain") { // Create an invisible A element const a = document.createElement("a"); // Set the HREF to a Blob representation of the data to be downloaded var dat = atob(data); var dat2 = this.strToArray(dat); var blob = new Blob([dat2], { type:type }); var href = window.URL.createObjectURL(blob); a.href = href; // Use download attribute to set set desired file name a.setAttribute("download", fileName); // Trigger the download by simulating click a.click(); // Cleanup window.URL.revokeObjectURL(a.href); }, strToArray(bstr) { var result = new Uint8Array(bstr.length); for(var i=0;i console.log('Service worker registered')) .catch(error => console.error('Service worker registration failed:', error)); } }, escapeInvalidChars(text) { return text .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\r\n/g, '\\r\\n'); }, getVal(obj, path) { if (!path) return obj; var keys = path.split('.'); return keys.reduce((acc, currentKey) => { return acc && acc[currentKey] !== undefined ? acc[currentKey] : null; }, obj); } } LS = { pre:"", get(k) { var val = localStorage.getItem(this.pre+"_"+k); return val !="undefined"?val:null; }, set(k,v) { localStorage.setItem(this.pre+"_"+k, v?v:""); }, remove(k) { localStorage.removeItem(this.pre+"_"+k); } } var ROUTER = { base:"", setDefault(base) { this.base = base; }, setHomePath(path) { this.path = path; }, navigate(path, props = {}) { const basePath = this.base.replace(/\/$/, ""); // Ensure no trailing slash var cleanPath = path.startsWith('/') ? path : '/' + path; var fullPath = basePath + cleanPath; if (this.path && path == this.path) { history.replaceState(props, '', fullPath); } else if (location.pathname !== fullPath) { history.pushState(props, '', fullPath); // CRITICAL: Trigger the event so Vue components know the path changed window.dispatchEvent(new PopStateEvent('popstate')); } }, decodePath() { const basePath = this.base; var path = location.pathname; if (path.startsWith(basePath)) { path = path.substring(basePath.length); } path = path.replace(/^\//, ""); // Remove leading / path = path.replace(/\/$/, ""); // Remove trailing / var parts = path.split('/'); return parts; }, decodeQuery() { var query = location.search.substr(1); // Remove the '?' var result = {}; query.split("&").forEach(function(part) { var item = part.split("="); var key = decodeURIComponent(item[0]); var val = decodeURIComponent(item[1] || ""); if(key&&val) result[key] = val; }); return result; } } Vue.prototype.$vmodel = function(vn) { this[vn] = this.value; this.$watch('value',(nv, ov)=>{ if(nv!=ov) this[vn] = nv;}); this.$watch(vn,(nv, ov)=>{ if(nv!=ov) this.$emit("input",nv);}); } var COMP = { loaded:{}, css:{}, base: { props:["value"], data(){return {val:{}}}, created() { this.$vmodel("val");}, }, initMain() { this.vue = new Vue ({ mixins:[{methods:UTIL}], el:"#main", template: '', data: { width:0, comp: null, props: {}, }, created() { this.loadCoreComp(); window.onpopstate = ()=> this.$emit("onPathChange", ROUTER.decodePath()); window.onresize = ()=> this.updateWidth() }, mounted() { this.$emit("onPathChange", ROUTER.decodePath()); this.updateWidth() }, computed: { onMobile() { var isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); var isTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; var mobile = isMobileDevice || isTouchScreen; // Use the reactive 'this.width' property var w = this.width; var h = window.innerHeight; var ratio = w/h var res = mobile || (w <= 1270 && ratio<1); return res; }, }, methods: { loadCoreComp() { var el = document.querySelector(this.$options.el); if (el) { this.comp = 'core-b-comp'; this.props = { start_comp: el.getAttribute('start_comp'), default_tab: el.getAttribute('default_tab') }; } else console.error("Root element '#main' not found."); }, updateWidth() { this.width = window.innerWidth; }, }, }); Vue.directive('DynamicEvents', { bind: (el, binding, vnode) => { const allEvents = binding.value; if(allEvents) { Object.keys(allEvents).forEach((event) => { vnode.componentInstance.$on(event, (eventData) => { const targetEvent = allEvents[event]; if(typeof targetEvent == "function") targetEvent(eventData) }); }); } }, unbind: function (el, binding, vnode) { vnode.componentInstance.$off(); }, }); }, // Load(comp,comp_path,props,events) // { // if(!comp) // { // console.info("No Comp"); // return; // } // if(!comp_path) // { // console.info("No Path"); // return; // } // comp.comp = ""; // comp.comp_props = {}; // comp.events = {}; // if(comp_path.substr(0,1)=="/") comp_path = comp_path.replace(/\//g,"-"); // comp.comp = comp_path; // comp.comp_props = props; // if(!UTIL.Empty(events))comp.events = events; // }, async externLoadComponent (resolv, reject, tag) { var path = "./comp.php?comp="+tag; try { if(COMP) { var json = COMP.loaded[tag]; var style = COMP.css[tag]; if(!json) { var response = await fetch(path); json = await response.text(); var comp = {}; if(json) { COMP.loaded[tag] = json try { eval(json); } catch (error) { console.log(error); console.log(path,tag); resolv(comp) } if(template) comp.template = template; if(newStyle) { if(!style) { COMP.css[tag] = newStyle; const head = document.head; const styleId = tag; const styleTag = document.getElementById(styleId); if (styleTag) { styleTag.innerHTML += newStyle; } else { const newStyleTag = document.createElement('style'); newStyleTag.id = styleId; newStyleTag.innerHTML = newStyle; head.appendChild(newStyleTag); } } } if(!comp.methods) comp.methods = {}; if(comp.data && typeof(comp.data)=='function') comp.methods.__data = comp.data; // create a backup of the original data function comp.data = function() { var d = this.__data ? this.__data() : {}; d.tag = tag; return d }; resolv(comp) } else console.log("No Json"); } else console.log("No Json"); } } catch (error) { console.log("tag error",tag,error); reject(error) } }, } COMP.initMain(); var MENU= { mixins:[COMP.base], methods: { buildTab(name,tag,svg,cls,onClick) { return{ name, tag, svg, cls, onClick } }, }, } var FORM= { mixins:[COMP.base], props:["btns_show"], methods: { setFieldCls(field,label_cls,field_cls) { var field = this.fields[field]; if(label_cls)field.label_cls = label_cls; if(field_cls)field.field_cls = field_cls; }, buildField(label,type,readonly) { return{ label:label, type:type||"text", readonly:readonly } }, buildOption(label,options,default_,onSelect) { return{ label:label, type:"option", options:options, default:default_, onSelect:onSelect?onSelect:()=>{} } }, buildImage(label,cls) { return {label,type:"image",col_cls:cls} }, buildFunction(label,cls_header,cls,func) { return {label:label,type:"function",cls_header:cls_header,cls,func:func} }, buildToggle(label,on,off,cls) { return { label,type:"toggle",cls,on,off } }, loadBtns() { console.log("SET BTNS"); var btns = {} if(!this.readonly) { if(this.val?.id) btns.delete = { text:"Delete", onClick:this.delete } btns.save = { text:"Save", onClick:this.save, }; } this.btns = btns; }, async save() { console.log("SET UP SAVE"); }, async delete() { console.log("SET UP DELETE"); } }, } var LIST = { mixins:[COMP.base], watch: { parent: { deep:true, handler(vn,vo) { if(!UTIL.isEqual(vn,vo)) this.requestFetch(); } }, }, data() { return { path:"", search:"", fields:{}, rows:{}, action:null } }, created() { // if(!this.path) console.log("no path to api"); }, methods: { async load(input) { // var rows_pre = UTIL.deepCopy(this.rows); if( !UTIL.Empty(this.parent)) { // var type = this.parent.type; // this.path = "./d/api/"+type+".php"; input.parent = this.parent; } if( !UTIL.Empty(this.child)) { input.child = this.child; } var req = { action: this.action, input: input } var rows= await WEB.api(this.path,req); if(this.$refs.list) { if(this.$refs.list.can_lock) for (var k in rows) { var row = rows[k]; // console.log(row.lock); if(!row.lock) row.lock=false; } } this.rows = rows; }, onSelect() { console.log("please add a onSelect method"); }, select(row) { this.$emit("onSelect",row); }, requestFetch() { if(this.$refs.list) this.$refs.list.requestFetch(); }, buildIputText(label,cls) { return {label:label,type:"input_text",cls_header:cls} }, buildImage(label,cls) { return {label:label,type:"image",col_cls:cls} }, buildText(label,cls) { return {label:label,col_cls:cls} }, buildSvg(label,path,cls) { // console.log( {label,type:"svg",path,col_cls:cls}); return {label,type:"svg",path,col_cls:cls} }, buildIputNumber(label,cls,func) { return {label:label,type:"input_number",cls_header:cls,func:func} }, buildFunction(label,cls,func) { return {label:label,type:"function",cls_header:cls,func:func} }, buildTypeOptions(label,options,cls) { return { label:label, type:"option", options:options, cls_header:cls } }, }, } var TREE = { props:["parent","entity","isRoot","isLast","tree_root"], watch: { entity: { deep:true, handler(val) { this.load(); } } }, created() { this.load(); }, mounted() { // this.setRootSub(); }, data() { return { subs: {}, isExpanded:false, } }, methods: { async load() { console.log("ADD LOAD METHOD"); }, option() { console.log("ADD OPTION METHOD"); }, } } var FILE = { loaded :{}, async save(file,account_id) { var res = {}; if(file.data) { file.name = file.name?file.name:UTIL.genHash(6); var path_file = "./d/api/file.php"; var res_file = { action:"save", input: { name:file.name, data:file.data, account_id:account_id } } if(file.id)res_file.input.id = file.id; console.log(res_file); res = await WEB.api(path_file,res_file); } return res; }, async get(image) { // var image_id = image?.id; // var image_size = image?.size||"<300"; // var res = FILE.loaded[image_id+"_"+image_size]; // if(!res) // { var path = "./d/api/file.php"; var res_file = { action:"get", input: image } res = await WEB.api(path,res_file); // console.log(res,"res_file"); // FILE.loaded[image_id+"_"+image_size]=res; // } return res; }, async toCsv(text) { var path = "./d/api/csv.php"; var req = { action:"getArrayFromCSV", input: { data:text } } return awaitWEB.api(path,req, true); } }