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

Side by Side Diff: template_binding/lib/src/template_iterator.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/template.dart ('k') | template_binding/lib/template_binding.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 // This code is a port of what was formerly known as Model-Driven-Views, now
8 // located at:
9 // https://github.com/polymer/TemplateBinding
10 // https://github.com/polymer/NodeBind
11
12 // TODO(jmesserly): not sure what kind of boolean conversion rules to
13 // apply for template data-binding. HTML attributes are true if they're
14 // present. However Dart only treats "true" as true. Since this is HTML we'll
15 // use something closer to the HTML rules: null (missing) and false are false,
16 // everything else is true.
17 // See: https://github.com/polymer/TemplateBinding/issues/59
18 bool _toBoolean(value) => null != value && false != value;
19
20 // Dart note: this was added to decouple the MustacheTokens.parse function from
21 // the rest of template_binding.
22 _getDelegateFactory(name, node, delegate) {
23 if (delegate == null) return null;
24 return (pathString) => delegate.prepareBinding(pathString, name, node);
25 }
26
27 _InstanceBindingMap _getBindings(Node node, BindingDelegate delegate) {
28 if (node is Element) {
29 return _parseAttributeBindings(node, delegate);
30 }
31
32 if (node is Text) {
33 var tokens = MustacheTokens.parse(node.text,
34 _getDelegateFactory('text', node, delegate));
35 if (tokens != null) return new _InstanceBindingMap(['text', tokens]);
36 }
37
38 return null;
39 }
40
41 void _addBindings(Node node, model, [BindingDelegate delegate]) {
42 final bindings = _getBindings(node, delegate);
43 if (bindings != null) {
44 _processBindings(node, bindings, model);
45 }
46
47 for (var c = node.firstChild; c != null; c = c.nextNode) {
48 _addBindings(c, model, delegate);
49 }
50 }
51
52 MustacheTokens _parseWithDefault(Element element, String name,
53 BindingDelegate delegate) {
54
55 var v = element.attributes[name];
56 if (v == '') v = '{{}}';
57 return MustacheTokens.parse(v, _getDelegateFactory(name, element, delegate));
58 }
59
60 _InstanceBindingMap _parseAttributeBindings(Element element,
61 BindingDelegate delegate) {
62
63 var bindings = null;
64 var ifFound = false;
65 var bindFound = false;
66 var isTemplateNode = isSemanticTemplate(element);
67
68 element.attributes.forEach((name, value) {
69 // Allow bindings expressed in attributes to be prefixed with underbars.
70 // We do this to allow correct semantics for browsers that don't implement
71 // <template> where certain attributes might trigger side-effects -- and
72 // for IE which sanitizes certain attributes, disallowing mustache
73 // replacements in their text.
74 while (name[0] == '_') {
75 name = name.substring(1);
76 }
77
78 if (isTemplateNode &&
79 (name == 'bind' || name == 'if' || name == 'repeat')) {
80 return;
81 }
82
83 var tokens = MustacheTokens.parse(value,
84 _getDelegateFactory(name, element, delegate));
85 if (tokens != null) {
86 if (bindings == null) bindings = [];
87 bindings..add(name)..add(tokens);
88 }
89 });
90
91 if (isTemplateNode) {
92 if (bindings == null) bindings = [];
93 var result = new _TemplateBindingMap(bindings)
94 .._if = _parseWithDefault(element, 'if', delegate)
95 .._bind = _parseWithDefault(element, 'bind', delegate)
96 .._repeat = _parseWithDefault(element, 'repeat', delegate);
97
98 // Treat <template if> as <template bind if>
99 if (result._if != null && result._bind == null && result._repeat == null) {
100 result._bind = MustacheTokens.parse('{{}}',
101 _getDelegateFactory('bind', element, delegate));
102 }
103
104 return result;
105 }
106
107 return bindings == null ? null : new _InstanceBindingMap(bindings);
108 }
109
110 _processOneTimeBinding(String name, MustacheTokens tokens, Node node, model) {
111
112 if (tokens.hasOnePath) {
113 var delegateFn = tokens.getPrepareBinding(0);
114 var value = delegateFn != null ? delegateFn(model, node, true) :
115 tokens.getPath(0).getValueFrom(model);
116 return tokens.isSimplePath ? value : tokens.combinator(value);
117 }
118
119 // Tokens uses a striding scheme to essentially store a sequence of structs in
120 // the list. See _MustacheTokens for more information.
121 var values = new List(tokens.length);
122 for (int i = 0; i < tokens.length; i++) {
123 Function delegateFn = tokens.getPrepareBinding(i);
124 values[i] = delegateFn != null ?
125 delegateFn(model, node, false) :
126 tokens.getPath(i).getValueFrom(model);
127 }
128 return tokens.combinator(values);
129 }
130
131 _processSinglePathBinding(String name, MustacheTokens tokens, Node node,
132 model) {
133 Function delegateFn = tokens.getPrepareBinding(0);
134 var observer = delegateFn != null ?
135 delegateFn(model, node, false) :
136 new PathObserver(model, tokens.getPath(0));
137
138 return tokens.isSimplePath ? observer :
139 new ObserverTransform(observer, tokens.combinator);
140 }
141
142 _processBinding(String name, MustacheTokens tokens, Node node, model) {
143 if (tokens.onlyOneTime) {
144 return _processOneTimeBinding(name, tokens, node, model);
145 }
146 if (tokens.hasOnePath) {
147 return _processSinglePathBinding(name, tokens, node, model);
148 }
149
150 var observer = new CompoundObserver();
151
152 for (int i = 0; i < tokens.length; i++) {
153 bool oneTime = tokens.getOneTime(i);
154 Function delegateFn = tokens.getPrepareBinding(i);
155
156 if (delegateFn != null) {
157 var value = delegateFn(model, node, oneTime);
158 if (oneTime) {
159 observer.addPath(value);
160 } else {
161 observer.addObserver(value);
162 }
163 continue;
164 }
165
166 PropertyPath path = tokens.getPath(i);
167 if (oneTime) {
168 observer.addPath(path.getValueFrom(model));
169 } else {
170 observer.addPath(model, path);
171 }
172 }
173
174 return new ObserverTransform(observer, tokens.combinator);
175 }
176
177 void _processBindings(Node node, _InstanceBindingMap map, model,
178 [List<Bindable> instanceBindings]) {
179
180 final bindings = map.bindings;
181 final nodeExt = nodeBind(node);
182 for (var i = 0; i < bindings.length; i += 2) {
183 var name = bindings[i];
184 var tokens = bindings[i + 1];
185
186 var value = _processBinding(name, tokens, node, model);
187 var binding = nodeExt.bind(name, value, oneTime: tokens.onlyOneTime);
188 if (binding != null && instanceBindings != null) {
189 instanceBindings.add(binding);
190 }
191 }
192
193 nodeExt.bindFinished();
194 if (map is! _TemplateBindingMap) return;
195
196 final templateExt = nodeBindFallback(node);
197 templateExt._model = model;
198
199 var iter = templateExt._processBindingDirectives(map);
200 if (iter != null && instanceBindings != null) {
201 instanceBindings.add(iter);
202 }
203 }
204
205
206 // Note: this doesn't really implement most of Bindable. See:
207 // https://github.com/Polymer/TemplateBinding/issues/147
208 class _TemplateIterator extends Bindable {
209 final TemplateBindExtension _templateExt;
210
211 final List<DocumentFragment> _instances = [];
212
213 /** A copy of the last rendered [_presentValue] list state. */
214 final List _iteratedValue = [];
215
216 List _presentValue;
217
218 bool _closed = false;
219
220 // Dart note: instead of storing these in a Map like JS, or using a separate
221 // object (extra memory overhead) we just inline the fields.
222 var _ifValue, _value;
223
224 // TODO(jmesserly): lots of booleans in this object. Bitmask?
225 bool _hasIf, _hasRepeat;
226 bool _ifOneTime, _oneTime;
227
228 StreamSubscription _listSub;
229
230 bool _initPrepareFunctions = false;
231 PrepareInstanceModelFunction _instanceModelFn;
232 PrepareInstancePositionChangedFunction _instancePositionChangedFn;
233
234 _TemplateIterator(this._templateExt);
235
236 open(callback) => throw new StateError('binding already opened');
237 get value => _value;
238
239 Element get _templateElement => _templateExt._node;
240
241 void _closeDependencies() {
242 if (_ifValue is Bindable) {
243 _ifValue.close();
244 _ifValue = null;
245 }
246 if (_value is Bindable) {
247 _value.close();
248 _value = null;
249 }
250 }
251
252 void _updateDependencies(_TemplateBindingMap directives, model) {
253 _closeDependencies();
254
255 final template = _templateElement;
256
257 _hasIf = directives._if != null;
258 _hasRepeat = directives._repeat != null;
259
260 var ifValue = true;
261 if (_hasIf) {
262 _ifOneTime = directives._if.onlyOneTime;
263 _ifValue = _processBinding('if', directives._if, template, model);
264 ifValue = _ifValue;
265
266 // oneTime if & predicate is false. nothing else to do.
267 if (_ifOneTime && !_toBoolean(ifValue)) {
268 _valueChanged(null);
269 return;
270 }
271
272 if (!_ifOneTime) {
273 ifValue = (ifValue as Bindable).open(_updateIfValue);
274 }
275 }
276
277 if (_hasRepeat) {
278 _oneTime = directives._repeat.onlyOneTime;
279 _value = _processBinding('repeat', directives._repeat, template, model);
280 } else {
281 _oneTime = directives._bind.onlyOneTime;
282 _value = _processBinding('bind', directives._bind, template, model);
283 }
284
285 var value = _value;
286 if (!_oneTime) {
287 value = _value.open(_updateIteratedValue);
288 }
289
290 if (!_toBoolean(ifValue)) {
291 _valueChanged(null);
292 return;
293 }
294
295 _updateValue(value);
296 }
297
298 /// Gets the updated value of the bind/repeat. This can potentially call
299 /// user code (if a bindingDelegate is set up) so we try to avoid it if we
300 /// already have the value in hand (from Observer.open).
301 Object _getUpdatedValue() {
302 var value = _value;
303 // Dart note: x.discardChanges() is x.value in Dart.
304 if (!_toBoolean(_oneTime)) value = value.value;
305 return value;
306 }
307
308 void _updateIfValue(ifValue) {
309 if (!_toBoolean(ifValue)) {
310 _valueChanged(null);
311 return;
312 }
313 _updateValue(_getUpdatedValue());
314 }
315
316 void _updateIteratedValue(value) {
317 if (_hasIf) {
318 var ifValue = _ifValue;
319 if (!_ifOneTime) ifValue = (ifValue as Bindable).value;
320 if (!_toBoolean(ifValue)) {
321 _valueChanged([]);
322 return;
323 }
324 }
325
326 _updateValue(value);
327 }
328
329 void _updateValue(Object value) {
330 if (!_hasRepeat) value = [value];
331 _valueChanged(value);
332 }
333
334 void _valueChanged(Object value) {
335 if (value is! List) {
336 if (value is Iterable) {
337 // Dart note: we support Iterable by calling toList.
338 // But we need to be careful to observe the original iterator if it
339 // supports that.
340 value = (value as Iterable).toList();
341 } else {
342 value = [];
343 }
344 }
345
346 if (identical(value, _iteratedValue)) return;
347
348 _unobserve();
349 _presentValue = value;
350
351 if (value is ObservableList && _hasRepeat && !_oneTime) {
352 // Make sure any pending changes aren't delivered, since we're getting
353 // a snapshot at this point in time.
354 value.discardListChages();
355 _listSub = value.listChanges.listen(_handleSplices);
356 }
357
358 _handleSplices(ObservableList.calculateChangeRecords(
359 _iteratedValue != null ? _iteratedValue : [],
360 _presentValue != null ? _presentValue : []));
361 }
362
363 Node _getLastInstanceNode(int index) {
364 if (index == -1) return _templateElement;
365 // TODO(jmesserly): we could avoid this expando lookup by caching the
366 // instance extension instead of the instance.
367 var instance = _instanceExtension[_instances[index]];
368 var terminator = instance._terminator;
369 if (terminator == null) return _getLastInstanceNode(index - 1);
370
371 if (!isSemanticTemplate(terminator) ||
372 identical(terminator, _templateElement)) {
373 return terminator;
374 }
375
376 var subtemplateIterator = templateBindFallback(terminator)._iterator;
377 if (subtemplateIterator == null) return terminator;
378
379 return subtemplateIterator._getLastTemplateNode();
380 }
381
382 Node _getLastTemplateNode() => _getLastInstanceNode(_instances.length - 1);
383
384 void _insertInstanceAt(int index, DocumentFragment fragment) {
385 var previousInstanceLast = _getLastInstanceNode(index - 1);
386 var parent = _templateElement.parentNode;
387
388 _instances.insert(index, fragment);
389 parent.insertBefore(fragment, previousInstanceLast.nextNode);
390 }
391
392 DocumentFragment _extractInstanceAt(int index) {
393 var previousInstanceLast = _getLastInstanceNode(index - 1);
394 var lastNode = _getLastInstanceNode(index);
395 var parent = _templateElement.parentNode;
396 var instance = _instances.removeAt(index);
397
398 while (lastNode != previousInstanceLast) {
399 var node = previousInstanceLast.nextNode;
400 if (node == lastNode) lastNode = previousInstanceLast;
401
402 instance.append(node..remove());
403 }
404
405 return instance;
406 }
407
408 void _handleSplices(List<ListChangeRecord> splices) {
409 if (_closed || splices.isEmpty) return;
410
411 final template = _templateElement;
412
413 if (template.parentNode == null) {
414 close();
415 return;
416 }
417
418 ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices);
419
420 final delegate = _templateExt.bindingDelegate;
421
422 // Dart note: the JavaScript code relies on the distinction between null
423 // and undefined to track whether the functions are prepared. We use a bool.
424 if (!_initPrepareFunctions) {
425 _initPrepareFunctions = true;
426 final delegate = _templateExt._self.bindingDelegate;
427 if (delegate != null) {
428 _instanceModelFn = delegate.prepareInstanceModel(template);
429 _instancePositionChangedFn =
430 delegate.prepareInstancePositionChanged(template);
431 }
432 }
433
434 // Instance Removals.
435 var instanceCache = new HashMap(equals: identical);
436 var removeDelta = 0;
437 for (var splice in splices) {
438 for (var model in splice.removed) {
439 var instance = _extractInstanceAt(splice.index + removeDelta);
440 if (instance != _emptyInstance) {
441 instanceCache[model] = instance;
442 }
443 }
444
445 removeDelta -= splice.addedCount;
446 }
447
448 for (var splice in splices) {
449 for (var addIndex = splice.index;
450 addIndex < splice.index + splice.addedCount;
451 addIndex++) {
452
453 var model = _iteratedValue[addIndex];
454 DocumentFragment instance = instanceCache.remove(model);
455 if (instance == null) {
456 try {
457 if (_instanceModelFn != null) {
458 model = _instanceModelFn(model);
459 }
460 if (model == null) {
461 instance = _emptyInstance;
462 } else {
463 instance = _templateExt.createInstance(model, delegate);
464 }
465 } catch (e, s) {
466 // Dart note: we propagate errors asynchronously here to avoid
467 // disrupting the rendering flow. This is different than in the JS
468 // implementation but it should probably be fixed there too. Dart
469 // hits this case more because non-existing properties in
470 // [PropertyPath] are treated as errors, while JS treats them as
471 // null/undefined.
472 // TODO(sigmund): this should be a synchronous throw when this is
473 // called from createInstance, but that requires enough refactoring
474 // that it should be done upstream first. See dartbug.com/17789.
475 new Completer().completeError(e, s);
476 instance = _emptyInstance;
477 }
478 }
479
480 _insertInstanceAt(addIndex, instance);
481 }
482 }
483
484 for (var instance in instanceCache.values) {
485 _closeInstanceBindings(instance);
486 }
487
488 if (_instancePositionChangedFn != null) _reportInstancesMoved(splices);
489 }
490
491 void _reportInstanceMoved(int index) {
492 var instance = _instances[index];
493 if (instance == _emptyInstance) return;
494
495 _instancePositionChangedFn(nodeBind(instance).templateInstance, index);
496 }
497
498 void _reportInstancesMoved(List<ListChangeRecord> splices) {
499 var index = 0;
500 var offset = 0;
501 for (var splice in splices) {
502 if (offset != 0) {
503 while (index < splice.index) {
504 _reportInstanceMoved(index);
505 index++;
506 }
507 } else {
508 index = splice.index;
509 }
510
511 while (index < splice.index + splice.addedCount) {
512 _reportInstanceMoved(index);
513 index++;
514 }
515
516 offset += splice.addedCount - splice.removed.length;
517 }
518
519 if (offset == 0) return;
520
521 var length = _instances.length;
522 while (index < length) {
523 _reportInstanceMoved(index);
524 index++;
525 }
526 }
527
528 void _closeInstanceBindings(DocumentFragment instance) {
529 var bindings = _instanceExtension[instance]._bindings;
530 for (var binding in bindings) binding.close();
531 }
532
533 void _unobserve() {
534 if (_listSub == null) return;
535 _listSub.cancel();
536 _listSub = null;
537 }
538
539 void close() {
540 if (_closed) return;
541
542 _unobserve();
543 _instances.forEach(_closeInstanceBindings);
544 _instances.clear();
545 _closeDependencies();
546 _templateExt._iterator = null;
547 _closed = true;
548 }
549 }
550
551 // Dart note: the JavaScript version just puts an expando on the array.
552 class _BoundNodes {
553 final List<Node> nodes;
554 final List<Bindable> instanceBindings;
555 _BoundNodes(this.nodes, this.instanceBindings);
556 }
OLDNEW
« no previous file with comments | « template_binding/lib/src/template.dart ('k') | template_binding/lib/template_binding.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698