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);
}
/**