compomint
Version:
A lightweight JavaScript component engine for building web applications with a focus on component-based architecture and template system.
1,048 lines (1,044 loc) • 85 kB
JavaScript
const firstElementChild = function (ele) {
if (ele.firstElementChild)
return ele.firstElementChild;
const children = ele.childNodes;
for (let i = 0, size = children.length; i < size; i++) {
if (children[i] instanceof Element) {
return children[i];
}
}
return null;
};
const childElementCount = function (ele) {
return (ele.childElementCount ||
Array.prototype.filter.call(ele.childNodes, function (child) {
return child instanceof Element;
}).length);
};
const cleanNode = function (node) {
for (let n = 0; n < node.childNodes.length; n++) {
const child = node.childNodes[n];
if (child.nodeType === 8 || // Comment node
(child.nodeType === 3 && !/\S/.test(child.nodeValue || '')) // Text node with only whitespace
) {
node.removeChild(child);
n--; // Adjust index after removal
}
else if (child.nodeType === 1) {
// Element node
cleanNode(child); // Recurse
}
}
};
const domParser = new DOMParser();
const stringToElement = function (str) {
if (typeof str === 'number' || !isNaN(Number(str))) {
return document.createTextNode(String(str));
}
else if (typeof str === 'string') {
try {
const doc = domParser.parseFromString(str, "text/html");
const body = doc.body;
if (body.childNodes.length === 1) {
return body.firstChild;
}
else {
const fragment = document.createDocumentFragment();
while (body.firstChild) {
fragment.appendChild(body.firstChild);
}
return fragment;
}
}
catch (e) {
return document.createTextNode(str);
}
}
else {
return document.createTextNode('');
}
};
const isPlainObject = function (value) {
return Object.prototype.toString.call(value) === '[object Object]';
};
//
// Default template settings
//
const defaultTemplateEngine = (configs, compomint) => ({
rules: {
style: {
pattern: /(\<style id=[\s\S]+?\>[\s\S]+?\<\/style\>)/g,
exec: function (style) {
var _a;
// Create a temporary element to parse the style tag
const dumy = document.createElement("template");
dumy.innerHTML = style;
const styleNode = (dumy.content || dumy).querySelector("style");
if (!styleNode || !styleNode.id)
return ""; // Skip if no style node or ID
const oldStyleNode = document.getElementById(styleNode.id);
if (oldStyleNode)
(_a = oldStyleNode.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(oldStyleNode);
document.head.appendChild(styleNode);
return "";
},
},
commentArea: {
pattern: /##\*([\s\S]+?)##/g,
exec: function (commentArea) {
// Return an empty string to remove the comment block
return ``;
},
},
preEvaluate: {
pattern: /##!([\s\S]+?)##/g,
exec: function (preEvaluate, tmplId) {
try {
// Execute the code in a new function context
new Function("compomint", "tmplId", preEvaluate)(compomint, tmplId);
}
catch (e) {
if (configs.throwError) {
console.error(`Template preEvaluate error in "${tmplId}", ${e.name}: ${e.message}`);
throw e;
}
else {
console.warn(`Template preEvaluate error in "${tmplId}", ${e.name}: ${e.message}`);
}
}
return ``;
},
},
interpolate: {
pattern: /##=([\s\S]+?)##/g,
exec: function (interpolate) {
// Construct JavaScript code to interpolate the value
const interpolateSyntax = `typeof (interpolate)=='function' ? (interpolate)() : (interpolate)`;
return `';\n{let __t, interpolate=${interpolate};\n__p+=((__t=(${interpolateSyntax}))==null ? '' : String(__t) );};\n__p+='`; // Ensure string conversion
},
},
escape: {
pattern: /##-([\s\S]+?)##/g,
exec: function (escape) {
const escapeSyntax = `compomint.tools.escapeHtml.escape(typeof (escape)=='function' ? (escape)() : (escape))`;
// Construct JavaScript code to escape HTML characters in the value
return `';\n{let __t, escape=${escape};\n__p+=((__t=(${escapeSyntax}))==null ? '' : String(__t) );};\n__p+='`; // Ensure string conversion before escape
},
},
elementProps: {
pattern: /data-co-props="##:([\s\S]+?)##"/g,
exec: function (props) {
const source = `';\n{const eventId = (__lazyScope.elementPropsArray.length);\n__p+='data-co-props="'+eventId+'"';\n
__lazyScope.elementPropsArray[eventId] = ${props}};\n__p+='`; // Store props in lazy scope
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
// Iterate over stored props and apply them to elements
lazyScope.elementPropsArray.forEach(function (props, eventId) {
if (!props)
return;
// Find the element with the corresponding data-co-props attribute
const $elementTrigger = wrapper.querySelector(`[data-co-props="${eventId}"]`);
// Remove the temporary attribute and set the properties
if (!$elementTrigger)
return;
delete $elementTrigger.dataset.coProps;
Object.keys(props).forEach(function (key) {
$elementTrigger.setAttribute(key, String(props[key])); // Ensure value is string
});
});
},
},
namedElement: {
pattern: /data-co-named-element="##:([\s\S]+?)##"/g,
exec: function (key) {
const source = `';\nconst eventId = (__lazyScope.namedElementArray.length);\n__p+='data-co-named-element="'+eventId+'"';\n
__lazyScope.namedElementArray[eventId] = ${key};\n__p+='`; // Store the key in lazy scope
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
// Iterate over stored keys and assign elements to the component
lazyScope.namedElementArray.forEach(function (key, eventId) {
// Find the element with the corresponding data-co-named-element attribute
const $elementTrigger = wrapper.querySelector(`[data-co-named-element="${eventId}"]`);
// Assign the element to the component using the key
if (!$elementTrigger) {
if (configs.debug)
console.warn(`Named element target not found for ID ${eventId} in template ${component.tmplId}`);
return;
}
delete $elementTrigger.dataset.coNamedElement;
component[key] = $elementTrigger;
});
},
},
elementRef: {
pattern: /data-co-element-ref="##:([\s\S]+?)##"/g,
exec: function (key) {
const source = `';\n{const eventId = (__lazyScope.elementRefArray.length);\n__p+='data-co-element-ref="'+eventId+'"';
var ${key} = null;\n__lazyScope.elementRefArray[eventId] = function(target) {${key} = target;}};\n__p+='`; // Store a function to assign the element
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
// Iterate over stored functions and call them with the corresponding elements
lazyScope.elementRefArray.forEach(function (func, eventId) {
// Find the element with the corresponding data-co-element-ref attribute
const $elementTrigger = wrapper.querySelector(`[data-co-element-ref="${eventId}"]`);
// Call the stored function with the element
if (!$elementTrigger) {
if (configs.debug)
console.warn(`Element ref target not found for ID ${eventId} in template ${component.tmplId}`);
return;
}
delete $elementTrigger.dataset.coElementRef;
func.call($elementTrigger, $elementTrigger);
});
},
},
elementLoad: {
pattern: /data-co-load="##:([\s\S]+?)##"/g,
exec: function (elementLoad) {
const elementLoadSplitArray = elementLoad.split("::");
// Store the load function and custom data in lazy scope
const source = `';\n{const eventId = (__lazyScope.elementLoadArray.length);\n__p+='data-co-load="'+eventId+'"';
__lazyScope.elementLoadArray[eventId] = {loadFunc: ${elementLoadSplitArray[0]}, customData: ${elementLoadSplitArray[1]}};}\n__p+='`; // 'customData' is determined when compiled, so it does not change even if refreshed.
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
// Iterate over stored load functions and execute them with the corresponding elements
lazyScope.elementLoadArray.forEach(function (elementLoad, eventId) {
// Find the element with the corresponding data-co-load attribute
const $elementTrigger = wrapper.querySelector(`[data-co-load="${eventId}"]`);
if (!$elementTrigger) {
if (configs.debug)
console.warn(`Element load target not found for ID ${eventId} in template ${component.tmplId}`);
return;
}
// Execute the load function with the element and context
delete $elementTrigger.dataset.coLoad;
try {
if (typeof elementLoad.loadFunc === "function") {
const loadFuncParams = [
$elementTrigger,
$elementTrigger,
{
data: data,
element: $elementTrigger,
customData: elementLoad.customData,
component: component,
compomint: compomint,
},
];
elementLoad.loadFunc.call(...loadFuncParams);
}
}
catch (e) {
console.error(`Error executing elementLoad function for ID ${eventId} in template ${component.tmplId}:`, e, elementLoad.loadFunc);
if (configs.throwError)
throw e;
}
});
},
},
event: {
pattern: /data-co-event="##:([\s\S]+?)##"/g,
exec: function (event) {
const eventStrArray = event.split(":::");
// eventStrArray = ["eventFunc::customData", "eventFunc::customData"]
// Store event handlers in lazy scope
let source = `';\n{const eventId = (__lazyScope.eventArray.length);\n__p+='data-co-event="'+eventId+'"';\n`;
const eventArray = [];
for (let i = 0, size = eventStrArray.length; i < size; i++) {
const eventSplitArray = eventStrArray[i].split("::");
eventArray.push(`{eventFunc: ${eventSplitArray[0]}, $parent: this, customData: ${eventSplitArray[1]}}`);
}
source += `__lazyScope.eventArray[eventId] = [${eventArray.join(",")}];}\n__p+='`;
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
const self = this; // Cast self to TemplateSettings
const attacher = self.attacher;
if (!attacher)
return; // Guard against missing attacher
// Iterate over stored event handlers and attach them to elements
lazyScope.eventArray.forEach(function (selectedArray, eventId) {
// Find the element with the corresponding data-co-event attribute
const $elementTrigger = wrapper.querySelector(`[data-co-event="${eventId}"]`);
if (!$elementTrigger) {
if (configs.debug)
console.warn(`Event target not found for ID ${eventId} in template ${component.tmplId}`); // Debugging: Log if target not found
return;
}
delete $elementTrigger.dataset.coEvent;
for (let i = 0, size = selectedArray.length; i < size; i++) {
const selected = selectedArray[i];
if (selected.eventFunc) {
if (Array.isArray(selected.eventFunc)) {
selected.eventFunc.forEach(function (func) {
attacher(self, data, lazyScope, component, wrapper, $elementTrigger, func, selected);
});
}
else {
attacher(self, data, lazyScope, component, wrapper, $elementTrigger, selected.eventFunc, selected);
}
}
}
});
},
trigger: function (target, eventName) {
const customEvent = new Event(eventName, {
// Dispatch a custom event on the target element
bubbles: true,
cancelable: true,
});
target.dispatchEvent(customEvent);
},
attacher: function (self, // Type properly if possible
data, lazyScope, component, wrapper, $elementTrigger, eventFunc, eventData) {
const trigger = self.trigger;
const $childTarget = firstElementChild(wrapper);
const $targetElement = childElementCount(wrapper) === 1 ? $childTarget : null;
// Attach event listeners based on the type of eventFunc
if (!eventFunc) {
return;
}
const eventFuncParams = [
$elementTrigger,
null,
{
data: data,
customData: eventData.customData,
element: $elementTrigger,
componentElement: $targetElement || ($childTarget === null || $childTarget === void 0 ? void 0 : $childTarget.parentElement),
component: component,
compomint: compomint,
},
];
// Basic case: eventFunc is a single function
if (typeof eventFunc === "function") {
const eventListener = function (event) {
event.stopPropagation();
eventFuncParams[1] = event;
try {
eventFunc.call(...eventFuncParams);
}
catch (e) {
console.error(`Error in event handler for template ${component.tmplId}:`, e, eventFunc);
if (configs.throwError)
throw e;
}
};
// Attach a click event listener for a single function
$elementTrigger.addEventListener("click", eventListener);
eventData.element = $elementTrigger; // For remove eventListener
eventData.eventFunc = eventListener; // For remove eventListener
return;
}
if (!isPlainObject(eventFunc)) {
return;
}
// Advanced case: eventFunc is an object mapping event types to handlers
const eventMap = eventFunc;
// Handle event map with multiple event types
const triggerName = eventMap.triggerName; // Optional key to store trigger functions
if (triggerName) {
component.trigger = component.trigger || {};
component.trigger[triggerName] = {};
}
Object.keys(eventMap).forEach(function (eventType) {
const selectedEventFunc = eventMap[eventType];
// Handle special event types like "load", "namedElement", and "triggerName"
if (eventType === "load") {
eventFuncParams[1] = $elementTrigger;
try {
selectedEventFunc.call(...eventFuncParams);
}
catch (e) {
console.error(`Error in 'load' event handler for template ${component.tmplId}:`, e, selectedEventFunc);
if (configs.throwError)
throw e;
}
return;
}
else if (eventType === "namedElement") {
component[selectedEventFunc] = $elementTrigger;
return;
}
else if (eventType === "triggerName") {
return;
// Attach event listeners for other event types
}
const eventListener = function (event) {
event.stopPropagation();
eventFuncParams[1] = event;
try {
selectedEventFunc.call(...eventFuncParams);
}
catch (e) {
console.error(`Error in '${eventType}' event handler for template ${component.tmplId}:`, e, selectedEventFunc);
if (configs.throwError)
throw e;
}
};
$elementTrigger.addEventListener(eventType, eventListener);
eventData.element = $elementTrigger; // For remove eventListener
eventFunc[eventType] = eventListener; // For remove eventListener
if (triggerName && trigger) {
component.trigger[triggerName][eventType] = function () {
trigger($elementTrigger, eventType);
};
}
});
},
},
element: {
pattern: /##%([\s\S]+?)##/g,
exec: function (target) {
// Store element insertion information in lazy scope
const elementSplitArray = target.split("::");
const source = `';\n{
const elementId = (__lazyScope.elementArray.length);
__p+='<template data-co-tmpl-element-id="'+elementId+'"></template>';
__lazyScope.elementArray[elementId] = {childTarget: ${elementSplitArray[0]}, nonblocking: ${elementSplitArray[1] || false}};};
__p+='`;
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
lazyScope.elementArray.forEach(function (ele, elementId) {
// Retrieve element insertion details from lazy scope
const childTarget = ele.childTarget;
const nonblocking = ele.nonblocking;
// Find the placeholder element
const $tmplElement = wrapper.querySelector(`template[data-co-tmpl-element-id="${elementId}"]`);
// Perform the element insertion
if (!$tmplElement) {
if (configs.debug)
console.warn(`Element insertion placeholder not found for ID ${elementId} in template ${component.tmplId}`);
return;
}
if (!$tmplElement.parentNode) {
if (configs.debug)
console.warn(`Element insertion placeholder for ID ${elementId} in template ${component.tmplId} has no parent.`);
return;
}
const doFunc = function () {
if (!$tmplElement || !$tmplElement.parentNode) {
if (configs.debug)
console.warn(`Placeholder for ID ${elementId} removed before insertion in template ${component.tmplId}.`);
return;
}
// Handle different types of childTarget for insertion
try {
if (childTarget instanceof Array) {
const docFragment = document.createDocumentFragment();
childTarget.forEach(function (child) {
if (!child)
return;
const childElement = child.element || child;
let nodeToAppend = null;
// Convert child to a DOM node if necessary
if (typeof childElement === "string" ||
typeof childElement === "number") {
nodeToAppend = stringToElement(childElement);
}
else if (typeof childElement === "function") {
nodeToAppend = stringToElement(childElement());
}
else if (childElement instanceof Node) {
nodeToAppend = childElement;
}
else {
if (configs.debug)
console.warn(`Invalid item type in element array for ID ${elementId}, template ${component.tmplId}:`, childElement);
return;
}
// Append the node to the document fragment
if (child.beforeAppendTo) {
try {
child.beforeAppendTo();
}
catch (e) {
console.error("Error in beforeAppendTo (array item):", e);
}
}
if (nodeToAppend)
docFragment.appendChild(nodeToAppend);
});
// Replace the placeholder with the document fragment
$tmplElement.parentNode.replaceChild(docFragment, $tmplElement);
// Call afterAppendTo for each child
childTarget.forEach(function (child) {
if (child && child.afterAppendTo) {
setTimeout(() => {
try {
child.afterAppendTo();
}
catch (e) {
console.error("Error in afterAppendTo (array item):", e);
}
}, 0);
}
});
// Handle string, number, or function types
}
else if (typeof childTarget === "string" ||
typeof childTarget === "number") {
$tmplElement.parentNode.replaceChild(stringToElement(childTarget), $tmplElement);
// Handle function type
}
else if (typeof childTarget === "function") {
$tmplElement.parentNode.replaceChild(stringToElement(childTarget()), $tmplElement);
// Handle Node or ComponentScope types
}
else if (childTarget &&
(childTarget.element || childTarget) instanceof Node) {
const childScope = childTarget; // Assume it might be a scope
const childElement = childScope.element || childScope;
// Replace the placeholder with the child element
if (childScope.beforeAppendTo) {
try {
childScope.beforeAppendTo();
}
catch (e) {
console.error("Error in beforeAppendTo:", e);
}
}
$tmplElement.parentNode.replaceChild(childElement, $tmplElement);
// Call afterAppendTo if available
if (childScope.afterAppendTo) {
setTimeout(() => {
try {
if (childScope.afterAppendTo)
childScope.afterAppendTo();
}
catch (e) {
console.error("Error in afterAppendTo:", e);
}
}, 0);
}
// Set parentComponent if it's a component
if (childScope.tmplId) {
childScope.parentComponent = component;
}
// Handle invalid target types
}
else {
if (configs.debug)
console.warn(`Invalid target for element insertion ID ${elementId}, template ${component.tmplId}:`, childTarget);
$tmplElement.parentNode.removeChild($tmplElement);
}
}
catch (e) {
console.error(`Error during element insertion for ID ${elementId}, template ${component.tmplId}:`, e);
if (configs.throwError)
throw e;
if ($tmplElement && $tmplElement.parentNode) {
try {
$tmplElement.parentNode.removeChild($tmplElement);
}
catch (removeError) {
/* Ignore */
}
}
} // end try
}; // end doFunc
nonblocking === undefined || nonblocking === false
? // Execute immediately or with a delay based on nonblocking
doFunc()
: setTimeout(doFunc, typeof nonblocking === "number" ? nonblocking : 0);
}); // end forEach
},
},
lazyEvaluate: {
pattern: /###([\s\S]+?)##/g,
exec: function (lazyEvaluate) {
const source = `';\n__lazyScope.lazyEvaluateArray.push(function(data) {${lazyEvaluate}});\n__p+='`;
// Store the lazy evaluation function in lazy scope
return source;
},
lazyExec: function (data, lazyScope, component, wrapper) {
// Execute stored lazy evaluation functions
const $childTarget = firstElementChild(wrapper);
const $targetElement = childElementCount(wrapper) === 1 ? $childTarget : null;
lazyScope.lazyEvaluateArray.forEach(function (selectedFunc, idx) {
// Call the function with the appropriate context
try {
selectedFunc.call($targetElement || wrapper, data); // Use wrapper if multiple elements
}
catch (e) {
console.error(`Error in lazyEvaluate block ${idx} for template ${component.tmplId}:`, e, selectedFunc);
if (configs.throwError)
throw e;
}
});
return;
},
},
evaluate: {
pattern: /##([\s\S]+?)##/g,
exec: (evaluate) => {
// Insert arbitrary JavaScript code into the template function
return "';\n" + evaluate + "\n__p+='";
},
},
escapeSyntax: {
pattern: /#\\#([\s\S]+?)#\\#/g,
exec: function (syntax) {
return `'+\n'##${syntax}##'+\n'`;
},
},
},
keys: {
dataKeyName: "data",
statusKeyName: "status",
componentKeyName: "component",
i18nKeyName: "i18n",
},
});
const applyBuiltInTemplates = (addTmpl) => {
// co-Ele is a shorthand for co-Element, it will generate a div element with the given props and event
addTmpl("co-Ele", `<##=data[0]##></##=data[0]##>###compomint.tools.applyElementProps(this, data[1]);##`);
addTmpl("co-Element", `##
data.tag = data.tag || 'div';
##
<##=data.tag##
##=data.id ? 'id="' + (data.id === true ? component._id : data.id) + '"' : ''##
data-co-props="##:data.props##"
data-co-event="##:data.event##">
##if (typeof data.content === "string") {##
##=data.content##
##} else {##
##%data.content##
##}##
</##=data.tag##>`);
};
/*
* Copyright (c) 2025-present, Choi Sungho
* Code released under the MIT license
*/
// Polyfill for Object.assign
if (typeof Object.assign != "function") {
Object.defineProperty(Object, "assign", {
value: function assign(target, ...params) {
if (target == null) {
throw new TypeError("Cannot convert undefined or null to object");
}
const to = Object(target);
for (let index = 0, length = params.length; index < length; index++) {
const nextSource = params[index];
if (nextSource != null) {
for (let nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true,
});
}
// Polyfill for ChildNode.remove
(function (arr) {
arr.forEach(function (item) {
if (!item || item.hasOwnProperty("remove")) {
return;
}
Object.defineProperty(item, "remove", {
configurable: true,
enumerable: true,
writable: true,
value: function remove() {
if (this.parentNode !== null) {
this.parentNode.removeChild(this);
}
},
});
});
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
// Polyfill for Node.isConnected
(function (supported) {
if (supported)
return;
Object.defineProperty(window.Node.prototype, "isConnected", {
get: function () {
return document.body.contains(this);
},
});
})("isConnected" in window.Node.prototype);
const compomint = {};
const tmpl = {};
const tools = (compomint.tools = compomint.tools || {});
const configs = (compomint.configs = Object.assign({ printExecTime: false, debug: false, throwError: true }, compomint.configs));
const cachedTmpl = (compomint.tmplCache =
compomint.tmplCache || new Map());
if (!cachedTmpl.has("anonymous")) {
cachedTmpl.set("anonymous", { elements: new Set() }); // Cast to TemplateMeta
}
const isSupportTemplateTag = "content" in document.createElement("template");
const noMatch = /(.)^/;
const escapes = {
"'": "\\'",
"\\": "\\\\",
"\r": "\\r",
"\n": "\\n",
"\t": "\\t",
"\u2028": "\u2028",
"\u2029": "\u2029",
"><": "><",
"<": "<",
">": ">",
};
const escaper = /\>( |\n)+\<|\>( |\n)+|( |\n)+\<|\\|'|\r|\n|\t|\u2028|\u2029/g;
// set default template config
compomint.templateEngine = defaultTemplateEngine(configs, compomint);
const escapeHtml = (function () {
const escapeMap = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'", // Use HTML entity for single quote
"`": "`", // Use HTML entity for backtick
//"\n": " ", // Keep newline escaping if needed, otherwise remove
};
const unescapeMap = Object.keys(escapeMap).reduce((acc, key) => {
acc[escapeMap[key]] = key;
return acc;
}, {});
const createEscaper = function (map) {
const escaper = function (match) {
return map[match];
};
const source = `(?:${Object.keys(map).join("|").replace(/\\/g, "\\\\")})`; // Escape backslashes if any keys have them
const testRegexp = RegExp(source);
const replaceRegexp = RegExp(source, "g");
return function (string) {
string = string == null ? "" : `${string}`;
return testRegexp.test(string)
? string.replace(replaceRegexp, escaper)
: string;
};
};
return {
escape: createEscaper(escapeMap),
unescape: createEscaper(unescapeMap),
};
})();
tools.escapeHtml = escapeHtml;
const matcherFunc = function (templateRules) {
const patternArray = [];
const execArray = [];
const lazyExecMap = {};
const lazyScopeSeed = {};
Object.keys(templateRules).forEach(function (key) {
const templateRule = templateRules[key]; // Type assertion
if (templateRule &&
typeof templateRule === "object" &&
templateRule.pattern instanceof RegExp &&
typeof templateRule.exec === "function") {
patternArray.push((templateRule.pattern || noMatch).source);
execArray.push(templateRule.exec);
}
if (templateRule &&
typeof templateRule === "object" &&
typeof templateRule.lazyExec === "function") {
const arrayKey = `${key}Array`;
lazyExecMap[arrayKey] = templateRule.lazyExec;
lazyScopeSeed[arrayKey] = [];
}
});
return {
templateRules: templateRules,
pattern: new RegExp(patternArray.join("|"), "g"),
exec: execArray,
lazyExecKeys: Object.keys(lazyScopeSeed),
lazyExec: lazyExecMap,
lazyScopeSeed: JSON.stringify(lazyScopeSeed),
};
};
const escapeFunc = function (match) {
return escapes[match] || escapes[match.replace(/[ \n]/g, "")] || "";
};
const defaultMatcher = matcherFunc(compomint.templateEngine.rules);
const templateParser = function (tmplId, text, matcher) {
if (configs.printExecTime)
console.time(`tmpl: ${tmplId}`);
let index = 0;
let source = "";
text.replace(matcher.pattern, function (...params) {
const match = params[0];
const offset = params[params.length - 2];
source += text.slice(index, offset).replace(escaper, escapeFunc);
let selectedMatchContent;
let matchIndex = null;
params.slice(1, -2).some(function (value, idx) {
if (value !== undefined) {
selectedMatchContent = value;
matchIndex = idx;
return true;
}
return false;
});
if (selectedMatchContent !== undefined && matchIndex !== null) {
try {
source += matcher.exec[matchIndex].call(matcher.templateRules, selectedMatchContent, tmplId);
}
catch (e) {
console.error(`Error executing template rule index ${matchIndex} for match "${selectedMatchContent}" in template "${tmplId}":`, e);
if (configs.throwError)
throw e;
source += "";
}
}
else {
source += match.replace(escaper, escapeFunc);
}
index = offset + match.length;
return match;
});
source += text.slice(index).replace(escaper, escapeFunc);
if (configs.printExecTime)
console.timeEnd(`tmpl: ${tmplId}`);
return source;
};
const templateBuilder = (compomint.template =
function compomint_templateBuilder(tmplId, templateText, customTemplateEngine) {
let templateEngine = compomint.templateEngine;
let matcher = defaultMatcher;
if (customTemplateEngine) {
templateEngine = {
rules: Object.assign({}, templateEngine.rules, customTemplateEngine.rules || {}),
keys: Object.assign({}, templateEngine.keys, customTemplateEngine.keys || {}),
};
matcher = matcherFunc(templateEngine.rules);
}
const source = `
/* tmplId: ${tmplId} */
//# sourceURL=http://tmpl//${tmplId.split("-").join("//")}.js
// if (__debugger) {
// debugger;
// }
let __p='';
__p+='${templateParser(tmplId, templateText, matcher)}';
return __p;`;
let sourceGenFunc = null;
try {
sourceGenFunc = new Function(templateEngine.keys.dataKeyName, templateEngine.keys.statusKeyName, templateEngine.keys.componentKeyName, templateEngine.keys.i18nKeyName, "compomint", "tmpl", "__lazyScope", "__debugger", source);
}
catch (e) {
if (configs.throwError) {
console.error(`Template compilation error in "${tmplId}", ${e.name}: ${e.message}`);
try {
// Attempt re-run for potential browser debugging
new Function(templateEngine.keys.dataKeyName, templateEngine.keys.statusKeyName, templateEngine.keys.componentKeyName, templateEngine.keys.i18nKeyName, "compomint", "tmpl", "__lazyScope", "__debugger", source);
}
catch (_a) {
/* Ignore re-run error */
}
throw e;
}
else {
return () => ({}); // Return a dummy function if not throwing
}
}
const renderingFunc = function compomint_renderingFuncBuilder(...params) {
let data;
let wrapperElement;
let callback;
let baseComponent;
// Argument parsing logic
const firstArg = params[0];
if (firstArg &&
typeof firstArg === "object" &&
(firstArg.$wrapperElement ||
firstArg.$callback ||
firstArg.$baseComponent)) {
data = Object.assign({}, firstArg); // Clone data object
wrapperElement = data.$wrapperElement;
delete data.$wrapperElement;
callback = data.$callback;
delete data.$callback;
baseComponent = data.$baseComponent;
delete data.$baseComponent;
}
else {
data = firstArg;
if (typeof params[1] === "function") {
wrapperElement = undefined;
callback = params[1];
baseComponent = params[2];
}
else {
wrapperElement = params[1];
callback = params[2];
baseComponent = params[3];
}
}
const dataKeyName = templateEngine.keys.dataKeyName;
const statusKeyName = templateEngine.keys.statusKeyName;
const lazyScope = JSON.parse(matcher.lazyScopeSeed);
const component = Object.assign(baseComponent || {}, {
tmplId: tmplId,
element: null, // Initialize element
status: (baseComponent && baseComponent.status) || {}, // Ensure status exists
replace: function (newComponent) {
const self = this;
if (!self.element ||
!(self.element instanceof Node) ||
!self.element.parentElement) {
if (configs.debug)
console.warn(`Cannot replace template "${tmplId}": element not in DOM.`);
return;
}
self.element.parentElement.replaceChild(newComponent.element ||
newComponent, self.element);
},
remove: function (spacer = false) {
const self = this;
if (self.beforeRemove) {
try {
self.beforeRemove();
}
catch (e) {
console.error("Error in beforeRemove:", e);
}
}
// Remote event event listener
// Iterate through all event handlers stored in lazyScope.eventArray
if (lazyScope.eventArray) {
lazyScope.eventArray.forEach(function (event) {
// For each event entry, iterate through its associated event listeners
event.forEach(function (selectedEvent) {
if (selectedEvent.element) {
if (typeof selectedEvent.eventFunc === "function") {
selectedEvent.element.removeEventListener("click", selectedEvent.eventFunc); // Remove click event listener
}
else {
Object.keys(selectedEvent.eventFunc).forEach(function (eventType) {
selectedEvent.element.removeEventListener(eventType, selectedEvent.eventFunc[eventType]);
});
}
Object.keys(selectedEvent).forEach((key) => delete selectedEvent[key]);
}
// Clear the selectedEvent object to release references
});
});
}
const parent = self.element instanceof Node ? self.element.parentElement : null;
const removedElement = self.element; // Store reference
if (parent) {
if (spacer) {
const dumy = document.createElement("template");
parent.replaceChild(dumy, self.element);
self.element = dumy; // Update scope's element reference
}
else {
parent.removeChild(self.element);
}
}
else if (configs.debug) {
console.warn(`Cannot remove template "${tmplId}": element not in DOM.`);
}
if (self.afterRemove) {
try {
self.afterRemove();
}
catch (e) {
console.error("Error in afterRemove:", e);
}
}
return removedElement;
},
appendTo: function (parentElement) {
const self = this;
if (self.beforeAppendTo) {
try {
self.beforeAppendTo();
}
catch (e) {
console.error("Error in beforeAppendTo:", e);
}
}
if (parentElement && self.element instanceof Node) {
parentElement.appendChild(self.element);
}
else if (configs.debug) {
console.warn(`Cannot append template "${tmplId}": parentElement or scope.element is missing or not a Node.`);
}
if (self.afterAppendTo) {
setTimeout(() => {
try {
self.afterAppendTo();
}
catch (e) {
console.error("Error in afterAppendTo:", e);
}
}, 0);
}
return self;
},
release: function () {
/* Implementation below */
},
render: function (newData) {
/* Implementation below */ return this;
},
refresh: function (reflashData) {
/* Implementation below */ return this;
},
reflash: function (reflashData) {
/* Implementation below */ return this;
},
}); // Cast to ComponentScope
if (!component._id) {
component._id = tools.genId(tmplId);
}
component[dataKeyName] = data;
if (component[statusKeyName] == undefined) {
component[statusKeyName] = {};
}
const hasParent = wrapperElement instanceof Element;
const temp = document.createElement("template");
if (configs.printExecTime)
console.time(`render: ${tmplId}`);
let returnTarget = null;
let renderedHTML = null;
try {
renderedHTML = !data
? `<template data-co-empty-template="${tmplId}"></template>`
: sourceGenFunc.call(
// Use non-null assertion
wrapperElement || null, data, component[statusKeyName], component, compomint.i18n[tmplId], compomint, tmpl, lazyScope, configs.debug // Pass debug flag for __debugger
);
}
catch (e) {
if (configs.throwError) {
console.error(`Runtime error during render of "${tmplId}":`, e.message);
console.log("--- Data ---", data, "------------");
try {
// Attempt re-run with debugger
sourceGenFunc.call(wrapperElement || null, data, component[statusKeyName], component, compomint.i18n[tmplId], lazyScope, true);
}
catch (_a) {
/* Ignore */
}
throw e;
}
else {
console.warn(`Render failed for "${tmplId}". Returning scope with comment node.`);
component.element = document.createComment(`Render Error: ${tmplId}`);
return component;
}
}
if (configs.printExecTime)
console.timeEnd(`render: ${tmplId}`);
temp.innerHTML = renderedHTML;
let docFragment = temp.content || temp;
if (docFragment.tagName == "TEMPLATE" &&
!temp.content) {
// Check for IE11 case
const children = Array.from(docFragment.childNodes);
docFragment = document.createDocumentFragment();