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

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

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
« no previous file with comments | « template_binding/lib/src/node.dart ('k') | template_binding/lib/src/template_iterator.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of template_binding;
6
7 /** Extensions to [Element]s that behave as templates. */
8 class TemplateBindExtension extends NodeBindExtension {
9 var _model;
10 BindingDelegate _bindingDelegate;
11 _TemplateIterator _iterator;
12 bool _setModelScheduled = false;
13
14 Element _templateInstanceRef;
15
16 // Note: only used if `this is! TemplateElement`
17 DocumentFragment _content;
18 bool _templateIsDecorated;
19
20 HtmlDocument _stagingDocument;
21
22 _InstanceBindingMap _bindingMap;
23
24 Node _refContent;
25
26 TemplateBindExtension._(Element node) : super._(node);
27
28 Element get _node => super._node;
29
30 TemplateBindExtension get _self => _node is TemplateBindExtension
31 ? _node : this;
32
33 Bindable bind(String name, value, {bool oneTime: false}) {
34 if (name != 'ref') return super.bind(name, value, oneTime: oneTime);
35
36 var ref = oneTime ? value : value.open((ref) {
37 _node.attributes['ref'] = ref;
38 _refChanged();
39 });
40
41 _node.attributes['ref'] = ref;
42 _refChanged();
43 if (oneTime) return null;
44
45 if (bindings == null) bindings = {};
46 return bindings['ref'] = value;
47 }
48
49 _TemplateIterator _processBindingDirectives(_TemplateBindingMap directives) {
50 if (_iterator != null) _iterator._closeDependencies();
51
52 if (directives._if == null &&
53 directives._bind == null &&
54 directives._repeat == null) {
55
56 if (_iterator != null) {
57 _iterator.close();
58 _iterator = null;
59 }
60 return null;
61 }
62
63 if (_iterator == null) {
64 _iterator = new _TemplateIterator(this);
65 }
66
67 _iterator._updateDependencies(directives, model);
68
69 _templateObserver.observe(_node,
70 attributes: true, attributeFilter: ['ref']);
71
72 return _iterator;
73 }
74
75 /**
76 * Creates an instance of the template, using the provided [model] and
77 * optional binding [delegate].
78 *
79 * If [instanceBindings] is supplied, each [Bindable] in the returned
80 * instance will be added to the list. This makes it easy to close all of the
81 * bindings without walking the tree. This is not normally necessary, but is
82 * used internally by the system.
83 */
84 DocumentFragment createInstance([model, BindingDelegate delegate]) {
85 if (delegate == null) delegate = _bindingDelegate;
86 if (_refContent == null) _refContent = templateBind(_ref).content;
87
88 var content = _refContent;
89 if (content.firstChild == null) return _emptyInstance;
90
91 final map = _getInstanceBindingMap(content, delegate);
92 final staging = _getTemplateStagingDocument();
93 final instance = _stagingDocument.createDocumentFragment();
94
95 final instanceExt = new _InstanceExtension();
96 _instanceExtension[instance] = instanceExt
97 .._templateCreator = _node
98 .._protoContent = content;
99
100 final instanceRecord = new TemplateInstance(model);
101 nodeBindFallback(instance)._templateInstance = instanceRecord;
102
103 var i = 0;
104 bool collectTerminator = false;
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
112 final childMap = map != null ? map.getChild(i) : null;
113 var clone = _cloneAndBindInstance(c, instance, _stagingDocument,
114 childMap, model, delegate, instanceExt._bindings);
115
116 nodeBindFallback(clone)._templateInstance = instanceRecord;
117 if (collectTerminator) instanceExt._terminator = clone;
118 }
119
120 instanceRecord._firstNode = instance.firstChild;
121 instanceRecord._lastNode = instance.lastChild;
122
123 instanceExt._protoContent = null;
124 instanceExt._templateCreator = null;
125 return instance;
126 }
127
128 /** The data model which is inherited through the tree. */
129 get model => _model;
130
131 void set model(value) {
132 _model = value;
133 _ensureSetModelScheduled();
134 }
135
136 static Node _deepCloneIgnoreTemplateContent(Node node, stagingDocument) {
137 var clone = stagingDocument.importNode(node, false);
138 if (isSemanticTemplate(clone)) return clone;
139
140 for (var c = node.firstChild; c != null; c = c.nextNode) {
141 clone.append(_deepCloneIgnoreTemplateContent(c, stagingDocument));
142 }
143 return clone;
144 }
145
146 /**
147 * The binding delegate which is inherited through the tree. It can be used
148 * to configure custom syntax for `{{bindings}}` inside this template.
149 */
150 BindingDelegate get bindingDelegate => _bindingDelegate;
151
152
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 }
158 _bindingDelegate = value;
159
160 // Clear cached state based on the binding delegate.
161 _bindingMap = null;
162 if (_iterator != null) {
163 _iterator._initPrepareFunctions = false;
164 _iterator._instanceModelFn = null;
165 _iterator._instancePositionChangedFn = null;
166 }
167 }
168
169 _ensureSetModelScheduled() {
170 if (_setModelScheduled) return;
171 _decorate();
172 _setModelScheduled = true;
173 scheduleMicrotask(_setModel);
174 }
175
176 void _setModel() {
177 _setModelScheduled = false;
178 var map = _getBindings(_node, _bindingDelegate);
179 _processBindings(_node, map, _model);
180 }
181
182 _refChanged() {
183 if (_iterator == null || _refContent == templateBind(_ref).content) return;
184
185 _refContent = null;
186 _iterator._valueChanged(null);
187 _iterator._updateIteratedValue(this._iterator._getUpdatedValue());
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
204 /** Gets the template this node refers to. */
205 Element get _ref {
206 _decorate();
207
208 var ref = _searchRefId(_node, _node.attributes['ref']);
209 if (ref == null) {
210 ref = _templateInstanceRef;
211 if (ref == null) return _node;
212 }
213
214 var nextRef = templateBindFallback(ref)._ref;
215 return nextRef != null ? nextRef : ref;
216 }
217
218 /**
219 * Gets the content of this template.
220 */
221 DocumentFragment get content {
222 _decorate();
223 return _content != null ? _content : (_node as TemplateElement).content;
224 }
225
226 /**
227 * Ensures proper API and content model for template elements.
228 *
229 * [instanceRef] can be used to set the [Element.ref] property of [template],
230 * and use the ref's content will be used as source when createInstance() is
231 * invoked.
232 *
233 * Returns true if this template was just decorated, or false if it was
234 * already decorated.
235 */
236 static bool decorate(Element template, [Element instanceRef]) =>
237 templateBindFallback(template)._decorate(instanceRef);
238
239 bool _decorate([Element instanceRef]) {
240 // == true check because it starts as a null field.
241 if (_templateIsDecorated == true) return false;
242
243 _injectStylesheet();
244 _globalBaseUriWorkaround();
245
246 var templateElementExt = this;
247 _templateIsDecorated = true;
248 var isNativeHtmlTemplate = _node is TemplateElement;
249 final bootstrapContents = isNativeHtmlTemplate;
250 final liftContents = !isNativeHtmlTemplate;
251 var liftRoot = false;
252
253 if (!isNativeHtmlTemplate) {
254 if (_isAttributeTemplate(_node)) {
255 if (instanceRef != null) {
256 // Dart note: this is just an assert in JS.
257 throw new ArgumentError('instanceRef should not be supplied for '
258 'attribute templates.');
259 }
260 templateElementExt = templateBind(
261 _extractTemplateFromAttributeTemplate(_node));
262 templateElementExt._templateIsDecorated = true;
263 isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
264 liftRoot = true;
265 } else if (_isSvgTemplate(_node)) {
266 templateElementExt = templateBind(
267 _extractTemplateFromSvgTemplate(_node));
268 templateElementExt._templateIsDecorated = true;
269 isNativeHtmlTemplate = templateElementExt._node is TemplateElement;
270 }
271 }
272
273 if (!isNativeHtmlTemplate) {
274 var doc = _getOrCreateTemplateContentsOwner(templateElementExt._node);
275 templateElementExt._content = doc.createDocumentFragment();
276 }
277
278 if (instanceRef != null) {
279 // template is contained within an instance, its direct content must be
280 // empty
281 templateElementExt._templateInstanceRef = instanceRef;
282 } else if (liftContents) {
283 _liftNonNativeChildrenIntoContent(templateElementExt, _node, liftRoot);
284 } else if (bootstrapContents) {
285 bootstrap(templateElementExt.content);
286 }
287
288 return true;
289 }
290
291 static final _contentsOwner = new Expando();
292 static final _ownerStagingDocument = new Expando();
293
294 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html# dfn-template-contents-owner
295 static HtmlDocument _getOrCreateTemplateContentsOwner(Element template) {
296 var doc = template.ownerDocument;
297 if (doc.window == null) return doc;
298
299 var d = _contentsOwner[doc];
300 if (d == null) {
301 // TODO(arv): This should either be a Document or HTMLDocument depending
302 // on doc.
303 d = doc.implementation.createHtmlDocument('');
304 while (d.lastChild != null) {
305 d.lastChild.remove();
306 }
307 _contentsOwner[doc] = d;
308 }
309 return d;
310 }
311
312 HtmlDocument _getTemplateStagingDocument() {
313 if (_stagingDocument == null) {
314 var owner = _node.ownerDocument;
315 var doc = _ownerStagingDocument[owner];
316 if (doc == null) {
317 doc = owner.implementation.createHtmlDocument('');
318 _isStagingDocument[doc] = true;
319 _baseUriWorkaround(doc);
320 _ownerStagingDocument[owner] = doc;
321 }
322 _stagingDocument = doc;
323 }
324 return _stagingDocument;
325 }
326
327 // For non-template browsers, the parser will disallow <template> in certain
328 // locations, so we allow "attribute templates" which combine the template
329 // element with the top-level container node of the content, e.g.
330 //
331 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
332 //
333 // becomes
334 //
335 // <template repeat="{{ foo }}">
336 // + #document-fragment
337 // + <tr class="bar">
338 // + <td>Bar</td>
339 //
340 static Element _extractTemplateFromAttributeTemplate(Element el) {
341 var template = el.ownerDocument.createElement('template');
342 el.parentNode.insertBefore(template, el);
343
344 for (var name in el.attributes.keys.toList()) {
345 switch (name) {
346 case 'template':
347 el.attributes.remove(name);
348 break;
349 case 'repeat':
350 case 'bind':
351 case 'ref':
352 template.attributes[name] = el.attributes.remove(name);
353 break;
354 }
355 }
356
357 return template;
358 }
359
360 static Element _extractTemplateFromSvgTemplate(Element el) {
361 var template = el.ownerDocument.createElement('template');
362 el.parentNode.insertBefore(template, el);
363 template.attributes.addAll(el.attributes);
364
365 el.attributes.clear();
366 el.remove();
367 return template;
368 }
369
370 static void _liftNonNativeChildrenIntoContent(TemplateBindExtension template,
371 Element el, bool useRoot) {
372
373 var content = template.content;
374 if (useRoot) {
375 content.append(el);
376 return;
377 }
378
379 var child;
380 while ((child = el.firstChild) != null) {
381 content.append(child);
382 }
383 }
384
385 /**
386 * This used to decorate recursively all templates from a given node.
387 *
388 * By default [decorate] will be called on templates lazily when certain
389 * properties such as [model] are accessed, but it can be run eagerly to
390 * decorate an entire tree recursively.
391 */
392 // TODO(rafaelw): Review whether this is the right public API.
393 static void bootstrap(Node content) {
394 void _bootstrap(template) {
395 if (!TemplateBindExtension.decorate(template)) {
396 bootstrap(templateBind(template).content);
397 }
398 }
399
400 // Need to do this first as the contents may get lifted if |node| is
401 // template.
402 // TODO(jmesserly): content is DocumentFragment or Element
403 var descendents =
404 (content as dynamic).querySelectorAll(_allTemplatesSelectors);
405 if (isSemanticTemplate(content)) {
406 _bootstrap(content);
407 }
408
409 descendents.forEach(_bootstrap);
410 }
411
412 static final String _allTemplatesSelectors =
413 'template, ' +
414 _SEMANTIC_TEMPLATE_TAGS.keys.map((k) => "$k[template]").join(", ");
415
416 static bool _initStyles;
417
418 // This is to replicate template_element.css
419 // TODO(jmesserly): move this to an opt-in CSS file?
420 static void _injectStylesheet() {
421 if (_initStyles == true) return;
422 _initStyles = true;
423
424 var style = new StyleElement()
425 ..text = '$_allTemplatesSelectors { display: none; }';
426 document.head.append(style);
427 }
428
429 static bool _initBaseUriWorkaround;
430
431 static void _globalBaseUriWorkaround() {
432 if (_initBaseUriWorkaround == true) return;
433 _initBaseUriWorkaround = true;
434
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 }
445 }
446 }
447
448 // TODO(rafaelw): Remove when fix for
449 // https://codereview.chromium.org/164803002/
450 // makes it to Chrome release.
451 static void _baseUriWorkaround(HtmlDocument doc) {
452 BaseElement base = doc.createElement('base');
453 base.href = document.baseUri;
454 doc.head.append(base);
455 }
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 }
519 }
520
521 _getInstanceRoot(node) {
522 while (node.parentNode != null) {
523 node = node.parentNode;
524 }
525 _InstanceExtension instance = _instanceExtension[node];
526 return instance != null && instance._templateCreator != null ? node : null;
527 }
528
529 // Note: JS code tests that getElementById is present. We can't do that
530 // easily, so instead check for the types known to implement it.
531 bool _hasGetElementById(Node node) =>
532 node is Document || node is ShadowRoot || node is SvgSvgElement;
533
534 final Expando<_InstanceExtension> _instanceExtension = new Expando();
535
536 final _isStagingDocument = new Expando();
OLDNEW
« no previous file with comments | « template_binding/lib/src/node.dart ('k') | template_binding/lib/src/template_iterator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698