diff --git a/packages/html/exports.js b/packages/html/exports.js index 2d0450e9ea..ba481df73c 100644 --- a/packages/html/exports.js +++ b/packages/html/exports.js @@ -13,6 +13,8 @@ HTML = { EmitCode: makeTagFunc('EmitCode'), // e.g. `Special({ ... stuff ... })` Special: makeTagFunc('Special'), + // e.g. `Raw("
")` + Raw: makeTagFunc('Raw'), asciiLowerCase: asciiLowerCase, properCaseTagName: properCaseTagName, diff --git a/packages/html/tags.js b/packages/html/tags.js index 3a13a52d47..cbc07a5fda 100644 --- a/packages/html/tags.js +++ b/packages/html/tags.js @@ -90,6 +90,14 @@ var checkComment = function (comment) { throw new Error("Comment should have exactly one content item, a simple string"); }; +// checks that a pseudoDOM node with tagName "Raw" is well-formed. +var checkRaw = function (raw) { + if (raw.attrs) + throw new Error("Raw can't have attributes"); + if (raw.length !== 1 || (typeof raw[0] !== 'string')) + throw new Error("Raw should have exactly one content item, a simple string"); +}; + // checks that a pseudoDOM node with tagName "Comment" is well-formed. var checkEmitCode = function (node) { if (node.attrs) @@ -122,6 +130,9 @@ typeOf = function (node) { } else if (node.tagName === 'Special') { checkSpecial(node); return 'special'; + } else if (node.tagName === 'Raw') { + checkRaw(node); + return 'raw'; } else { return 'tag'; } diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index c4cfe4a7d8..f53f9ecf30 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -1196,17 +1196,23 @@ Spacebars.parse2 = function (input) { var optimize = function (tree) { - var pushRawHTML = function (array, html, dontCoallesce) { - if ((! dontCoallesce) && array.length > 0 && - array[array.length - 1].tagName === 'Raw') { - array[array.length - 1][0] += html; + var pushRawHTML = function (array, html) { + var N = array.length; + if (N > 0 && array[N-1].tagName === 'Raw') { + array[N-1][0] += html; } else { array.push(HTML.Raw(html)); } }; - var optimizeArrayParts = function (array, optimizePartsFunc, dontCoallesce) { + var isPureChars = function (html) { + return (html.indexOf('&') < 0 && html.indexOf('<') < 0); + }; + + var optimizeArrayParts = function (array, optimizePartsFunc, forceOptimize) { var result = null; + if (forceOptimize) + result = []; for (var i = 0, N = array.length; i < N; i++) { var part = optimizePartsFunc(array[i]); if (part !== null) { @@ -1215,31 +1221,40 @@ var optimize = function (tree) { // This is our first special item. Stringify the other parts. result = []; for (var j = 0; j < i; j++) - pushRawHTML(result, UI.toHTML(array[j]), dontCoallesce); + pushRawHTML(result, UI.toHTML(array[j])); } result.push(part); } else { // just plain HTML found if (result !== null) { // we've already found something special, so convert this to Raw - pushRawHTML(result, UI.toHTML(array[i]), dontCoallesce); + pushRawHTML(result, UI.toHTML(array[i])); } } } + if (result !== null) { + // clean up unnecessary HTML.Raw wrappers around pure character data + for (var j = 0; j < result.length; j++) { + if (result[j].tagName === 'Raw' && + isPureChars(result[j][0])) + // replace HTML.Raw with simple string + result[j] = result[j][0]; + } + } return result; }; - var optimizeAttributeValueParts = function (v) { - // If we have nothing special going on, returns `null` (so that the - // parent can optimize). Otherwise returns a replacement for `v` - // with optimized parts. + var doesAttributeValueHaveSpecials = function (v) { var type = HTML.typeOf(v); if (type === 'null' || type === 'string' || type === 'charref') { - return null; + return false; } else if (type === 'special') { - return v; + return true; } else if (type === 'array') { - return optimizeArrayParts(v, optimizeAttributeValueParts); + for (var i = 0; i < v.length; i++) + if (doesAttributeValueHaveSpecials(v[i])) + return true; + return false; } else { throw new Error("Unexpected node in attribute value: " + v); } @@ -1262,46 +1277,27 @@ var optimize = function (tree) { } else if (type === 'tag') { var mustOptimize = false; - var newChildren = optimizeArrayParts(node, optimizeParts); - - var newAttrs = null; if (node.attrs) { var attrs = node.attrs; if (typeof attrs === 'function') { - newAttrs = attrs; + mustOptimize = true; } else { - var attrNames = []; - var attrValues = []; - _.each(attrs, function (v, n) { - attrNames.push(n); - attrValues.push(v); - }); - if (newChildren) { - // forced by special children of tag to optimize now. - // trick optimizeArrayParts into doing that by adding - // a fake attrValue that won't be picked up when we - // iterate over attrNames. - attrValues.push(function () {}); - } - var newValues = optimizeArrayParts(attrValues, - optimizeAttributeValueParts, - true); - if (newValues) { - newAttrs = {}; - for (var i = 0; i < attrNames.length; i++) - newAttrs[attrNames[i]] = newValues[i]; + for (var k in attrs) { + if (doesAttributeValueHaveSpecials(attrs[k])) { + mustOptimize = true; + break; + } } } } - if ((! newAttrs) && (! newChildren)) + var newChildren = optimizeArrayParts(node, optimizeParts, mustOptimize); + + if (newChildren === null) return null; - if (! newChildren) - newChildren = [HTML.Raw(UI.toHTML(Array.prototype.slice.call(node)))]; - var newTag = HTML.getTag(node.tagName).apply(null, newChildren); - newTag.attrs = newAttrs; + newTag.attrs = node.attrs; return newTag; } else if (type === 'array') { @@ -1321,7 +1317,40 @@ var optimize = function (tree) { }; }; - return optimizeParts(tree) || HTML.Raw(UI.toHTML(tree)); + var optTree = optimizeParts(tree); + if (optTree !== null) + // tree was optimized in parts + return optTree; + + optTree = HTML.Raw(UI.toHTML(tree)); + + if (isPureChars(optTree[0])) + return optTree[0]; + + return optTree; +}; + +var specialsToSessionGet = function (node) { + if (UI.isComponent(node)) { + return node; + } else { + var type = HTML.typeOf(node); + if (type === 'tag') { + // potential optimization: don't always create a new tag + var newChildren = _.map(Array.prototype.slice.call(node), specialsToSessionGet); + var newTag = HTML.getTag(node.tagName).apply(null, newChildren); + newTag.attrs = node.attrs; + return newTag; + } else if (type === 'array') { + return _.map(node, specialsToSessionGet); + } else if (type === 'special') { + if (node.attrs.type !== 'DOUBLE') + return node; + return HTML.EmitCode('function () { return Session.get("' + node.attrs.path.join('.') + '"); }'); + } else { + return node; + } + }; }; Spacebars.compile2 = function (input) { @@ -1335,6 +1364,8 @@ Spacebars.compile2 = function (input) { tree = optimize(tree); + tree = specialsToSessionGet(tree); + var code = '(function () { var self = this; return '; code += UI.toCode(tree); diff --git a/packages/ui/render2.js b/packages/ui/render2.js index 22bf4c349d..0dbb310bf0 100644 --- a/packages/ui/render2.js +++ b/packages/ui/render2.js @@ -594,7 +594,7 @@ var toCode = function (node) { var type = HTML.typeOf(node); if (type === 'emitcode') { result += node[0]; - } else if (type === 'comment' || type === 'charref' || + } else if (type === 'comment' || type === 'charref' || type === 'raw' || type === 'tag' || type === 'special') { var isNonTag = (type !== 'tag');