diff --git a/st.js b/st.js index 2568411..f503a3c 100644 --- a/st.js +++ b/st.js @@ -1,12 +1,12 @@ -(function() { +(function () { var $context = this; var root; // root context var Helper = { - is_template: function(str) { + is_template: function (str) { var re = /\{\{(.+)\}\}/g; return re.test(str); }, - is_array: function(item) { + is_array: function (item) { return ( Array.isArray(item) || (!!item && @@ -15,7 +15,7 @@ ) ); }, - resolve: function(o, path, new_val) { + resolve: function (o, path, new_val) { // 1. Takes any object // 2. Finds subtree based on path // 3. Sets the value to new_val @@ -30,7 +30,7 @@ }, }; var Conditional = { - run: function(template, data) { + run: function (template, data) { // expecting template as an array of objects, // each of which contains '#if', '#elseif', 'else' as key @@ -74,7 +74,7 @@ // so return null return null; }, - is: function(template) { + is: function (template) { // TRUE ONLY IF it's in a correct format. // Otherwise return the original template // Condition 0. Must be an array @@ -140,7 +140,7 @@ // Condition 4. // in case there's more than two items, everything between the first and the last item should be #elseif var they_are_all_elseifs = true; - for (var template_index = 1; template_index < template.length-1; template_index++) { + for (var template_index = 1; template_index < template.length - 1; template_index++) { var template_item = template[template_index]; for (var template_key in template_item) { func = TRANSFORM.tokenize(template_key); @@ -160,7 +160,7 @@ // Now we need to check the validity of the last item // Condition 5. // in case there's more than one item, it should end with #else or #elseif - var last = template[template.length-1]; + var last = template[template.length - 1]; for (var last_key in last) { func = TRANSFORM.tokenize(last_key); if (['#else', '#elseif'].indexOf(func.name.toLowerCase()) === -1) { @@ -173,10 +173,11 @@ }; var TRANSFORM = { memory: {}, - transform: function(template, data, injection, serialized) { + templates: {}, // Modified by Jakub Mifek + transform: function (template, data, injection, serialized) { var selector = null; if (/#include/.test(JSON.stringify(template))) { - selector = function(key, value) { return /#include/.test(key) || /#include/.test(value); }; + selector = function (key, value) { return /#include/.test(key) || /#include/.test(value); }; } var res; if (injection) { @@ -203,7 +204,7 @@ return res; } }, - tokenize: function(str) { + tokenize: function (str) { // INPUT : string // OUTPUT : {name: FUNCTION_NAME:STRING, args: ARGUMENT:ARRAY} var re = /\{\{(.+)\}\}/g; @@ -228,15 +229,17 @@ } return null; }, - run: function(template, data) { + run: function (template, data) { var result; var fun; if (typeof template === 'string') { // Leaf node, so call TRANSFORM.fillout() if (Helper.is_template(template)) { + var include_string_re = /\{\{([ ]*#include)[ ]*([^ ]*)\}\}/g; + fun = TRANSFORM.tokenize(template); + if (include_string_re.test(template)) { - fun = TRANSFORM.tokenize(template); if (fun.expression) { // if #include has arguments, evaluate it before attaching result = TRANSFORM.fillout(data, '{{' + fun.expression + '}}', true); @@ -245,6 +248,23 @@ // {'wrapper': '{{#include}}'} result = template; } + + /////////////////////////////// + /// Modified by Jakub Mifek /// + /////////////////////////////// + + } else if (fun && fun.name === "#template") { + + // insert one of stored templates + + let partial_template = TRANSFORM.templates[fun.expression]; + + result = TRANSFORM.run(partial_template, data); + + /////////////////////////// + /// End of modification /// + /////////////////////////// + } else { // non-#include result = TRANSFORM.fillout(data, template); @@ -275,9 +295,9 @@ // This needs to precede everything else since it's meant to be overwritten // in case of collision var include_object_re = /\{\{([ ]*#include)[ ]*(.*)\}\}/; - var include_keys = Object.keys(template).filter(function(key) { return include_object_re.test(key); }); + var include_keys = Object.keys(template).filter(function (key) { return include_object_re.test(key); }); if (include_keys.length > 0) { - // find the first key with #include + // find the first key with #include fun = TRANSFORM.tokenize(include_keys[0]); if (fun.expression) { // if #include has arguments, evaluate it before attaching @@ -296,6 +316,39 @@ if (fun) { if (fun.name === '#include') { // this was handled above (before the for loop) so just ignore + + /////////////////////////////// + /// Modified by Jakub Mifek /// + /////////////////////////////// + } else if (fun.name === '#optional') { + let ret = TRANSFORM.run(template[key], data); + + if(!ret || ret == null || ret == undefined || typeof ret === 'object' && Object.keys(ret).length === 0 || Helper.is_array(ret) && ret.length === 0) { + // We want to ignore these cases + } else { + result[fun.expression] = ret; + } + } else if (fun.name === '#flatten') { + let arr = TRANSFORM.run(template[key], data); + result = []; + + if(Helper.is_array(arr)) { + + // For each item in the array + for(let i = 0; i < arr.length; i++) { + + // If array then flatten + if(Helper.is_array(arr[i])) { + + for(let j = 0; j < arr[i].length; j++) + result.push(arr[i][j]); + + // Just push if anything else + } else { + result.push(arr[i]); + } + } + } } else if (fun.name === '#let') { if (Helper.is_array(template[key]) && template[key].length == 2) { var defs = template[key][0]; @@ -305,18 +358,87 @@ var parsed_keys = TRANSFORM.run(defs, data); // 2. modify the data - for(var parsed_key in parsed_keys) { + let originals = {}; + let moriginals = {}; + for (var parsed_key in parsed_keys) { + moriginals[parsed_key] = TRANSFORM.memory[parsed_key]; + TRANSFORM.memory[parsed_key] = parsed_keys[parsed_key]; + originals[parsed_key] = data[parsed_key]; + data[parsed_key] = parsed_keys[parsed_key]; + } + + // 3. Pass it into TRANSFORM.run + var exp = TRANSFORM.run(real_template, data); + for (var e in exp) { + result[e] = exp[e]; + } + + // 4. Remove the data from memory + for (var parsed_key in parsed_keys) { + if (moriginals[parsed_key]) + TRANSFORM.memory[parsed_key] = moriginals[parsed_key]; + else + delete TRANSFORM.memory[parsed_key]; + + if (originals[parsed_key]) + data[parsed_key] = originals[parsed_key]; + else + delete data[parsed_key]; + } + + /////////////////////////// + /// End of modification /// + /////////////////////////// + } + } else if (fun.name === '#let*') { + if (Helper.is_array(template[key]) && template[key].length == 2) { + var defs = template[key][0]; + var real_template = template[key][1]; + + // 1. Parse the first item to assign variables + var parsed_keys = {}; + let originals = {}; + let moriginals = {}; + for (var def in defs) { + var dd = {}; + dd[def] = defs[def] + var x = TRANSFORM.run(dd, data); + parsed_keys[def] = x[def]; + originals[def] = data[def]; + data[def] = parsed_keys[def]; + } + + // 2. modify the data + for (var parsed_key in parsed_keys) { + moriginals[parsed_key] = TRANSFORM.memory[parsed_key]; TRANSFORM.memory[parsed_key] = parsed_keys[parsed_key]; + originals[parsed_key] = data[parsed_key]; data[parsed_key] = parsed_keys[parsed_key]; } - // 2. Pass it into TRANSFORM.run - result = TRANSFORM.run(real_template, data); + // 3. Pass it into TRANSFORM.run + var exp = TRANSFORM.run(real_template, data); + for (var e in exp) { + result[e] = exp[e]; + } + + // 4. Remove the data from memory + for (var parsed_key in parsed_keys) { + if (moriginals[parsed_key]) + TRANSFORM.memory[parsed_key] = moriginals[parsed_key]; + else + delete TRANSFORM.memory[parsed_key]; + + if (originals[parsed_key]) + data[parsed_key] = originals[parsed_key]; + else + delete data[parsed_key]; + } } } else if (fun.name === '#concat') { if (Helper.is_array(template[key])) { result = []; - template[key].forEach(function(concat_item) { + template[key].forEach(function (concat_item) { var res = TRANSFORM.run(concat_item, data); result = result.concat(res); }); @@ -332,7 +454,7 @@ } else if (fun.name === '#merge') { if (Helper.is_array(template[key])) { result = {}; - template[key].forEach(function(merge_item) { + template[key].forEach(function (merge_item) { var res = TRANSFORM.run(merge_item, data); for (var key in res) { result[key] = res[key]; @@ -342,7 +464,7 @@ // necessary because #merge merges multiple objects into one, // and one of them may be 'this', in which case the $index attribute // will have snuck into the final result - if(typeof data === 'object') { + if (typeof data === 'object') { delete result["$index"]; // #let handling @@ -373,12 +495,16 @@ // Ideally newData should be an array since it was prefixed by #each if (newData && Helper.is_array(newData)) { result = []; + let originals = {}; // Modified by Jakub Mifek for (var index = 0; index < newData.length; index++) { - // temporarily set $index - if(typeof newData[index] === 'object') { + // temporarily set $index and $this + if (typeof newData[index] === 'object') { newData[index]["$index"] = index; + newData[index]["$this"] = newData[index]; // #let handling + for (var declared_vars in TRANSFORM.memory) { + originals[declared_vars] = newData[index][declared_vars]; // Modified by Jakub Mifek newData[index][declared_vars] = TRANSFORM.memory[declared_vars]; } } else { @@ -400,12 +526,16 @@ // run var loop_item = TRANSFORM.run(template[key], newData[index]); - // clean up $index - if(typeof newData[index] === 'object') { + // clean up $index and $this + if (typeof newData[index] === 'object') { delete newData[index]["$index"]; + delete newData[index]["$this"]; // #let handling - for (var declared_vars in TRANSFORM.memory) { - delete newData[index][declared_vars]; + for (var declared_vars in TRANSFORM.memory) { // Modified by Jakub Mifek + if(originals[declared_vars]) + newData[index][declared_vars] = originals[declared_vars]; + else + delete newData[index][declared_vars]; } } else { delete String.prototype.$index; @@ -487,12 +617,14 @@ } return result; }, - fillout: function(data, template, raw) { + fillout: function (data, template, raw) { // 1. fill out if possible // 2. otherwise return the original var replaced = template; // Run fillout() only if it's a template. Otherwise just return the original string + if (Helper.is_template(template)) { + var re = /\{\{(.*?)\}\}/g; // variables are all instances of {{ }} in the current expression @@ -528,7 +660,7 @@ } return replaced; }, - _fillout: function(options) { + _fillout: function (options) { // Given a template and fill it out with passed slot and its corresponding data var re = /\{\{(.*?)\}\}/g; var full_re = /^\{\{((?!\}\}).)*\}\}$/; @@ -627,14 +759,14 @@ $selected: [], $injected: [], $progress: null, - exec: function(current, path, filter) { + exec: function (current, path, filter) { // if current matches the pattern, put it in the selected array if (typeof current === 'string') { // leaf node should be ignored // we're lookin for keys only } else if (Helper.is_array(current)) { - for (var i=0; i 0) { - SELECT.$selected.sort(function(a, b) { + SELECT.$selected.sort(function (a, b) { // sort by path length, so that deeper level items will be replaced first // TODO: may need to look into edge cases return b.path.length - a.path.length; - }).forEach(function(selection) { - //SELECT.$selected.forEach(function(selection) { + }).forEach(function (selection) { + //SELECT.$selected.forEach(function(selection) { // parse selected var parsed_object = TRANSFORM.run(template, selection.object); @@ -764,7 +896,7 @@ // update selected object with the parsed result selection.object = parsed_object; }); - SELECT.$selected.sort(function(a, b) { + SELECT.$selected.sort(function (a, b) { return a.index - b.index; }); } else { @@ -779,7 +911,7 @@ delete Boolean.prototype.$root; return SELECT; }, - transform: function(obj, serialized) { + transform: function (obj, serialized) { SELECT.$parsed = []; SELECT.$progress = null; /* @@ -808,11 +940,11 @@ root = data; if (SELECT.$selected && SELECT.$selected.length > 0) { - SELECT.$selected.sort(function(a, b) { + SELECT.$selected.sort(function (a, b) { // sort by path length, so that deeper level items will be replaced first // TODO: may need to look into edge cases return b.path.length - a.path.length; - }).forEach(function(selection) { + }).forEach(function (selection) { // parse selected var parsed_object = TRANSFORM.run(selection.object, data); // apply the result to root @@ -822,7 +954,7 @@ // update selected object with the parsed result selection.object = parsed_object; }); - SELECT.$selected.sort(function(a, b) { + SELECT.$selected.sort(function (a, b) { return a.index - b.index; }); } else { @@ -840,53 +972,53 @@ }, // Terminal methods - objects: function() { + objects: function () { SELECT.$progress = null; if (SELECT.$selected) { - return SELECT.$selected.map(function(item) { return item.object; }); + return SELECT.$selected.map(function (item) { return item.object; }); } else { return [SELECT.$selected_root]; } }, - keys: function() { + keys: function () { SELECT.$progress = null; if (SELECT.$selected) { - return SELECT.$selected.map(function(item) { return item.key; }); + return SELECT.$selected.map(function (item) { return item.key; }); } else { if (Array.isArray(SELECT.$selected_root)) { - return Object.keys(SELECT.$selected_root).map(function(key) { return parseInt(key); }); + return Object.keys(SELECT.$selected_root).map(function (key) { return parseInt(key); }); } else { return Object.keys(SELECT.$selected_root); } } }, - paths: function() { + paths: function () { SELECT.$progress = null; if (SELECT.$selected) { - return SELECT.$selected.map(function(item) { return item.path; }); + return SELECT.$selected.map(function (item) { return item.path; }); } else { if (Array.isArray(SELECT.$selected_root)) { - return Object.keys(SELECT.$selected_root).map(function(item) { + return Object.keys(SELECT.$selected_root).map(function (item) { // key is integer return '[' + item + ']'; }); } else { - return Object.keys(SELECT.$selected_root).map(function(item) { + return Object.keys(SELECT.$selected_root).map(function (item) { // key is string return '["' + item + '"]'; }); } } }, - values: function() { + values: function () { SELECT.$progress = null; if (SELECT.$selected) { - return SELECT.$selected.map(function(item) { return item.value; }); + return SELECT.$selected.map(function (item) { return item.value; }); } else { return Object.values(SELECT.$selected_root); } }, - root: function() { + root: function () { SELECT.$progress = null; return SELECT.$selected_root; }, @@ -894,13 +1026,13 @@ // Native JSON object override var _stringify = JSON.stringify; - JSON.stringify = function(val, replacer, spaces) { + JSON.stringify = function (val, replacer, spaces) { var t = typeof val; if (['number', 'string', 'boolean'].indexOf(t) !== -1) { return _stringify(val, replacer, spaces); } if (!replacer) { - return _stringify(val, function(key, val) { + return _stringify(val, function (key, val) { if (SELECT.$injected && SELECT.$injected.length > 0 && SELECT.$injected.indexOf(key) !== -1) { return undefined; } if (key === '$root' || key === '$index') { return undefined; @@ -919,6 +1051,26 @@ } }; + /////////////////////////////// + /// Modified by Jakub Mifek /// + /////////////////////////////// + + var setTemplates = function(templates) { + for(let template in templates) + TRANSFORM.templates[template] = templates[template]; + + return { + select: SELECT.select, + inject: SELECT.inject, + transform: TRANSFORM.transform, + setTemplates: setTemplates, + }; + } + + /////////////////////////// + /// End of modification /// + /////////////////////////// + // Export if (typeof exports !== 'undefined') { var x = { @@ -930,6 +1082,7 @@ inject: SELECT.inject, select: SELECT.select, transform: TRANSFORM.transform, + setTemplates: setTemplates, // Modified by Jakub Mifek }; if (typeof module !== 'undefined' && module.exports) { exports = module.exports = x; } exports = x; @@ -938,6 +1091,7 @@ select: SELECT.select, inject: SELECT.inject, transform: TRANSFORM.transform, + setTemplates: setTemplates, // Modified by Jakub Mifek }; } }());