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: {
+                    "<": "&lt;",
+                    ">": "&gt;",
+                    "&": "&amp;",
+                    "\"": "&quot;"
+                },
+                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