From c898d16c43559fd20f1cee2b1c7cbec834924c9d Mon Sep 17 00:00:00 2001 From: Commander-lol <ljcapitanio@gmail.com> Date: Fri, 26 Jun 2015 04:46:24 +0100 Subject: [PATCH] Initial version of plugin. Floating render and insertion render available --- test/sb.js | 325 +++++++++++++++++++++++++++++++++++ test/td.js | 1 + test/test.html | 36 ++++ tinyDOM-Sideburns.js | 43 +++++ tinyDOM-Sideburns.min.js | 2 + tinyDOM-Sideburns.min.js.map | 1 + 6 files changed, 408 insertions(+) create mode 100644 test/sb.js create mode 100644 test/td.js create mode 100644 test/test.html create mode 100644 tinyDOM-Sideburns.js create mode 100644 tinyDOM-Sideburns.min.js create mode 100644 tinyDOM-Sideburns.min.js.map diff --git a/test/sb.js b/test/sb.js new file mode 100644 index 0000000..7d9406c --- /dev/null +++ b/test/sb.js @@ -0,0 +1,325 @@ +/*jslint node: true */ +(function () { + 'use strict'; + var globalOptions = { + ignoreUndefined: false, + escape: "general", + escapeSets: { + xml: { + "<": "<", + ">": ">", + "&": "&", + "\"": """ + }, + general: { + "\"": "\\\"", + "\'": "\\\'" + } + } + }, + deepMergeJson = function (obja, objb) { + var prop; + for (prop in objb) { + if (objb.hasOwnProperty(prop)) { + if (typeof (obja[prop]) === 'object' && typeof (objb[prop]) === 'object') { + obja[prop] = deepMergeJson(obja[prop], objb[prop]); + } else { + obja[prop] = objb[prop]; + } + } + } + return obja; + }, + setDeepProperty = function (ident, value, obj) { + var list, + recurse = function (propList, value, obj) { + if (propList.length > 1) { + recurse(propList, value, obj[list.shift()]); + } else { + obj[propList[0]] = value; + } + }; + + if (!ident.push && !ident.map) { + list = ident.split(".").map(function (e) {return e.trim(); }); + } else { + list = ident; + } + + recurse(list, value, obj); + return obj; + }, + getDeepProperty = function (ident, obj) { + var ret = null, + list, + recurse = function (propList, obj) { + var curident; + if (propList.length > 1) { + if (obj.hasOwnProperty(propList[0])) { + recurse(propList, obj[list.shift()]); + } else { + curident = propList.reduce(function (a, b, i) { + return a + (i > 0 ? "." : "") + b; + }); + throw new Error("Invalid proprty, missing expected data \"" + curident + "\" (IDENT: " + ident + ") from data " + JSON.stringify(obj)); + } + } else { + if (obj.hasOwnProperty(propList[0])) { + ret = obj[propList[0]]; + } else { + throw new Error("Invalid proprty, missing expected data \"" + propList[0] + "\" (IDENT: " + ident + ") from data " + JSON.stringify(obj)); + } + } + }; + + if (!ident.push && !ident.map) { + list = ident.split(".").map(function (e) {return e.trim(); }); + } else { + list = ident; + } + + recurse(list, obj); + return ret; + }, + resolveNamespace = function (ident, blockStack) { + var l = blockStack.length, i, ret = ""; + for (i = 0; i < l; i += 1) { + ret += blockStack[i] + "."; + } + return ret + ident; + }, + safeDeepMergeJson = function (obja, objb) { + if (typeof (obja) !== 'object') { + throw new TypeError("Cannot deep merge with an " + typeof (obja) + ": [Param 1]"); + } + if (typeof (objb) !== 'object') { + throw new TypeError("Cannot deep merge with an " + typeof (objb) + ": [Param 2]"); + } + + return deepMergeJson(deepMergeJson({}, obja), objb); + }, + /** + * Using this Regex + * This regular expression will match and split all valid tags for Sideburns, and provides + * all data needed to appropriately lex each tag + * + * Result indexes (from re.exec()): + * [0] The whole tag that has been matched + * [1, 9] These are the opening and closing double square brackets, respectively. Always present + * [2] The Tag modifier. This will either be '/' for a closing tag, '#' for a pre-process directive + * or undefined for all other tags. Processing should branch here depending on whether or not this + * token is a hash ('#'). Indexes for one branch will be undefined in case of the other branch. + * + * == In case of Directive == + * [3] The identifier of the directive to set. Always present + * [4] The value to set the directive. Always present + * + * == In case of other tag == + * [5] The Block modifier. Will be '*' for arrays, '&' for context and undefined for simple tags + * [6] The Data modifier. Will be '!' for an escape with the global set, '!([7])' for an escape with a + * specified set or undefined for unescaped data + * [7] If a set has been specified, this will be the name of the set on its own, seperate from the data + * modifier. It will otherwise be undefined + * [8] The identifier for the data or block that the tag represents. Always present + **/ + captureTags = /(\[\[)(\#|\/)?\s*(?:([a-zA-Z]+[a-zA-Z0-9]*)\s*\:\s*([a-zA-Z]+[a-zA-Z0-9]*)|([\*\&]?)\s*((?:\!(?:\(([a-zA-Z]+[a-zA-Z0-9]*)\))?)?)\s*([a-zA-Z](?:[a-zA-Z0-9]*(?:\.(?=[a-zA-Z]))?)+))\s*(\]\])/, + Stack = function () { + var self = this; + this.length = 0; + this.push = function (val) { + self[self.length] = val; + self.length += 1; + }; + this.pop = function (val) { + var ret = null; + if (self.length > 0) { + ret = self[self.length - 1]; + self[self.length - 1] = null; + self.length -= 1; + } + return ret; + }; + + this.peek = function () { + return this.length > 0 ? self[self.length - 1] : null; + }; + this.contains = function (value, compare) { + compare = compare || function (a, b) { + return a === b; + }; + var i = this.length; + while (i--) { + if (compare(this[i], value)) { + return true; + } + } + return false; + }; + return this; + }, + Node = function (type, val, modifiers) { + this.ident = type || "N_NULL"; + this.val = val || null; + this.content = modifiers || {}; + return this; + }, + Token = function (ident, val, info) { + this.ident = ident || "T_NULL"; + this.val = val || null; + this.info = info || {}; + return this; + }, + tokenise = function (src) { + var tokens = [], + last = 0, + i = 0, + match = null, + matcher, + chunk, + tok; + + matcher = function () { + return ((match = captureTags.exec(chunk)) !== null); + }; + while (i < src.length) { + chunk = src.substr(i); + if (matcher()) { + if (match.index > 0) { + tokens.push(new Token("STRING", match.input.substr(0, match.index))); + } + tok = new Token("T_NULL", null, {close: false, escape: false, escapeType: null}); + if (match[2] === '#') { + tok.ident = "T_DIRECTIVE"; + tok.val = {}; + tok.val[match[3]] = match[4]; + } else { + if (match[2] === '/') { + tok.info.close = true; + } + + if (match[5] === "*") { + tok.ident = "T_LOOP"; + } else if (match[5] === "&") { + tok.ident = "T_BLOCK"; + } else { + tok.ident = "T_DATA"; + } + + if (typeof (match[6]) !== 'undefined' && match[6].charAt(0) === "!") { + tok.info.escape = true; + if (typeof (match[7]) !== 'undefined') { + tok.info.escapeType = match[7]; + } + } + tok.val = match[8]; + } + tokens.push(tok); + i += match.index + match[0].length; + } else { + tokens.push(new Token("STRING", chunk)); + i += chunk.length; + } + } + tokens.push(new Token("EOD")); + return tokens; + }, + collapseParse = function (tokens) { + var tokenList, + i = 0, + j, + targetToken, + targetNode; + + while (i < tokens.length) { + if (tokens[i].info && tokens[i].info.close) { + tokenList = []; + targetToken = tokens[i]; + j = i - 1; + + while (!(tokens[j].ident === targetToken.ident && tokens[j].val === targetToken.val)) { + tokenList.push(tokens[j]); + j -= 1; + if (j < 0) { + throw new Error("Unmatched Closing Tag " + targetNode + " at index " + i); + } + } + + if (targetToken.ident === "T_LOOP") { + targetNode = new Node("N_LOOP", targetToken.val, tokenList.slice().reverse()); + } else { + targetNode = new Node("N_BLOCK", targetToken.val, tokenList.slice().reverse()); + } + + tokens.splice(j, (i - j) + 1, targetNode); + i = 0; + } else { + i += 1; + } + } + return tokens; + + }, + unwindNode = function (output, node, index, arr) { + var innerArr, dataArr, content, i, dataVal; + switch (node.ident) { + case "STRING": + return output + node.val; + + case "T_DATA": + dataVal = node.val; + if (arr.loopTag) { + if (node.val === arr.loopTag.slice(0, -1)) { + dataVal = arr.loopTag + "." + (arr.i).toString(); + } + } + return output + getDeepProperty(dataVal, arr.data); + + case "N_LOOP": + content = ""; + + innerArr = node.content; + innerArr.data = arr.data; + innerArr.loopTag = node.val; + + dataArr = getDeepProperty(node.val, arr.data); + + for (i = 0; i < dataArr.length; i += 1) { + innerArr.i = i; + content += innerArr.reduce(unwindNode, ""); + } + + return output + content; + + case "N_BLOCK": + innerArr = node.content; + innerArr.data = getDeepProperty(node.val, arr.data); + innerArr.loopTag = null; + return output + innerArr.reduce(unwindNode, ""); + + default: + return output; + } + }, + render = function (src, data, options) { + var nodes = collapseParse(tokenise(src)); + nodes.data = data; + nodes.loopTag = null; + nodes.i = null; + return nodes.reduce(unwindNode, ""); + }; + + render.partial = function (src) { + return function (tokens, data, options) { + tokens.data = data; + tokens.loopTag = null; + tokens.i = null; + return tokens.reduce(unwindNode, ""); + }.bind(null, collapseParse(tokenise(src))); + }; + + if (typeof module !== "undefined" && module.exports) { + module.exports = render; + } else { + window.sideburns = render; + } +}()); diff --git a/test/td.js b/test/td.js new file mode 100644 index 0000000..e576755 --- /dev/null +++ b/test/td.js @@ -0,0 +1 @@ +!function(){"use strict";var t,e,n;Element&&!Element.prototype.matches&&(t=Element.prototype,t.matches=t.matchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector||t.webkitMatchesSelector),e=function(t){var e,n,r;if(null===t||"undefined"==typeof t)this.length=0;else if("string"==typeof t)for(e=document.querySelectorAll(t),this.length=e.length,n=0;n<e.length;n++)r=e.item(n),this[n]=r;else if(t.length)for(n=0;n<t.length;n++)r=t[n],this[n]=r;else this[0]=t,this.length=1;return this},n=function(t){return new e(t)},n.fn=e.prototype={each:function(t){for(var e=this.length;e--;)t(e,this[e],this);return this},on:function(t,e,n){return"string"==typeof e?this.each(function(r,s){s.addEventListener(t,function(t){var r=t.target,i=!1;do r&&r.matches(e)?(n.call(r,t),i=!0):(r=r.parentNode,r&&r.matches&&r!==s||(i=!0));while(i!==!0)})}):(n=e,this.each(function(e,r){r.addEventListener(t,n)})),this},first:function(){return"undefined"!=typeof this[0]?n(this[0]):null},parent:function(t){var e=this[0].parentNode,r=!0;if(n.exists(t)){for(;null!==e&&e!==document;){if(e.matches(t)){r=!1;break}e=e.parentNode}e=r?null:e}return n(e)},children:function(){var t,e=this[0].childNodes,r=[];for(t=0;t<e.length;t++)n.isElement(e[t])&&r.push(e[t]);return n(r)},data:function(t,e){return"undefined"!=typeof e?(this.each(function(n,r){r.setAttribute("data-"+t,e)}),this):this[0].getAttribute("data-"+t)},attr:function(t,e){return"undefined"!=typeof e?(this.each(function(n,r){r.setAttribute(t,e)}),this):this[0].getAttribute(t)},"class":function(t,e){return this.each(n.exists(e)?function(n,r){r.classList.toggle(t,e)}:function(e,n){n.classList.toggle(t)}),this},trigger:function(t,e,r,s){r=n.exists(r)?r:!0,s=n.exists(s)?s:!0;var i=new CustomEvent(t,e,r,s);return this.each(function(t,e){e.dispatchEvent(i)}),this}},n.ready=function(t){document.addEventListener("DOMContentLoaded",t)},n.isElement=function(t){var e=!1;try{e=t instanceof HTMLElement}catch(n){e=t.nodeType&&1===t.nodeType}return e},n.isJson=function(t){try{return JSON.parse(t),null!==t&&"undefined"!=typeof t}catch(e){return!1}},n.exists=function(t){return null!==t&&"undefined"!=typeof t},n.merge=function(t,e){if(this.exists(t)&&this.exists(e)){var n;for(n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}return null},n.byID=function(t){return n(document.getElementById(t))},n.triggerOn=function(t,e,r,s,i){s=n.exists(s)?s:!0,i=n.exists(i)?i:!0,t.dispatchEvent(new CustomEvent(e,r,s,i))},n.json={keys:function(t){var e,n=[];for(e in t)t.hasOwnProperty(e)&&n.push(e);return n}},n.ajax=function(t){var e,n,r=new XMLHttpRequest,s={method:"GET",url:"",async:!0,user:null,password:null,responseType:"text",data:null,headers:[],callbacks:{}},i=function(t){return function(e){t(e.currentTarget.response,e)}};if(this.merge(s,t),r.responseType=s.responseType,this.exists(s.callbacks))for(e in s.callbacks)s.callbacks.hasOwnProperty(e)&&r.addEventListener(e,i(s.callbacks[e]));for(r.open(s.method,s.url,s.async,s.user,s.password),n=0;n<s.headers.length;n++)r.setRequestHeader(s.headers[n].header,s.headers[n].value);return r.send(s.data),r},window.μ||(window.μ=n),window.mu||(window.mu=n)}(); diff --git a/test/test.html b/test/test.html new file mode 100644 index 0000000..26cfda8 --- /dev/null +++ b/test/test.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> + +<html> + + <head> + <meta charset="utf-8"> + <title>SB test</title> + <script src="td.js"></script> + <script src="sb.js"></script> + <script src="../tinyDOM-Sideburns.js"></script> + </head> + + <body> + <main id="content"></main> + + <script type="x-template/sideburns" data-name="list"> + <ul> + [[* items ]] + <li>[[item]]</li> + [[/* items ]] + </ul> + </script> + + <script type="application/javascript"> + mu.ready(function(){ + mu("#content").render("list", + { + items: ["hello", "this", "is", "list"] + }, + { + useHtml: true + }); + }); + </script> + </body> +</html> diff --git a/tinyDOM-Sideburns.js b/tinyDOM-Sideburns.js new file mode 100644 index 0000000..dd9190a --- /dev/null +++ b/tinyDOM-Sideburns.js @@ -0,0 +1,43 @@ +/*globals console, mu */ +(function () { + "use strict"; + + if (!window.mu) { + throw new Error("tinyDOM-Sideburns requires tinyDOM to be in use on the page"); + } + + mu.templates = {}; + + mu.render = function (path, data, options) { + if (mu.templates.hasOwnProperty(path)) { + return mu.templates[path](data, options); + } else { + return window.sideburns(path, data, options); + } + }; + + + mu.fn.render = function (path, data, options) { + var rendered = mu.render(path, data, options), + opts = options || {useHtml: true}; + if (typeof opts.useHtml === "undefined") { + opts.useHtml = true; + } + + this.each(function (i, e) { + if (opts.useHtml) { + e.innerHTML = rendered; + } else { + e.textContent = rendered; + } + }); + }; + + mu.ready(function () { + mu("[type='x-template/sideburns']").each(function (i, e) { + var name = e.getAttribute("data-name"); + mu.templates[name] = window.sideburns.partial(e.textContent); + }); + }); + +}()); diff --git a/tinyDOM-Sideburns.min.js b/tinyDOM-Sideburns.min.js new file mode 100644 index 0000000..4f76e67 --- /dev/null +++ b/tinyDOM-Sideburns.min.js @@ -0,0 +1,2 @@ +!function(){"use strict";if(!window.mu)throw new Error("tinyDOM-Sideburns requires tinyDOM to be in use on the page");mu.templates={},mu.render=function(e,t,n){return mu.templates.hasOwnProperty(e)?mu.templates[e](t,n):window.sideburns(e,t,n)},mu.fn.render=function(e,t,n){var r=mu.render(e,t,n),u=n||{useHtml:!0};"undefined"==typeof u.useHtml&&(u.useHtml=!0),this.each(function(e,t){u.useHtml?t.innerHTML=r:t.textContent=r})},mu.ready(function(){mu("[type='x-template/sideburns']").each(function(e,t){var n=t.getAttribute("data-name");mu.templates[n]=window.sideburns.partial(t.textContent)})})}(); +//# sourceMappingURL=tinyDOM-Sideburns.min.js.map diff --git a/tinyDOM-Sideburns.min.js.map b/tinyDOM-Sideburns.min.js.map new file mode 100644 index 0000000..3282fcb --- /dev/null +++ b/tinyDOM-Sideburns.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tinyDOM-Sideburns.js"],"names":["window","mu","Error","templates","render","path","data","options","hasOwnProperty","sideburns","fn","rendered","opts","useHtml","this","each","i","e","innerHTML","textContent","ready","name","getAttribute","partial"],"mappings":"CACC,WACG,YAEA,KAAKA,OAAOC,GACR,KAAM,IAAIC,OAAM,8DAGpBD,IAAGE,aAEHF,GAAGG,OAAS,SAAUC,EAAMC,EAAMC,GAC9B,MAAIN,IAAGE,UAAUK,eAAeH,GACrBJ,GAAGE,UAAUE,GAAMC,EAAMC,GAEzBP,OAAOS,UAAUJ,EAAMC,EAAMC,IAK5CN,GAAGS,GAAGN,OAAS,SAAUC,EAAMC,EAAMC,GACjC,GAAII,GAAWV,GAAGG,OAAOC,EAAMC,EAAMC,GACjCK,EAAOL,IAAYM,SAAS,EACJ,oBAAjBD,GAAKC,UACZD,EAAKC,SAAU,GAGnBC,KAAKC,KAAK,SAAUC,EAAGC,GACfL,EAAKC,QACLI,EAAEC,UAAYP,EAEdM,EAAEE,YAAcR,KAK5BV,GAAGmB,MAAM,WACLnB,GAAG,iCAAiCc,KAAK,SAAUC,EAAGC,GAClD,GAAII,GAAOJ,EAAEK,aAAa,YAC1BrB,IAAGE,UAAUkB,GAAQrB,OAAOS,UAAUc,QAAQN,EAAEE"} \ No newline at end of file -- GitLab