diff --git a/readme.md b/readme.md index 6bb77a3..c0724f5 100644 --- a/readme.md +++ b/readme.md @@ -1,28 +1,24 @@ Template -=========== +=== +JS Template engine Features ------------- +--- * Variables (String, Number, Array, Object) -* Loops (for Array only) +* Loops * Conditions -* Functions (in progress) +* Functions * Event based * Smarty'ish syntax - -Todo ------------- -* [x] Includes +* ES6 Compliant +* Template inclusion Dependencies ------------- -* [Class](https://github.com/arno06/M4/blob/master/src/Class.js) -* [Event](https://github.com/arno06/M4/blob/master/src/Event.js) -* [EventDispatcher](https://github.com/arno06/M4/blob/master/src/EventDispatcher.js) -* [Request](http://github.com/arno06/Request) +--- +* [Request](http://github.com/arno06/Request) : Async template loading Example ------------- +--- Html template definition : ```html @@ -30,30 +26,29 @@ Html template definition : Template.js - Sample - - +

Template.js - Sample

- - + + ``` @@ -62,46 +57,48 @@ Javascript part (js/Sample.js) : ```js function init() { - var fooFunc = function(){return "bar"}; - var table = ["This", "must", "work"]; - var me = {"name":"Template.js", "mood":"pretty good"}; - var tpl = new Template("firstTemplate"); - tpl.assign("var1", "world"); - tpl.assign("me", me); - tpl.assign("myTable", table); - tpl.setFunction("fooFunc", fooFunc); - tpl.addEventListener(TemplateEvent.RENDER_INIT, tplRenderInitHandler, false); - tpl.addEventListener(TemplateEvent.RENDER_COMPLETE, tplRenderCompleteHandler, false); - tpl.addEventListener(TemplateEvent.RENDER_COMPLETE_LOADED, tplRenderCompleteLoadedHandler, false); - tpl.render("#holder"); + var fooFunc = function(named_var){return "bar "+named_var;}; + var table = ["This", "must", "work"]; + var me = {"name":"Template.js", "mood":"pretty good"}; + var tpl = new Template("firstTemplate"); + tpl.assign("var1", "world"); + tpl.assign("me", me); + tpl.assign("myTable", table); + tpl.setFunction("fooFunc", fooFunc); + tpl.addEventListener(TemplateEvent.RENDER_INIT, tplRenderInitHandler, false); + tpl.addEventListener(TemplateEvent.RENDER_COMPLETE, tplRenderCompleteHandler, false); + tpl.addEventListener(TemplateEvent.RENDER_COMPLETE_LOADED, tplRenderCompleteLoadedHandler, false); + tpl.render("#holder"); } window.addEventListener("load", init, false); function tplRenderInitHandler(e) { - console.log("Sample.js : init"); + console.log("Sample.js : init"); } function tplRenderCompleteHandler(e) { - //Ready to display result - console.log("Sample.js : complete"); + //Ready to display result + console.log("Sample.js : complete"); } function tplRenderCompleteLoadedHandler(e) { - //Ready to display & everything is loaded (images) - console.log("Sample.js : complete & loaded"); + //Ready to display & everything is loaded (images) + console.log("Sample.js : complete & loaded"); } ``` API Reference -------------- +--- ### Template -#### Template(pIdTemplate) + +#### Template(pIdTemplate, pData) Constructor - Instanciate a Template Object * pIdTemplate (*string*) : Script template's id within DOM +* pData (*object*) : Default assigned data #### assign(pName, pValue) Variable asignation method @@ -123,9 +120,9 @@ Function definition method * pName (*string*) : Function's name * pFunction (*function*) : function to execute each time template refer to it ```js -tpl.setFunction("round", function(pValue) +tpl.setFunction("round", function(value) { - return Math.round(pValue); + return Math.round(value); }); ``` @@ -145,6 +142,7 @@ tpl.setFunction("round", function(pValue) (inherited) ### TemplateEvent + #### RENDER_INIT Event triggered once rendering initialized diff --git a/samples/01_basics.html b/samples/01_basics.html index cc8194d..0530c5f 100644 --- a/samples/01_basics.html +++ b/samples/01_basics.html @@ -2,8 +2,7 @@ JS Templating - - + + + + + +

Template.js - Sample

+ \ No newline at end of file diff --git a/samples/06_advance_testing.html b/samples/06_advance_testing.html index ae6f6ca..37851d0 100644 --- a/samples/06_advance_testing.html +++ b/samples/06_advance_testing.html @@ -2,8 +2,17 @@ Template.js - Sample - + \ No newline at end of file diff --git a/samples/07_puppy.html b/samples/07_databinding.html similarity index 69% rename from samples/07_puppy.html rename to samples/07_databinding.html index 16c2886..44e03fb 100644 --- a/samples/07_puppy.html +++ b/samples/07_databinding.html @@ -3,18 +3,18 @@ Puppy - - + +
-
+
diff --git a/src/Puppy.js b/src/Puppy.js deleted file mode 100644 index 45b6daf..0000000 --- a/src/Puppy.js +++ /dev/null @@ -1,78 +0,0 @@ -var Puppy = (function(){ - var sources = {}; - var events = { - UPDATED_DATA: 'evt_updated_data' - }; - - function Container(pElement, pDataSource, pTemplate) - { - this.element = pElement; - this.source = pDataSource; - this.template = pTemplate; - this.template.addEventListener(TemplateEvent.RENDER_COMPLETE, this.dispatchEvent.proxy(this)); - this.source.addEventListener(events.UPDATED_DATA, this._dataUpdated.proxy(this), false); - this._dataUpdated(); - } - - Class.define(Container, [EventDispatcher], { - _dataUpdated:function() - { - this.template._content = this.source.getData(); - this.element.innerHTML = ''; - this.template.render(this.element); - } - }); - - - function DataSource(pData) - { - this._data = pData; - this.dispatchEvent(new Event(events.UPDATED_DATA)); - } - - Class.define(DataSource, [EventDispatcher], { - setData:function(pData) - { - this._data = pData; - this.dispatchEvent(new Event(events.UPDATED_DATA)); - }, - setValue:function(pName, pData) - { - this._data[pName] = pData; - this.dispatchEvent(new Event(events.UPDATED_DATA)); - }, - getData:function() - { - return this._data; - } - }); - - if(!NodeList.prototype.forEach) - NodeList.prototype.forEach = Array.prototype.forEach; - - - function defineContainer(pElement) - { - if(!pElement.getAttribute('data-source') || !pElement.getAttribute('data-template')) - return false; - var src = pElement.getAttribute('data-source'); - var tpl = pElement.getAttribute('data-template'); - if(!sources[src]) - return false; - return new Container(pElement, sources[src], new Template(tpl)); - } - - function defineSource(pName, pData) - { - pName = 'Puppy.'+pName; - sources[pName] = new DataSource(pData); - return sources[pName]; - } - - return { - define: { - container:defineContainer, - source:defineSource - } - } -})(); \ No newline at end of file diff --git a/src/Template.js b/src/Template.js index e4233ad..43ce2b8 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,427 +1,465 @@ -function Template(pIdTemplate, pContent) +"use strict"; +/** + * @author Arnaud NICOLAS + * @repo https://github.com/arno06/Template + */ +class EventEmitter { - this.removeAllEventListener(); - this._content = pContent||{}; - this._c = {}; - this._functions = Template.FUNCTIONS||{}; - this.time = null; - this._id = pIdTemplate; + constructor() + { + this.delegate = document.createDocumentFragment(); + } + + addEventListener(...args){ + this.delegate.addEventListener(...args); + } + + dispatchEvent(event){ + this.delegate.dispatchEvent(event); + } + + removeEventListener(...args){ + this.delegate.removeEventListener(...args); + } } -Class.define(Template, [EventDispatcher], +class Template extends EventEmitter { - _content:{}, - assign:function(pName, pValue) - { - this._content[pName] = pValue; - }, - setFunction:function(pName, pCallBack) - { - this._functions[pName] = pCallBack; - }, - render:function(pParentNode) - { - var self = this; - var p = pParentNode; - if((typeof p).toLowerCase()=="string") - p = document.querySelector(pParentNode); - if(!p) - return; + constructor(pIdTemplate, pContent = {}) + { + super(); + this._content = pContent; + this._c = {}; + this._functions = Template.FUNCTIONS||{}; + this._setReFuncs(); + this.time = null; + this._id = pIdTemplate; + } - this.dispatchEvent(new TemplateEvent(TemplateEvent.RENDER_INIT, 0, false)); + assign(pName, pValue) + { + this._content[pName] = pValue; + } - p.innerHTML += this.evaluate(); + setFunction(pName, pFunction) + { + this._functions[pName] = pFunction; + this._setReFuncs(); + } - this.dispatchEvent(new TemplateEvent(TemplateEvent.RENDER_COMPLETE, this.time, false)); + _setReFuncs(){ + let funcs = []; + for(let k in this._functions){ + funcs.push(k); + } + this.RE_FUNCS = new RegExp(Template.TAG[0]+"("+funcs.join("|")+")\\s([^"+Template.TAG[1]+"]+)"+Template.TAG[1], "gi"); + } + + render(pParent) + { + let p = pParent; + if((typeof p).toLowerCase()==="string") + p = document.querySelector(pParent); + if(!p) + return; - var imgs = p.querySelectorAll("img"); + this.dispatchEvent(new Event(TemplateEvent.RENDER_INIT)); - var max = imgs.length; + p.innerHTML += this.evaluate(); - if(!max) - { - this.dispatchEvent(new TemplateEvent(TemplateEvent.RENDER_COMPLETE_LOADED, this.time, false)); - return; - } + this.dispatchEvent(new Event(TemplateEvent.RENDER_COMPLETE)); - var i = 0; + let images = p.querySelectorAll("img"); - function tick() + let max = images.length; + + if(!max) { - if(++i==max) - self.dispatchEvent(new TemplateEvent(TemplateEvent.RENDER_COMPLETE_LOADED, self.time, false)); + this.dispatchEvent(new Event(TemplateEvent.RENDER_COMPLETE_LOADED)); + return; } - imgs.forEach(function(img) - { - if(img.complete && (++i==max)) + let i = 0; + + let tick = _ => { + + if(++i===max) + this.dispatchEvent(new Event(TemplateEvent.RENDER_COMPLETE_LOADED)); + }; + + images.forEach(img => + { + if(img.complete && (++i===max)) { - self.dispatchEvent(new TemplateEvent(TemplateEvent.RENDER_COMPLETE_LOADED, self.time, false)); + this.dispatchEvent(new Event(TemplateEvent.RENDER_COMPLETE_LOADED)); } - img.onload = tick; + img.onload = tick; img.onerror = tick; - }); + }); + } - }, - evaluate:function() - { - this._c = JSON.parse(JSON.stringify(this._content)); - var start = new Date().getTime(); - var t = Template.$[this._id]; - if(!t) - return ""; - - var t0 = Template.TAG[0]; - var t1 = Template.TAG[1]; - - var re_blocs = new RegExp("(\\"+t0+"[a-z]+|\\"+t0+"\/[a-z]+)(\\s|\\"+t1+"){1}", "gi"); - - var opener = [t0+"foreach", t0+"if"]; - var closer = [t0+"\/foreach", t0+"\/if"]; - var neutral= [t0+"else"]; - - var step = 0; - - var result, tag, currentId; - - var opened = []; - - while (result = re_blocs.exec(t)) - { - tag = result[1]; - if(opener.indexOf(tag)>-1) - { - currentId = ++step; - opened.unshift(currentId); - } - else if (closer.indexOf(tag)>-1) - { - currentId = opened.shift(); - } - else if (neutral.indexOf(tag)>-1) - { - currentId = opened[0]; - } - else - continue; - - t = t.replace(result[0], tag+"_"+currentId+result[2]); - } - var eval = this._parseBlock(t, this._c); - var end = new Date().getTime(); - this.time = end - start; - return eval; - }, - _parseBlock:function(pString, pData) - { - var t_0 = Template.TAG[0]; - var t_1 = Template.TAG[1]; - - //{opener_X} - var opener = new RegExp('\\'+t_0+'([a-z]+)(_[0-9]+)([^\}]*)\\'+t_1, 'i'); - - //$path.to.var - var rea = /\$([a-z0-9\._\-]+)*/i; - - var o, start, neutral, n, closer, c, length, totalBlock, blc, alt, params; - - while(o = opener.exec(pString)) - { - start = o.index; - - closer = new RegExp('\\'+t_0+'\/'+o[1]+o[2]+'\\'+t_1, 'gi'); - c = closer.exec(pString); - - if(!c) - { - console.log("no end tag"); - break; - } - - blc = pString.substr((start + o[0].length), c.index - (start + o[0].length)); - alt = ""; - - neutral = new RegExp('\\'+t_0+'else'+o[2]+'\\'+t_1, 'gi'); - - n = neutral.exec(pString); - if(n) - { - blc = pString.substr(start+o[0].length, n.index - (start + o[0].length)); - alt = pString.substr(n.index+n[0].length, c.index - (n.index+n[0].length)); - } - - length = (c.index + c[0].length) - start; - - totalBlock = pString.substr(start, length); - - var r = ""; - switch(o[1]) - { - case "foreach": - params = o[3].split(" ");//Setup [*, tablename, itemname, keyname] - params[1] = params[1].replace("$",""); - var d = this._getVariable(params[1], pData); - if(d) - { - var val = t_0+(params[2]||"$v")+t_1; - var key = t_0+(params[3]||"$k")+t_1; - var c_key = (params[3]||"$k").replace("$", ""); - var re = new RegExp("\\"+t_0+"\\"+(params[2]||"$v")+"([a-z0-9\.\_\-]+)*\\"+t_1, "gi"); - var empty = true; - var v = ""; - var tmp = ""; - var vr; - for(var j in d) - { + evaluate() + { + this._c = Object.assign({}, this._content); + let start = new Date().getTime(); + let t = Template.$[this._id]; + + if(!t) + return ""; + + let t0 = Template.TAG[0]; + let t1 = Template.TAG[1]; + + let re_blocs = new RegExp("(\\"+t0+"[a-z]+|\\"+t0+"\/[a-z]+)(\\s|\\"+t1+"){1}", "gi"); + + let opener = [t0+"foreach", t0+"if"]; + let closer = [t0+"\/foreach", t0+"\/if"]; + let neutral= [t0+"else"]; + + let step = 0; + + let result, currentId; + + let opened = []; + + let allblocks = [...t.matchAll(re_blocs)]; + + allblocks.forEach((pBlock)=>{ + let tag = pBlock[1]; + if(opener.indexOf(tag)>-1) + { + currentId = ++step; + opened.unshift(currentId); + } + else if (closer.indexOf(tag)>-1) + { + currentId = opened.shift(); + } + else if (neutral.indexOf(tag)>-1) + { + currentId = opened[0]; + } + else{ + return; + } + + t = t.replace(pBlock[0], tag+"_"+currentId+pBlock[2]); + }); + let evaluation = this._parseBlock(t, this._c); + let end = new Date().getTime(); + this.time = end - start; + return evaluation; + } + + _parseBlock(pString, pData) + { + let t_0 = Template.TAG[0]; + let t_1 = Template.TAG[1]; + + //{opener_X} + let opener = new RegExp('\\'+t_0+'([a-z]+)(_[0-9]+)([^\}]*)\\'+t_1, 'gi'); + + //$path.to.var + let rea = /\$([a-z0-9\-_\\.]+)+(?=\.|>|<|\!|\||=|\s|$)/gi; + + let o; + + let allblocks = [...pString.matchAll(opener)]; + allblocks.forEach((pOpener)=>{ + + let params; + let start = pString.indexOf(pOpener[0]); + if(start===-1){ + //Block already replaced + return; + } + let closer = new RegExp('\\'+t_0+'\/'+pOpener[1]+pOpener[2]+'\\'+t_1); + let c = closer.exec(pString); + + if(!c) + { + console.log("no end tag"); + return; + } + + let blc = pString.substr((start + pOpener[0].length), c.index - (start + pOpener[0].length)); + let alt = ""; + + let neutral = new RegExp('\\'+t_0+'else'+pOpener[2]+'\\'+t_1, 'gi'); + + let n = neutral.exec(pString); + if(n) + { + blc = pString.substr(start+pOpener[0].length, n.index - (start + pOpener[0].length)); + alt = pString.substr(n.index+n[0].length, c.index - (n.index+n[0].length)); + } + + let length = (c.index + c[0].length) - start; + + let totalBlock = pString.substr(start, length); + + let r = ""; + switch(pOpener[1]) + { + case "foreach": + + params = Template.parseParams(pOpener[3], {from:null, item:"item", key:"key"}); + let d = this._getVariable(params.from, pData); + if(d) + { + let empty = true; + let c_key = params.key; + let re = new RegExp("\\"+t_0+"\\$"+params.item+"([a-z0-9\.\_\-]+)*\\"+t_1, "gi"); + for(let j in d) + { + let vr; if(!d.hasOwnProperty(j)) continue; empty = false; - v = blc.replace(val, d[j]); - tmp = v; - while(vr = re.exec(v))//Keep exec on "v" and replacing on "tmp" (loosing string index) - { - vr[1] = vr[1].substr(1, vr[1].length-1); - tmp = tmp.replace(vr[0], this._getVariable(vr[1], d[j])); - } - v = tmp.replace(key, t_0+(params[2]||"$v")+"."+c_key+t_1); - v = v.replace("$"+c_key, (params[2]||"$v")+"."+c_key); - if(typeof d[j] == "string" || typeof d[j] == "number" || typeof d[j] == "boolean" || d[j] === null) - { - tmp = d[j]; - d[j] = {}; - d[j][(params[2]||"$v")] = tmp; - } - d[j][c_key] = j; - - var dataCloned = Object.clone(pData); - dataCloned[(params[2]||"$v").replace("$", "")] = d[j]; - dataCloned[c_key] = j; - v = this._parseBlock(v, dataCloned); - r += v; - } + let v = blc; + let dataCloned = Object.assign({}, pData);//Data cloning + dataCloned[params.item] = d[j]; + dataCloned[c_key] = j; + v = this._parseBlock(v, dataCloned); + r += v; + } if(empty === true) { r = this._parseBlock(alt, pData); } - } - else - r = this._parseBlock(alt, pData); - break; - case "if": - var f = this._parseVariables(o[3], pData, rea, true); - while(f[0]==" ") - f = f.replace(/^\s/, ''); - if(/^\s*$/.exec(f)||/^(!|=|>|<)/.exec(f)||/(\||&)(!|=|>|<)/.exec(f)) - f = false; - r = eval("(function(){var r = false; try { r = "+f+"; } catch(e){ r= false;} return r;})()"); - r = r?blc:(alt||""); - r = this._parseBlock(r, pData); - break; - default: - continue; - break; - } - - pString = pString.replace(totalBlock, r); - } - - pString = this._parseVariables(pString, pData, Template.REGXP_VAR); - - var func, a, p; - while(func = Template.REGXP_FUNC.exec(pString)) - { - var funcName = func[1]; - if(!this._functions[funcName]) - { - throw new Error("Call to undefined function "+funcName); - } - params = func[2]; - p = []; - params = params.replace(/,\s/g, ","); - params = params.split(","); - for(var i = 0, max = params.length;i|<)/.exec(f)||/(\||&)(!|=|>|<)/.exec(f)) + f = false; + let cond = eval("(_ => {let r = false; try { r = "+f+"; } catch(e){ r= false;} return r;})()"); + r = cond?blc:(alt||""); + r = this._parseBlock(r, pData); + break; + default: + return; + } + pString = pString.replace(totalBlock, r); + }); + let allFuncs = [...pString.matchAll(this.RE_FUNCS)]; + allFuncs.forEach((pFunc)=>{ + let funcName = pFunc[1]; + let p = []; + if(!this._functions[funcName]) + { + throw new Error("Call to undefined function "+funcName); + } + + let params = Template.parseParams(pFunc[2], {}, false); + for(let k in params){ + if(!params.hasOwnProperty(k)){ + continue; + } + + if(params[k]){ + if(params[k][0]==="$"){ + params[k] = this._getVariable(params[k].replace("$", ""), pData); + } + else + { + if(/^[0-9][0-9\.]*[0-9]*$/.exec(params[k])) + params[k] = Number(params[k]); + if(/^("|')/.exec(params[k])) + params[k] = params[k].substr(1, params[k].length-2); + } + } + } + + let pa = /function\(([^\)]+)\)/.exec(this._functions[funcName].toString()); + + if(pa){ + pa = pa[1].split(',').map((pName)=>pName.trim()); + pa.forEach((pName)=>{ + p.push(params[pName]||null); + }); + } + p.push(params); + pString = pString.replace(pFunc[0], this._functions[funcName].apply(null, p)); + }); + + let allVars = [...pString.matchAll(Template.REGEXP_VAR)]; + allVars.forEach((pRes)=>{ + let val = this._parseVariables("$"+pRes[1], pData, rea); + pString = pString.replace(pRes[0], val); + }); + + return pString; + } + + _parseVariables(pString, pData, pREGEXP = Template.REGEXP_ID, pEscapeString = false) + { + let res; + while(res = pREGEXP.exec(pString)) + { + let value = this._getVariable(res[1], pData); + if(pEscapeString&& (typeof value )== "string") + value = "'"+value.replace(/\'/g, "\\'")+"'"; + if(pString.indexOf("()")>-1){ + let method = pString.replace("$"+res[1]+".", "").replace("()", ""); + res[0] = "$"+res[1]+"."+method+"()"; + value = value[method](); + } + pString = pString.replace(res[0], value); + } + return pString; + } + + static parseParams(pString, pDefaults, pEscape = true){ + pString = pString.split(" "); + let params = {...pDefaults}; + let index = 0; + pString.filter((pVal)=>pVal.length).forEach((pParam)=>{ + let [name, val] = pParam.split('='); + if(!val){ + val = name; + name = "param"+(index++); + } + params[name] = pEscape?val.replace(/("|'|\$)/g, ''):val; + }); + return params; + } + + _getVariable(pName, pContext) + { + let default_value = ""; + let data = pContext||this._c; + let result = Template.REGEXP_ID.exec(pName); + + if(!result) + return default_value; + + let levels = result[1].split("."); + + for(let i = 0, max = levels.length;i=_data.length) + { + if(_callBack) + _callBack(); + return; + } - if(!result) - return default_value; + Request.load(_data[_currentIndex].file).onComplete(templateLoadedHandler).onError(next); + } - var levels = result[1].split("."); + next(); - for(var i = 0, max = levels.length;i=_data.length) - { - if(_callBack) - _callBack(); - return; - } - - Request.load(_data[_currentIndex].file, {}, "get").onComplete(templateLoadedHandler).onError(next); - } - - next(); - - return { - onComplete:function(pCallBack) - { - _callBack = pCallBack; - return this; - } - }; -}; +NodeList.prototype.forEach||(NodeList.prototype.forEach = Array.prototype.forEach); window.addEventListener("DOMContentLoaded", Template.setup, false); - -function TemplateEvent(pType, pTime, pBubbles) -{ - this.time = pTime||0; - this.super("constructor", pType, pBubbles); -} - -Class.define(TemplateEvent, [Event], -{ - clone:function(){var e = new TemplateEvent(this.type, this.time, this.bubbles);e.target = this.target;return e;}, - toString:function(){return this.formatToString("type", "time", "eventPhase", "target", "currentTarget", "bubbles");} -}); - -TemplateEvent.RENDER_INIT = "evt_render_start"; -TemplateEvent.RENDER_COMPLETE = "evt_render_complete"; -TemplateEvent.RENDER_COMPLETE_LOADED = "evt_render_loaded_complete"; \ No newline at end of file diff --git a/src/TemplateDataBinding.js b/src/TemplateDataBinding.js new file mode 100644 index 0000000..afc43d4 --- /dev/null +++ b/src/TemplateDataBinding.js @@ -0,0 +1,86 @@ +"use strict"; + +Template = Template||{}; + +Template.DataBinding = (function(){ + let sources = {}; + let events = { + UPDATED_DATA: 'evt_updated_data' + }; + + class Container extends EventEmitter + { + constructor(pElement, pDataSource, pTemplate) + { + super(); + this.element = pElement; + this.source = pDataSource; + this.template = pTemplate; + this.template.addEventListener(TemplateEvent.RENDER_COMPLETE, (e)=>{ + this.dispatchEvent(new e.constructor(e.type, e)); + }); + this.source.addEventListener(events.UPDATED_DATA, this._dataUpdated.bind(this), false); + this._dataUpdated(); + } + + _dataUpdated() + { + this.template._content = this.source.data; + this.element.innerHTML = ''; + this.template.render(this.element); + } + } + + class DataSource extends EventEmitter + { + constructor(pData = null) + { + super(); + if(pData !== null) + this.data = pData; + } + + setValue(pName, pData) + { + this._data[pName] = pData; + this.dispatchEvent(new Event(events.UPDATED_DATA)); + } + + set data(pValue) + { + this._data = pValue; + this.dispatchEvent(new Event(events.UPDATED_DATA)); + } + + get data() + { + return this._data; + } + } + + if(!NodeList.prototype.forEach) + NodeList.prototype.forEach = Array.prototype.forEach; + + + function defineContainer(pElement) + { + if(!pElement.getAttribute('data-source') || !pElement.getAttribute('data-template')) + return false; + let src = pElement.getAttribute('data-source'); + let tpl = pElement.getAttribute('data-template'); + if(!sources[src]) + return false; + return new Container(pElement, sources[src], new Template(tpl)); + } + + function defineSource(pName, pData) + { + sources[pName] = new DataSource(pData); + return sources[pName]; + } + + return { + container:defineContainer, + source:defineSource + } +})(); \ No newline at end of file