Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(822)

Side by Side Diff: pkg/template_binding/lib/src/template.dart

Issue 132403010: big update to observe, template_binding, polymer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of template_binding; 5 part of template_binding;
6 6
7 /** Extensions to [Element]s that behave as templates. */ 7 /** Extensions to [Element]s that behave as templates. */
8 class TemplateBindExtension extends _ElementExtension { 8 class TemplateBindExtension extends _ElementExtension {
9 var _model; 9 var _model;
10 BindingDelegate _bindingDelegate; 10 BindingDelegate _bindingDelegate;
11 _TemplateIterator _iterator; 11 _TemplateIterator _iterator;
12 bool _scheduled = false; 12 bool _setModelScheduled = false;
13 13
14 Element _templateInstanceRef; 14 Element _templateInstanceRef;
15 15
16 // Note: only used if `this is! TemplateElement` 16 // Note: only used if `this is! TemplateElement`
17 DocumentFragment _content; 17 DocumentFragment _content;
18 bool _templateIsDecorated; 18 bool _templateIsDecorated;
19 19
20 HtmlDocument _stagingDocument; 20 HtmlDocument _stagingDocument;
21 21
22 var _bindingMap; 22 _InstanceBindingMap _bindingMap;
23 23
24 TemplateBindExtension._(Element node) : super(node); 24 TemplateBindExtension._(Element node) : super(node);
25 25
26 Element get _node => super._node; 26 Element get _node => super._node;
27 27
28 TemplateBindExtension get _self => super._node is TemplateBindExtension 28 TemplateBindExtension get _self => super._node is TemplateBindExtension
29 ? _node : this; 29 ? _node : this;
30 30
31 NodeBinding bind(String name, model, [String path]) { 31 _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) {
32 path = path != null ? path : ''; 32 if (_iterator != null) _iterator._closeDependencies();
33
34 if (directives._if == null &&
35 directives._bind == null &&
36 directives._repeat == null) {
37
38 if (_iterator != null) {
39 _iterator.close();
40 _iterator = null;
41 bindings.remove('iterator');
42 }
43 return null;
44 }
33 45
34 if (_iterator == null) { 46 if (_iterator == null) {
35 // TODO(jmesserly): since there's only one iterator, we could just 47 bindings['iterator'] = _iterator = new _TemplateIterator(this);
36 // inline it into this object.
37 _iterator = new _TemplateIterator(this);
38 } 48 }
39 49
40 // Dart note: we return _TemplateBinding instead of _iterator. 50 _iterator._updateDependencies(directives, model);
41 // See comment on _TemplateBinding class. 51 return _iterator;
42 switch (name) {
43 case 'bind':
44 _iterator..hasBind = true
45 ..bindModel = model
46 ..bindPath = path;
47 _scheduleIterator();
48 return bindings[name] = new _TemplateBinding(this, name, model, path);
49 case 'repeat':
50 _iterator..hasRepeat = true
51 ..repeatModel = model
52 ..repeatPath = path;
53 _scheduleIterator();
54 return bindings[name] = new _TemplateBinding(this, name, model, path);
55 case 'if':
56 _iterator..hasIf = true
57 ..ifModel = model
58 ..ifPath = path;
59 _scheduleIterator();
60 return bindings[name] = new _TemplateBinding(this, name, model, path);
61 default:
62 return super.bind(name, model, path);
63 }
64 }
65
66 void unbind(String name) {
67 switch (name) {
68 case 'bind':
69 if (_iterator == null) return;
70 _iterator..hasBind = false
71 ..bindModel = null
72 ..bindPath = null;
73 _scheduleIterator();
74 bindings.remove(name);
75 return;
76 case 'repeat':
77 if (_iterator == null) return;
78 _iterator..hasRepeat = false
79 ..repeatModel = null
80 ..repeatPath = null;
81 _scheduleIterator();
82 bindings.remove(name);
83 return;
84 case 'if':
85 if (_iterator == null) return;
86 _iterator..hasIf = false
87 ..ifModel = null
88 ..ifPath = null;
89 _scheduleIterator();
90 bindings.remove(name);
91 return;
92 default:
93 super.unbind(name);
94 return;
95 }
96 }
97
98 void _scheduleIterator() {
99 if (!_iterator.depsChanging) {
100 _iterator.depsChanging = true;
101 scheduleMicrotask(_iterator.resolve);
102 }
103 } 52 }
104 53
105 /** 54 /**
106 * Creates an instance of the template, using the provided model and optional 55 * Creates an instance of the template, using the provided [model] and
107 * binding delegate. 56 * optional binding [delegate].
57 *
58 * If [instanceBindings] is supplied, each [Bindable] in the returned
59 * instance will be added to the list. This makes it easy to close all of the
60 * bindings without walking the tree. This is not normally necesssary, but is
61 * used internally by the system.
108 */ 62 */
109 DocumentFragment createInstance([model, BindingDelegate delegate, 63 DocumentFragment createInstance([model, BindingDelegate delegate,
110 List<NodeBinding> bound]) { 64 List<Bindable> instanceBindings]) {
111 var ref = templateBind(this.ref); 65
112 var content = ref.content; 66 final content = templateBind(ref).content;
113 // Dart note: we store _bindingMap on the TemplateBindExtension instead of 67 // Dart note: we store _bindingMap on the TemplateBindExtension instead of
114 // the "content" because we already have an expando for it. 68 // the "content" because we already have an expando for it.
115 var map = ref._bindingMap; 69 var map = _bindingMap;
116 if (map == null) { 70 if (map == null || !identical(map.content, content)) {
117 // TODO(rafaelw): Setup a MutationObserver on content to detect 71 // TODO(rafaelw): Setup a MutationObserver on content to detect
118 // when the instanceMap is invalid. 72 // when the instanceMap is invalid.
119 map = _createInstanceBindingMap(content, delegate); 73 map = _createInstanceBindingMap(content, delegate);
120 ref._bindingMap = map; 74 map.content = content;
75 _bindingMap = map;
121 } 76 }
122 77
123 var staging = _getTemplateStagingDocument(); 78 final staging = _getTemplateStagingDocument();
124 var instance = _deepCloneIgnoreTemplateContent(content, staging); 79 final instance = _stagingDocument.createDocumentFragment();
80 _templateCreator[instance] = _node;
125 81
126 _addMapBindings(instance, map, model, delegate, bound); 82 final instanceRecord = new TemplateInstance(model);
127 // TODO(rafaelw): We can do this more lazily, but setting a sentinel 83
128 // in the parent of the template element, and creating it when it's 84 var i = 0;
129 // asked for by walking back to find the iterating template. 85 for (var c = content.firstChild; c != null; c = c.nextNode, i++) {
130 _addTemplateInstanceRecord(instance, model); 86 final childMap = map != null ? map.getChild(i) : null;
87 var clone = _cloneAndBindInstance(c, instance, _stagingDocument,
88 childMap, model, delegate, instanceBindings);
89 nodeBindFallback(clone)._templateInstance = instanceRecord;
90 }
91
92 instanceRecord._firstNode = instance.firstChild;
93 instanceRecord._lastNode = instance.lastChild;
94
131 return instance; 95 return instance;
132 } 96 }
133 97
134 /** The data model which is inherited through the tree. */ 98 /** The data model which is inherited through the tree. */
135 get model => _model; 99 get model => _model;
136 100
137 void set model(value) { 101 void set model(value) {
138 _model = value; 102 _model = value;
139 _ensureSetModelScheduled(); 103 _ensureSetModelScheduled();
140 } 104 }
141 105
142 static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) { 106 static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) {
143 var clone = stagingDocument.importNode(node, false); 107 var clone = stagingDocument.importNode(node, false);
144 if (isSemanticTemplate(clone)) return clone; 108 if (isSemanticTemplate(clone)) return clone;
145 109
146 for (var c = node.firstChild; c != null; c = c.nextNode) { 110 for (var c = node.firstChild; c != null; c = c.nextNode) {
147 clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument)); 111 clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument));
148 } 112 }
149 return clone; 113 return clone;
150 } 114 }
151 115
152 /** 116 /**
153 * The binding delegate which is inherited through the tree. It can be used 117 * The binding delegate which is inherited through the tree. It can be used
154 * to configure custom syntax for `{{bindings}}` inside this template. 118 * to configure custom syntax for `{{bindings}}` inside this template.
155 */ 119 */
156 BindingDelegate get bindingDelegate => _bindingDelegate; 120 BindingDelegate get bindingDelegate => _bindingDelegate;
157 121
158 void set bindingDelegate(BindingDelegate value) { 122 void set bindingDelegate(BindingDelegate value) {
159 _bindingDelegate = value; 123 _bindingDelegate = value;
160 _ensureSetModelScheduled(); 124
125 // Clear cached state based on the binding delegate.
126 _bindingMap = null;
127 if (_iterator != null) {
128 _iterator._initPrepareFunctions = false;
129 _iterator._instanceModelFn = null;
130 _iterator._instancePositionChangedFn = null;
131 }
161 } 132 }
162 133
163 _ensureSetModelScheduled() { 134 _ensureSetModelScheduled() {
164 if (_scheduled) return; 135 if (_setModelScheduled) return;
165 _decorate(); 136 _decorate();
166 _scheduled = true; 137 _setModelScheduled = true;
167 scheduleMicrotask(_setModel); 138 scheduleMicrotask(_setModel);
168 } 139 }
169 140
170 void _setModel() { 141 void _setModel() {
171 _scheduled = false; 142 _setModelScheduled = false;
172 _addBindings(_node, _model, _bindingDelegate); 143 var map = _getBindings(_node, _bindingDelegate);
144 _processBindings(_node, map, _model);
173 } 145 }
174 146
175 /** Gets the template this node refers to. */ 147 /** Gets the template this node refers to. */
176 Element get ref { 148 Element get ref {
177 _decorate(); 149 _decorate();
178 150
179 Element result = null; 151 Element result = null;
180 var refId = _node.attributes['ref']; 152 var refId = _node.attributes['ref'];
181 if (refId != null) { 153 if (refId != null) {
182 var treeScope = _getTreeScope(_node); 154 var treeScope = _getTreeScope(_node);
183 if (treeScope != null) { 155 if (treeScope != null) {
184 result = treeScope.getElementById(refId); 156 result = treeScope.getElementById(refId);
185 } 157 }
158 if (result == null) {
159 var instanceRoot = _getInstanceRoot(_node);
160
161 // TODO(jmesserly): this won't work if refId is a number
162 // Similar to bug: https://github.com/Polymer/ShadowDOM/issues/340
163 if (instanceRoot != null) {
164 result = instanceRoot.querySelector('#$refId');
165 }
166 }
186 } 167 }
187 168
188 if (result == null) { 169 if (result == null) {
189 result = _templateInstanceRef; 170 result = _templateInstanceRef;
190 if (result == null) return _node; 171 if (result == null) return _node;
191 } 172 }
192 173
193 var nextRef = templateBind(result).ref; 174 var nextRef = templateBind(result).ref;
194 return nextRef != null ? nextRef : result; 175 return nextRef != null ? nextRef : result;
195 } 176 }
(...skipping 20 matching lines...) Expand all
216 templateBindFallback(template)._decorate(instanceRef); 197 templateBindFallback(template)._decorate(instanceRef);
217 198
218 bool _decorate([Element instanceRef]) { 199 bool _decorate([Element instanceRef]) {
219 // == true check because it starts as a null field. 200 // == true check because it starts as a null field.
220 if (_templateIsDecorated == true) return false; 201 if (_templateIsDecorated == true) return false;
221 202
222 _injectStylesheet(); 203 _injectStylesheet();
223 204
224 var templateElementExt = this; 205 var templateElementExt = this;
225 _templateIsDecorated = true; 206 _templateIsDecorated = true;
226 var isNative = _node is TemplateElement; 207 var isNativeHtmlTemplate = _node is TemplateElement;
227 var bootstrapContents = isNative; 208 final bootstrapContents = isNativeHtmlTemplate;
228 var liftContents = !isNative; 209 final liftContents = !isNativeHtmlTemplate;
229 var liftRoot = false; 210 var liftRoot = false;
230 211
231 if (!isNative && _isAttributeTemplate(_node)) { 212 if (!isNativeHtmlTemplate) {
232 if (instanceRef != null) { 213 if (_isAttributeTemplate(_node)) {
233 // TODO(jmesserly): this is just an assert in TemplateBinding. 214 if (instanceRef != null) {
234 throw new ArgumentError('instanceRef should not be supplied for ' 215 // Dart note: this is just an assert in JS.
235 'attribute templates.'); 216 throw new ArgumentError('instanceRef should not be supplied for '
217 'attribute templates.');
218 }
219 templateElementExt = templateBind(
220 _extractTemplateFromAttributeTemplate(_node));
221 templateElementExt._templateIsDecorated = true;
222 isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
223 liftRoot = true;
224 } else if (_isSvgTemplate(_node)) {
225 templateElementExt = templateBind(
226 _extractTemplateFromSvgTemplate(_node));
227 templateElementExt._templateIsDecorated = true;
228 isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
236 } 229 }
237 templateElementExt = templateBind( 230 }
238 _extractTemplateFromAttributeTemplate(_node));
239 templateElementExt._templateIsDecorated = true;
240 isNative = templateElementExt._node is TemplateElement;
241 liftRoot = true;
242 }
243 231
244 if (!isNative) { 232 if (!isNativeHtmlTemplate) {
245 var doc = _getOrCreateTemplateContentsOwner(templateElementExt._node); 233 var doc = _getOrCreateTemplateContentsOwner(templateElementExt._node);
246 templateElementExt._content = doc.createDocumentFragment(); 234 templateElementExt._content = doc.createDocumentFragment();
247 } 235 }
248 236
249 if (instanceRef != null) { 237 if (instanceRef != null) {
250 // template is contained within an instance, its direct content must be 238 // template is contained within an instance, its direct content must be
251 // empty 239 // empty
252 templateElementExt._templateInstanceRef = instanceRef; 240 templateElementExt._templateInstanceRef = instanceRef;
253 } else if (liftContents) { 241 } else if (liftContents) {
254 _liftNonNativeChildrenIntoContent(templateElementExt, _node, liftRoot); 242 _liftNonNativeChildrenIntoContent(templateElementExt, _node, liftRoot);
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
319 case 'bind': 307 case 'bind':
320 case 'ref': 308 case 'ref':
321 template.attributes[name] = el.attributes.remove(name); 309 template.attributes[name] = el.attributes.remove(name);
322 break; 310 break;
323 } 311 }
324 } 312 }
325 313
326 return template; 314 return template;
327 } 315 }
328 316
317 static Element _extractTemplateFromSvgTemplate(Element el) {
318 var template = el.ownerDocument.createElement('template');
319 el.parentNode.insertBefore(template, el);
320 template.attributes.addAll(el.attributes);
321
322 el.attributes.clear();
323 el.remove();
324 return template;
325 }
326
329 static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template, 327 static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template,
330 Element el, bool useRoot) { 328 Element el, bool useRoot) {
331 329
332 var content = template.content; 330 var content = template.content;
333 if (useRoot) { 331 if (useRoot) {
334 content.append(el); 332 content.append(el);
335 return; 333 return;
336 } 334 }
337 335
338 var child; 336 var child;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 static void _injectStylesheet() { 377 static void _injectStylesheet() {
380 if (_initStyles == true) return; 378 if (_initStyles == true) return;
381 _initStyles = true; 379 _initStyles = true;
382 380
383 var style = new StyleElement() 381 var style = new StyleElement()
384 ..text = '$_allTemplatesSelectors { display: none; }'; 382 ..text = '$_allTemplatesSelectors { display: none; }';
385 document.head.append(style); 383 document.head.append(style);
386 } 384 }
387 } 385 }
388 386
389 // TODO(jmesserly): https://github.com/polymer/templatebinding uses 387 final _templateCreator = new Expando();
390 // TemplateIterator as the binding. This is a nice performance optimization,
391 // however it means it doesn't share any of the reflective APIs with
392 // NodeBinding: https://github.com/Polymer/TemplateBinding/issues/147
393 class _TemplateBinding implements NodeBinding {
394 TemplateBindExtension _ext;
395 Object _model;
396 final String property;
397 final String path;
398
399 Node get node => _ext._node;
400
401 get model => _model;
402
403 bool get closed => _ext == null;
404
405 get value => _observer.value;
406
407 set value(newValue) {
408 _observer.value = newValue;
409 }
410
411 // No need to cache this since we only have it to support get/set value.
412 get _observer {
413 if ((_model is PathObserver || _model is CompoundPathObserver) &&
414 path == 'value') {
415 return _model;
416 }
417 return new PathObserver(_model, path);
418 }
419
420 _TemplateBinding(this._ext, this.property, this._model, this.path);
421
422 void valueChanged(newValue) {}
423
424 sanitizeBoundValue(value) => value == null ? '' : '$value';
425
426 void close() {
427 if (closed) return;
428
429 // TODO(jmesserly): unlike normal NodeBinding.close methods this will remove
430 // the binding from _node.bindings. Is that okay?
431 _ext.unbind(property);
432
433 _model = null;
434 _ext = null;
435 }
436 }
437 388
438 _getTreeScope(Node node) { 389 _getTreeScope(Node node) {
439 while (node.parentNode != null) { 390 while (true) {
440 node = node.parentNode; 391 var parent = node.parentNode;
392 if (parent != null) {
393 node = parent;
394 } else {
395 var creator = _templateCreator[node];
396 if (creator == null) break;
397
398 node = creator;
399 }
441 } 400 }
442 401
443 // Note: JS code tests that getElementById is present. We can't do that 402 // Note: JS code tests that getElementById is present. We can't do that
444 // easily, so instead check for the types known to implement it. 403 // easily, so instead check for the types known to implement it.
445 if (node is Document || node is ShadowRoot || node is SvgSvgElement) { 404 if (node is Document || node is ShadowRoot || node is SvgSvgElement) {
446 return node; 405 return node;
447 } 406 }
448 return null; 407 return null;
449 } 408 }
409
410 _getInstanceRoot(node) {
411 while (node.parentNode != null) {
412 node = node.parentNode;
413 }
414 return _templateCreator[node] != null ? node : null;
415 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698