OLD | NEW |
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 // This code is a port of what was formerly known as Model-Driven-Views, now | 7 // This code is a port of what was formerly known as Model-Driven-Views, now |
8 // located at: | 8 // located at: |
9 // https://github.com/polymer/TemplateBinding | 9 // https://github.com/polymer/TemplateBinding |
10 // https://github.com/polymer/NodeBind | 10 // https://github.com/polymer/NodeBind |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
171 } | 171 } |
172 } | 172 } |
173 | 173 |
174 return new ObserverTransform(observer, tokens.combinator); | 174 return new ObserverTransform(observer, tokens.combinator); |
175 } | 175 } |
176 | 176 |
177 void _processBindings(Node node, _InstanceBindingMap map, model, | 177 void _processBindings(Node node, _InstanceBindingMap map, model, |
178 [List<Bindable> instanceBindings]) { | 178 [List<Bindable> instanceBindings]) { |
179 | 179 |
180 final bindings = map.bindings; | 180 final bindings = map.bindings; |
| 181 final nodeExt = nodeBind(node); |
181 for (var i = 0; i < bindings.length; i += 2) { | 182 for (var i = 0; i < bindings.length; i += 2) { |
182 var name = bindings[i]; | 183 var name = bindings[i]; |
183 var tokens = bindings[i + 1]; | 184 var tokens = bindings[i + 1]; |
184 | 185 |
185 var value = _processBinding(name, tokens, node, model); | 186 var value = _processBinding(name, tokens, node, model); |
186 var binding = nodeBind(node).bind(name, value, oneTime: tokens.onlyOneTime); | 187 var binding = nodeExt.bind(name, value, oneTime: tokens.onlyOneTime); |
187 if (binding != null && instanceBindings != null) { | 188 if (binding != null && instanceBindings != null) { |
188 instanceBindings.add(binding); | 189 instanceBindings.add(binding); |
189 } | 190 } |
190 } | 191 } |
191 | 192 |
| 193 nodeExt.bindFinished(); |
192 if (map is! _TemplateBindingMap) return; | 194 if (map is! _TemplateBindingMap) return; |
193 | 195 |
194 final templateExt = nodeBindFallback(node); | 196 final templateExt = nodeBindFallback(node); |
195 templateExt._model = model; | 197 templateExt._model = model; |
196 | 198 |
197 var iter = templateExt._processBindingDirectives(map); | 199 var iter = templateExt._processBindingDirectives(map); |
198 if (iter != null && instanceBindings != null) { | 200 if (iter != null && instanceBindings != null) { |
199 instanceBindings.add(iter); | 201 instanceBindings.add(iter); |
200 } | 202 } |
201 } | 203 } |
202 | 204 |
203 | 205 |
204 // Note: this doesn't really implement most of Bindable. See: | 206 // Note: this doesn't really implement most of Bindable. See: |
205 // https://github.com/Polymer/TemplateBinding/issues/147 | 207 // https://github.com/Polymer/TemplateBinding/issues/147 |
206 class _TemplateIterator extends Bindable { | 208 class _TemplateIterator extends Bindable { |
207 final TemplateBindExtension _templateExt; | 209 final TemplateBindExtension _templateExt; |
208 | 210 |
209 /** | 211 final List<DocumentFragment> _instances = []; |
210 * Flattened array of tuples: | |
211 * <instanceTerminatorNode, [bindingsSetupByInstance]> | |
212 */ | |
213 final List _terminators = []; | |
214 | 212 |
215 /** A copy of the last rendered [_presentValue] list state. */ | 213 /** A copy of the last rendered [_presentValue] list state. */ |
216 final List _iteratedValue = []; | 214 final List _iteratedValue = []; |
217 | 215 |
218 List _presentValue; | 216 List _presentValue; |
219 | 217 |
220 bool _closed = false; | 218 bool _closed = false; |
221 | 219 |
222 // Dart note: instead of storing these in a Map like JS, or using a separate | 220 // Dart note: instead of storing these in a Map like JS, or using a separate |
223 // object (extra memory overhead) we just inline the fields. | 221 // object (extra memory overhead) we just inline the fields. |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
325 // a snapshot at this point in time. | 323 // a snapshot at this point in time. |
326 value.discardListChages(); | 324 value.discardListChages(); |
327 _listSub = value.listChanges.listen(_handleSplices); | 325 _listSub = value.listChanges.listen(_handleSplices); |
328 } | 326 } |
329 | 327 |
330 _handleSplices(ObservableList.calculateChangeRecords( | 328 _handleSplices(ObservableList.calculateChangeRecords( |
331 _iteratedValue != null ? _iteratedValue : [], | 329 _iteratedValue != null ? _iteratedValue : [], |
332 _presentValue != null ? _presentValue : [])); | 330 _presentValue != null ? _presentValue : [])); |
333 } | 331 } |
334 | 332 |
335 Node _getTerminatorAt(int index) { | 333 Node _getLastInstanceNode(int index) { |
336 if (index == -1) return _templateElement; | 334 if (index == -1) return _templateElement; |
337 var terminator = _terminators[index * 2]; | 335 // TODO(jmesserly): we could avoid this expando lookup by caching the |
| 336 // instance extension instead of the instance. |
| 337 var instance = _instanceExtension[_instances[index]]; |
| 338 var terminator = instance._terminator; |
| 339 if (terminator == null) return _getLastInstanceNode(index - 1); |
| 340 |
338 if (!isSemanticTemplate(terminator) || | 341 if (!isSemanticTemplate(terminator) || |
339 identical(terminator, _templateElement)) { | 342 identical(terminator, _templateElement)) { |
340 return terminator; | 343 return terminator; |
341 } | 344 } |
342 | 345 |
343 var subIter = templateBindFallback(terminator)._iterator; | 346 var subtemplateIterator = templateBindFallback(terminator)._iterator; |
344 if (subIter == null) return terminator; | 347 if (subtemplateIterator == null) return terminator; |
345 | 348 |
346 return subIter._getTerminatorAt(subIter._terminators.length ~/ 2 - 1); | 349 return subtemplateIterator._getLastTemplateNode(); |
347 } | 350 } |
348 | 351 |
349 // TODO(rafaelw): If we inserting sequences of instances we can probably | 352 Node _getLastTemplateNode() => _getLastInstanceNode(_instances.length - 1); |
350 // avoid lots of calls to _getTerminatorAt(), or cache its result. | |
351 void _insertInstanceAt(int index, DocumentFragment fragment, | |
352 List<Node> instanceNodes, List<Bindable> instanceBindings) { | |
353 | 353 |
354 var previousTerminator = _getTerminatorAt(index - 1); | 354 void _insertInstanceAt(int index, DocumentFragment fragment) { |
355 var terminator = null; | 355 var previousInstanceLast = _getLastInstanceNode(index - 1); |
356 if (fragment != null) { | 356 var parent = _templateElement.parentNode; |
357 terminator = fragment.lastChild; | |
358 } else if (instanceNodes != null && instanceNodes.isNotEmpty) { | |
359 terminator = instanceNodes.last; | |
360 } | |
361 if (terminator == null) terminator = previousTerminator; | |
362 | 357 |
363 _terminators.insertAll(index * 2, [terminator, instanceBindings]); | 358 _instances.insert(index, fragment); |
364 var parent = _templateElement.parentNode; | 359 parent.insertBefore(fragment, previousInstanceLast.nextNode); |
365 var insertBeforeNode = previousTerminator.nextNode; | |
366 | |
367 if (fragment != null) { | |
368 parent.insertBefore(fragment, insertBeforeNode); | |
369 } else if (instanceNodes != null) { | |
370 for (var node in instanceNodes) { | |
371 parent.insertBefore(node, insertBeforeNode); | |
372 } | |
373 } | |
374 } | 360 } |
375 | 361 |
376 _BoundNodes _extractInstanceAt(int index) { | 362 DocumentFragment _extractInstanceAt(int index) { |
377 var instanceNodes = <Node>[]; | 363 var previousInstanceLast = _getLastInstanceNode(index - 1); |
378 var previousTerminator = _getTerminatorAt(index - 1); | 364 var lastNode = _getLastInstanceNode(index); |
379 var terminator = _getTerminatorAt(index); | 365 var parent = _templateElement.parentNode; |
380 var instanceBindings = _terminators[index * 2 + 1]; | 366 var instance = _instances.removeAt(index); |
381 _terminators.removeRange(index * 2, index * 2 + 2); | |
382 | 367 |
383 var parent = _templateElement.parentNode; | 368 while (lastNode != previousInstanceLast) { |
384 while (terminator != previousTerminator) { | 369 var node = previousInstanceLast.nextNode; |
385 var node = previousTerminator.nextNode; | 370 if (node == lastNode) lastNode = previousInstanceLast; |
386 if (node == terminator) terminator = previousTerminator; | 371 |
387 node.remove(); | 372 instance.append(node..remove()); |
388 instanceNodes.add(node); | |
389 } | 373 } |
390 return new _BoundNodes(instanceNodes, instanceBindings); | 374 |
| 375 return instance; |
391 } | 376 } |
392 | 377 |
393 void _handleSplices(List<ListChangeRecord> splices) { | 378 void _handleSplices(List<ListChangeRecord> splices) { |
394 if (_closed || splices.isEmpty) return; | 379 if (_closed || splices.isEmpty) return; |
395 | 380 |
396 final template = _templateElement; | 381 final template = _templateElement; |
397 | 382 |
398 if (template.parentNode == null) { | 383 if (template.parentNode == null) { |
399 close(); | 384 close(); |
400 return; | 385 return; |
401 } | 386 } |
402 | 387 |
403 ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices); | 388 ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices); |
404 | 389 |
405 final delegate = _templateExt.bindingDelegate; | 390 final delegate = _templateExt.bindingDelegate; |
406 | 391 |
407 // Dart note: the JavaScript code relies on the distinction between null | 392 // Dart note: the JavaScript code relies on the distinction between null |
408 // and undefined to track whether the functions are prepared. We use a bool. | 393 // and undefined to track whether the functions are prepared. We use a bool. |
409 if (!_initPrepareFunctions) { | 394 if (!_initPrepareFunctions) { |
410 _initPrepareFunctions = true; | 395 _initPrepareFunctions = true; |
411 final delegate = _templateExt._self.bindingDelegate; | 396 final delegate = _templateExt._self.bindingDelegate; |
412 if (delegate != null) { | 397 if (delegate != null) { |
413 _instanceModelFn = delegate.prepareInstanceModel(template); | 398 _instanceModelFn = delegate.prepareInstanceModel(template); |
414 _instancePositionChangedFn = | 399 _instancePositionChangedFn = |
415 delegate.prepareInstancePositionChanged(template); | 400 delegate.prepareInstancePositionChanged(template); |
416 } | 401 } |
417 } | 402 } |
418 | 403 |
419 var instanceCache = new HashMap<Object, _BoundNodes>(equals: identical); | 404 // Instance Removals. |
| 405 var instanceCache = new HashMap(equals: identical); |
420 var removeDelta = 0; | 406 var removeDelta = 0; |
421 for (var splice in splices) { | 407 for (var splice in splices) { |
422 for (var model in splice.removed) { | 408 for (var model in splice.removed) { |
423 instanceCache[model] = _extractInstanceAt(splice.index + removeDelta); | 409 var instance = _extractInstanceAt(splice.index + removeDelta); |
| 410 if (instance != _emptyInstance) { |
| 411 instanceCache[model] = instance; |
| 412 } |
424 } | 413 } |
425 | 414 |
426 removeDelta -= splice.addedCount; | 415 removeDelta -= splice.addedCount; |
427 } | 416 } |
428 | 417 |
429 for (var splice in splices) { | 418 for (var splice in splices) { |
430 for (var addIndex = splice.index; | 419 for (var addIndex = splice.index; |
431 addIndex < splice.index + splice.addedCount; | 420 addIndex < splice.index + splice.addedCount; |
432 addIndex++) { | 421 addIndex++) { |
433 | 422 |
434 var model = _iteratedValue[addIndex]; | 423 var model = _iteratedValue[addIndex]; |
435 var fragment = null; | 424 DocumentFragment instance = instanceCache.remove(model); |
436 var instance = instanceCache.remove(model); | 425 if (instance == null) { |
437 List instanceBindings; | |
438 List instanceNodes = null; | |
439 if (instance != null && instance.nodes.isNotEmpty) { | |
440 instanceBindings = instance.instanceBindings; | |
441 instanceNodes = instance.nodes; | |
442 } else { | |
443 try { | 426 try { |
444 instanceBindings = []; | |
445 if (_instanceModelFn != null) { | 427 if (_instanceModelFn != null) { |
446 model = _instanceModelFn(model); | 428 model = _instanceModelFn(model); |
447 } | 429 } |
448 if (model != null) { | 430 if (model == null) { |
449 fragment = _templateExt.createInstance(model, delegate, | 431 instance = _emptyInstance; |
450 instanceBindings); | 432 } else { |
| 433 instance = _templateExt.createInstance(model, delegate); |
451 } | 434 } |
452 } catch (e, s) { | 435 } catch (e, s) { |
453 // Dart note: we propagate errors asynchronously here to avoid | 436 // Dart note: we propagate errors asynchronously here to avoid |
454 // disrupting the rendering flow. This is different than in the JS | 437 // disrupting the rendering flow. This is different than in the JS |
455 // implementation but it should probably be fixed there too. Dart | 438 // implementation but it should probably be fixed there too. Dart |
456 // hits this case more because non-existing properties in | 439 // hits this case more because non-existing properties in |
457 // [PropertyPath] are treated as errors, while JS treats them as | 440 // [PropertyPath] are treated as errors, while JS treats them as |
458 // null/undefined. | 441 // null/undefined. |
459 // TODO(sigmund): this should be a synchronous throw when this is | 442 // TODO(sigmund): this should be a synchronous throw when this is |
460 // called from createInstance, but that requires enough refactoring | 443 // called from createInstance, but that requires enough refactoring |
461 // that it should be done upstream first. See dartbug.com/17789. | 444 // that it should be done upstream first. See dartbug.com/17789. |
462 new Completer().completeError(e, s); | 445 new Completer().completeError(e, s); |
| 446 instance = _emptyInstance; |
463 } | 447 } |
464 } | 448 } |
465 | 449 |
466 _insertInstanceAt(addIndex, fragment, instanceNodes, instanceBindings); | 450 _insertInstanceAt(addIndex, instance); |
467 } | 451 } |
468 } | 452 } |
469 | 453 |
470 for (var instance in instanceCache.values) { | 454 for (var instance in instanceCache.values) { |
471 _closeInstanceBindings(instance.instanceBindings); | 455 _closeInstanceBindings(instance); |
472 } | 456 } |
473 | 457 |
474 if (_instancePositionChangedFn != null) _reportInstancesMoved(splices); | 458 if (_instancePositionChangedFn != null) _reportInstancesMoved(splices); |
475 } | 459 } |
476 | 460 |
477 void _reportInstanceMoved(int index) { | 461 void _reportInstanceMoved(int index) { |
478 var previousTerminator = _getTerminatorAt(index - 1); | 462 var instance = _instances[index]; |
479 var terminator = _getTerminatorAt(index); | 463 if (instance == _emptyInstance) return; |
480 if (identical(previousTerminator, terminator)) { | |
481 return; // instance has zero nodes. | |
482 } | |
483 | 464 |
484 // We must use the first node of the instance, because any subsequent | 465 _instancePositionChangedFn(nodeBind(instance).templateInstance, index); |
485 // nodes may have been generated by sub-templates. | |
486 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the | |
487 // first node was removed by script. | |
488 var instance = nodeBind(previousTerminator.nextNode).templateInstance; | |
489 _instancePositionChangedFn(instance, index); | |
490 } | 466 } |
491 | 467 |
492 void _reportInstancesMoved(List<ListChangeRecord> splices) { | 468 void _reportInstancesMoved(List<ListChangeRecord> splices) { |
493 var index = 0; | 469 var index = 0; |
494 var offset = 0; | 470 var offset = 0; |
495 for (var splice in splices) { | 471 for (var splice in splices) { |
496 if (offset != 0) { | 472 if (offset != 0) { |
497 while (index < splice.index) { | 473 while (index < splice.index) { |
498 _reportInstanceMoved(index); | 474 _reportInstanceMoved(index); |
499 index++; | 475 index++; |
500 } | 476 } |
501 } else { | 477 } else { |
502 index = splice.index; | 478 index = splice.index; |
503 } | 479 } |
504 | 480 |
505 while (index < splice.index + splice.addedCount) { | 481 while (index < splice.index + splice.addedCount) { |
506 _reportInstanceMoved(index); | 482 _reportInstanceMoved(index); |
507 index++; | 483 index++; |
508 } | 484 } |
509 | 485 |
510 offset += splice.addedCount - splice.removed.length; | 486 offset += splice.addedCount - splice.removed.length; |
511 } | 487 } |
512 | 488 |
513 if (offset == 0) return; | 489 if (offset == 0) return; |
514 | 490 |
515 var length = _terminators.length ~/ 2; | 491 var length = _instances.length; |
516 while (index < length) { | 492 while (index < length) { |
517 _reportInstanceMoved(index); | 493 _reportInstanceMoved(index); |
518 index++; | 494 index++; |
519 } | 495 } |
520 } | 496 } |
521 | 497 |
522 void _closeInstanceBindings(List<Bindable> instanceBindings) { | 498 void _closeInstanceBindings(DocumentFragment instance) { |
523 for (var binding in instanceBindings) binding.close(); | 499 var bindings = _instanceExtension[instance]._bindings; |
| 500 for (var binding in bindings) binding.close(); |
524 } | 501 } |
525 | 502 |
526 void _unobserve() { | 503 void _unobserve() { |
527 if (_listSub == null) return; | 504 if (_listSub == null) return; |
528 _listSub.cancel(); | 505 _listSub.cancel(); |
529 _listSub = null; | 506 _listSub = null; |
530 } | 507 } |
531 | 508 |
532 void close() { | 509 void close() { |
533 if (_closed) return; | 510 if (_closed) return; |
534 | 511 |
535 _unobserve(); | 512 _unobserve(); |
536 for (var i = 1; i < _terminators.length; i += 2) { | 513 _instances.forEach(_closeInstanceBindings); |
537 _closeInstanceBindings(_terminators[i]); | 514 _instances.clear(); |
538 } | |
539 | |
540 _terminators.clear(); | |
541 _closeDependencies(); | 515 _closeDependencies(); |
542 _templateExt._iterator = null; | 516 _templateExt._iterator = null; |
543 _closed = true; | 517 _closed = true; |
544 } | 518 } |
545 } | 519 } |
546 | 520 |
547 // Dart note: the JavaScript version just puts an expando on the array. | 521 // Dart note: the JavaScript version just puts an expando on the array. |
548 class _BoundNodes { | 522 class _BoundNodes { |
549 final List<Node> nodes; | 523 final List<Node> nodes; |
550 final List<Bindable> instanceBindings; | 524 final List<Bindable> instanceBindings; |
551 _BoundNodes(this.nodes, this.instanceBindings); | 525 _BoundNodes(this.nodes, this.instanceBindings); |
552 } | 526 } |
OLD | NEW |