| import * as dom from "./utils_dom";
|
| import {getObjectValue} from "./shared_utils";
|
|
|
| const CONFIG_DEFAULT = {
|
| attrBind: "data-bind",
|
| attrIf: "data-if",
|
| attrIfIs: "data-if-is",
|
| };
|
|
|
| const CONFIG = Object.assign({}, CONFIG_DEFAULT, {
|
| attrBind: "bind",
|
| attrIf: "if",
|
| attrIfIs: "if-is",
|
| });
|
|
|
| export interface BindOptions {
|
| |
| |
| |
|
|
| onlyDefined?: boolean;
|
|
|
|
|
| singleScoped?: boolean;
|
|
|
|
|
| contextElement?: HTMLElement;
|
| }
|
|
|
| export interface InflateOptions {
|
| skipInit?: boolean;
|
| bindOptions?: BindOptions;
|
| }
|
|
|
| export interface TemplateData {
|
| fragment: DocumentFragment;
|
| preProcessScript: (data: any) => void;
|
| }
|
|
|
| export interface BindingContext {
|
| data: any;
|
| contextElement: HTMLElement;
|
| currentElement: HTMLElement;
|
| }
|
|
|
|
|
| const RGX_COMPARISON = (() => {
|
|
|
| let value = "((?:\\!*)[_a-z0-9\\.\\-\\[\\]'\"]+)";
|
| let comparison = "((?:<|>|==|\\!=)=?)";
|
| return new RegExp(`^(?:\\!*)\\(?${value}\\s*${comparison}\\s*${value}\\)?$`, "i");
|
| })();
|
|
|
| const RGXPART_BIND_FN_TEMPLATE_STRING = "template|tpl";
|
| const RGXPART_BIND_FN_ELEMENT_STRING = "element|el";
|
| const RGX_BIND_FN_TEMPLATE = new RegExp(
|
| `^(?:${RGXPART_BIND_FN_TEMPLATE_STRING})\\(([^\\)]+)\\)`,
|
| "i",
|
| );
|
| const RGX_BIND_FN_ELEMENT = new RegExp(
|
| `^(?:${RGXPART_BIND_FN_ELEMENT_STRING})\\(([^\\)]+)\\)`,
|
| "i",
|
| );
|
| const RGX_BIND_FN_TEMPLATE_OR_ELEMENT = new RegExp(
|
| `^(?:${RGXPART_BIND_FN_TEMPLATE_STRING}|${RGXPART_BIND_FN_ELEMENT_STRING})\\(([^\\)]+)\\)`,
|
| "i",
|
| );
|
| const RGX_BIND_FN_LENGTH = /^(?:length|len|size)\(([^\)]+)\)/i;
|
| const RGX_BIND_FN_FORMAT = /^(?:format|fmt)\(([^\,]+),([^\)]+)\)/i;
|
| const RGX_BIND_FN_CALL = /^([^\(]+)\(([^\)]*)\)/i;
|
|
|
| const EMPTY_PREPROCESS_FN = (data: any) => data;
|
|
|
|
|
|
|
|
|
| const RGX_BIND_DECLARATIONS =
|
| /\s*(\!*(?:[\$_a-z0-9-\.\'\"]|\?\?|\|\||\&\&|(?:(?:<|>|==|\!=)=?))+(?:\`[^\`]+\`)?(?:\([^\)]*\))?)(?::(.*?))?(\s|$)/gi;
|
|
|
| |
| |
|
|
| function localAssertNotFalsy<T>(input?: T | null, errorMsg = `Input is not of type.`): T {
|
| if (input == null) {
|
| throw new Error(errorMsg);
|
| }
|
| return input;
|
| }
|
|
|
| |
| |
|
|
| function cleanKey(key: string) {
|
| return key.toLowerCase().trim().replace(/\s/g, "");
|
| }
|
|
|
| |
| |
|
|
| function toArray(value: any | any[]): any[] {
|
| if (Array.isArray(value)) {
|
| return value;
|
| }
|
| if (value instanceof Set) {
|
| return Array.from(value);
|
| }
|
|
|
| if (typeof value === "object" && typeof value.length === "number") {
|
| return [].slice.call(value);
|
| }
|
| return [value];
|
| }
|
|
|
| |
| |
|
|
| function flattenArray(arr: any | any[]): any[] {
|
| return toArray(arr).reduce((acc, val) => {
|
| return acc.concat(Array.isArray(val) ? flattenArray(val) : val);
|
| }, []);
|
| }
|
|
|
| |
| |
|
|
| function getObjValue(lookup: string, obj: any): any {
|
|
|
| let booleanMatch: string[] = lookup.match(/^(\!+)(.+?)$/i) || [];
|
| let booleanNots: string[] = [];
|
| if (booleanMatch[1] && booleanMatch[2]) {
|
| booleanNots = booleanMatch[1].split("");
|
| lookup = booleanMatch[2];
|
| }
|
|
|
| let value = getObjectValue(obj, lookup);
|
| while (booleanNots.length) {
|
| value = !value;
|
| booleanNots.shift();
|
| }
|
| return value;
|
| }
|
|
|
| |
| |
|
|
| function getPrimitiveOrObjValue(stringValue: string | null | undefined, data: any) {
|
| let value;
|
| if (stringValue == null) {
|
| return stringValue;
|
| }
|
| let negate = getNegates(stringValue);
|
| if (negate != null) {
|
| stringValue = stringValue.replace(/^\!+/, "");
|
| }
|
| try {
|
| const cleanedStringValue = stringValue.replace(/^'(.*)'$/, '"$1"');
|
| value = JSON.parse(cleanedStringValue);
|
| } catch (e) {
|
| value = getObjValue(stringValue, data);
|
| }
|
| value = negate !== null ? (negate === 1 ? !value : !!value) : value;
|
| return value;
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| function getNegates(stringValue: string): number | null {
|
| let negate = null;
|
| let negateMatches = stringValue.match(/^(\!+)(.*)/);
|
| if (negateMatches && negateMatches.length >= 3) {
|
| negate = negateMatches[1]!.length % 2;
|
| }
|
| return negate;
|
| }
|
|
|
| |
| |
| |
| |
|
|
| function getStringComparisonExpression(
|
| bindingPropName: string,
|
| data: any,
|
| ): boolean | null | undefined {
|
| let comparisonMatches = bindingPropName.match(RGX_COMPARISON);
|
| if (!comparisonMatches?.length) {
|
| return null;
|
| }
|
| let a = getPrimitiveOrObjValue(comparisonMatches[1]!, data);
|
| let b = getPrimitiveOrObjValue(comparisonMatches[3]!, data);
|
| let c = comparisonMatches[2];
|
| let value = (() => {
|
| switch (c) {
|
| case "===":
|
| return a === b;
|
| case "==":
|
| return a == b;
|
| case "<=":
|
| return a <= b;
|
| case ">=":
|
| return a >= b;
|
| case "!==":
|
| return a !== b;
|
| case "!=":
|
| return a != b;
|
| case "<":
|
| return a < b;
|
| case ">":
|
| return a > b;
|
| default:
|
| return undefined;
|
| }
|
| })();
|
| return value;
|
| }
|
|
|
| |
| |
| |
|
|
| function replaceTplElementWithChildren(
|
| tplEl: HTMLElement,
|
| fragOrElOrEls: DocumentFragment | HTMLElement | Array<DocumentFragment | HTMLElement>,
|
| ) {
|
| const els = Array.isArray(fragOrElOrEls) ? fragOrElOrEls : [fragOrElOrEls];
|
| tplEl.replaceWith(...els);
|
|
|
| const numOfChildren = Array.isArray(fragOrElOrEls)
|
| ? fragOrElOrEls.length
|
| : fragOrElOrEls.childElementCount;
|
| if (numOfChildren === 1) {
|
| const firstChild = Array.isArray(fragOrElOrEls)
|
| ? fragOrElOrEls[0]
|
| : fragOrElOrEls.firstElementChild;
|
| if (firstChild instanceof Element) {
|
| if (tplEl.className.length) {
|
| firstChild.className += ` ${tplEl.className}`;
|
| }
|
| let attr = tplEl.getAttribute("data");
|
| if (attr) {
|
| firstChild.setAttribute("data", attr);
|
| }
|
| attr = tplEl.getAttribute(CONFIG.attrBind);
|
| if (attr) {
|
| firstChild.setAttribute(CONFIG.attrBind, attr);
|
| }
|
| }
|
| }
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| function getValueForBinding(bindingPropName: string, context: BindingContext) {
|
| console.log("getValueForBinding", bindingPropName, context);
|
| const data = context.data;
|
| let stringTemplate = null;
|
| let stringTemplates = /^(.*?)\`([^\`]*)\`$/.exec(bindingPropName.trim());
|
| if (stringTemplates?.length === 3) {
|
| bindingPropName = stringTemplates[1]!;
|
| stringTemplate = stringTemplates[2];
|
| }
|
| let value = null;
|
|
|
| let hadALogicalOp = false;
|
| const opsToValidation = new Map([
|
| [/\s*\?\?\s*/, (v: any) => v != null],
|
| [/\s*\|\|\s*/, (v: any) => !!v],
|
| [/\s*\&\&\s*/, (v: any) => !v],
|
| ]);
|
| for (const [op, fn] of opsToValidation.entries()) {
|
| if (bindingPropName.match(op)) {
|
| hadALogicalOp = true;
|
| const bindingPropNames = bindingPropName.split(op);
|
| for (const propName of bindingPropNames) {
|
| value = getValueForBindingPropName(propName, context);
|
| if (fn(value)) {
|
| break;
|
| }
|
| }
|
| break;
|
| }
|
| }
|
|
|
| if (!hadALogicalOp) {
|
| value = getValueForBindingPropName(bindingPropName, context);
|
| }
|
|
|
| return stringTemplate && value != null
|
| ? stringTemplate.replace(/\$\{value\}/g, String(value))
|
| : value;
|
| }
|
|
|
| |
| |
|
|
| function getValueForBindingPropName(bindingPropName: string, context: BindingContext) {
|
| const data = context.data;
|
| let negate = getNegates(bindingPropName);
|
| if (negate != null) {
|
| bindingPropName = bindingPropName.replace(/^\!+/, "");
|
| }
|
| let value;
|
| RGX_COMPARISON.lastIndex = 0;
|
| if (RGX_COMPARISON.test(bindingPropName)) {
|
| value = getStringComparisonExpression(bindingPropName, data);
|
| } else if (RGX_BIND_FN_LENGTH.test(bindingPropName)) {
|
| bindingPropName = RGX_BIND_FN_LENGTH.exec(bindingPropName)![1]!;
|
| value = getPrimitiveOrObjValue(bindingPropName, data);
|
| value = (value && value.length) || 0;
|
| } else if (RGX_BIND_FN_FORMAT.test(bindingPropName)) {
|
| let matches = RGX_BIND_FN_FORMAT.exec(bindingPropName);
|
| bindingPropName = matches![1]!;
|
| value = getPrimitiveOrObjValue(bindingPropName, data);
|
| value = matches![2]!.replace(/^['"]/, "").replace(/['"]$/, "").replace(/\$1/g, value);
|
| } else if (RGX_BIND_FN_CALL.test(bindingPropName)) {
|
| console.log("-----");
|
| console.log(bindingPropName);
|
| let matches = RGX_BIND_FN_CALL.exec(bindingPropName);
|
| const functionName = matches![1]!;
|
| const maybeDataName = matches![2] ?? null;
|
| value = getPrimitiveOrObjValue(maybeDataName, data);
|
| console.log(functionName, maybeDataName, value);
|
|
|
| if (typeof value?.[functionName] === "function") {
|
| value = value[functionName](value, data, context.currentElement, context.contextElement);
|
| } else if (typeof data?.[functionName] === "function") {
|
| value = data[functionName](value, data, context.currentElement, context.contextElement);
|
| } else if (typeof (context.currentElement as any)?.[functionName] === "function") {
|
| value = (context.currentElement as any)[functionName](
|
| value,
|
| data,
|
| context.currentElement,
|
| context.contextElement,
|
| );
|
| } else if (typeof (context.contextElement as any)?.[functionName] === "function") {
|
| value = (context.contextElement as any)[functionName](
|
| value,
|
| data,
|
| context.currentElement,
|
| context.contextElement,
|
| );
|
| } else {
|
| console.error(
|
| `No method named ${functionName} on data or element instance. Just calling regular value.`,
|
| );
|
| value = getPrimitiveOrObjValue(bindingPropName, data);
|
| }
|
| } else {
|
| value = getPrimitiveOrObjValue(bindingPropName, data);
|
| }
|
|
|
| if (value !== undefined) {
|
| value = negate !== null ? (negate === 1 ? !value : !!value) : value;
|
| }
|
| return value;
|
| }
|
|
|
| |
| |
| |
| |
| |
|
|
| function removeBindingAttributes(
|
| elOrEls: DocumentFragment | HTMLElement | HTMLElement[],
|
| deep = false,
|
| ) {
|
| flattenArray(elOrEls || []).forEach((el) => {
|
| el.removeAttribute(CONFIG.attrBind);
|
| const innerBinds = dom.queryAll(`:scope [${CONFIG.attrBind}]`, el);
|
|
|
| const innerTplBinds = deep ? [] : dom.queryAll(`:scope [data-tpl] [${CONFIG.attrBind}]`);
|
|
|
| innerBinds.forEach((el) => {
|
| if (deep || !innerTplBinds.includes(el)) {
|
| el.removeAttribute(CONFIG.attrBind);
|
| }
|
| });
|
| });
|
| }
|
|
|
| const templateCache: {[key: string]: TemplateData} = {};
|
|
|
| |
| |
|
|
| export function checkKey(key: string) {
|
| return !!templateCache[cleanKey(key)];
|
| }
|
|
|
| |
| |
| |
| |
|
|
| export function register(
|
| key: string,
|
| htmlOrElement: string | Element | null = null,
|
| preProcessScript?: (data: any) => void,
|
| ) {
|
| key = cleanKey(key);
|
| if (templateCache[key]) {
|
| return templateCache[key];
|
| }
|
|
|
| let fragment: DocumentFragment | null = null;
|
| if (typeof htmlOrElement === "string") {
|
| const frag = document.createDocumentFragment();
|
| if (htmlOrElement.includes("<")) {
|
| const html = htmlOrElement.trim();
|
| const htmlParentTag =
|
| (html.startsWith("<tr") && "tbody") ||
|
| (/^<t(body|head|foot)/i.test(html) && "table") ||
|
| (/^<t(d|h)/i.test(html) && "tr") ||
|
| "div";
|
| const temp = document.createElement(htmlParentTag);
|
| temp.innerHTML = html;
|
| for (const child of temp.children) {
|
| frag.appendChild(child);
|
| }
|
| } else {
|
| frag.appendChild(dom.createElement(htmlOrElement));
|
| }
|
| fragment = frag;
|
| } else if (htmlOrElement instanceof Element) {
|
| const element = htmlOrElement as HTMLElement;
|
| const tag = element.nodeName.toLowerCase();
|
| if (tag === "template" && (element as HTMLTemplateElement).content) {
|
| fragment = (element as HTMLTemplateElement).content;
|
| } else {
|
| throw Error("Non-template element not handled");
|
| }
|
| } else if (!htmlOrElement) {
|
| let element = dom.query(`template[id="${key}"],template[data-id="${key}"]`);
|
| if (element && (element as HTMLTemplateElement).content) {
|
| fragment = (element as HTMLTemplateElement).content;
|
| } else {
|
| throw Error("Non-template element not handled");
|
| }
|
| }
|
|
|
| if (fragment) {
|
| templateCache[key] = {
|
| fragment,
|
| preProcessScript: preProcessScript || EMPTY_PREPROCESS_FN,
|
| };
|
| }
|
| return templateCache[key] || null;
|
| }
|
|
|
| export function getPreProcessScript(keyOrEl: string | Element) {
|
| if (typeof keyOrEl === "string") {
|
| if (!templateCache[keyOrEl]) {
|
| throw Error(`Template key does not exist ${keyOrEl}`);
|
| }
|
| return templateCache[keyOrEl].preProcessScript;
|
| }
|
| if (keyOrEl instanceof Element) {
|
| const tpl = keyOrEl.getAttribute("data-tpl") || "";
|
| return templateCache[tpl]?.preProcessScript || EMPTY_PREPROCESS_FN;
|
| }
|
| return EMPTY_PREPROCESS_FN;
|
| }
|
|
|
|
|
| export function getTemplateFragment(key: string): DocumentFragment {
|
| key = cleanKey(key);
|
| if (!checkKey(key)) {
|
| register(key);
|
| }
|
| let templateData = templateCache[key];
|
| if (templateData && templateData.fragment) {
|
| let imported: DocumentFragment;
|
| if (document.importNode) {
|
| imported = document.importNode(templateData.fragment, true);
|
| } else {
|
| imported = templateData.fragment.cloneNode(true) as DocumentFragment;
|
| }
|
| (imported as any).__templateid__ = key;
|
| return imported;
|
| } else {
|
| throw new Error("Ain't no template called " + key + " (" + typeof templateCache[key] + ")");
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| export function inflate(
|
| nodeOrKey: string | HTMLElement | DocumentFragment,
|
| templateData: any = null,
|
| inflateOptions: InflateOptions = {},
|
| ) {
|
| let node = nodeOrKey as HTMLElement | DocumentFragment;
|
| if (typeof node === "string") {
|
| node = getTemplateFragment(node);
|
| }
|
| if (node) {
|
|
|
|
|
|
|
|
|
| const els = dom.queryAll("[data-template], [data-templateid], [template]", node);
|
| for (const child of els) {
|
|
|
| let className = child.className || null;
|
| let childTemplateId = localAssertNotFalsy(
|
| child.getAttribute("data-template") ||
|
| child.getAttribute("data-templateid") ||
|
| child.getAttribute("template"),
|
| "No child template id provided.",
|
| );
|
|
|
| const dataAttribute = child.getAttribute("data") || "";
|
|
|
| const childData = (dataAttribute && getObjValue(dataAttribute, templateData)) || templateData;
|
| const tplsInflateOptions = Object.assign({}, inflateOptions);
|
|
|
|
|
| if (tplsInflateOptions.skipInit != null) {
|
| tplsInflateOptions.skipInit = true;
|
| }
|
| let tpls = localAssertNotFalsy(
|
| inflate(childTemplateId, childData, tplsInflateOptions),
|
| `No template inflated from ${childTemplateId}.`,
|
| );
|
| tpls = !Array.isArray(tpls) ? [tpls] : tpls;
|
| if (className) {
|
| for (const tpl of tpls) {
|
| tpl.classList.add(className);
|
| }
|
| }
|
| if (child.nodeName.toUpperCase() === "TPL") {
|
| replaceTplElementWithChildren(child, tpls);
|
| } else {
|
| child.append(...tpls);
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| child.remove();
|
| }
|
|
|
| let children: HTMLElement[] = [];
|
| for (const child of node.children) {
|
| let tplAttributes = (child.getAttribute("data-tpl") || "").split(" ");
|
| if (!tplAttributes.includes((node as any).__templateid__)) {
|
| tplAttributes.push((node as any).__templateid__);
|
| }
|
| child.setAttribute("data-tpl", tplAttributes.join(" ").trim());
|
| children.push(child as HTMLElement);
|
| }
|
| let childOrChildren = children.length === 1 ? children[0]! : children;
|
| if (!inflateOptions.skipInit) {
|
| init(childOrChildren, templateData, inflateOptions.bindOptions);
|
| }
|
| return childOrChildren;
|
| }
|
| return null;
|
| }
|
|
|
| export function inflateSingle(
|
| nodeOrKey: string | HTMLElement,
|
| scriptData: any = null,
|
| bindOptions: InflateOptions = {},
|
| ): HTMLElement {
|
| const inflated = localAssertNotFalsy(inflate(nodeOrKey, scriptData, bindOptions));
|
| return Array.isArray(inflated) ? inflated[0]! : inflated;
|
| }
|
|
|
| |
| |
| |
| |
|
|
| export function inflateOnce(
|
| nodeOrKey: string | HTMLElement | DocumentFragment,
|
| templateData: any = null,
|
| inflateOptions: InflateOptions = {},
|
| ) {
|
| let children = inflate(nodeOrKey, templateData, inflateOptions);
|
| children && removeBindingAttributes(children, false);
|
| return children;
|
| }
|
|
|
| export function inflateSingleOnce(
|
| nodeOrKey: string | HTMLElement,
|
| scriptData: any = null,
|
| bindOptions: InflateOptions = {},
|
| ): HTMLElement {
|
| const inflated = inflate(nodeOrKey, scriptData, bindOptions) || [];
|
| removeBindingAttributes(inflated, false);
|
| return Array.isArray(inflated) ? inflated[0]! : inflated;
|
| }
|
|
|
| |
| |
| |
| |
|
|
| export function init(els: HTMLElement | HTMLElement[], data: any, bindOptions: BindOptions = {}) {
|
| (!els ? [] : els instanceof Element ? [els] : els).forEach((el) => {
|
| const dataTplAttr = el.getAttribute("data-tpl");
|
| if (dataTplAttr) {
|
| const tpls = dataTplAttr.split(" ");
|
| tpls.forEach((tpl) => {
|
|
|
|
|
|
|
| const dataAttribute = el.getAttribute("data") || "";
|
| const childData = (dataAttribute && getObjValue(dataAttribute, data)) || data;
|
| bind(el, childData, bindOptions);
|
| });
|
| } else {
|
| bind(el, data, bindOptions);
|
| }
|
| });
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| export function bind(
|
| elOrEls: HTMLElement | ShadowRoot | HTMLElement[],
|
| data: any = {},
|
| bindOptions: BindOptions = {},
|
| ) {
|
| if (elOrEls instanceof HTMLElement) {
|
| data = getPreProcessScript(elOrEls)({...data});
|
| }
|
|
|
|
|
|
|
| if (typeof data !== "object") {
|
| data = {value: data};
|
| if (
|
| elOrEls instanceof HTMLElement &&
|
| elOrEls.children.length === 0 &&
|
| !elOrEls.getAttribute(CONFIG.attrBind)
|
| ) {
|
| dom.setAttributes(elOrEls, {[CONFIG.attrBind]: "value"});
|
| }
|
| }
|
|
|
|
|
| let passedEls = !Array.isArray(elOrEls) ? [elOrEls] : elOrEls;
|
| for (const el of passedEls) {
|
|
|
|
|
| const conditionEls = toArray(dom.queryAll(`[${CONFIG.attrIf}]`, el));
|
| const contextElement =
|
| bindOptions.contextElement ?? (el instanceof ShadowRoot ? (el.host as HTMLElement) : el);
|
|
|
| for (const conditionEl of conditionEls) {
|
| getValueForBindingPropName;
|
|
|
| let isTrue = getValueForBinding(conditionEl.getAttribute(CONFIG.attrIf), {
|
| data,
|
| contextElement: contextElement,
|
| currentElement: conditionEl,
|
| });
|
| conditionEl.setAttribute(CONFIG.attrIfIs, String(!!isTrue));
|
| }
|
|
|
| let toBindEls = toArray(
|
| dom.queryAll(
|
| `:not([${CONFIG.attrIfIs}="false"]) [${CONFIG.attrBind}]:not([data-tpl]):not([${CONFIG.attrIfIs}="false"])`,
|
| el,
|
| ),
|
| );
|
| if (el instanceof HTMLElement && el.getAttribute(CONFIG.attrBind)) {
|
| toBindEls.unshift(el);
|
| }
|
|
|
| if (toBindEls.length) {
|
|
|
|
|
| let innerBindsElements = dom.queryAll(
|
| `:scope [data-tpl] [${CONFIG.attrBind}], :scope [data-autobind="false"] [${CONFIG.attrBind}]`,
|
| el,
|
| );
|
| toBindEls = toBindEls.filter((maybeBind) => !innerBindsElements.includes(maybeBind));
|
| toBindEls.forEach((child) => {
|
|
|
|
|
| RGX_BIND_DECLARATIONS.lastIndex = 0;
|
| let bindings = [];
|
| let bindingMatch;
|
| while (
|
| (bindingMatch = RGX_BIND_DECLARATIONS.exec(
|
| child.getAttribute(CONFIG.attrBind).replace(/\s+/, " ").trim(),
|
| )) !== null
|
| ) {
|
| bindings.push([bindingMatch[1], bindingMatch[2]]);
|
| }
|
|
|
|
|
|
|
| bindings.forEach((bindings) => {
|
|
|
| let bindingDataProperty = localAssertNotFalsy(bindings.shift());
|
| let bindingFields = ((bindings.length && bindings[0]) || "default")
|
| .trim()
|
| .replace(/^\[(.*?)\]$/i, "$1")
|
| .split(",");
|
|
|
| let value = getValueForBinding(bindingDataProperty, {
|
| data,
|
| contextElement: contextElement,
|
| currentElement: child,
|
| });
|
| if (value === undefined) {
|
| if (bindOptions.onlyDefined === true) {
|
| return;
|
| } else {
|
| value = null;
|
| }
|
| }
|
| bindingFields.forEach((field) => {
|
| if (field.startsWith("style.")) {
|
| let stringVal = String(value);
|
| if (
|
| value &&
|
| !stringVal.includes("url(") &&
|
| stringVal !== "none" &&
|
| (field.includes("background-image") || stringVal.startsWith("http"))
|
| ) {
|
| value = `url(${value})`;
|
| }
|
| dom.setStyle(child, field.replace("style.", ""), value);
|
|
|
|
|
| } else if (field.startsWith("el.")) {
|
| if (field === "el.remove") {
|
| if (value === true) {
|
| child.remove();
|
| }
|
| } else if (field === "el.toggle") {
|
| dom.setStyle(child, "display", value === true ? "" : "none");
|
| } else if (field.startsWith("el.classList.toggle")) {
|
| const cssClass = field.replace(/el.classList.toggle\(['"]?(.*?)['"]?\)/, "$1");
|
| child.classList.toggle(cssClass, !!value);
|
| }
|
|
|
|
|
| } else if (RGX_BIND_FN_TEMPLATE_OR_ELEMENT.test(field)) {
|
| dom.empty(child);
|
| let elementOrTemplateName = RGX_BIND_FN_TEMPLATE_OR_ELEMENT.exec(field)![1]!;
|
| if (Array.isArray(value) || value instanceof Set) {
|
| const arrayVals = toArray(value);
|
| let isElement = RGX_BIND_FN_ELEMENT.test(field);
|
| let frag = document.createDocumentFragment();
|
| arrayVals.forEach((item, index) => {
|
| let itemData: {};
|
| if (typeof item === "object") {
|
| itemData = Object.assign({$index: index}, item);
|
| } else {
|
| itemData = {$index: index, value: item};
|
| }
|
|
|
| const els = bindToElOrTemplate(elementOrTemplateName, itemData);
|
| frag.append(...els);
|
| });
|
|
|
| if (child.nodeName.toUpperCase() === "TPL") {
|
| replaceTplElementWithChildren(child, frag);
|
| } else {
|
| dom.empty(child).appendChild(frag);
|
| }
|
| } else if (value) {
|
| const els = bindToElOrTemplate(elementOrTemplateName, value);
|
|
|
| if (child.nodeName.toUpperCase() === "TPL") {
|
| replaceTplElementWithChildren(child, els);
|
| } else {
|
| child.append(...els);
|
| }
|
| }
|
| } else {
|
| dom.setAttributes(child, {[field]: value});
|
| }
|
| });
|
| });
|
| });
|
| }
|
|
|
|
|
|
|
| if (bindOptions.singleScoped !== true) {
|
| let toInitEls = toArray(el.querySelectorAll(":scope *[data-tpl]"));
|
| if (toInitEls.length) {
|
|
|
| let innerInits = dom.queryAll(
|
| ':scope *[data-tpl] *[data-tpl], :scope [data-autobind="false"] [data-tpl]',
|
| el,
|
| );
|
| toInitEls = toInitEls.filter((maybeInitEl) => {
|
|
|
| if (innerInits.includes(maybeInitEl)) {
|
| return false;
|
| }
|
|
|
|
|
| let tplKey = maybeInitEl.getAttribute("data-tpl");
|
| if (data && (data[tplKey] || data[tplKey.replace("tpl:", "")])) {
|
| return true;
|
| }
|
|
|
|
|
|
|
| return maybeInitEl.getAttribute("data-autobind") !== "false";
|
| });
|
| toInitEls.forEach((toInitEl) => {
|
| var tplKey = toInitEl.getAttribute("data-tpl");
|
| init(toInitEl, (data && (data[tplKey] || data[tplKey.replace("tpl:", "")])) || data);
|
| });
|
| }
|
| }
|
| }
|
| }
|
|
|
| function bindToElOrTemplate(elementOrTemplateName: string, data: any) {
|
| let el: DocumentFragment | HTMLElement | HTMLElement[] | null =
|
| getTemplateFragment(elementOrTemplateName);
|
| if (!el) {
|
| el = dom.createElement(elementOrTemplateName, data);
|
| } else {
|
|
|
| el = inflateOnce(el, data, {skipInit: false});
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| const els = (Array.isArray(el) ? el : [el]).filter((el) => !!el) as HTMLElement[];
|
| els.forEach((el) => {
|
| el.removeAttribute("data-tpl");
|
| let toBindEls = dom.queryAll("[data-tpl]", el);
|
|
|
| let innerBindsElements = dom.queryAll(
|
| `:scope [data-tpl] [${CONFIG.attrBind}], :scope [data-autobind="false"] [${CONFIG.attrBind}]`,
|
| el,
|
| );
|
| toBindEls = toBindEls.filter((maybeBind) => !innerBindsElements.includes(maybeBind));
|
| toBindEls.forEach((c) => c.removeAttribute("data-tpl"));
|
| });
|
| return els;
|
| }
|
|
|