UNPKG

@cimo/jsmvcfw

Version:

Javascript mvc framework. Light, fast and secure.

211 lines 8.32 kB
const safeHtml = (html) => { const template = document.createElement("template"); template.innerHTML = html; const blockElementList = ["script", "iframe", "object", "embed", "applet", "meta", "link", "style"].join(","); template.content.querySelectorAll(blockElementList).forEach((element) => element.remove()); const elementList = template.content.querySelectorAll("*"); for (const element of elementList) { const attributeList = [...element.attributes]; for (const attribute of attributeList) { const name = attribute.name.toLowerCase(); const value = attribute.value.trim(); if (name.startsWith("on")) { element.removeAttribute(attribute.name); continue; } if (name === "href" || name === "src" || name === "xlink:href" || name === "action" || name === "formaction") { const normalized = value.replace(/\s+/g, "").toLowerCase(); const isJs = normalized.startsWith("javascript:"); const isHtmlData = normalized.startsWith("data:text/html"); const isUnsafeData = normalized.startsWith("data:") && !normalized.startsWith("data:image/"); if (isJs || isHtmlData || isUnsafeData) { element.removeAttribute(attribute.name); } } if (name === "srcdoc") { element.removeAttribute(attribute.name); } } } return template.innerHTML; }; const applyProperty = (element, key, valueNew, valueOld) => { if (key === "jsmvcfw-html") { if (typeof valueNew === "string") { element.innerHTML = safeHtml(valueNew); } else { element.innerHTML = ""; } return; } if (key.startsWith("on") && typeof valueNew === "function") { const eventName = key.slice(2).toLowerCase(); if (typeof valueOld === "function") { element.removeEventListener(eventName, valueOld); } element.addEventListener(eventName, valueNew); } else if (typeof valueNew === "boolean") { if (key === "selected" && "selected" in element) { element.selected = valueNew; } else if (key === "checked" && "checked" in element) { element.checked = valueNew; } valueNew ? element.setAttribute(key, "") : element.removeAttribute(key); } else if (typeof valueNew === "string" || typeof valueNew === "number") { if (key === "value" && "value" in element) { element.value = valueNew.toString(); } element.setAttribute(key, valueNew.toString()); } else if (Array.isArray(valueNew)) { let stringValue = ""; for (const value of valueNew) { if (typeof value === "string") { stringValue += value + " "; } } element.setAttribute(key, stringValue.trim()); } else if (valueNew == null) { if (key === "selected" && "selected" in element) { element.selected = false; } else if (key === "checked" && "checked" in element) { element.checked = false; } else if (key === "value" && "value" in element) { element.value = ""; } element.removeAttribute(key); } }; const updateProperty = (element, oldList, newList) => { for (const key in oldList) { if (!(key in newList)) { if (key === "jsmvcfw-html") { element.innerHTML = ""; } else if (key.startsWith("on") && typeof oldList[key] === "function") { element.removeEventListener(key.slice(2).toLowerCase(), oldList[key]); } else { element.removeAttribute(key); } } } for (const [key, value] of Object.entries(newList)) { const valueOld = oldList[key]; if (value !== valueOld) { applyProperty(element, key, value, valueOld); } } }; const updateChildren = (element, nodeOldListValue, nodeNewListValue) => { const nodeOldList = Array.isArray(nodeOldListValue) ? nodeOldListValue : []; const nodeNewList = Array.isArray(nodeNewListValue) ? nodeNewListValue : []; const keyOldObject = {}; for (let a = 0; a < nodeOldList.length; a++) { const node = nodeOldList[a]; if (typeof node === "object" && node.key) { keyOldObject[node.key] = { node, dom: element.childNodes[a] }; } } const nodeMaxLength = Math.max(nodeOldList.length, nodeNewList.length); for (let a = 0; a < nodeMaxLength; a++) { const nodeOld = nodeOldList[a]; const nodeNew = nodeNewList[a]; const nodeDom = element.childNodes[a]; if (!nodeNew && nodeDom) { const isControllerName = nodeDom.nodeType === Node.ELEMENT_NODE && nodeDom.hasAttribute("jsmvcfw-controllername"); if (!isControllerName) { element.removeChild(nodeDom); } continue; } if (typeof nodeNew === "string") { if (!nodeDom) { element.appendChild(document.createTextNode(nodeNew)); } else if (nodeDom.nodeType === Node.TEXT_NODE) { if (nodeDom.textContent !== nodeNew) { nodeDom.textContent = nodeNew; } } else { element.replaceChild(document.createTextNode(nodeNew), nodeDom); } } else if (typeof nodeNew === "object") { const isElementNode = nodeDom !== null && nodeDom !== undefined && nodeDom.nodeType === Node.ELEMENT_NODE; const isControllerName = isElementNode && nodeDom.hasAttribute("jsmvcfw-controllername"); if (isControllerName && !nodeNew.key) { continue; } if (nodeNew.key && keyOldObject[nodeNew.key]) { const { node, dom } = keyOldObject[nodeNew.key]; updateVirtualNode(dom, node, nodeNew); if (nodeDom !== dom) { element.insertBefore(dom, nodeDom); } } else if (typeof nodeOld === "object" && nodeOld.tag === nodeNew.tag && nodeDom) { updateVirtualNode(nodeDom, nodeOld, nodeNew); } else { const domNew = createVirtualNode(nodeNew); if (nodeDom) { element.replaceChild(domNew, nodeDom); } else { element.appendChild(domNew); } } } } while (element.childNodes.length > nodeNewList.length) { const nodeExtra = element.childNodes[nodeNewList.length]; const isControllerName = nodeExtra.nodeType === Node.ELEMENT_NODE && nodeExtra.hasAttribute("jsmvcfw-controllername"); if (!isControllerName) { element.removeChild(nodeExtra); } else { break; } } }; export const createVirtualNode = (node) => { const element = document.createElement(node.tag); for (const [key, value] of Object.entries(node.propertyObject || {})) { applyProperty(element, key, value); } if (Array.isArray(node.childrenList)) { for (const child of node.childrenList) { if (typeof child === "string") { element.appendChild(document.createTextNode(child)); } else { element.appendChild(createVirtualNode(child)); } } } return element; }; export const updateVirtualNode = (element, nodeOld, nodeNew) => { if (nodeOld.tag !== nodeNew.tag) { const elementNew = createVirtualNode(nodeNew); element.replaceWith(elementNew); return; } const propertyOld = nodeOld.propertyObject || {}; const propertyNew = nodeNew.propertyObject || {}; updateProperty(element, propertyOld || {}, propertyNew || {}); if ("jsmvcfw-html" in propertyNew) { return; } updateChildren(element, nodeOld.childrenList, nodeNew.childrenList); }; //# sourceMappingURL=JsMvcFwDom.js.map