1st commit
This commit is contained in:
513
wco/apx/apx.js
Normal file
513
wco/apx/apx.js
Normal file
@@ -0,0 +1,513 @@
|
||||
/*eslint no-undef:0*/
|
||||
/*eslint-env browser*/
|
||||
|
||||
"use strict";
|
||||
var apx = apx || {};
|
||||
|
||||
/**************************************************************************
|
||||
* apx.js manage data to interact with an apxtri instance from a webpage *
|
||||
* component can be add with name-wco
|
||||
*************************************************************************
|
||||
* This code is not minify and target to be understood by a maximum of
|
||||
* curious people to audit and give any feedback to support@ndda.fr
|
||||
*
|
||||
* To audit it in a browser like chrome:
|
||||
* - open web developpement (F12)
|
||||
* - Menu Sources: apx.js , see below for more information
|
||||
* - : apxid.js, this is the authentification module that works as a microservice
|
||||
*
|
||||
*
|
||||
* Main principle:
|
||||
* - Usage of localStorage to store any usefull data and save it between session
|
||||
* - All data and template can be download from an apxtri
|
||||
* get /api/apxtri/wwws/updatelocaldb{ano}/{tribe}/{xapp}/{pagename}/{version}
|
||||
* ano = empty if authenticated anonymous if not
|
||||
* tribe = the tribe where tha xapp is strore
|
||||
* xapp = the folder name in /tribe/xapp/
|
||||
* pagename = the name of the page without _xx (language)
|
||||
* version = 0 or the version currently store (version is to manage cach)
|
||||
*
|
||||
* ------------------
|
||||
* ? State management
|
||||
* ------------------
|
||||
* html page must have apxtri with at least headers key
|
||||
*
|
||||
<script>
|
||||
const apxtri={
|
||||
version:0, // 0 mean it is a first visit if you force to 0 then it will reload all app data
|
||||
pagename:"pagename", // without _lg.html ex:pageadmin
|
||||
pageauth:"apxid" local page with authentification without _lg,
|
||||
headers:{
|
||||
xalias:"anonymous",
|
||||
xhash:"anonymous",
|
||||
xtribe:"tribeId",
|
||||
xapp:"app name",
|
||||
xlang:"en",
|
||||
xdays:0,
|
||||
xuuid:"0",
|
||||
xtrkversion:1}
|
||||
}
|
||||
wco:{}
|
||||
//specific init data for this webpage
|
||||
} ;
|
||||
</script>
|
||||
* ++++++++
|
||||
* ? apx.ready(callback) await DOM is ready equivalent of jquery Document.ready()
|
||||
* +++++++++
|
||||
* ? apx.readyafterupdate(callback) allow to add function callback that will be load after apx.ready to add a function to execute apx.readyafterupdate(functname) without () after functname
|
||||
* +++++++++
|
||||
* ? apx.listendatawco(newpropertie) allow to store data in apx.data.wco and to listen any change to update HTML DOM content <tag data-wco="propertie"></tag> if apx.data.wco.propertie value change then it change every where data-wco=propertie exist innerHTML, textContent, class, ...value
|
||||
* to add a new propertie to listen just add it apx.data.wco.propertie={innerHTML:"<p>test</p>"} and run apx.listendatawco(propertie)
|
||||
* +++++++++
|
||||
* ? apx.notification(selector, data) insert in tag selector an apx return call into data{status:xxx,ref:"Ref",msg:"key",data:{}}, it use apx.data.ref[data.ref].msg as template and render it with data
|
||||
* +++++++++
|
||||
* ? apx.save() save apx.data as {xapp} value in localStorage to be able for the same domain to share data
|
||||
* +++++++++
|
||||
* ? apx.update() :
|
||||
* Run at least once after loading a pagename, what it does:
|
||||
* - url/page.html?k1=v1&k2=v2#h1=v4&h2=v5 => store in apx.pagecontext = { search: {k1:v1,k2:v2}, hash: {h1:v4,h2:v5} };
|
||||
* - updatelocaldb from api to localstorage name xapp (options / ref / schema / tpl /tpldata) store in {tribe}/wwws/{xapp}.json with key {pagename}
|
||||
* - run apx.listendatawco() to allow any apx.data.wco variable to update DOM
|
||||
* - run any function that were store in apx.afterupdate.
|
||||
* - run apx.lazyload() to load image in background (this is to decrease time loading for search engine)
|
||||
* ++++++++++
|
||||
* */
|
||||
|
||||
apx.ready = (callback) => {
|
||||
if (!callback) {
|
||||
alert(
|
||||
"You have an unknown callback apx.ready(callback), you need to order your code callback = ()=>{} then apx.ready(callback), boring but js rules ;-)"
|
||||
);
|
||||
}
|
||||
if (document.readyState != "loading") callback();
|
||||
// modern browsers
|
||||
else if (document.addEventListener)
|
||||
document.addEventListener("DOMContentLoaded", callback);
|
||||
// IE <= 8
|
||||
else
|
||||
document.attachEvent("onreadystatechange", function () {
|
||||
if (document.readyState == "complete") callback();
|
||||
});
|
||||
};
|
||||
|
||||
apx.readyafterupdate = (callback) => {
|
||||
if (!apx.afterupdate) apx.afterupdate = [];
|
||||
apx.afterupdate.push(callback);
|
||||
};
|
||||
|
||||
apx.lazyload = () => {
|
||||
document.querySelectorAll("img[data-lazysrc]").forEach((e) => {
|
||||
// in src/page.html: src contain original img and data-lazysrc='true'
|
||||
// in dist/page.html src is removed img src is in data-lazysrc=newimage.webp or svg
|
||||
let src = e.getAttribute("src")
|
||||
? e.getAttribute("src")
|
||||
: e.getAttribute("data-lazysrc");
|
||||
if (e.getAttribute("data-trksrckey")) {
|
||||
src = `/trk/${src}?alias=${apx.data.headers.xalias}&uuid=${
|
||||
apx.data.headers.xuuid
|
||||
}&srckey=${e.getAttribute("data-trksrckey")}&version=${
|
||||
apx.data.headers.xtrkversion
|
||||
}&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
|
||||
apx.data.headers.xlang
|
||||
}`;
|
||||
}
|
||||
e.setAttribute("src", src);
|
||||
console.log("lazyload track:", src);
|
||||
e.removeAttribute("data-lazysrc");
|
||||
});
|
||||
document.querySelectorAll("[data-lazybgsrc]").forEach((e) => {
|
||||
e.style.backgroundImage = `url(${e.getAttribute("src")})`;
|
||||
e.removeAttribute("data-lazybgsrc");
|
||||
});
|
||||
document.querySelectorAll("a[data-trksrckey]").forEach((e) => {
|
||||
let urldestin = e.getAttribute("href");
|
||||
if (
|
||||
urldestin.substring(0, 4) != "http" &&
|
||||
urldestin.substring(0, 1) != "/"
|
||||
) {
|
||||
urldestin = "/" + urldestin;
|
||||
}
|
||||
const hreftrack = `/trk/redirect?alias=${apx.data.headers.xalias}&uuid=${
|
||||
apx.data.headers.xuuid
|
||||
}&srckey=${e.getAttribute("data-trksrckey")}&version=${
|
||||
apx.data.headers.xtrkversion
|
||||
}&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
|
||||
apx.data.headers.xlang
|
||||
}&url=${urldestin}`;
|
||||
console.log("href track:", hreftrack);
|
||||
e.setAttribute("href", hreftrack);
|
||||
e.removeAttribute("data-trksrckey");
|
||||
});
|
||||
};
|
||||
apx.notification = (selector, data, clearbefore) => {
|
||||
/**
|
||||
* @selector a text to use querySelector() in document
|
||||
* @data apxtri return from any request {status:200,ref:"",msg:"key", data }
|
||||
* if {status,multimsg:[{ref,msg,data}]} then a multi feedback are in
|
||||
* @clearbefore boolean if true then it remove the previous message
|
||||
* @return update the dom selctor with the relevant render message in the relevant language
|
||||
*/
|
||||
console.log("notification of ", data);
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) {
|
||||
console.log(
|
||||
`WARNING !!! check apx.notification selector:${selector} does not exist in this page`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (clearbefore) el.innerHTML = "";
|
||||
const multimsg = data.multimsg ? data.multimsg : [data];
|
||||
multimsg.forEach((info) => {
|
||||
if (!apx.data.ref[info.ref]) {
|
||||
console.log(
|
||||
`check apx.data.ref, ${info.ref} does not exist in this page, add it `
|
||||
);
|
||||
return false;
|
||||
} else if (!apx.data.ref[info.ref][info.msg]) {
|
||||
console.log(
|
||||
`check apx.data.ref.${info.ref} does not contain ${info.msg} update /schema/lg or /model/lg`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
el.innerHTML +=
|
||||
" " + Mustache.render(apx.data.ref[info.ref][info.msg], info.data);
|
||||
if (data.status == 200) {
|
||||
el.classList.remove("text-red");
|
||||
el.classList.add("text-green");
|
||||
} else {
|
||||
el.classList.add("text-red");
|
||||
el.classList.remove("text-green");
|
||||
}
|
||||
});
|
||||
};
|
||||
apx.listendatawco = (newpropertie) => {
|
||||
// listen any change in apx.wco.newpropertie and update interface with this new value
|
||||
// < data-wco="propertie of apx.wco"> is updated with content text, html any attribute in this new value
|
||||
// to init run apx.listendatawco() this is done by apx.update()
|
||||
// to dynamicaly add a new propertie to listen just run apx.listendatawco(propertietoadd);
|
||||
// Then manage your data by modifying apx.wco and it will be update anywhere it use in the webpage
|
||||
//example:
|
||||
// <img data-wco="logodetoto" src="urltoimage" "alt">
|
||||
// apx.wco.logodetoto={src:"newurltoimage",alt:"newalt"} then it will change every where data-wco=logodetoto
|
||||
//
|
||||
// <p data-wco="claim">Blabla</p>
|
||||
// apx.wco.claim={html:"newblabla"}
|
||||
console.log("From apx.data.wco:", apx.data.wco);
|
||||
if (!apx.wco) apx.wco = {};
|
||||
console.log(
|
||||
"wco dynamic into the webpage",
|
||||
apx.wco,
|
||||
"no propertie to add:",
|
||||
!newpropertie
|
||||
);
|
||||
if (!apx.data.wco || Object.keys(apx.data.wco).length == 0) return false;
|
||||
newpropertie = !newpropertie ? Object.keys(apx.data.wco) : [newpropertie];
|
||||
console.log("listen apx.data.wco properties:", newpropertie);
|
||||
newpropertie.forEach((p) => {
|
||||
const actionprop = (val, elt) => {
|
||||
if (val.innerHTML) elt.innerHTML = val.innerHTML;
|
||||
if (val.textContent) elt.textContent = val.textContent;
|
||||
for (const h in ["innerHTML", "textContent"]) {
|
||||
if (val[h]) elt[h] = val[h];
|
||||
}
|
||||
for (const a in ["src", "alt", "placeholder", "class", "href"]) {
|
||||
if (val[a]) elt.setAttribute(a, val[a]);
|
||||
}
|
||||
};
|
||||
const elements = document.querySelectorAll(`[data-wco='${p}']`);
|
||||
elements.forEach((e) => actionprop(apx.data.wco[p], e));
|
||||
//console.log(p, Object.hasOwnProperty(apx.wco));
|
||||
if (Object.hasOwnProperty(apx.wco)) {
|
||||
Object.defineProperty(apx.wco, p, {
|
||||
set: (newv) => {
|
||||
this[p] = newv;
|
||||
elements.forEach((e) => actionprop(newv, e));
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
apx.wcoobserver = () => {
|
||||
/**
|
||||
* wco web component observer if apxtri.wcoobserver==true,
|
||||
* Observe existing or creation of any element in DOM with <div wco-name="wconame" id="uniqueid" wco-YYY="aa"></div>
|
||||
* if create or if any wco-YYY value change it runs apx[wconame].loadwco(id,ctx) where ctx={YYY:aa}
|
||||
* Example:
|
||||
* <div id="monComponent" wco-name="monComposant" wco-screen="dashboard" wco-theme="dark" wco-ref="123"></div>
|
||||
* if innerHTML this OR if any wco-YYYY change =>
|
||||
* run apx.monComposant.loadwco('monComponent',
|
||||
* {'wco-name': 'monComposant',
|
||||
* 'wco-screen': 'dashboard',
|
||||
* 'wco-theme': 'dark',
|
||||
* 'wco-ref': '123'});
|
||||
* Used with wco component manage in apx to communicate between autonomous component to reload content if contexte change.
|
||||
* Typical example of a wco menu that will load another wco content if not exist and will change a wco-screen to change the content of the wco content
|
||||
*/
|
||||
console.log("listen wcoobserver");
|
||||
const processElement = (element) => {
|
||||
if (element.nodeType === 1 && element.tagName === "DIV") {
|
||||
if (element.id && element.hasAttribute("wco-name")) {
|
||||
const ctx = {};
|
||||
for (const attr of element.attributes) {
|
||||
if (attr.name.startsWith("wco-")) {
|
||||
ctx[attr.name.slice(4)] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
const wcoName = element.getAttribute("wco-name");
|
||||
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||
apx[wcoName].loadwco(element.id, ctx);
|
||||
} else {
|
||||
console.log(`ERROR: apx.${wcoName}.loadwco() not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process children recursively
|
||||
if (element.children) {
|
||||
Array.from(element.children).forEach(processElement);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === "childList") {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
processElement(node);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName.startsWith("wco-")
|
||||
) {
|
||||
const element = mutation.target;
|
||||
if (element.id && element.hasAttribute("wco-name")) {
|
||||
const ctx = {};
|
||||
for (const attr of element.attributes) {
|
||||
if (attr.name.startsWith("wco-")) {
|
||||
ctx[attr.name.slice(4)] = attr.value;
|
||||
}
|
||||
}
|
||||
|
||||
const wcoName = element.getAttribute("wco-name");
|
||||
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||
apx[wcoName].loadwco(element.id, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true, // <-- active la détection de changements d'attributs
|
||||
});
|
||||
|
||||
// Pour les éléments déjà présents au chargement
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll("div[wco-name]").forEach((element) => {
|
||||
if (element.id) {
|
||||
const ctx = {};
|
||||
for (const attr of element.attributes) {
|
||||
if (attr.name.startsWith("wco-")) {
|
||||
ctx[attr.name.slice(4)] = attr.value;
|
||||
}
|
||||
}
|
||||
const wcoName = element.getAttribute("wco-name");
|
||||
console.log(`load observer of wco for ${wcoName} in id=${element.id}`);
|
||||
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||
apx[wcoName].loadwco(element.id, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
//load existing wco-name in the html page to initiate the wco process
|
||||
// it read and write the wco-name that will trig the observer as a change
|
||||
document.querySelectorAll("div[wco-name]").forEach((e) => {
|
||||
const wconame = e.getAttribute("wco-name");
|
||||
console.log("load wco-name:", wconame);
|
||||
e.setAttribute("wco-name", wconame);
|
||||
});
|
||||
};
|
||||
|
||||
// State management
|
||||
apx.save = () => {
|
||||
localStorage.setItem(apx.data.headers.xapp, JSON.stringify(apx.data));
|
||||
};
|
||||
apx.update = async () => {
|
||||
if (!apxtri) {
|
||||
console.log(
|
||||
'Please add to the html page header, this line const apxtri = { headers: { xtrkversion: 1, xtribe: "smatchit", xapp: "pwa", xlang: "fr", xalias: "anonymous", xhash: "anonymous", xdays: 0} ,pagename:"apxid"} '
|
||||
);
|
||||
return;
|
||||
}
|
||||
//if (apxtri.forcereload){localStorage.setItem("forcereload",true)};
|
||||
if (document.querySelector("html").getAttribute("lang")) {
|
||||
apxtri.headers.xlang = document.querySelector("html").getAttribute("lang");
|
||||
}
|
||||
//alert(localStorage.getItem(apxtri.headers.xapp))
|
||||
if (localStorage.getItem(apxtri.headers.xapp)) {
|
||||
apx.data = JSON.parse(localStorage.getItem(apxtri.headers.xapp));
|
||||
//update with current pagename and eventualy pageauth
|
||||
apx.data.pagename = apxtri.pagename;
|
||||
if (apxtri.pageauth) apx.data.pageauth = apxtri.pageauth;
|
||||
// check localstorage in line with current webpage
|
||||
if (
|
||||
apx.data.headers.xtribe != apxtri.headers.xtribe ||
|
||||
apx.data.headers.xlang != apxtri.headers.xlang ||
|
||||
apx.data.headers.xtrkversion != apxtri.headers.xtrkversion
|
||||
) {
|
||||
// if an app change of tribe
|
||||
localStorage.removeItem(apxtri.headers.xapp);
|
||||
delete apx.data;
|
||||
}
|
||||
}
|
||||
if (!apx.data) {
|
||||
console.log("init or reinit apx.data");
|
||||
apx.data = apxtri;
|
||||
}
|
||||
apx.pagecontext = { search: {}, hash: {} };
|
||||
if (window.location.hash != "") {
|
||||
window.location.hash
|
||||
.slice(1)
|
||||
.split("&")
|
||||
.forEach((kv) => {
|
||||
const keyval = kv.split("=");
|
||||
apx.pagecontext.hash[keyval[0]] = keyval[1];
|
||||
});
|
||||
}
|
||||
if (window.location.search != "") {
|
||||
window.location.search
|
||||
.slice(1)
|
||||
.split("&")
|
||||
.forEach((kv) => {
|
||||
const keyval = kv.split("=");
|
||||
apx.pagecontext.hash[keyval[0]] = keyval[1];
|
||||
});
|
||||
}
|
||||
console.log("apx.pagecontext:", apx.pagecontext);
|
||||
|
||||
// Set authenticate parameter if in pagecontext and redirect to the requested url
|
||||
console.log(
|
||||
apx.pagecontext.hash.xdays,
|
||||
apx.pagecontext.hash.xprofils,
|
||||
apx.pagecontext.hash.xtribe,
|
||||
dayjs(apx.pagecontext.hash.xdays),
|
||||
dayjs(apx.pagecontext.hash.xdays).diff(dayjs(), "hours") < 25,
|
||||
apx.pagecontext.hash.xhash
|
||||
);
|
||||
if (
|
||||
apx.pagecontext.hash.xhash &&
|
||||
apx.pagecontext.hash.xdays &&
|
||||
apx.pagecontext.hash.xprofils &&
|
||||
apx.pagecontext.hash.xtribe &&
|
||||
dayjs(apx.pagecontext.hash.xdays) &&
|
||||
dayjs(apx.pagecontext.hash.xdays).diff(dayjs(), "hours") < 25
|
||||
) {
|
||||
//Means this page is called from an external auth app
|
||||
let headervalid = true;
|
||||
const headerkey = [
|
||||
"xalias",
|
||||
"xhash",
|
||||
"xdays",
|
||||
"xprofils",
|
||||
"xtribe",
|
||||
"xlang",
|
||||
];
|
||||
headerkey.forEach((h) => {
|
||||
if (apx.pagecontext.hash[h]) {
|
||||
apx.data.headers[h] = (h==="xprofils")? apx.pagecontext.hash[h].split(","):apx.pagecontext.hash[h];
|
||||
} else {
|
||||
headervalid = false;
|
||||
}
|
||||
});
|
||||
console.log(headervalid, apx.data.headers);
|
||||
if (headervalid) {
|
||||
apx.save();
|
||||
if (apx.pagecontext.hash.url) {
|
||||
window.location.href = apx.pagecontext.hash.url;
|
||||
}
|
||||
} else {
|
||||
console.log("Your try to access a page failled with ", apx.pagecontext);
|
||||
}
|
||||
}
|
||||
if (
|
||||
apx.data.allowedprofils &&
|
||||
!apx.data.allowedprofils.includes("anonymous") &&
|
||||
apx.data.pagename !== apx.data.pageauth
|
||||
) {
|
||||
const profilintersect = apx.data.allowedprofils.filter((x) =>
|
||||
apx.data.headers.xprofils.includes(x)
|
||||
);
|
||||
console.log("profils authorized:", profilintersect);
|
||||
if (profilintersect.length == 0) {
|
||||
alert(apx.data.ref.Middlewares.notallowtoaccess);
|
||||
return false;
|
||||
}
|
||||
if (dayjs().valueOf() - apx.data.headers.xdays > 86400000) {
|
||||
// need to refresh authentification if possible by opening the pageauth with url context
|
||||
// the pageauth redirect to this current page after authentification, if not then wait credential
|
||||
document.location.href = `/${apx.data.pageauth}_${apx.data.headers.xlang}.html#url=${apx.data.pagename}_${apx.data.headers.xlang}.html`;
|
||||
}
|
||||
}
|
||||
console.log("authorized to access");
|
||||
/* à voir si utile redirect to authentification page pageauth with a redirection if authentify to the pagename (check if /src/ then add it)
|
||||
window.location.href = `${apxtri.pageauth}_${
|
||||
apxtri.headers.xlang
|
||||
}.html?url=${window.location.href.includes("/src/") ? "/src/" : ""}${
|
||||
apxtri.pagename
|
||||
}_${apxtri.headers.xlang}.html`;
|
||||
*/
|
||||
////////////////////////////////////////////
|
||||
apx.data.version = 0; //this force an update to be removed in production
|
||||
///////////////////////////////////////////
|
||||
const ano = apx.data.headers.xalias == "anonymous" ? "anonymous" : "";
|
||||
const initdb = `/api/apxtri/wwws/updatelocaldb${ano}/${apx.data.headers.xtribe}/${apx.data.headers.xapp}/${apx.data.pagename}/${apx.data.version}`;
|
||||
let initset = {};
|
||||
try {
|
||||
initset = await axios.get(initdb, {
|
||||
headers: apx.data.headers,
|
||||
timeout: 2000,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
initset = { data: { msg: "unavailableAPI" } };
|
||||
}
|
||||
console.log("recupe inidb for ", initdb, initset);
|
||||
if (initset.data.msg == "forbidenaccess") {
|
||||
alert(apx.data.ref.Middlewares.notallowtoaccess);
|
||||
return false;
|
||||
}
|
||||
if (initset.data.msg == "unavailableAPI") {
|
||||
console.log("Your api endpoint is down check your hosted server");
|
||||
//try again in 30 seconds
|
||||
setTimeout(apx.update, 30000);
|
||||
}
|
||||
if (initset.data.msg == "datamodelupdate") {
|
||||
// mise à jour local
|
||||
/*if (initset.data.data.wco) {
|
||||
|
||||
console.log("WARNING!!, local apxtri.wco was erase by updatelocaldb.wco");
|
||||
}*/
|
||||
Object.keys(initset.data.data).forEach((k) => {
|
||||
if (k != "headers") {
|
||||
apx.data[k] = initset.data.data[k];
|
||||
}
|
||||
});
|
||||
/* if (apx.data.confpage.wco && !apx.data.wco){
|
||||
console.log("update apx.data.wco with localdb cause does not exist")
|
||||
apx.data.wco=apx.data.confpage.wco;
|
||||
}
|
||||
*/
|
||||
console.log("local update done");
|
||||
apx.save();
|
||||
}
|
||||
|
||||
apx.listendatawco(); // listen any data-wco tag and update it when apxdatawco propertie change
|
||||
if (apxtri.wcoobserver) apx.wcoobserver();
|
||||
if (apx.afterupdate) apx.afterupdate.forEach((cb) => cb()); //run all function store in apx.afterupdate in order
|
||||
apx.lazyload(); //reload image or any media that takes time to load to improve engine search
|
||||
apx.save(); //store in local the modification
|
||||
};
|
||||
apx.ready(apx.update); //2nd param optional=> true mean does not wait same if apx.lock is set
|
Reference in New Issue
Block a user