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

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

Issue 336013003: Version 1.5.0-dev.4.14 (Closed) Base URL: http://dart.googlecode.com/svn/trunk/
Patch Set: Created 6 years, 6 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 _setModelScheduled = 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 _InstanceBindingMap _bindingMap; 22 _InstanceBindingMap _bindingMap;
23 23
24 Node _refContent;
25
24 TemplateBindExtension._(Element node) : super(node); 26 TemplateBindExtension._(Element node) : super(node);
25 27
26 Element get _node => super._node; 28 Element get _node => super._node;
27 29
28 TemplateBindExtension get _self => super._node is TemplateBindExtension 30 TemplateBindExtension get _self => super._node is TemplateBindExtension
29 ? _node : this; 31 ? _node : this;
30 32
33 Bindable bind(String name, value, {bool oneTime: false}) {
34 if (name != 'ref') return super.bind(name, value, oneTime: oneTime);
35
36
37 var ref = oneTime ? value : value.open((ref) {
38 _node.attributes['ref'] = ref;
39 _refChanged();
40 });
41
42 _node.attributes['ref'] = ref;
43 _refChanged();
44 if (oneTime) return null;
45
46 return _updateBindings('ref', value);
47 }
48
31 _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) { 49 _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) {
32 if (_iterator != null) _iterator._closeDependencies(); 50 if (_iterator != null) _iterator._closeDependencies();
33 51
34 if (directives._if == null && 52 if (directives._if == null &&
35 directives._bind == null && 53 directives._bind == null &&
36 directives._repeat == null) { 54 directives._repeat == null) {
37 55
38 if (_iterator != null) { 56 if (_iterator != null) {
39 _iterator.close(); 57 _iterator.close();
40 _iterator = null; 58 _iterator = null;
41 bindings.remove('iterator');
42 } 59 }
43 return null; 60 return null;
44 } 61 }
45 62
46 if (_iterator == null) { 63 if (_iterator == null) {
47 bindings['iterator'] = _iterator = new _TemplateIterator(this); 64 _iterator = new _TemplateIterator(this);
48 } 65 }
49 66
50 _iterator._updateDependencies(directives, model); 67 _iterator._updateDependencies(directives, model);
68
69 _templateObserver.observe(_node,
70 attributes: true, attributeFilter: ['ref']);
71
51 return _iterator; 72 return _iterator;
52 } 73 }
53 74
54 /** 75 /**
55 * Creates an instance of the template, using the provided [model] and 76 * Creates an instance of the template, using the provided [model] and
56 * optional binding [delegate]. 77 * optional binding [delegate].
57 * 78 *
58 * If [instanceBindings] is supplied, each [Bindable] in the returned 79 * 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 80 * 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 81 * bindings without walking the tree. This is not normally necesssary, but is
61 * used internally by the system. 82 * used internally by the system.
62 */ 83 */
63 DocumentFragment createInstance([model, BindingDelegate delegate, 84 DocumentFragment createInstance([model, BindingDelegate delegate]) {
64 List<Bindable> instanceBindings]) { 85 if (delegate == null) delegate = _bindingDelegate;
86 if (_refContent == null) _refContent = templateBind(_ref).content;
65 87
66 final content = templateBind(ref).content; 88 var content = _refContent;
67 // Dart note: we store _bindingMap on the TemplateBindExtension instead of 89 if (content.firstChild == null) return _emptyInstance;
68 // the "content" because we already have an expando for it.
69 var map = _bindingMap;
70 if (map == null || !identical(map.content, content)) {
71 // TODO(rafaelw): Setup a MutationObserver on content to detect
72 // when the instanceMap is invalid.
73 map = _createInstanceBindingMap(content, delegate);
74 map.content = content;
75 _bindingMap = map;
76 }
77 90
91 final map = _getInstanceBindingMap(content, delegate);
78 final staging = _getTemplateStagingDocument(); 92 final staging = _getTemplateStagingDocument();
79 final instance = _stagingDocument.createDocumentFragment(); 93 final instance = _stagingDocument.createDocumentFragment();
80 _templateCreator[instance] = _node; 94
95 final instanceExt = new _InstanceExtension();
96 _instanceExtension[instance] = instanceExt
97 .._templateCreator = _node
98 .._protoContent = content;
81 99
82 final instanceRecord = new TemplateInstance(model); 100 final instanceRecord = new TemplateInstance(model);
101 nodeBindFallback(instance)._templateInstance = instanceRecord;
83 102
84 var i = 0; 103 var i = 0;
104 bool collectTerminator = false;
85 for (var c = content.firstChild; c != null; c = c.nextNode, i++) { 105 for (var c = content.firstChild; c != null; c = c.nextNode, i++) {
106 // The terminator of the instance is the clone of the last child of the
107 // content. If the last child is an active template, it may produce
108 // instances as a result of production, so simply collecting the last
109 // child of the instance after it has finished producing may be wrong.
110 if (c.nextNode == null) collectTerminator = true;
111
86 final childMap = map != null ? map.getChild(i) : null; 112 final childMap = map != null ? map.getChild(i) : null;
87 var clone = _cloneAndBindInstance(c, instance, _stagingDocument, 113 var clone = _cloneAndBindInstance(c, instance, _stagingDocument,
88 childMap, model, delegate, instanceBindings); 114 childMap, model, delegate, instanceExt._bindings);
115
89 nodeBindFallback(clone)._templateInstance = instanceRecord; 116 nodeBindFallback(clone)._templateInstance = instanceRecord;
117 if (collectTerminator) instanceExt._terminator = clone;
90 } 118 }
91 119
92 instanceRecord._firstNode = instance.firstChild; 120 instanceRecord._firstNode = instance.firstChild;
93 instanceRecord._lastNode = instance.lastChild; 121 instanceRecord._lastNode = instance.lastChild;
94 122
123 instanceExt._protoContent = null;
124 instanceExt._templateCreator = null;
95 return instance; 125 return instance;
96 } 126 }
97 127
98 /** The data model which is inherited through the tree. */ 128 /** The data model which is inherited through the tree. */
99 get model => _model; 129 get model => _model;
100 130
101 void set model(value) { 131 void set model(value) {
102 _model = value; 132 _model = value;
103 _ensureSetModelScheduled(); 133 _ensureSetModelScheduled();
104 } 134 }
105 135
106 static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) { 136 static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) {
107 var clone = stagingDocument.importNode(node, false); 137 var clone = stagingDocument.importNode(node, false);
108 if (isSemanticTemplate(clone)) return clone; 138 if (isSemanticTemplate(clone)) return clone;
109 139
110 for (var c = node.firstChild; c != null; c = c.nextNode) { 140 for (var c = node.firstChild; c != null; c = c.nextNode) {
111 clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument)); 141 clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument));
112 } 142 }
113 return clone; 143 return clone;
114 } 144 }
115 145
116 /** 146 /**
117 * The binding delegate which is inherited through the tree. It can be used 147 * The binding delegate which is inherited through the tree. It can be used
118 * to configure custom syntax for `{{bindings}}` inside this template. 148 * to configure custom syntax for `{{bindings}}` inside this template.
119 */ 149 */
120 BindingDelegate get bindingDelegate => _bindingDelegate; 150 BindingDelegate get bindingDelegate => _bindingDelegate;
121 151
152
122 void set bindingDelegate(BindingDelegate value) { 153 void set bindingDelegate(BindingDelegate value) {
154 if (_bindingDelegate != null) {
155 throw new StateError('Template must be cleared before a new '
156 'bindingDelegate can be assigned');
157 }
123 _bindingDelegate = value; 158 _bindingDelegate = value;
124 159
125 // Clear cached state based on the binding delegate. 160 // Clear cached state based on the binding delegate.
126 _bindingMap = null; 161 _bindingMap = null;
127 if (_iterator != null) { 162 if (_iterator != null) {
128 _iterator._initPrepareFunctions = false; 163 _iterator._initPrepareFunctions = false;
129 _iterator._instanceModelFn = null; 164 _iterator._instanceModelFn = null;
130 _iterator._instancePositionChangedFn = null; 165 _iterator._instancePositionChangedFn = null;
131 } 166 }
132 } 167 }
133 168
134 _ensureSetModelScheduled() { 169 _ensureSetModelScheduled() {
135 if (_setModelScheduled) return; 170 if (_setModelScheduled) return;
136 _decorate(); 171 _decorate();
137 _setModelScheduled = true; 172 _setModelScheduled = true;
138 scheduleMicrotask(_setModel); 173 scheduleMicrotask(_setModel);
139 } 174 }
140 175
141 void _setModel() { 176 void _setModel() {
142 _setModelScheduled = false; 177 _setModelScheduled = false;
143 var map = _getBindings(_node, _bindingDelegate); 178 var map = _getBindings(_node, _bindingDelegate);
144 _processBindings(_node, map, _model); 179 _processBindings(_node, map, _model);
145 } 180 }
146 181
182 _refChanged() {
183 if (_iterator == null || _refContent == templateBind(_ref).content) return;
184
185 _refContent = null;
186 _iterator._valueChanged(null);
187 _iterator._updateIteratedValue(null);
188 }
189
190 void clear() {
191 _model = null;
192 _bindingDelegate = null;
193 if (bindings != null) {
194 var ref = bindings.remove('ref');
195 if (ref != null) ref.close();
196 }
197 _refContent = null;
198 if (_iterator == null) return;
199 _iterator._valueChanged(null);
200 _iterator.close();
201 _iterator = null;
202 }
203
147 /** Gets the template this node refers to. */ 204 /** Gets the template this node refers to. */
148 Element get ref { 205 Element get _ref {
149 _decorate(); 206 _decorate();
150 207
151 Element result = null; 208 var ref = _searchRefId(_node, _node.attributes['ref']);
152 var refId = _node.attributes['ref']; 209 if (ref == null) {
153 if (refId != null) { 210 ref = _templateInstanceRef;
154 var treeScope = _getTreeScope(_node); 211 if (ref == null) return _node;
155 if (treeScope != null) {
156 result = treeScope.getElementById(refId);
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 }
167 } 212 }
168 213
169 if (result == null) { 214 var nextRef = templateBindFallback(ref)._ref;
170 result = _templateInstanceRef; 215 return nextRef != null ? nextRef : ref;
171 if (result == null) return _node;
172 }
173
174 var nextRef = templateBind(result).ref;
175 return nextRef != null ? nextRef : result;
176 } 216 }
177 217
178 /** 218 /**
179 * Gets the content of this template. 219 * Gets the content of this template.
180 */ 220 */
181 DocumentFragment get content { 221 DocumentFragment get content {
182 _decorate(); 222 _decorate();
183 return _content != null ? _content : (_node as TemplateElement).content; 223 return _content != null ? _content : (_node as TemplateElement).content;
184 } 224 }
185 225
186 /** 226 /**
187 * Ensures proper API and content model for template elements. 227 * Ensures proper API and content model for template elements.
188 * 228 *
189 * [instanceRef] can be used to set the [Element.ref] property of [template], 229 * [instanceRef] can be used to set the [Element.ref] property of [template],
190 * and use the ref's content will be used as source when createInstance() is 230 * and use the ref's content will be used as source when createInstance() is
191 * invoked. 231 * invoked.
192 * 232 *
193 * Returns true if this template was just decorated, or false if it was 233 * Returns true if this template was just decorated, or false if it was
194 * already decorated. 234 * already decorated.
195 */ 235 */
196 static bool decorate(Element template, [Element instanceRef]) => 236 static bool decorate(Element template, [Element instanceRef]) =>
197 templateBindFallback(template)._decorate(instanceRef); 237 templateBindFallback(template)._decorate(instanceRef);
198 238
199 bool _decorate([Element instanceRef]) { 239 bool _decorate([Element instanceRef]) {
200 // == true check because it starts as a null field. 240 // == true check because it starts as a null field.
201 if (_templateIsDecorated == true) return false; 241 if (_templateIsDecorated == true) return false;
202 242
203 _injectStylesheet(); 243 _injectStylesheet();
244 _globalBaseUriWorkaround();
204 245
205 var templateElementExt = this; 246 var templateElementExt = this;
206 _templateIsDecorated = true; 247 _templateIsDecorated = true;
207 var isNativeHtmlTemplate = _node is TemplateElement; 248 var isNativeHtmlTemplate = _node is TemplateElement;
208 final bootstrapContents = isNativeHtmlTemplate; 249 final bootstrapContents = isNativeHtmlTemplate;
209 final liftContents = !isNativeHtmlTemplate; 250 final liftContents = !isNativeHtmlTemplate;
210 var liftRoot = false; 251 var liftRoot = false;
211 252
212 if (!isNativeHtmlTemplate) { 253 if (!isNativeHtmlTemplate) {
213 if (_isAttributeTemplate(_node)) { 254 if (_isAttributeTemplate(_node)) {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
267 } 308 }
268 return d; 309 return d;
269 } 310 }
270 311
271 HtmlDocument _getTemplateStagingDocument() { 312 HtmlDocument _getTemplateStagingDocument() {
272 if (_stagingDocument == null) { 313 if (_stagingDocument == null) {
273 var owner = _node.ownerDocument; 314 var owner = _node.ownerDocument;
274 var doc = _ownerStagingDocument[owner]; 315 var doc = _ownerStagingDocument[owner];
275 if (doc == null) { 316 if (doc == null) {
276 doc = owner.implementation.createHtmlDocument(''); 317 doc = owner.implementation.createHtmlDocument('');
318 _isStagingDocument[doc] = true;
319 _baseUriWorkaround(doc);
277 _ownerStagingDocument[owner] = doc; 320 _ownerStagingDocument[owner] = doc;
278 } 321 }
279 _stagingDocument = doc; 322 _stagingDocument = doc;
280 } 323 }
281 return _stagingDocument; 324 return _stagingDocument;
282 } 325 }
283 326
284 // For non-template browsers, the parser will disallow <template> in certain 327 // For non-template browsers, the parser will disallow <template> in certain
285 // locations, so we allow "attribute templates" which combine the template 328 // locations, so we allow "attribute templates" which combine the template
286 // element with the top-level container node of the content, e.g. 329 // element with the top-level container node of the content, e.g.
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
375 // This is to replicate template_element.css 418 // This is to replicate template_element.css
376 // TODO(jmesserly): move this to an opt-in CSS file? 419 // TODO(jmesserly): move this to an opt-in CSS file?
377 static void _injectStylesheet() { 420 static void _injectStylesheet() {
378 if (_initStyles == true) return; 421 if (_initStyles == true) return;
379 _initStyles = true; 422 _initStyles = true;
380 423
381 var style = new StyleElement() 424 var style = new StyleElement()
382 ..text = '$_allTemplatesSelectors { display: none; }'; 425 ..text = '$_allTemplatesSelectors { display: none; }';
383 document.head.append(style); 426 document.head.append(style);
384 } 427 }
385 }
386 428
387 final _templateCreator = new Expando(); 429 static bool _initBaseUriWorkaround;
388 430
389 _getTreeScope(Node node) { 431 static void _globalBaseUriWorkaround() {
390 while (true) { 432 if (_initBaseUriWorkaround == true) return;
391 var parent = node.parentNode; 433 _initBaseUriWorkaround = true;
392 if (parent != null) {
393 node = parent;
394 } else {
395 var creator = _templateCreator[node];
396 if (creator == null) break;
397 434
398 node = creator; 435 var t = document.createElement('template');
436 if (t is TemplateElement) {
437 var d = t.content.ownerDocument;
438 if (d.documentElement == null) {
439 d.append(d.createElement('html')).append(d.createElement('head'));
440 }
441 // don't patch this if TemplateBinding.js already has.
442 if (d.head.querySelector('base') == null) {
443 _baseUriWorkaround(d);
444 }
399 } 445 }
400 } 446 }
401 447
402 // Note: JS code tests that getElementById is present. We can't do that 448 // TODO(rafaelw): Remove when fix for
403 // easily, so instead check for the types known to implement it. 449 // https://codereview.chromium.org/164803002/
404 if (node is Document || node is ShadowRoot || node is SvgSvgElement) { 450 // makes it to Chrome release.
405 return node; 451 static void _baseUriWorkaround(HtmlDocument doc) {
452 BaseElement base = doc.createElement('base');
453 base.href = document.baseUri;
454 doc.head.append(base);
406 } 455 }
407 return null; 456
457 static final _templateObserver = new MutationObserver((records, _) {
458 for (MutationRecord record in records) {
459 templateBindFallback(record.target)._refChanged();
460 }
461 });
462
463 }
464
465 final DocumentFragment _emptyInstance = () {
466 var empty = new DocumentFragment();
467 _instanceExtension[empty] = new _InstanceExtension();
468 return empty;
469 }();
470
471 // TODO(jmesserly): if we merged with wtih TemplateInstance, it seems like it
472 // would speed up some operations (e.g. _getInstanceRoot wouldn't need to walk
473 // the parent chain).
474 class _InstanceExtension {
475 final List _bindings = [];
476 Node _terminator;
477 Element _templateCreator;
478 DocumentFragment _protoContent;
479 }
480
481 // TODO(jmesserly): this is private in JS but public for us because pkg:polymer
482 // uses it.
483 List getTemplateInstanceBindings(DocumentFragment fragment) {
484 var ext = _instanceExtension[fragment];
485 return ext != null ? ext._bindings : ext;
486 }
487
488 /// Gets the root of the current node's parent chain
489 _getFragmentRoot(Node node) {
490 var p;
491 while ((p = node.parentNode) != null) {
492 node = p;
493 }
494 return node;
495 }
496
497 Node _searchRefId(Node node, String id) {
498 if (id == null || id == '') return null;
499
500 final selector = '#$id';
501 while (true) {
502 node = _getFragmentRoot(node);
503
504 Node ref = null;
505
506 _InstanceExtension instance = _instanceExtension[node];
507 if (instance != null && instance._protoContent != null) {
508 ref = instance._protoContent.querySelector(selector);
509 } else if (_hasGetElementById(node)) {
510 ref = (node as dynamic).getElementById(id);
511 }
512
513 if (ref != null) return ref;
514
515 if (instance == null) return null;
516 node = instance._templateCreator;
517 if (node == null) return null;
518 }
408 } 519 }
409 520
410 _getInstanceRoot(node) { 521 _getInstanceRoot(node) {
411 while (node.parentNode != null) { 522 while (node.parentNode != null) {
412 node = node.parentNode; 523 node = node.parentNode;
413 } 524 }
414 return _templateCreator[node] != null ? node : null; 525 _InstanceExtension instance = _instanceExtension[node];
526 return instance != null && instance._templateCreator != null ? node : null;
415 } 527 }
528
529 final Expando<_InstanceExtension> _instanceExtension = new Expando();
530
531 final _isStagingDocument = new Expando();
OLDNEW
« no previous file with comments | « dart/pkg/template_binding/lib/src/select_element.dart ('k') | dart/pkg/template_binding/lib/src/template_iterator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698