From 2c379a393ee73a80eda4ad3b38f6ec6a064c89c9 Mon Sep 17 00:00:00 2001 From: Rafael Weinstein Date: Thu, 26 Dec 2013 15:55:00 -0800 Subject: [PATCH] Produce sub-templates synchronously. This patch make handling of bindings for template bind/if/repeat special, in that they are processed privately as a group (and are not available as public binding names). Also, the processing order for binding recursive is now post-order, this means that all children elements will be bound and templates produced before a given node binds. R=arv BUG= Review URL: https://codereview.appspot.com/45890043 --- benchmark/benchmark.js | 2 +- src/TemplateBinding.js | 375 ++++++++++++++++++----------------------- tests/tests.js | 99 +---------- 3 files changed, 175 insertions(+), 301 deletions(-) diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js index f9e2d30..c08705a 100644 --- a/benchmark/benchmark.js +++ b/benchmark/benchmark.js @@ -225,7 +225,7 @@ }, teardownMDVVariant: function() { - this.template.bindings.repeat.close(); + this.template.bindings.iterator.close(); this.template = undefined; }, diff --git a/src/TemplateBinding.js b/src/TemplateBinding.js index 9a42a13..78bfa6a 100644 --- a/src/TemplateBinding.js +++ b/src/TemplateBinding.js @@ -23,13 +23,22 @@ var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); function getTreeScope(node) { - while (node.parentNode) { - node = node.parentNode; + while (node.parentNode || node.templateCreator_) { + if (!node.parentNode && node.templateCreator_) + node = node.templateCreator_; + else + node = node.parentNode; } return typeof node.getElementById === 'function' ? node : null; } + function getInstanceRoot(node) { + while (node.parentNode) + node = node.parentNode; + return node.templateCreator_ ? node : null; + } + var Map; if (global.Map && typeof global.Map.prototype.forEach === 'function') { Map = global.Map; @@ -441,7 +450,9 @@ function ensureSetModelScheduled(template) { if (!template.setModelFn_) { template.setModelFn_ = function() { - addBindings(template, template.model, template.prepareBindingFn_); + processBindings(template, + getBindings(template, template.prepareBindingFn_), + template.model_); }; } @@ -449,101 +460,34 @@ } mixin(HTMLTemplateElement.prototype, { - bind: function(name, value, oneTime) { - if (!this.iterator_) - this.iterator_ = new TemplateIterator(this); - - this.bindings = this.bindings || {}; - if (name === 'bind') { - this.unbind(name); - this.iterator_.hasBind = true; - if (!oneTime) { - value.open(this.iterator_.depsChanged, this.iterator_); - this.iterator_.bindObserver = value; - } else { - this.iterator_.bindValue = value; + processBindingDirectives_: function(directives) { + if (this.iterator_) { + if (this.iterator_.deps) { + if (this.iterator_.deps.ifOneTime === false) + this.iterator_.deps.ifValue.close(); + if (this.iterator_.deps.oneTime === false) + this.iterator_.deps.value.close(); } - - this.iterator_.depsChanged(); - return this.bindings.bind = this.iterator_; } - if (name === 'repeat') { - this.unbind(name); - this.iterator_.hasRepeat = true; - if (!oneTime) { - value.open(this.iterator_.depsChanged, this.iterator_); - this.iterator_.repeatObserver = value; - } else { - this.iterator_.repeatValue = value; + if (!directives.if && !directives.bind &&!directives.repeat) { + if (this.iterator_) { + this.iterator_.close(); + this.iterator_ = undefined; + this.bindings.iterator = undefined; } - this.iterator_.depsChanged(); - return this.bindings.repeat = this.iterator_; - } - - if (name === 'if') { - this.unbind(name); - this.iterator_.hasIf = true; - if (!oneTime) { - value.open(this.iterator_.depsChanged, this.iterator_); - this.iterator_.ifObserver = value; - } else { - this.iterator_.ifValue = value; - } - - this.iterator_.depsChanged(); - return this.bindings.if = this.iterator_; - } - - return HTMLElement.prototype.bind.call(this, name, value, oneTime); - }, - - unbind: function(name) { - if (name === 'bind') { - if (!this.iterator_) - return; - - this.iterator_.hasBind = false; - this.iterator_.bindValue = undefined; - if (this.iterator_.bindObserver) - this.iterator_.bindObserver.close(); - this.iterator_.bindObserver = undefined; - - this.iterator_.depsChanged(); - return this.bindings.bind = undefined; - } - - if (name === 'repeat') { - if (!this.iterator_) - return; - - this.iterator_.hasRepeat = false; - this.iterator_.repeatValue = undefined; - if (this.iterator_.repeatObserver) - this.iterator_.repeatObserver.close(); - this.iterator_.repeatObserver = undefined; - - this.iterator_.depsChanged(); - return this.bindings.repeat = undefined; + return; } - if (name === 'if') { - if (!this.iterator_) - return; - - this.iterator_.hasIf = false; - this.iterator_.ifValue = undefined; - - if (this.iterator_.ifObserver) - this.iterator_.ifObserver.close(); - this.iterator_.ifObserver = undefined; - - this.iterator_.depsChanged(); - return this.bindings.if = undefined; + if (!this.iterator_) { + this.iterator_ = new TemplateIterator(this); + this.bindings = this.bindings || {}; + this.bindings.iterator = this.iterator_; } - return HTMLElement.prototype.unbind.call(this, name); + this.iterator_.updateDependencies(directives, this.model_); + return this.iterator_; }, createInstance: function(model, instanceBindings) { @@ -557,15 +501,29 @@ } var stagingDocument = getTemplateStagingDocument(this); - var instance = createAndBindInstance(content, null, stagingDocument, map, - model, - this.bindingDelegate_, - instanceBindings); - - // TODO(rafaelw): We can do this more lazily, but setting a sentinel - // in the parent of the template element, and creating it when it's - // asked for by walking back to find the iterating template. - addTemplateInstanceRecord(instance, model); + var instance = stagingDocument.createDocumentFragment(); + instance.templateCreator_ = this; + + var instanceRecord = { + firstNode: null, + lastNode: null, + model: model + }; + + var i = 0; + for (var child = content.firstChild; child; child = child.nextSibling) { + var clone = cloneAndBindingInstance(child, instance, stagingDocument, + map.children[i++], + model, + this.bindingDelegate_, + instanceBindings); + clone.templateInstance_ = instanceRecord; + } + + instanceRecord.firstNode = instance.firstChild; + // TODO(rafaelw): This could be wrong if the last node is