diff --git a/packages/spacebars-compiler/spacebars-compiler.js b/packages/spacebars-compiler/spacebars-compiler.js index b72989492e..b4d6158ec4 100644 --- a/packages/spacebars-compiler/spacebars-compiler.js +++ b/packages/spacebars-compiler/spacebars-compiler.js @@ -29,6 +29,10 @@ var optimize = function (tree) { return (html.indexOf('&') < 0 && html.indexOf('<') < 0); }; + // Returns `null` if no specials are found in the array, so that the + // parent can perform the actual optimization. Otherwise, returns + // an array of parts which have been optimized as much as possible. + // `forceOptimize` forces the latter case. var optimizeArrayParts = function (array, optimizePartsFunc, forceOptimize) { var result = null; if (forceOptimize) @@ -102,7 +106,14 @@ var optimize = function (tree) { var mustOptimize = false; - if (node.attrs) { + // Avoid ever producing HTML containing `...`, because the + // browser will insert a TBODY. If we just `createElement("table")` and + // `createElement("tr")`, on the other hand, no TBODY is necessary + // (assuming IE 8+). + if (tagName === 'table') + mustOptimize = true; + + if (node.attrs && ! mustOptimize) { var attrs = node.attrs; for (var k in attrs) { if (doesAttributeValueHaveSpecials(attrs[k])) { diff --git a/packages/spacebars-tests/template_tests.html b/packages/spacebars-tests/template_tests.html index 2a49eb661e..60b1ce693f 100644 --- a/packages/spacebars-tests/template_tests.html +++ b/packages/spacebars-tests/template_tests.html @@ -663,3 +663,11 @@ Hi there! + + + + diff --git a/packages/spacebars-tests/template_tests.js b/packages/spacebars-tests/template_tests.js index 8f10f85f9c..4b1986d6cc 100644 --- a/packages/spacebars-tests/template_tests.js +++ b/packages/spacebars-tests/template_tests.js @@ -1832,3 +1832,19 @@ Tinytest.add( document.body.removeChild(div); } ); + +Tinytest.add("spacebars - template - tables", function (test) { + var tmpl1 = Template.spacebars_test_tables1; + + var div = renderToDiv(tmpl1); + test.equal(_.pluck(div.querySelectorAll('*'), 'tagName'), + ['TABLE', 'TR', 'TD']); + divRendersTo(test, div, '
Foo
'); + + var tmpl2 = Template.spacebars_test_tables2; + tmpl2.foo = 'Foo'; + div = renderToDiv(tmpl2); + test.equal(_.pluck(div.querySelectorAll('*'), 'tagName'), + ['TABLE', 'TR', 'TD']); + divRendersTo(test, div, '
Foo
'); +}); diff --git a/packages/ui/domrange.js b/packages/ui/domrange.js index 39edbc31a1..d4326a62a0 100644 --- a/packages/ui/domrange.js +++ b/packages/ui/domrange.js @@ -3,7 +3,6 @@ // - UI hooks (expose, test) // - Quick remove/add (mark "leaving" members; needs UI hooks) // - Event removal on removal -// - Event moving on TBODY move var DomBackend = UI.DomBackend; @@ -98,9 +97,6 @@ var rangeParented = function (range) { // element. This is really just for IE 9+ // TextNode GC issues, but we can't do reliable // feature detection (i.e. bug detection). - // Note that because we keep a direct pointer to - // `parentNode.$_uiranges`, it doesn't matter - // if we are reparented (e.g. wrapped in a TBODY). var parentNode = range.parentNode(); var rangeDict = ( parentNode.$_uiranges || @@ -306,11 +302,6 @@ _extend(DomRange.prototype, { range.owner = this; var nodes = range.getNodes(); - if (tbodyFixNeeded(nodes, parentNode)) - // may cause a refresh(); important that the - // member isn't added yet - parentNode = moveWithOwnersIntoTbody(this); - members[id] = newMember; for (var i = 0; i < nodes.length; i++) insertNode(nodes[i], parentNode, nextNode); @@ -327,11 +318,6 @@ _extend(DomRange.prototype, { if (node.nodeType !== 3) node.$ui = this; - if (tbodyFixNeeded(node, parentNode)) - // may cause a refresh(); important that the - // member isn't added yet - parentNode = moveWithOwnersIntoTbody(this); - members[id] = newMember; insertNode(node, parentNode, nextNode); } @@ -720,8 +706,6 @@ DomRange.getComponents = function (element) { // `parentNode` must be an ELEMENT, not a fragment DomRange.insert = function (range, parentNode, nextNode) { var nodes = range.getNodes(); - if (tbodyFixNeeded(nodes, parentNode)) - parentNode = makeOrFindTbody(parentNode, nextNode); for (var i = 0; i < nodes.length; i++) insertNode(nodes[i], parentNode, nextNode); rangeParented(range); @@ -741,65 +725,6 @@ DomRange.getContainingComponent = function (element) { return null; }; -///// TBODY FIX for compatibility with jQuery. -// -// Because people might use jQuery from UI hooks, and -// jQuery is unable to do $(myTable).append(myTR) without -// adding a TBODY (for historical reasons), we move any DomRange -// that gains a TR, and its immediately enclosing DomRanges, -// into a TBODY. -// -// See http://www.quora.com/David-Greenspan/Posts/The-Great-TBODY-Debacle -var tbodyFixNeeded = function (childOrChildren, parent) { - if (parent.nodeName !== 'TABLE') - return false; - - if (isArray(childOrChildren)) { - var foundTR = false; - for (var i = 0, N = childOrChildren.length; i < N; i++) { - var n = childOrChildren[i]; - if (n.nodeType === 1 && n.nodeName === 'TR') { - foundTR = true; - break; - } - } - if (! foundTR) - return false; - } else { - var n = childOrChildren; - if (! (n.nodeType === 1 && n.nodeName === 'TR')) - return false; - } - - return true; -}; - -var makeOrFindTbody = function (parent, next) { - // we have a TABLE > TR situation - var tbody = parent.getElementsByTagName('tbody')[0]; - if (! tbody) { - tbody = parent.ownerDocument.createElement("tbody"); - parent.insertBefore(tbody, next || null); - } - return tbody; -}; - -var moveWithOwnersIntoTbody = function (range) { - while (range.owner) - range = range.owner; - - var nodes = range.getNodes(); // causes refresh - var tbody = makeOrFindTbody(range.parentNode(), - range.end.nextSibling); - for (var i = 0; i < nodes.length; i++) - tbody.appendChild(nodes[i]); - - // XXX complete the reparenting by moving event - // HandlerRecs of `range`. - - return tbody; -}; - ///// FIND BY SELECTOR DomRange.prototype.contains = function (compOrNode) { diff --git a/packages/ui/domrange_tests.js b/packages/ui/domrange_tests.js index 7ef3d94d94..fb042f2dea 100644 --- a/packages/ui/domrange_tests.js +++ b/packages/ui/domrange_tests.js @@ -489,133 +489,6 @@ Tinytest.add("ui - DomRange - external moves", function (test) { strip('-------------- (aXb ghiWjkl)')); }); -Tinytest.add("ui - DomRange - tables", function (test) { - var range = function (x) { - // create a range x.dom containing an element x.el, - // inside that element, the range x.content.dom - x.dom = new DomRange; - if (x.el) { - x.dom.add(x.el); - if (x.content) - DomRange.insert(x.content.dom, x.el); - } - return x; - }; - var tr, td; - var table = range({ - el: document.createElement('table'), - content: tr = range({ - el: document.createElement('tr'), - content: td = range({ - el: document.createElement('td') - }) - }) - }); - - // TBODY got inserted automatically. - // This tests DomRange.insert. - test.equal(table.el.childNodes.length, 1); - test.equal(table.el.firstChild.nodeName, 'TBODY'); - // TBODY contains [start, TR, end] - test.equal(table.el.firstChild.childNodes.length, 3); - test.equal(table.el.firstChild.childNodes[1], tr.el); - test.equal(tr.el.childNodes.length, 3); - test.equal(tr.el.childNodes[1], td.el); - - // start over - $(table.el).empty(); - test.equal(table.el.childNodes.length, 0); - - table.content = range({}); - DomRange.insert(table.content.dom, table.el); - // table has two children (start/end markers), no elements - test.equal(table.el.childNodes.length, 2); - test.notEqual(table.el.firstChild.nodeType, 1); - test.notEqual(table.el.lastChild.nodeType, 1); - - // shazam, adding a TR should move the whole range - // into a TBODY. This tests range.add(node). - table.content.dom.add(document.createElement('tr')); - - test.equal(table.el.childNodes.length, 1); - test.equal(table.el.firstChild.nodeName, 'TBODY'); - test.equal(table.el.firstChild.childNodes.length, 3); - test.equal(table.el.firstChild.childNodes[1].nodeName, 'TR'); - - // start over. - $(table.el).empty(); - test.equal(table.el.childNodes.length, 0); - - table.content = range({}); - DomRange.insert(table.content.dom, table.el); - var a1 = range({}); - var a2 = range({}); - a1.dom.add(a2.dom); - table.content.dom.add(a1.dom); - // 6 marker nodes in table, no elements - test.equal(table.el.childNodes.length, 6); - test.equal($(table.el).find("*").length, 0); - // shazam, adding a TR to the innermost range - // should move all the ranges into a TBODY. - a2.dom.add(document.createElement('tr')); - test.equal(table.el.childNodes.length, 1); - test.equal(table.el.firstChild.nodeName, 'TBODY'); - test.equal(table.el.firstChild.childNodes.length, 7); - test.equal(table.el.firstChild.childNodes[3].nodeName, 'TR'); - - // start over. this time test adding a range containing - // a TR. - $(table.el).empty(); - test.equal(table.el.childNodes.length, 0); - - table.content = range({}); - DomRange.insert(table.content.dom, table.el); - var b1 = range({}); - var b2 = range({}); - table.content.dom.add(b1.dom); - b2.dom.add(document.createElement('tr')); - // 4 marker nodes in table, no elements - test.equal(table.el.childNodes.length, 4); - test.equal($(table.el).find("*").length, 0); - // shazam, adding b2, which contains a TR, - // should move all the ranges into a TBODY. - b1.dom.add(b2.dom); - test.equal(table.el.childNodes.length, 1); - test.equal(table.el.firstChild.nodeName, 'TBODY'); - test.equal(table.el.firstChild.childNodes.length, 7); - test.equal(table.el.firstChild.childNodes[3].nodeName, 'TR'); - - test.equal(b2.dom.parentNode().nodeName, 'TBODY'); - test.equal(b1.dom.parentNode().nodeName, 'TBODY'); - test.equal(table.content.dom.parentNode().nodeName, 'TBODY'); - - - // start over. now test two TR ranges. - $(table.el).empty(); - test.equal(table.el.childNodes.length, 0); - - var c1 = range({}); - var c2 = range({}); - DomRange.insert(c1.dom, table.el); - DomRange.insert(c2.dom, table.el); - test.equal(table.el.childNodes.length, 4); - test.equal($(table.el).find("*").length, 0); - c2.dom.add(document.createElement('tr')); - test.equal(table.el.childNodes.length, 3); - test.equal($(table.el).find("> *").length, 1); - test.equal($(table.el).find("> tbody").length, 1); - c1.dom.add(document.createElement('tr')); - // now there should be a single TBODY with two - // ranges in it containing TRs - test.equal(table.el.childNodes.length, 1); - test.equal(table.el.firstChild.nodeName, 'TBODY'); - var tbody = table.el.firstChild; - test.equal(tbody.childNodes.length, 6); - test.equal($(tbody).find("> *").length, 2); // 2 elements - test.equal(tbody.childNodes[1].nodeName, 'TR'); - test.equal(tbody.childNodes[4].nodeName, 'TR'); -}); - Tinytest.add("ui - DomRange - basic events", function (test) { // test.equal doesn't work on arrays of DOM nodes, so // we need this. It's `===` that descends recursively