diff --git a/examples/example8.html b/examples/example8.html new file mode 100644 index 0000000..df1966f --- /dev/null +++ b/examples/example8.html @@ -0,0 +1,90 @@ + + + + + Example 8: Example with shadow dom and custom components + + + + + +

Example 8: Web component use-case

+

Shows how to use BSS inside a web component with or without shadow dom (requires v. 1.2.6+)

+
+ + + + + + + diff --git a/src/bss-1.2.6.js b/src/bss-1.2.6.js new file mode 100644 index 0000000..a9f4639 --- /dev/null +++ b/src/bss-1.2.6.js @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2016 Marco Lettere + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + * */ + +var BSS = { + + /* Unified edi call to centralize error handling and logging */ + _call : function(f, e, d, i){ + try{ + return f(e,d,i) + } catch (err){ + console.error("While calling edi function", f, "with (e d i) parameters", e, d, i, "got: ", err) + } + }, + + /* Evaluates a field from a BSS instance checking whether it's an "edi function"*/ + _get : function (field, e, d, i){ + return typeof(field) == 'function' ? this._call(field, e, d, i) : field + }, + + /* Managing target which may be a function returning already a NodeList in addition to a css selector*/ + _selectTarget : function (tgt, e, d, i){ + return typeof(tgt) == 'function' ? this._call(tgt, e, d, i) : e.querySelector(tgt) + }, + + _selectTargets : function (tgt, e, d, i){ + return typeof(tgt) == 'function' ? this._call(tgt, e, d, i) : e.querySelectorAll(tgt) + }, + + _hasTemplate : function (bss){ + return typeof(bss["template"]) !== "undefined" && bss["template"] != null + }, + + //1.2.6: root bss is always necessary in order to access the rootdocument + _getTemplate : function(bss, e, d, i){ + var templ = bss["template"] + var template = typeof(templ) == 'function' ? this._call(templ, e, d, i) : bss.rootdocument.querySelector(templ) + if(template == null){ + throw "No template found " + templ + } + //Note that: importNode has to be performed on top level document + return document.importNode(template.content, true) + }, + + _setRootDocument : function(bss, rootdocument){ + //get root document to apply querySelector to. If not overriden in apply call or bss, defaults to global document. + var rd = rootdocument ? rootdocument : this._get(bss["rootdocument"]) + rd = rd ? rd : document + bss.rootdocument = rd + return rd + }, + + /* + * Polyfill for templates suitable for older IE browsers, + * Big thanks to https://github.com/neovov/template-element-polyfill/blob/master/template.js for inspiration + */ + polyfill : function(){ + var support = ("content" in document.createElement("template")); + if (!support) { + var templates = Array.prototype.slice.call(document.getElementsByTagName("template")) + for(var t in templates) { + var template = templates[t] + var content = template.children + var fragment = document.createDocumentFragment() + template.style.display = "none"; + var node = null + for (j = 0; node = content[j]; j++) { + fragment.appendChild(node); + } + template.content = fragment; + } + } + }, + + /* Commits all databindings of one BSS instance back to the model*/ + commit : function (bss){ + this._bindingCache.commit(bss) + }, + + /* Destroys all bindings cached in bindingCache and erases all generated HTML output*/ + clear : function (bss){ + this._bindingCache.clear(bss) + //1.2.6: the document to be used for querySelector is set on root bss in order to support querying in shadow doms + var rd = this._setRootDocument(bss, bss.rootdocument ? bss.rootdocument : document) + var target = this._selectTarget(bss.target, rd) + if(target){ + target.innerHTML = '' + }else{ + console.error("Target not found") + } + }, + + /* Re-apply all cached bindings. This is useful for speeding up the change of shown values + * when no structural changes have occurred in the model*/ + reapply : function (bss){ + this._bindingCache.reapply(bss) + }, + + /* Public entry point for the application of a BSS instance. + * Takes care of extracting the BSS template (if any) and after applying the binding algorithm + * pastes the HTML output into the target declared at the top of the BSS */ + apply : function (bss, rootdocument){ + //var start = new Date() + if(!bss.id) bss.id = (Math.random() * 10e11)|0 + + this._bindingCache.clear(bss) + + //1.2.6: the document to be used for querySelector is set on root bss in order to support querying in shadow doms + var rd = this._setRootDocument(bss, rootdocument) + + this._bind(bss, bss, bss.rootdocument) + this.reapply(bss) + var target = this._selectTarget(bss["target"], rd) + if (target == null) { + console.error("Target not found", bss.target) + return + } + + //console.log(new Date() - start) + }, + + _bindSingle : function (bss, rootBss, e, din, dout, i) { + this._bindingCache.addApplyBinding(bss, rootBss, e, din, i) + if (dout) { + //expect output data to be homomorphic to input data thus it needs to be an array of same size as the input + this._bindingCache.addCommitBinding(bss, rootBss, e, dout, i) + } + this._registerEvents(bss, e) + }, + + _bind : function(bss, rootBss, rootElement, data, dataOut, index){ + + var targets = this._selectTargets(bss.target, rootElement, data, index) + for(var ti=0, tl=targets.length; ti < tl; ti++){ + + //for every element matched by target selector + var target = targets[ti] + + //get data bound to target possibly by resolving edi function + //if data is not rewritten by in/out/inout clause inherit data from previous level + var currentData = this._getCurrentData(bss, target, data, index) + var currentDataOut = this._getCurrentDataOut(bss, target, data, dataOut, index) + + //prepare for possible recursion + var recurse = this._prepareRecurse(bss, target, currentData, index) + + //console.log("recurse is",recurse, "currentData", currentData) + + if(currentData instanceof Array){ + //create replicas and remove originating node + var replicas = this._replaceTargetWithReplicas(bss, rootBss, target, currentData, index) + + //apply to all replicas + for(var i in replicas){ + var e = replicas[i] + // create edi-binding for each replicated e + this._bindSingle(bss, rootBss, e, currentData, currentDataOut, i) + for(var reci in recurse){ + this._bind(recurse[reci], rootBss, e, currentData[i], currentDataOut ? currentDataOut[i] : null, i) + } + } + }else{ + target = this._replaceTarget(bss, target, currentData, null) + + // create edi-binding for targeted e with no index + this._bindSingle(bss, rootBss, target, currentData, currentDataOut, null) + for(var recj in recurse){ + this._bind(recurse[recj], rootBss, target, currentData, currentDataOut, index) + } + } + } + }, + + _registerEvents : function(bss, e){ + var handlers = null + for(var k in bss){ + if (k === "on"){ + var eventMap = bss[k] + for(var event in eventMap){ + handlers = eventMap[event] instanceof Array ? eventMap[event] : [eventMap[event]] + handlers.forEach( function(h){e.addEventListener(event, h, false)} ) + } + }else if(k.lastIndexOf("on_") === 0){ + handlers = bss[k] instanceof Array ? bss[k] : [bss[k]] + handlers.forEach( function(h){e.addEventListener(k.substring(3), h, false)} ) + } + } + }, + + _getCurrentData : function(bss, target, data, index){ + var currentData = data + if(bss['inout']){ + currentData = this._get(bss['inout'], target, data, index) + } + if(bss['in']){ + currentData = this._get(bss['in'], target, data, index) + } + return currentData + }, + + _getCurrentDataOut : function(bss, target, data, dataOut, index){ + var currentDataOut = dataOut + if(bss['inout']){ + currentDataOut = this._get(bss['inout'], target, data, index) + } + if(bss['out']){ + currentDataOut = this._get(bss['out'], target, data, index) + } + return currentDataOut + }, + + _prepareRecurse : function(bss, target, currentData, index){ + var recurse = bss.recurse ? this._get(bss['recurse'], target, currentData, index) : [] + recurse = recurse ? recurse : [] + return recurse instanceof Array ? recurse : [recurse] + }, + + _replaceTarget : function(bss, target, data, index) { + var newtarget = target + if(this._hasTemplate(bss)){ + newtarget = this._getTemplate(bss, target, data, index).firstElementChild + target.parentNode.replaceChild(newtarget, target) + } + return newtarget + }, + + _replaceTargetWithReplicas : function(bss, rootBss, target, currentData, index){ + var replicas = [] + for(var i=0, j=currentData.length; i < j; i++){ + //redefining rootElement if template is overridden + var replica = null + if(this._hasTemplate(bss)){ + replica = this._getTemplate(rootBss, target, currentData, index).firstElementChild + }else{ + replica = target.cloneNode(true) + } + replicas.push(replica) + target.parentNode.appendChild(replica) + } + target.parentNode.removeChild(target) + return replicas + }, + + // The cache for apply and commit bindings + _bindingCache : { + applies : {}, + commits : {}, + + clear : function(bss){ + this.applies[bss.id] = (this.applies[bss.id] ? this.applies[bss.id].splice() : []) + this.commits[bss.id] = (this.commits[bss.id] ? this.commits[bss.id].splice() : []) + }, + + addApplyBinding : function(bss, rootBss, e, d, i){ + var b = { + element : e, + index : i, + transfer : (bss.apply ? bss.apply : function(){ return }), + data : (i ? d[i] : d), + container : (i ? d : null) + } + e.bss_input = b + this.applies[rootBss.id].push(b) + }, + + addCommitBinding : function(bss, rootBss, e, d, i){ + var b = { + element : e, + index : i, + transfer : (bss.commit ? bss.commit : function(){ return }), + data : (i ? d[i] : d), + container : (i ? d : null), + //shortcut to remove element from output container + remove : (i ? function(){ this.container.splice(this.index, 1) } : function(){}) + } + e.bss_output = b + e.bss_binding = b //for back-compatibility with versions before 1.2.3 + this.commits[rootBss.id].push(b) + }, + + reapply : function(bss){ + if(this.applies[bss.id]){ + this.applies[bss.id].forEach( + function(b){ + try{ b.transfer(b.element, b.data, b.index) } catch (err) { console.error("Error in apply", b.transfer, b.element, b.data, b.index) } + } + ) + } + }, + + commit : function(bss){ + if(this.commits[bss.id]){ + this.commits[bss.id].forEach( + function(b){ + try{ b.transfer(b.element, b.data, b.index) } catch (err){ console.error("Error in commit",b.transfer, b.element, b.data, b.index) } + } + ) + } + } + } +}