| 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 |