diff --git a/ui/wolf.lib.html b/ui/wolf.lib.html index 5c54047..1576753 100644 --- a/ui/wolf.lib.html +++ b/ui/wolf.lib.html @@ -1,17 +1,41 @@ - + + icon text + class + click + + + + + + + icon + text + click - + diff --git a/ui/wolf.ui.js b/ui/wolf.ui.js index 5b78282..b3afd53 100644 --- a/ui/wolf.ui.js +++ b/ui/wolf.ui.js @@ -1,4 +1,4 @@ -/* wolf.ui 0.6, UI components for wolf, a lightweight framework for web page creation. +/* wolf.ui 0.7, UI components for wolf, a lightweight framework for web page creation. * Copyright 2020 XWolfOverride (under lockdown) * * Licensed under the MIT License @@ -166,22 +166,116 @@ /** * Create the base element needed for the extensible UI and control building system */ - function InitExtensibleUI() { + function InitControlDefinitions() { + + /** + * Return the control instance attribute values + * @param {*} controller controller object of control definition + * @param {*} template template of control usage + */ + function getControlAttributesTable(controller, template) { + var values = {}; + // Control definition defaults + for (var k in controller) { + var attr = controller[k]; + if (attr && attr.default) + values[k] = attr.default; + } + // UI usage values + for (var k in template.w) { + var attr = template.w[k]; + values[k] = attr; + } + return values; + } + + /** + * Creates the attribute handler that links the definition value/event links to the control usage template + * @param {*} ui List of UI templates defined in control usage + */ + function getTemplateAttributeHandler(ui) { + var attrlinks = {}; + function preHookAdd(id, value) { + var hl = attrlinks[id]; + if (!hl) + hl = attrlinks[id] = []; + hl.push(value); + } + function preHookUI(templ) { + if (templ.c) + templ.c.forEach(preHookUI); + if (templ.value && templ.value[0] == "$") + preHookAdd(templ.value, { t: templ }); + for (var k in templ.a) { + var attr = templ.a[k]; + if (attr && attr[0] == "$") + preHookAdd(attr, { t: templ, a: k }); + } + for (var k in templ.e) { + var event = templ.e[k]; + if (event && event[0] == "$") + preHookAdd(event, { t: templ, e: k }); + } + } + for (var k in ui) + ui[k].forEach(templ => { + preHookUI(templ); + }); + + function forEachOf(template, cb) { + for (var k in attrlinks) { + var alink = attrlinks[k]; + alink.forEach(alink => { + if (alink.t == template) + cb(k, alink); + }); + } + } + return { forEachOf: forEachOf }; + } + + /** + * Generates the DOM of the control based on the script render method + * @param {*} ext ctor extended info + */ + function renderControlDOM(ext) { + var tux = ext.customController.render(); + var ux = []; + if (!Array.isArray(tux)) + tux = [tux]; + for (var i in tux) + ux = ux.concat(UI.instanceTemplate(tux[i], ext)); + return ux; + } + + /** + * Create event proxy for linked event call + * @param {string} method Controller method name + * @param {string} ename Event name + */ + function eventProxy(method, ename) { + return function (element, event) { + var ctrl = element.getController(); + var evtMethod = ctrl[method]; + if (!evtMethod) + throw new Error(`Method '${method}' not found for event '${ename}'.`); + evtMethod(element, event); + } + } + + /** + * Register wolf:control as control definition structure + */ UI.registerElement("control", { - init: template => { - }, - postInit: template => { + postInit: function (template) { var id = template.w.id; var controller = {}; var ui = {}; - var data = []; - var script; + var scriptFactory; - function _ui(name) { - return ui[name]; - } - - template.c.forEach(c => { + // Read definition + for (var i = 0; i < template.c.length; i++) { + var c = template.c[i]; switch (c.type) { case "attr": { if (c.c.length != 1 || c.c[0].type) @@ -199,168 +293,128 @@ attr.mandatory = c.a.mandatory == "true"; if (c.a.default) attr.default = c.a.default; - data.push(name); break; } case "event": { if (c.c.length != 1 || c.c[0].type) throw new Error("Control definition wolf:" + id + " event name missing or type error"); var name = c.c[0].value; + if (controller[name]) + throw new Error("Control definition wolf:" + id + " events and values can not share the same name (" + name + ")."); if (controller["event:" + name]) throw new Error("Control definition wolf:" + id + " event '" + name + "' already defined."); - controller["event:" + name] = { bindable: false, mandatory: false, event: true } + controller["event:" + name] = { bindable: false, mandatory: false }; break; } case "ui": { - var id = c.a.id || "·"; - if (ui[id]) - throw new Error("Control definition wolf:" + id + " ui " + (id == "·" ? "" : "'" + id + "'") + " already defined."); - ui[id] = c.c; + var uid = c.a.id || "·"; + if (ui[uid]) + throw new Error("Control definition wolf:" + id + " ui " + (uid == "·" ? "" : "'" + uid + "'") + " already defined."); + ui[uid] = c.c; break; } case "script": { - var scriptsrc = c.c[0].value; - if (scriptsrc && (typeof scriptsrc != "string" || scriptsrc.indexOf("use strict") < 1)) + if (scriptFactory) + throw new Error("Control definition wolf:" + id + " script already defined."); + var script = c.c[0].value; + if (script && (typeof script != "string" || script.indexOf("use strict") < 1)) throw new Error("Control definition wolf:" + id + " script 'use strict'; mandatory"); - var inc = new Function('ui', `${scriptsrc};\n//# sourceURL=_CONTROL_${id}`); - script = inc(_ui); + scriptFactory = new Function('control', `${script};\n//# sourceURL=_CONTROL_${id}`); break; } default: throw new Error("Control definition wolf:" + id + " " + c.type + " not allowed here."); } - }); - // Standarize script - if (!script) - script = {} - if (!script.render) - script.render = () => { - var u = ui["·"]; - if (u) - return u; - for (var k in ui)//return first if any - return ui[k]; - } + } - var prehooks = { - } - function preHookAdd(id, value) { - var hl = prehooks[id]; - if (!hl) - hl = prehooks[id] = []; - hl.push(value); - } - function preHookUI(templ) { - if (templ.c) - templ.c.forEach(preHookUI); - if (templ.value && templ.value[0] == "$") - preHookAdd(templ.value, { t: templ }); - for (var k in templ.a) { - var attr = templ.a[k]; - if (attr && attr[0] == "$") - preHookAdd(attr, { t: templ, a: k }); - } - } - for (var k in ui) - ui[k].forEach(templ => preHookUI(templ)); - + // Preprocess hooks + var alinkHanlder = getTemplateAttributeHandler(ui); // Controller logics - controller.init = script.init; controller.ctor = (template, ext) => { - // Generate data sheet - var values = {}; - // -- defaults - for (var k in controller) { - var attr = controller[k]; - if (attr && attr.default) - values[k] = attr.default; - } - // -- user - for (var k in template.w) { - var attr = template.w[k]; + ext = {}.merge(ext); //Copy of ext instance + // Initialize attributes and script + var values = getControlAttributesTable(controller, template); + var script = scriptFactory ? scriptFactory({ + ui: name => name ? ui[name] : ui["·"], + get: name => { + var data = values[name]; + if (data instanceof D.Binding) + data.getValue(ext.parent); + return data; + } + }) : {}; + + if (!script.render) + script.render = () => { + var u = ui["·"]; + if (u) + return u; + for (var k in ui)//return first if any + return ui[k]; + } + + // Event mirror + for (var k in values) if (k.startsWith("event:")) { - //TODO handle events here? - } else - values[k] = attr; - } + var name = k.substring(6); + script["$" + name] = eventProxy(values["event:" + name], name); + } + ext.customController = script; // Generate DOM - var tux = script.render(); - var ux = []; - if (!Array.isArray(tux)) - tux = [tux]; - for (var i in tux) - ux = ux.concat(UI.instanceTemplate(tux[i], ext)); + var ux = renderControlDOM(ext); - // Scan generated nodes and identify insertion hooks - var hooks = {}; - function hookAdd(k, n, h) { + // Scan generated nodes and set values or link bindings + // TODO: Workaround (use the new ext.onInit event for each control initialization) + function setValue(k, n, h) { if (k && k[0] == "$") k = k.substr(1); - var hl = hooks[k]; - if (!hl) - hl = hooks[k] = []; - if (h.a) - hl.push(data => { - if (data instanceof D.Binding) - data.bindExecutor(n, - null, - null, - read => n.setAttribute(h.a, read({ element: n }, "string")) - ); - else - n.setAttribute(h.a, data); - }); - else - hl.push(data => { - if (data instanceof D.Binding) - data.bindExecutor(n, - null, - null, - read => n.nodeValue = read({ element: n }, "string") - ); - else - n.nodeValue = data; - }); + var data = values[k]; + if (h.e) {// Hook to event + // if (!ext.parent) + // return; + // var pcontroller = ext.parent.getController(); + // if (!pcontroller) + // return; + // data = values["event:" + k]; + // if (!n.controlBase) + // n.controlBase = {}; + // n.controlBase[k] = pcontroller[data]; + } else if (h.a) // Hook to attribute + if (data instanceof D.Binding) + data.bindExecutor(n, + null, + null, + read => n.setAttribute(h.a, read({ element: n }, "string")) + ); + else + n.setAttribute(h.a, data); + else // Hook to text node + if (data instanceof D.Binding) + data.bindExecutor(n, + null, + null, + read => n.nodeValue = read({ element: n }, "string") + ); + else + n.nodeValue = data; } function scanDOM(node) { + node.controlBase = ext.parent; for (var i = 0; i < node.childNodes.length; i++) scanDOM(node.childNodes[i]); - var nt = node.getTemplate(); - for (var k in prehooks) { - var hooks = prehooks[k]; - hooks.forEach(hook => { - if (hook.t == nt) - hookAdd(k, node, hook); - }); - } + alinkHanlder.forEachOf(node.getTemplate(), (k, alink) => { + setValue(k, node, alink); + }) } ux.forEach(node => scanDOM(node)); - function setValue(k, val) { - var hl = hooks[k]; - if (!hl) - return; - for (var i in hl) - hl[i](val); - } - // Set defaults - for (var k in controller) { - var attr = controller[k]; - if (attr && attr.default) - setValue(k, attr.default); - } - // Value set and binding hook - for (var k in values) - setValue(k, values[k]); + return ux; }; - controller.controller = script; //Link script as controller UI.registerElement(id, controller); }, - ctor: (template, ext) => { - }, id: { bindable: false, mandatory: true }, childs: { bindable: false } }); @@ -370,7 +424,8 @@ // ==================== // == Injection // ==================== - InitExtensibleUI(); + InitControlDefinitions(); + wolf.merge({ // UI toast: toast, diff --git a/wolf.js b/wolf.js index 2725135..05b0723 100644 --- a/wolf.js +++ b/wolf.js @@ -1,4 +1,4 @@ -/* wolf 0.6, a lightweight framework for web page creation. +/* wolf 0.7, a lightweight framework for web page creation. * Copyright 2020 XWolfOverride (under lockdown) * * Licensed under the MIT License @@ -694,6 +694,13 @@ }); } + /** + * Bind an external API + * @param {*} node DOM node + * @param {*} fninit Function to execute at binding init + * @param {*} fnread Function to execute at binding element read (write to model) + * @param {*} fnwrite Function to execute at binding element wrtie (read from model) + */ function bindExecutor(node, fninit, fnread, fnwrite) { var fread; var fwrite = fnwrite ? () => { @@ -708,10 +715,19 @@ }); } + /** + * Return the actual value reflected bi the binding for the specified element + * @param {*} element DOM element + */ + function getValue(element) { + return read({ element: element }) + } + this.bindTextNode = bindTextNode; this.bindElementAttribute = bindElementAttribute; this.bindRepeater = bindRepeater; this.bindExecutor = bindExecutor; + this.getValue = getValue; } return { @@ -889,8 +905,9 @@ * @param {*} ext Extended data */ function processElement(element, template, ext) { - var contextPath = ext.contextPath; - delete ext.contextPath; + var contextPath = ext ? ext.contextPath : undefined; + var defaultParent = ext ? ext.parent : undefined; //TODO: UGLY WORKAROUND. default parent is used for parent tree calculation on early stages of control instancing. Avoid the need to have this default parent + var customController = ext ? ext.customController : undefined; /** * Return the associated template to this element @@ -978,7 +995,7 @@ * @param {string} [id] id of element */ function getParent(id) { - var dad = element.parentElement || ext.parent; + var dad = element.parentElement || defaultParent; if (id) { while (dad && dad != document.documentElement) { if (dad.getTemplate && dad.getTemplate().type == "#app") @@ -1085,17 +1102,26 @@ } // process events function installEvent(event, method) { - element.addEventListener(event, (evt) => { - var ctrl = element.getController(); - var evtMethod = ctrl[method]; - if (!evtMethod) - throw new Error(`event '${k}' method '${method}' not found.`); - evtMethod(element, evt); - }); + if (customController) + element.addEventListener(event, (evt) => { + var evtMethod = customController[method]; + if (!evtMethod) + throw new Error(`Method '${method}' not found for event '${k}' in custom controller.`); + evtMethod(element, evt); + }); + else + element.addEventListener(event, (evt) => { + var ctrl = element.getController(); + var evtMethod = ctrl[method]; + if (!evtMethod) + throw new Error(`Method '${method}' not found for event '${k}'.`); + evtMethod(element, evt); + }); } if (template.e) for (var k in template.e) installEvent(k, template.e[k]); + ext.onInit && ext.onInit(element, template); } /**