@anywhichway/lazui
Version:
Single page apps and lazy loading sites with minimal JavaScript or client build processes.
182 lines (181 loc) • 10.9 kB
JavaScript
const parseTrigger = (text) => {
if(text) {
const trigger = {},
parts = text.split(" ");
trigger.type = parts.shift();
trigger.once = parts.includes("once");
trigger.delay = parseInt((parts.find((part) => part.startsWith("delay:"))||"").split(":")[1]||0);
trigger.every = parseInt((parts.find((part) => part.startsWith("every:"))||"").split(":")[1]||0);
trigger.throttle = parseInt((parts.find((part) => part.startsWith("throttle:"))||"").split(":")[1]||0);
trigger.debounce = parseInt((parts.find((part) => part.startsWith("debounce:"))||"").split(":")[1]||0);
trigger.call = (parts.find((part) => part.startsWith("call:"))||"").split(":")[1];
trigger.dispatch = (parts.find((part) => part.startsWith("dispatch:"))||"").split(":")[1];
trigger.placeholder = text.slice(text.indexOf("placeholder:")).split(":")[1];
trigger.changed = parts.includes("changed");
return trigger;
}
}
async function trigger({el,attribute,state,root,lazui}) {
let throttle,debounce;
const {render,update,router,prefix,replaceBetween,url,JSON} = lazui,
{loadController} = await import(url + "/directives/lz/controller.js"),
triggers = attribute.value.split(",").map((text) => text.trim())
.reduce((acc,text) => {
if(text.length>0) acc.push(parseTrigger(text));
return acc;
},[]),
handler = (event) => {
if(event.target!==el) return;
const trigger = triggers.find((trigger) => trigger.type === event.type);
if(trigger) {
if (trigger.throttle) {
if (throttle) {
event.preventDefault();
event.stopPropagation();
return;
}
throttle = true;
setTimeout(() => throttle = false, trigger.throttle);
} else if (trigger.debounce && !event.detail.debounced) {
event.preventDefault();
event.stopPropagation();
if (debounce) {
clearTimeout(debounce);
debounce = setTimeout(() => {
debounce = null; // need to copy event data
setTimeout(() => el.dispatchEvent(new CustomEvent(event.type, {detail: {debounced: trigger.debounce}})), trigger.delay);
}, trigger.debounce);
} else {
debounce = setTimeout(() => {
debounce = null; // need to copy event data
setTimeout(() => el.dispatchEvent(new CustomEvent(event.type, {detail: {debounced: trigger.debounce}})), trigger.delay);
});
}
return;
}
if (trigger.delay && !event.detail.timeout) {
event.preventDefault();
event.stopPropagation(); // need to copy event data
if(trigger.placeholder) el.innerHTML = trigger.placeholder;
const timeout = setTimeout(() => el.dispatchEvent(new CustomEvent(event.type, {
detail: {
timeout,
sourceEvent: event
}
})), trigger.delay);
return;
}
if (trigger.every && !event.detail.interval) {
event.preventDefault();
event.stopPropagation();
if(trigger.placeholder) el.innerHTML = trigger.placeholder;
const interval = setInterval(() => el.dispatchEvent(new CustomEvent(event.type, {
detail: {
interval,
sourceEvent: event
}
})), trigger.every);
}
if (!(trigger.delay || trigger.every) || (trigger.delay && event.detail.timeout) || (trigger.every && event.detail.interval)) {
if (trigger.once) triggers.splice(triggers.indexOf(trigger), 1);
if (trigger.dispatch==="load" || event.type==="load") {
event.stopImmediatePropagation();
let src = el.getAttribute(`${prefix}:src`);
let request;
//if(src?.startsWith("#")) src = window.location.pathname + src;
try {
const json = JSON.parse(src),
mode = json.mode;
if(mode==="document") delete json.mode;
request = new Request(json.url,json);
if(mode==="document") {
Object.defineProperty(request,"mode",{value:mode});
}
} catch {
request = new Request(src);
}
router.fetch(request).then(async (response) => {
const string = replaceBetween(await response.text(), "`", "`", (text) => "`" + text.replaceAll(/</g, "<") + "`"),
where = el.getAttribute(`${prefix}:target`) || el.getAttribute("target") || undefined,
mode = el.getAttribute(`${prefix}:mode`),
controller = el.attributes[`${prefix}:controller`];
if (state || mode==="frame" || el.__state__) {
//let content = mode==="frame" ? string : document.createDocumentFragment();
if(mode==="frame") {
update({node:el, content:string, state, root:el, where, recurse: 1});
} else {
/* content.state = el.state;
const div = document.createElement("div");
div.innerHTML = string;
while(div.firstChild) content.appendChild(div.firstChild);
content = render(el,content,{state, root:el, where:null});
update({node:el, content, state, root:el, where, recurse: 1});*/
let content = document.createElement("html");
content.innerHTML = string;
if(el.__state__) Object.defineProperty(content,"__state__",{enumerable:false,value:el.__state__})
content.head = content.firstElementChild;
content.body = content.lastElementChild;
//if(state || el.state) {
// content = render(el,content,{state, root:el, where:null});
//}
for(const el of content.head.querySelectorAll("style")) {
content.body.insertAdjacentElement("afterbegin",el);
}
if(new URL(request.url).origin!==location.origin) {
content = content.body;
} else {
for(const el of content.head.querySelectorAll("script")) {
content.body.insertAdjacentElement("afterbegin",el);
}
//content = content.body;
}
render(el,content,{state, root:el, where,recurse:1});
//update({node:el, content, state, root:el, where, recurse: 1})
}
} else {
render(el, string, {state, root: el, where, recurse: 1});
if (controller) loadController({el, attribute: controller, state, root, lazui})
}
if(event.type!=="load") el.dispatchEvent(new CustomEvent("load",{detail:{sourceEvent:event}}));
})
}
if (trigger.call) {
const scope = trigger.call.slice(0, trigger.call.lastIndexOf(".")),
fname = trigger.call.slice(trigger.call.lastIndexOf(".") + 1);
if (scope === "window" || scope === "globalThis") {
globalThis[fname](event);
} else if (scope === "document") {
document[fname](event);
} else if (scope === "controller") {
const controller = el.closest(`[${prefix}\\:controller]`);
controller[fname](event);
} else if (scope === "src") {
const src = el.closest(`[${prefix}\\:src]`);
src[fname](event);
} else if (scope === "closest") {
let node = el;
while (typeof node[fname] !== "function" && node.parentElement) node = node.parentElement;
if (typeof node[fname] === "function") node[fname](event);
} else if (scope === "this") {
el[fname](event);
} else {
for (const el of root.querySelectorAll(scope) || []) {
if (typeof el[fname] === "function") el[fname](event);
}
}
}
}
} else { // ??
//el.dispatchEvent(new CustomEvent("load", {detail: {sourceEvent: event}}));
}
};
let loaded = false;
triggers.forEach((trigger) => setTimeout(()=> {
el.addEventListener(trigger.type,handler);
if(trigger.type==="load" && !triggers.some((trigger) => trigger.dispatch==="load")) {
loaded = true;
el.dispatchEvent(new CustomEvent("load", {detail: {sourceEvent: null}}));
}
}));
}
export {trigger}