Version 0.5.7.1 .
svn merge -c 22591 https://dart.googlecode.com/svn/branches/bleeding_edge/dart dart
Review URL: https://codereview.chromium.org//14651030
git-svn-id: http://dart.googlecode.com/svn/trunk@22592 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 0e2dbb5..cf0bbde 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -7716,8 +7716,9 @@
void set model(value) {
_ensureTemplate();
+ var syntax = TemplateElement.syntax[attributes['syntax']];
_model = value;
- _addBindings(this, model);
+ _addBindings(this, model, syntax);
}
// TODO(jmesserly): const set would be better
@@ -14772,17 +14773,6 @@
TemplateInstance _templateInstance;
- // TODO(arv): Consider storing all "NodeRareData" on a single object?
- int __instanceTerminatorCount;
- int get _instanceTerminatorCount {
- if (__instanceTerminatorCount == null) return 0;
- return __instanceTerminatorCount;
- }
- set _instanceTerminatorCount(int value) {
- if (value == 0) value = null;
- __instanceTerminatorCount = value;
- }
-
/** Gets the template instance that instantiated this node, if any. */
@Experimental
TemplateInstance get templateInstance =>
@@ -24984,11 +24974,61 @@
// more Dart-friendly.
@Experimental
abstract class CustomBindingSyntax {
+ /**
+ * This syntax method allows for a custom interpretation of the contents of
+ * mustaches (`{{` ... `}}`).
+ *
+ * When a template is inserting an instance, it will invoke this method for
+ * each mustache which is encountered. The function is invoked with four
+ * arguments:
+ *
+ * - [model]: The data context for which this instance is being created.
+ * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+ * - [name]: The context in which the mustache occurs. Within element
+ * attributes, this will be the name of the attribute. Within text,
+ * this will be 'text'.
+ * - [node]: A reference to the node to which this binding will be created.
+ *
+ * If the method wishes to handle binding, it is required to return an object
+ * which has at least a `value` property that can be observed. If it does,
+ * then MDV will call [Node.bind on the node:
+ *
+ * node.bind(name, retval, 'value');
+ *
+ * If the 'getBinding' does not wish to override the binding, it should return
+ * null.
+ */
// TODO(jmesserly): I had to remove type annotations from "name" and "node"
// Normally they are String and Node respectively. But sometimes it will pass
// (int name, CompoundBinding node). That seems very confusing; we may want
// to change this API.
- getBinding(model, String path, name, node);
+ getBinding(model, String path, name, node) => null;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate model than the
+ * one the template would otherwise use when producing an instance.
+ *
+ * When a template is about to create an instance, it will invoke this method
+ * The function is invoked with two arguments:
+ *
+ * - [template]: The template element which is about to create and insert an
+ * instance.
+ * - [model]: The data context for which this instance is being created.
+ *
+ * The template element will always use the return value of `getInstanceModel`
+ * as the model for the new instance. If the syntax does not wish to override
+ * the value, it should simply return the `model` value it was passed.
+ */
+ getInstanceModel(Element template, model) => model;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate expansion of
+ * the [template] contents. When the template wants to create an instance,
+ * it will call this method with the template element.
+ *
+ * By default this will call `template.createInstance()`.
+ */
+ getInstanceFragment(Element template) => template.createInstance();
}
/** The callback used in the [CompoundBinding.combinator] field. */
@@ -25356,7 +25396,7 @@
if (node is Element) {
_addAttributeBindings(node, model, syntax);
} else if (node is Text) {
- _parseAndBind(node, node.text, 'text', model, syntax);
+ _parseAndBind(node, 'text', node.text, model, syntax);
}
for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -25370,11 +25410,11 @@
if (value == '' && (name == 'bind' || name == 'repeat')) {
value = '{{}}';
}
- _parseAndBind(element, value, name, model, syntax);
+ _parseAndBind(element, name, value, model, syntax);
});
}
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
CustomBindingSyntax syntax) {
var tokens = _parseMustacheTokens(text);
@@ -25509,105 +25549,12 @@
_removeAllBindingsRecursively(child);
}
-class _InstanceCursor {
- final Element _template;
- Node _terminator;
- Node _previousTerminator;
- int _previousIndex = -1;
- int _index = 0;
-
- _InstanceCursor(this._template, [index]) {
- _terminator = _template;
- if (index != null) {
- while (index-- > 0) {
- next();
- }
- }
- }
-
- void next() {
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- while (_index > _terminator._instanceTerminatorCount) {
- _index -= _terminator._instanceTerminatorCount;
- _terminator = _terminator.nextNode;
- if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
- _index += _instanceCount(_terminator);
- }
- }
- }
-
- void abandon() {
- assert(_instanceCount(_template) > 0);
- assert(_terminator._instanceTerminatorCount > 0);
- assert(_index > 0);
-
- _terminator._instanceTerminatorCount--;
- _index--;
- }
-
- void insert(fragment) {
- assert(_template.parentNode != null);
-
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- _terminator = fragment.$dom_lastChild;
- if (_terminator == null) _terminator = _previousTerminator;
- _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
- _terminator._instanceTerminatorCount++;
- if (_terminator != _previousTerminator) {
- while (_previousTerminator._instanceTerminatorCount >
- _previousIndex) {
- _previousTerminator._instanceTerminatorCount--;
- _terminator._instanceTerminatorCount++;
- }
- }
- }
-
- void remove() {
- assert(_previousIndex != -1);
- assert(_previousTerminator != null &&
- (_previousIndex > 0 || _previousTerminator == _template));
- assert(_terminator != null && _index > 0);
- assert(_template.parentNode != null);
- assert(_instanceCount(_template) > 0);
-
- if (_previousTerminator == _terminator) {
- assert(_index == _previousIndex + 1);
- _terminator._instanceTerminatorCount--;
- _terminator = _template;
- _previousTerminator = null;
- _previousIndex = -1;
- return;
- }
-
- _terminator._instanceTerminatorCount--;
-
- var parent = _template.parentNode;
- while (_previousTerminator.nextNode != _terminator) {
- _removeTemplateChild(parent, _previousTerminator.nextNode);
- }
- _removeTemplateChild(parent, _terminator);
-
- _terminator = _previousTerminator;
- _index = _previousIndex;
- _previousTerminator = null;
- _previousIndex = -1; // 0?
- }
-}
-
class _TemplateIterator {
final Element _templateElement;
- int instanceCount = 0;
- List iteratedValue;
- bool observing = false;
+ final List<Node> terminators = [];
final CompoundBinding inputs;
+ List iteratedValue;
StreamSubscription _sub;
StreamSubscription _valueBinding;
@@ -25650,9 +25597,74 @@
}
}
- // TODO(jmesserly): port MDV v3.
- getInstanceModel(model, syntax) => model;
- getInstanceFragment(syntax) => _templateElement.createInstance();
+ Node getTerminatorAt(int index) {
+ if (index == -1) return _templateElement;
+ var terminator = terminators[index];
+ if (terminator is! Element) return terminator;
+
+ var subIterator = terminator._templateIterator;
+ if (subIterator == null) return terminator;
+
+ return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+ }
+
+ void insertInstanceAt(int index, Node fragment) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = fragment.$dom_lastChild;
+ if (terminator == null) terminator = previousTerminator;
+
+ terminators.insert(index, terminator);
+ var parent = _templateElement.parentNode;
+ parent.insertBefore(fragment, previousTerminator.nextNode);
+ }
+
+ void removeInstanceAt(int index) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = getTerminatorAt(index);
+ terminators.removeAt(index);
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void removeAllInstances() {
+ if (terminators.length == 0) return;
+
+ var previousTerminator = _templateElement;
+ var terminator = getTerminatorAt(terminators.length - 1);
+ terminators.length = 0;
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void clear() {
+ unobserve();
+ removeAllInstances();
+ iteratedValue = null;
+ }
+
+ getInstanceModel(model, syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceModel(_templateElement, model);
+ }
+ return model;
+ }
+
+ getInstanceFragment(syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceFragment(_templateElement);
+ }
+ return _templateElement.createInstance();
+ }
void _handleChanges(List<ListChangeRecord> splices) {
var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -25661,9 +25673,7 @@
if (splice is! ListChangeRecord) continue;
for (int i = 0; i < splice.removedCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
- cursor.remove();
- instanceCount--;
+ removeInstanceAt(splice.index);
}
for (var addIndex = splice.index;
@@ -25671,14 +25681,13 @@
addIndex++) {
var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
var fragment = getInstanceFragment(syntax);
_addBindings(fragment, model, syntax);
_addTemplateInstanceRecord(fragment, model);
- var cursor = new _InstanceCursor(_templateElement, addIndex);
- cursor.insert(fragment);
- instanceCount++;
+ insertInstanceAt(addIndex, fragment);
}
}
}
@@ -25689,31 +25698,12 @@
_sub = null;
}
- void clear() {
- unobserve();
-
- iteratedValue = null;
- if (instanceCount == 0) return;
-
- for (var i = 0; i < instanceCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, 1);
- cursor.remove();
- }
-
- instanceCount = 0;
- }
-
void abandon() {
unobserve();
_valueBinding.cancel();
inputs.dispose();
}
}
-
-int _instanceCount(Element element) {
- var templateIterator = element._templateIterator;
- return templateIterator != null ? templateIterator.instanceCount : 0;
-}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index e549cb6..3c83672 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -7999,8 +7999,9 @@
void set model(value) {
_ensureTemplate();
+ var syntax = TemplateElement.syntax[attributes['syntax']];
_model = value;
- _addBindings(this, model);
+ _addBindings(this, model, syntax);
}
// TODO(jmesserly): const set would be better
@@ -15723,17 +15724,6 @@
TemplateInstance _templateInstance;
- // TODO(arv): Consider storing all "NodeRareData" on a single object?
- int __instanceTerminatorCount;
- int get _instanceTerminatorCount {
- if (__instanceTerminatorCount == null) return 0;
- return __instanceTerminatorCount;
- }
- set _instanceTerminatorCount(int value) {
- if (value == 0) value = null;
- __instanceTerminatorCount = value;
- }
-
/** Gets the template instance that instantiated this node, if any. */
@Experimental
TemplateInstance get templateInstance =>
@@ -26677,11 +26667,61 @@
// more Dart-friendly.
@Experimental
abstract class CustomBindingSyntax {
+ /**
+ * This syntax method allows for a custom interpretation of the contents of
+ * mustaches (`{{` ... `}}`).
+ *
+ * When a template is inserting an instance, it will invoke this method for
+ * each mustache which is encountered. The function is invoked with four
+ * arguments:
+ *
+ * - [model]: The data context for which this instance is being created.
+ * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+ * - [name]: The context in which the mustache occurs. Within element
+ * attributes, this will be the name of the attribute. Within text,
+ * this will be 'text'.
+ * - [node]: A reference to the node to which this binding will be created.
+ *
+ * If the method wishes to handle binding, it is required to return an object
+ * which has at least a `value` property that can be observed. If it does,
+ * then MDV will call [Node.bind on the node:
+ *
+ * node.bind(name, retval, 'value');
+ *
+ * If the 'getBinding' does not wish to override the binding, it should return
+ * null.
+ */
// TODO(jmesserly): I had to remove type annotations from "name" and "node"
// Normally they are String and Node respectively. But sometimes it will pass
// (int name, CompoundBinding node). That seems very confusing; we may want
// to change this API.
- getBinding(model, String path, name, node);
+ getBinding(model, String path, name, node) => null;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate model than the
+ * one the template would otherwise use when producing an instance.
+ *
+ * When a template is about to create an instance, it will invoke this method
+ * The function is invoked with two arguments:
+ *
+ * - [template]: The template element which is about to create and insert an
+ * instance.
+ * - [model]: The data context for which this instance is being created.
+ *
+ * The template element will always use the return value of `getInstanceModel`
+ * as the model for the new instance. If the syntax does not wish to override
+ * the value, it should simply return the `model` value it was passed.
+ */
+ getInstanceModel(Element template, model) => model;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate expansion of
+ * the [template] contents. When the template wants to create an instance,
+ * it will call this method with the template element.
+ *
+ * By default this will call `template.createInstance()`.
+ */
+ getInstanceFragment(Element template) => template.createInstance();
}
/** The callback used in the [CompoundBinding.combinator] field. */
@@ -27049,7 +27089,7 @@
if (node is Element) {
_addAttributeBindings(node, model, syntax);
} else if (node is Text) {
- _parseAndBind(node, node.text, 'text', model, syntax);
+ _parseAndBind(node, 'text', node.text, model, syntax);
}
for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -27063,11 +27103,11 @@
if (value == '' && (name == 'bind' || name == 'repeat')) {
value = '{{}}';
}
- _parseAndBind(element, value, name, model, syntax);
+ _parseAndBind(element, name, value, model, syntax);
});
}
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
CustomBindingSyntax syntax) {
var tokens = _parseMustacheTokens(text);
@@ -27202,105 +27242,12 @@
_removeAllBindingsRecursively(child);
}
-class _InstanceCursor {
- final Element _template;
- Node _terminator;
- Node _previousTerminator;
- int _previousIndex = -1;
- int _index = 0;
-
- _InstanceCursor(this._template, [index]) {
- _terminator = _template;
- if (index != null) {
- while (index-- > 0) {
- next();
- }
- }
- }
-
- void next() {
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- while (_index > _terminator._instanceTerminatorCount) {
- _index -= _terminator._instanceTerminatorCount;
- _terminator = _terminator.nextNode;
- if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
- _index += _instanceCount(_terminator);
- }
- }
- }
-
- void abandon() {
- assert(_instanceCount(_template) > 0);
- assert(_terminator._instanceTerminatorCount > 0);
- assert(_index > 0);
-
- _terminator._instanceTerminatorCount--;
- _index--;
- }
-
- void insert(fragment) {
- assert(_template.parentNode != null);
-
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- _terminator = fragment.$dom_lastChild;
- if (_terminator == null) _terminator = _previousTerminator;
- _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
- _terminator._instanceTerminatorCount++;
- if (_terminator != _previousTerminator) {
- while (_previousTerminator._instanceTerminatorCount >
- _previousIndex) {
- _previousTerminator._instanceTerminatorCount--;
- _terminator._instanceTerminatorCount++;
- }
- }
- }
-
- void remove() {
- assert(_previousIndex != -1);
- assert(_previousTerminator != null &&
- (_previousIndex > 0 || _previousTerminator == _template));
- assert(_terminator != null && _index > 0);
- assert(_template.parentNode != null);
- assert(_instanceCount(_template) > 0);
-
- if (_previousTerminator == _terminator) {
- assert(_index == _previousIndex + 1);
- _terminator._instanceTerminatorCount--;
- _terminator = _template;
- _previousTerminator = null;
- _previousIndex = -1;
- return;
- }
-
- _terminator._instanceTerminatorCount--;
-
- var parent = _template.parentNode;
- while (_previousTerminator.nextNode != _terminator) {
- _removeTemplateChild(parent, _previousTerminator.nextNode);
- }
- _removeTemplateChild(parent, _terminator);
-
- _terminator = _previousTerminator;
- _index = _previousIndex;
- _previousTerminator = null;
- _previousIndex = -1; // 0?
- }
-}
-
class _TemplateIterator {
final Element _templateElement;
- int instanceCount = 0;
- List iteratedValue;
- bool observing = false;
+ final List<Node> terminators = [];
final CompoundBinding inputs;
+ List iteratedValue;
StreamSubscription _sub;
StreamSubscription _valueBinding;
@@ -27343,9 +27290,74 @@
}
}
- // TODO(jmesserly): port MDV v3.
- getInstanceModel(model, syntax) => model;
- getInstanceFragment(syntax) => _templateElement.createInstance();
+ Node getTerminatorAt(int index) {
+ if (index == -1) return _templateElement;
+ var terminator = terminators[index];
+ if (terminator is! Element) return terminator;
+
+ var subIterator = terminator._templateIterator;
+ if (subIterator == null) return terminator;
+
+ return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+ }
+
+ void insertInstanceAt(int index, Node fragment) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = fragment.$dom_lastChild;
+ if (terminator == null) terminator = previousTerminator;
+
+ terminators.insert(index, terminator);
+ var parent = _templateElement.parentNode;
+ parent.insertBefore(fragment, previousTerminator.nextNode);
+ }
+
+ void removeInstanceAt(int index) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = getTerminatorAt(index);
+ terminators.removeAt(index);
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void removeAllInstances() {
+ if (terminators.length == 0) return;
+
+ var previousTerminator = _templateElement;
+ var terminator = getTerminatorAt(terminators.length - 1);
+ terminators.length = 0;
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void clear() {
+ unobserve();
+ removeAllInstances();
+ iteratedValue = null;
+ }
+
+ getInstanceModel(model, syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceModel(_templateElement, model);
+ }
+ return model;
+ }
+
+ getInstanceFragment(syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceFragment(_templateElement);
+ }
+ return _templateElement.createInstance();
+ }
void _handleChanges(List<ListChangeRecord> splices) {
var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -27354,9 +27366,7 @@
if (splice is! ListChangeRecord) continue;
for (int i = 0; i < splice.removedCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
- cursor.remove();
- instanceCount--;
+ removeInstanceAt(splice.index);
}
for (var addIndex = splice.index;
@@ -27364,14 +27374,13 @@
addIndex++) {
var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
var fragment = getInstanceFragment(syntax);
_addBindings(fragment, model, syntax);
_addTemplateInstanceRecord(fragment, model);
- var cursor = new _InstanceCursor(_templateElement, addIndex);
- cursor.insert(fragment);
- instanceCount++;
+ insertInstanceAt(addIndex, fragment);
}
}
}
@@ -27382,31 +27391,12 @@
_sub = null;
}
- void clear() {
- unobserve();
-
- iteratedValue = null;
- if (instanceCount == 0) return;
-
- for (var i = 0; i < instanceCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, 1);
- cursor.remove();
- }
-
- instanceCount = 0;
- }
-
void abandon() {
unobserve();
_valueBinding.cancel();
inputs.dispose();
}
}
-
-int _instanceCount(Element element) {
- var templateIterator = element._templateIterator;
- return templateIterator != null ? templateIterator.instanceCount : 0;
-}
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
diff --git a/tests/html/binding_syntax_test.dart b/tests/html/binding_syntax_test.dart
index ce1c847..0aa7732 100644
--- a/tests/html/binding_syntax_test.dart
+++ b/tests/html/binding_syntax_test.dart
@@ -5,6 +5,7 @@
library binding_syntax_test;
import 'dart:async';
+import 'dart:collection';
import 'dart:html';
import 'package:mdv_observe/mdv_observe.dart';
import 'package:unittest/html_config.dart';
@@ -54,24 +55,93 @@
var testSyntax = new TestBindingSyntax();
TemplateElement.syntax['Test'] = testSyntax;
+ try {
+ var div = createTestHtml(
+ '<template bind syntax="Test">{{ foo }}' +
+ '<template bind>{{ foo }}</template></template>');
+ recursivelySetTemplateModel(div, model);
+ deliverChangeRecords();
+ expect(div.nodes.length, 4);
+ expect(div.nodes.last.text, 'bar');
+ expect(div.nodes[2].tagName, 'TEMPLATE');
+ expect(div.nodes[2].attributes['syntax'], 'Test');
- var div = createTestHtml(
- '<template bind syntax="Test">{{ foo }}' +
- '<template bind>{{ foo }}</template></template>');
- recursivelySetTemplateModel(div, model);
- deliverChangeRecords();
- expect(div.nodes.length, 4);
- expect(div.nodes.last.text, 'bar');
- expect(div.nodes[2].tagName, 'TEMPLATE');
- expect(div.nodes[2].attributes['syntax'], 'Test');
+ expect(testSyntax.log, [
+ [model, '', 'bind', 'TEMPLATE'],
+ [model, 'foo', 'text', null],
+ [model, '', 'bind', 'TEMPLATE'],
+ [model, 'foo', 'text', null],
+ ]);
+ } finally {
+ TemplateElement.syntax.remove('Test');
+ }
+ });
- expect(testSyntax.log, [
- [model, 'foo', 'text', null],
- [model, '', 'bind', 'TEMPLATE'],
- [model, 'foo', 'text', null],
- ]);
+ test('getInstanceModel', () {
+ var model = toObservable([{'foo': 1}, {'foo': 2}, {'foo': 3}]
+ .map(toSymbolMap));
- TemplateElement.syntax.remove('Test');
+ var testSyntax = new TestModelSyntax();
+ testSyntax.altModels.addAll([{'foo': 'a'}, {'foo': 'b'}, {'foo': 'c'}]
+ .map(toSymbolMap));
+
+ TemplateElement.syntax['Test'] = testSyntax;
+ try {
+
+ var div = createTestHtml(
+ '<template repeat syntax="Test">' +
+ '{{ foo }}</template>');
+
+ var template = div.nodes[0];
+ recursivelySetTemplateModel(div, model);
+ deliverChangeRecords();
+
+ expect(div.nodes.length, 4);
+ expect(div.nodes[0].tagName, 'TEMPLATE');
+ expect(div.nodes[1].text, 'a');
+ expect(div.nodes[2].text, 'b');
+ expect(div.nodes[3].text, 'c');
+
+ expect(testSyntax.log, [
+ [template, model[0]],
+ [template, model[1]],
+ [template, model[2]],
+ ]);
+
+ } finally {
+ TemplateElement.syntax.remove('Test');
+ }
+ });
+
+ // Note: this test was original, not a port of an existing test.
+ test('getInstanceFragment', () {
+ var model = toSymbolMap({'foo': 'bar'});
+
+ var testSyntax = new WhitespaceRemover();
+ TemplateElement.syntax['Test'] = testSyntax;
+ try {
+ var div = createTestHtml(
+ '''<template bind syntax="Test">
+ {{ foo }}
+ <template bind>
+ {{ foo }}
+ </template>
+ </template>''');
+
+ recursivelySetTemplateModel(div, model);
+ deliverChangeRecords();
+
+ expect(testSyntax.trimmed, 2);
+ expect(testSyntax.removed, 1);
+
+ expect(div.nodes.length, 4);
+ expect(div.nodes.last.text, 'bar');
+ expect(div.nodes[2].tagName, 'TEMPLATE');
+ expect(div.nodes[2].attributes['syntax'], 'Test');
+
+ } finally {
+ TemplateElement.syntax.remove('Test');
+ }
});
test('Basic', () {
@@ -115,6 +185,7 @@
var test2Log = TemplateElement.syntax['Test2'].log;
expect(testLog, [
+ [model, '', 'bind', 'TEMPLATE'],
[model, 'foo', 'text', null],
[model, '', 'bind', 'TEMPLATE']
]);
@@ -126,6 +197,8 @@
});
}
+// TODO(jmesserly): mocks would be cleaner here.
+
class TestBindingSyntax extends CustomBindingSyntax {
var log = [];
@@ -134,6 +207,49 @@
}
}
+class TestModelSyntax extends CustomBindingSyntax {
+ var log = [];
+ var altModels = new ListQueue();
+
+ getInstanceModel(template, model) {
+ log.add([template, model]);
+ return altModels.removeFirst();
+ }
+}
+
+// Note: this isn't a very smart whitespace handler. A smarter one would only
+// trim indentation, not all whitespace.
+// See "trimOrCompact" in the web_ui Pub package.
+class WhitespaceRemover extends CustomBindingSyntax {
+ int trimmed = 0;
+ int removed = 0;
+
+ DocumentFragment getInstanceFragment(Element template) {
+ var instance = template.createInstance();
+ var walker = new TreeWalker(instance, NodeFilter.SHOW_TEXT);
+
+ var toRemove = [];
+ while (walker.nextNode() != null) {
+ var node = walker.currentNode;
+ var text = node.text.replaceAll('\n', '').trim();
+ if (text.length != node.text.length) {
+ if (text.length == 0) {
+ toRemove.add(node);
+ } else {
+ trimmed++;
+ node.text = text;
+ }
+ }
+ }
+
+ for (var node in toRemove) node.remove();
+ removed += toRemove.length;
+
+ return instance;
+ }
+}
+
+
class TimesTwoSyntax extends CustomBindingSyntax {
getBinding(model, path, name, node) {
path = path.trim();
diff --git a/tools/VERSION b/tools/VERSION
index ee428f2..9c1bb6f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
MAJOR 0
MINOR 5
BUILD 7
-PATCH 0
+PATCH 1
diff --git a/tools/dom/src/TemplateBindings.dart b/tools/dom/src/TemplateBindings.dart
index f0a57e9..5192927 100644
--- a/tools/dom/src/TemplateBindings.dart
+++ b/tools/dom/src/TemplateBindings.dart
@@ -44,11 +44,61 @@
// more Dart-friendly.
@Experimental
abstract class CustomBindingSyntax {
+ /**
+ * This syntax method allows for a custom interpretation of the contents of
+ * mustaches (`{{` ... `}}`).
+ *
+ * When a template is inserting an instance, it will invoke this method for
+ * each mustache which is encountered. The function is invoked with four
+ * arguments:
+ *
+ * - [model]: The data context for which this instance is being created.
+ * - [path]: The text contents (trimmed of outer whitespace) of the mustache.
+ * - [name]: The context in which the mustache occurs. Within element
+ * attributes, this will be the name of the attribute. Within text,
+ * this will be 'text'.
+ * - [node]: A reference to the node to which this binding will be created.
+ *
+ * If the method wishes to handle binding, it is required to return an object
+ * which has at least a `value` property that can be observed. If it does,
+ * then MDV will call [Node.bind on the node:
+ *
+ * node.bind(name, retval, 'value');
+ *
+ * If the 'getBinding' does not wish to override the binding, it should return
+ * null.
+ */
// TODO(jmesserly): I had to remove type annotations from "name" and "node"
// Normally they are String and Node respectively. But sometimes it will pass
// (int name, CompoundBinding node). That seems very confusing; we may want
// to change this API.
- getBinding(model, String path, name, node);
+ getBinding(model, String path, name, node) => null;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate model than the
+ * one the template would otherwise use when producing an instance.
+ *
+ * When a template is about to create an instance, it will invoke this method
+ * The function is invoked with two arguments:
+ *
+ * - [template]: The template element which is about to create and insert an
+ * instance.
+ * - [model]: The data context for which this instance is being created.
+ *
+ * The template element will always use the return value of `getInstanceModel`
+ * as the model for the new instance. If the syntax does not wish to override
+ * the value, it should simply return the `model` value it was passed.
+ */
+ getInstanceModel(Element template, model) => model;
+
+ /**
+ * This syntax method allows a syntax to provide an alterate expansion of
+ * the [template] contents. When the template wants to create an instance,
+ * it will call this method with the template element.
+ *
+ * By default this will call `template.createInstance()`.
+ */
+ getInstanceFragment(Element template) => template.createInstance();
}
/** The callback used in the [CompoundBinding.combinator] field. */
@@ -416,7 +466,7 @@
if (node is Element) {
_addAttributeBindings(node, model, syntax);
} else if (node is Text) {
- _parseAndBind(node, node.text, 'text', model, syntax);
+ _parseAndBind(node, 'text', node.text, model, syntax);
}
for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
@@ -430,11 +480,11 @@
if (value == '' && (name == 'bind' || name == 'repeat')) {
value = '{{}}';
}
- _parseAndBind(element, value, name, model, syntax);
+ _parseAndBind(element, name, value, model, syntax);
});
}
-void _parseAndBind(Node node, String text, String name, model,
+void _parseAndBind(Node node, String name, String text, model,
CustomBindingSyntax syntax) {
var tokens = _parseMustacheTokens(text);
@@ -569,105 +619,12 @@
_removeAllBindingsRecursively(child);
}
-class _InstanceCursor {
- final Element _template;
- Node _terminator;
- Node _previousTerminator;
- int _previousIndex = -1;
- int _index = 0;
-
- _InstanceCursor(this._template, [index]) {
- _terminator = _template;
- if (index != null) {
- while (index-- > 0) {
- next();
- }
- }
- }
-
- void next() {
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- while (_index > _terminator._instanceTerminatorCount) {
- _index -= _terminator._instanceTerminatorCount;
- _terminator = _terminator.nextNode;
- if (_terminator is Element && _terminator.tagName == 'TEMPLATE') {
- _index += _instanceCount(_terminator);
- }
- }
- }
-
- void abandon() {
- assert(_instanceCount(_template) > 0);
- assert(_terminator._instanceTerminatorCount > 0);
- assert(_index > 0);
-
- _terminator._instanceTerminatorCount--;
- _index--;
- }
-
- void insert(fragment) {
- assert(_template.parentNode != null);
-
- _previousTerminator = _terminator;
- _previousIndex = _index;
- _index++;
-
- _terminator = fragment.$dom_lastChild;
- if (_terminator == null) _terminator = _previousTerminator;
- _template.parentNode.insertBefore(fragment, _previousTerminator.nextNode);
-
- _terminator._instanceTerminatorCount++;
- if (_terminator != _previousTerminator) {
- while (_previousTerminator._instanceTerminatorCount >
- _previousIndex) {
- _previousTerminator._instanceTerminatorCount--;
- _terminator._instanceTerminatorCount++;
- }
- }
- }
-
- void remove() {
- assert(_previousIndex != -1);
- assert(_previousTerminator != null &&
- (_previousIndex > 0 || _previousTerminator == _template));
- assert(_terminator != null && _index > 0);
- assert(_template.parentNode != null);
- assert(_instanceCount(_template) > 0);
-
- if (_previousTerminator == _terminator) {
- assert(_index == _previousIndex + 1);
- _terminator._instanceTerminatorCount--;
- _terminator = _template;
- _previousTerminator = null;
- _previousIndex = -1;
- return;
- }
-
- _terminator._instanceTerminatorCount--;
-
- var parent = _template.parentNode;
- while (_previousTerminator.nextNode != _terminator) {
- _removeTemplateChild(parent, _previousTerminator.nextNode);
- }
- _removeTemplateChild(parent, _terminator);
-
- _terminator = _previousTerminator;
- _index = _previousIndex;
- _previousTerminator = null;
- _previousIndex = -1; // 0?
- }
-}
-
class _TemplateIterator {
final Element _templateElement;
- int instanceCount = 0;
- List iteratedValue;
- bool observing = false;
+ final List<Node> terminators = [];
final CompoundBinding inputs;
+ List iteratedValue;
StreamSubscription _sub;
StreamSubscription _valueBinding;
@@ -710,9 +667,74 @@
}
}
- // TODO(jmesserly): port MDV v3.
- getInstanceModel(model, syntax) => model;
- getInstanceFragment(syntax) => _templateElement.createInstance();
+ Node getTerminatorAt(int index) {
+ if (index == -1) return _templateElement;
+ var terminator = terminators[index];
+ if (terminator is! Element) return terminator;
+
+ var subIterator = terminator._templateIterator;
+ if (subIterator == null) return terminator;
+
+ return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
+ }
+
+ void insertInstanceAt(int index, Node fragment) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = fragment.$dom_lastChild;
+ if (terminator == null) terminator = previousTerminator;
+
+ terminators.insert(index, terminator);
+ var parent = _templateElement.parentNode;
+ parent.insertBefore(fragment, previousTerminator.nextNode);
+ }
+
+ void removeInstanceAt(int index) {
+ var previousTerminator = getTerminatorAt(index - 1);
+ var terminator = getTerminatorAt(index);
+ terminators.removeAt(index);
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void removeAllInstances() {
+ if (terminators.length == 0) return;
+
+ var previousTerminator = _templateElement;
+ var terminator = getTerminatorAt(terminators.length - 1);
+ terminators.length = 0;
+
+ var parent = _templateElement.parentNode;
+ while (terminator != previousTerminator) {
+ var node = terminator;
+ terminator = node.previousNode;
+ _removeTemplateChild(parent, node);
+ }
+ }
+
+ void clear() {
+ unobserve();
+ removeAllInstances();
+ iteratedValue = null;
+ }
+
+ getInstanceModel(model, syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceModel(_templateElement, model);
+ }
+ return model;
+ }
+
+ getInstanceFragment(syntax) {
+ if (syntax != null) {
+ return syntax.getInstanceFragment(_templateElement);
+ }
+ return _templateElement.createInstance();
+ }
void _handleChanges(List<ListChangeRecord> splices) {
var syntax = TemplateElement.syntax[_templateElement.attributes['syntax']];
@@ -721,9 +743,7 @@
if (splice is! ListChangeRecord) continue;
for (int i = 0; i < splice.removedCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, splice.index + 1);
- cursor.remove();
- instanceCount--;
+ removeInstanceAt(splice.index);
}
for (var addIndex = splice.index;
@@ -731,14 +751,13 @@
addIndex++) {
var model = getInstanceModel(iteratedValue[addIndex], syntax);
+
var fragment = getInstanceFragment(syntax);
_addBindings(fragment, model, syntax);
_addTemplateInstanceRecord(fragment, model);
- var cursor = new _InstanceCursor(_templateElement, addIndex);
- cursor.insert(fragment);
- instanceCount++;
+ insertInstanceAt(addIndex, fragment);
}
}
}
@@ -749,28 +768,9 @@
_sub = null;
}
- void clear() {
- unobserve();
-
- iteratedValue = null;
- if (instanceCount == 0) return;
-
- for (var i = 0; i < instanceCount; i++) {
- var cursor = new _InstanceCursor(_templateElement, 1);
- cursor.remove();
- }
-
- instanceCount = 0;
- }
-
void abandon() {
unobserve();
_valueBinding.cancel();
inputs.dispose();
}
}
-
-int _instanceCount(Element element) {
- var templateIterator = element._templateIterator;
- return templateIterator != null ? templateIterator.instanceCount : 0;
-}
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index 89329db..e4faf0c 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -839,8 +839,9 @@
void set model(value) {
_ensureTemplate();
+ var syntax = TemplateElement.syntax[attributes['syntax']];
_model = value;
- _addBindings(this, model);
+ _addBindings(this, model, syntax);
}
// TODO(jmesserly): const set would be better
diff --git a/tools/dom/templates/html/impl/impl_Node.darttemplate b/tools/dom/templates/html/impl/impl_Node.darttemplate
index 935794b..04b03cd 100644
--- a/tools/dom/templates/html/impl/impl_Node.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Node.darttemplate
@@ -272,17 +272,6 @@
TemplateInstance _templateInstance;
- // TODO(arv): Consider storing all "NodeRareData" on a single object?
- int __instanceTerminatorCount;
- int get _instanceTerminatorCount {
- if (__instanceTerminatorCount == null) return 0;
- return __instanceTerminatorCount;
- }
- set _instanceTerminatorCount(int value) {
- if (value == 0) value = null;
- __instanceTerminatorCount = value;
- }
-
/** Gets the template instance that instantiated this node, if any. */
@Experimental
TemplateInstance get templateInstance =>