Chromium Code Reviews| 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 mdv; | 5 part of mdv; |
| 6 | 6 |
| 7 // This code is a port of Model-Driven-Views: | 7 // This code is a port of Model-Driven-Views: |
| 8 // https://github.com/polymer-project/mdv | 8 // https://github.com/polymer-project/mdv |
| 9 // The code mostly comes from src/template_element.js | 9 // The code mostly comes from src/template_element.js |
| 10 | 10 |
| (...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 270 | 270 |
| 271 var instanceRecord = new TemplateInstance( | 271 var instanceRecord = new TemplateInstance( |
| 272 fragment.$dom_firstChild, fragment.$dom_lastChild, model); | 272 fragment.$dom_firstChild, fragment.$dom_lastChild, model); |
| 273 | 273 |
| 274 var node = instanceRecord.firstNode; | 274 var node = instanceRecord.firstNode; |
| 275 while (node != null) { | 275 while (node != null) { |
| 276 _mdv(node)._templateInstance = instanceRecord; | 276 _mdv(node)._templateInstance = instanceRecord; |
| 277 node = node.nextNode; | 277 node = node.nextNode; |
| 278 } | 278 } |
| 279 } | 279 } |
| 280 | |
| 281 static void _removeAllBindingsRecursively(Node node) { | |
| 282 _nodeOrCustom(node).unbindAll(); | |
| 283 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 284 _removeAllBindingsRecursively(c); | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 static void _removeChild(Node parent, Node child) { | |
| 289 _mdv(child)._templateInstance = null; | |
| 290 if (child is Element && (child as Element).isTemplate) { | |
| 291 Element childElement = child; | |
| 292 // Make sure we stop observing when we remove an element. | |
| 293 var templateIterator = _mdv(childElement)._templateIterator; | |
| 294 if (templateIterator != null) { | |
| 295 templateIterator.abandon(); | |
| 296 _mdv(childElement)._templateIterator = null; | |
| 297 } | |
| 298 } | |
| 299 child.remove(); | |
| 300 _removeAllBindingsRecursively(child); | |
| 301 } | |
| 302 } | 280 } |
| 303 | 281 |
| 304 class _BindingToken { | 282 class _BindingToken { |
| 305 final String value; | 283 final String value; |
| 306 final bool isBinding; | 284 final bool isBinding; |
| 307 | 285 |
| 308 _BindingToken(this.value, {this.isBinding: false}); | 286 _BindingToken(this.value, {this.isBinding: false}); |
| 309 | 287 |
| 310 bool get isText => !isBinding; | 288 bool get isText => !isBinding; |
| 311 } | 289 } |
| 312 | 290 |
| 313 class _TemplateIterator { | 291 class _TemplateIterator { |
| 314 final Element _templateElement; | 292 final Element _templateElement; |
| 315 final List<Node> terminators = []; | 293 final List<Node> terminators = []; |
| 316 final CompoundBinding inputs; | 294 final CompoundBinding inputs; |
| 317 List iteratedValue; | 295 List iteratedValue; |
| 296 Object _lastValue; | |
| 318 | 297 |
| 319 StreamSubscription _sub; | 298 StreamSubscription _sub; |
| 320 StreamSubscription _valueBinding; | 299 StreamSubscription _valueBinding; |
| 321 | 300 |
| 322 _TemplateIterator(this._templateElement) | 301 _TemplateIterator(this._templateElement) |
| 323 : inputs = new CompoundBinding(resolveInputs) { | 302 : inputs = new CompoundBinding(resolveInputs) { |
| 324 | 303 |
| 325 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | 304 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); |
| 326 } | 305 } |
| 327 | 306 |
| 328 static Object resolveInputs(Map values) { | 307 static Object resolveInputs(Map values) { |
| 329 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) { | 308 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) { |
| 330 return null; | 309 return null; |
| 331 } | 310 } |
| 332 | 311 |
| 333 if (values.containsKey('repeat')) { | 312 if (values.containsKey('repeat')) { |
| 334 return values['repeat']; | 313 return values['repeat']; |
| 335 } | 314 } |
| 336 | 315 |
| 337 if (values.containsKey('bind')) { | 316 if (values.containsKey('bind')) { |
| 338 return [values['bind']]; | 317 return [values['bind']]; |
| 339 } | 318 } |
| 340 | 319 |
| 341 return null; | 320 return null; |
| 342 } | 321 } |
| 343 | 322 |
| 344 void valueChanged(value) { | 323 void valueChanged(value) { |
| 345 clear(); | 324 // TODO(jmesserly): should PathObserver do this for us? |
| 346 if (value is! List) return; | 325 var oldValue = _lastValue; |
| 326 _lastValue = value; | |
| 347 | 327 |
| 328 if (value is! List) { | |
|
justinfagnani
2013/06/26 20:01:13
List -> Iterable?
Jennifer Messerly
2013/06/26 20:47:50
good question. We do use indexing, but maybe if it
| |
| 329 value = []; | |
| 330 } | |
| 331 | |
| 332 unobserve(); | |
| 348 iteratedValue = value; | 333 iteratedValue = value; |
| 349 | 334 |
| 350 if (value is Observable) { | 335 if (value is Observable) { |
| 351 _sub = value.changes.listen(_handleChanges); | 336 _sub = value.changes.listen(_handleChanges); |
| 352 } | 337 } |
| 353 | 338 |
| 354 int len = iteratedValue.length; | 339 int addedCount = iteratedValue.length; |
| 355 if (len > 0) { | 340 var removedCount = oldValue is List ? (oldValue as List).length : 0; |
| 356 _handleChanges([new ListChangeRecord(0, addedCount: len)]); | 341 if (addedCount == 0 && removedCount == 0) return; // nothing to do. |
| 357 } | 342 |
| 343 _handleChanges([new ListChangeRecord(0, addedCount: addedCount, | |
| 344 removedCount: removedCount)]); | |
| 358 } | 345 } |
| 359 | 346 |
| 360 Node getTerminatorAt(int index) { | 347 Node getTerminatorAt(int index) { |
| 361 if (index == -1) return _templateElement; | 348 if (index == -1) return _templateElement; |
| 362 var terminator = terminators[index]; | 349 var terminator = terminators[index]; |
| 363 if (terminator is Element && (terminator as Element).isTemplate) { | 350 if (terminator is Element && (terminator as Element).isTemplate) { |
| 364 var subIterator = _mdv(terminator)._templateIterator; | 351 var subIterator = _mdv(terminator)._templateIterator; |
| 365 if (subIterator != null) { | 352 if (subIterator != null) { |
| 366 return subIterator.getTerminatorAt(subIterator.terminators.length - 1); | 353 return subIterator.getTerminatorAt(subIterator.terminators.length - 1); |
| 367 } | 354 } |
| 368 } | 355 } |
| 369 | 356 |
| 370 return terminator; | 357 return terminator; |
| 371 } | 358 } |
| 372 | 359 |
| 373 void insertInstanceAt(int index, Node fragment) { | 360 void insertInstanceAt(int index, List<Node> instanceNodes) { |
| 374 var previousTerminator = getTerminatorAt(index - 1); | 361 var previousTerminator = getTerminatorAt(index - 1); |
| 375 var terminator = fragment.$dom_lastChild; | 362 var terminator = instanceNodes.length > 0 ? instanceNodes.last |
| 376 if (terminator == null) terminator = previousTerminator; | 363 : previousTerminator; |
| 377 | 364 |
| 378 terminators.insert(index, terminator); | 365 terminators.insert(index, terminator); |
| 366 | |
| 379 var parent = _templateElement.parentNode; | 367 var parent = _templateElement.parentNode; |
| 380 parent.insertBefore(fragment, previousTerminator.nextNode); | 368 var insertBeforeNode = previousTerminator.nextNode; |
| 369 for (var node in instanceNodes) { | |
| 370 parent.insertBefore(node, insertBeforeNode); | |
| 371 } | |
| 381 } | 372 } |
| 382 | 373 |
| 383 void removeInstanceAt(int index) { | 374 List<Node> extractInstanceAt(int index) { |
| 375 var instanceNodes = <Node>[]; | |
| 384 var previousTerminator = getTerminatorAt(index - 1); | 376 var previousTerminator = getTerminatorAt(index - 1); |
| 385 var terminator = getTerminatorAt(index); | 377 var terminator = getTerminatorAt(index); |
| 386 terminators.removeAt(index); | 378 terminators.removeAt(index); |
| 387 | 379 |
| 388 var parent = _templateElement.parentNode; | 380 var parent = _templateElement.parentNode; |
| 389 while (terminator != previousTerminator) { | 381 while (terminator != previousTerminator) { |
| 390 var node = terminator; | 382 var node = terminator; |
| 391 terminator = node.previousNode; | 383 terminator = node.previousNode; |
| 392 _Bindings._removeChild(parent, node); | 384 node.remove(); |
| 385 instanceNodes.add(node); | |
| 393 } | 386 } |
| 394 } | 387 return instanceNodes; |
| 395 | |
| 396 void removeAllInstances() { | |
| 397 if (terminators.length == 0) return; | |
| 398 | |
| 399 var previousTerminator = _templateElement; | |
| 400 var terminator = getTerminatorAt(terminators.length - 1); | |
| 401 terminators.length = 0; | |
| 402 | |
| 403 var parent = _templateElement.parentNode; | |
| 404 while (terminator != previousTerminator) { | |
| 405 var node = terminator; | |
| 406 terminator = node.previousNode; | |
| 407 _Bindings._removeChild(parent, node); | |
| 408 } | |
| 409 } | |
| 410 | |
| 411 void clear() { | |
| 412 unobserve(); | |
| 413 removeAllInstances(); | |
| 414 iteratedValue = null; | |
| 415 } | 388 } |
| 416 | 389 |
| 417 getInstanceModel(model, syntax) { | 390 getInstanceModel(model, syntax) { |
| 418 if (syntax != null) { | 391 if (syntax != null) { |
| 419 return syntax.getInstanceModel(_templateElement, model); | 392 return syntax.getInstanceModel(_templateElement, model); |
| 420 } | 393 } |
| 421 return model; | 394 return model; |
| 422 } | 395 } |
| 423 | 396 |
| 424 getInstanceFragment(syntax) { | 397 Node getInstanceFragment(syntax) { |
| 425 if (syntax != null) { | 398 if (syntax != null) { |
| 426 return syntax.getInstanceFragment(_templateElement); | 399 return syntax.getInstanceFragment(_templateElement); |
| 427 } | 400 } |
| 428 return _templateElement.createInstance(); | 401 return _templateElement.createInstance(); |
| 429 } | 402 } |
| 430 | 403 |
| 431 void _handleChanges(List<ChangeRecord> splices) { | 404 List<Node> getInstanceNodes(model, syntax) { |
| 405 // TODO(jmesserly): this line of code is jumping ahead of what MDV supports. | |
| 406 // It doesn't let the custom syntax override createInstance(). | |
| 407 var fragment = getInstanceFragment(syntax); | |
| 408 | |
| 409 _Bindings._addBindings(fragment, model, syntax); | |
| 410 _Bindings._addTemplateInstanceRecord(fragment, model); | |
| 411 | |
| 412 var instanceNodes = fragment.nodes.toList(); | |
| 413 fragment.nodes.clear(); | |
| 414 return instanceNodes; | |
| 415 } | |
| 416 | |
| 417 void _handleChanges(Iterable<ChangeRecord> splices) { | |
| 418 splices = splices.where((s) => s is ListChangeRecord); | |
| 419 | |
| 432 var template = _templateElement; | 420 var template = _templateElement; |
| 433 var syntax = TemplateElement.syntax[template.attributes['syntax']]; | 421 var syntax = TemplateElement.syntax[template.attributes['syntax']]; |
| 434 | 422 |
| 435 if (template.parentNode == null || template.document.window == null) { | 423 if (template.parentNode == null || template.document.window == null) { |
| 436 // TODO(jmesserly): this is a little different than the MDV code, which | |
| 437 // removes _templateIterator from all instances (via the weak table). | |
| 438 // I think it has a similar effect. It might be a no-op. | |
| 439 removeAllInstances(); | |
| 440 abandon(); | 424 abandon(); |
| 425 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, | |
| 426 // but I think that's a no-op because only nodes are used as keys. | |
| 427 // See https://github.com/Polymer/mdv/pull/114. | |
| 441 return; | 428 return; |
| 442 } | 429 } |
| 443 | 430 |
| 431 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right | |
| 432 // now. See http://dartbug.com/4161. | |
| 433 var instanceCache = new IdentityMap(); | |
| 434 var removeDelta = 0; | |
| 444 for (var splice in splices) { | 435 for (var splice in splices) { |
| 445 if (splice is! ListChangeRecord) continue; | |
| 446 | |
| 447 for (int i = 0; i < splice.removedCount; i++) { | 436 for (int i = 0; i < splice.removedCount; i++) { |
| 448 removeInstanceAt(splice.index); | 437 var instanceNodes = extractInstanceAt(splice.index + removeDelta); |
| 438 var model = _mdv(instanceNodes.first)._templateInstance.model; | |
| 439 instanceCache[model] = instanceNodes; | |
| 449 } | 440 } |
| 450 | 441 |
| 442 removeDelta -= splice.addedCount; | |
| 443 } | |
| 444 | |
| 445 for (var splice in splices) { | |
| 451 for (var addIndex = splice.index; | 446 for (var addIndex = splice.index; |
| 452 addIndex < splice.index + splice.addedCount; | 447 addIndex < splice.index + splice.addedCount; |
| 453 addIndex++) { | 448 addIndex++) { |
| 454 | 449 |
| 455 var model = getInstanceModel(iteratedValue[addIndex], syntax); | 450 var model = getInstanceModel(iteratedValue[addIndex], syntax); |
| 456 | 451 |
| 457 var fragment = getInstanceFragment(syntax); | 452 var instanceNodes = instanceCache.remove(model); |
| 453 if (instanceNodes == null) { | |
| 454 instanceNodes = getInstanceNodes(model, syntax); | |
| 455 } | |
| 456 insertInstanceAt(addIndex, instanceNodes); | |
| 457 } | |
| 458 } | |
| 458 | 459 |
| 459 _Bindings._addBindings(fragment, model, syntax); | 460 for (var instanceNodes in instanceCache.values) { |
| 460 _Bindings._addTemplateInstanceRecord(fragment, model); | 461 instanceNodes.forEach(_unbindAllRecursively); |
| 461 | |
| 462 insertInstanceAt(addIndex, fragment); | |
| 463 } | |
| 464 } | 462 } |
| 465 } | 463 } |
| 466 | 464 |
| 467 void unobserve() { | 465 void unobserve() { |
| 468 if (_sub == null) return; | 466 if (_sub == null) return; |
| 469 _sub.cancel(); | 467 _sub.cancel(); |
| 470 _sub = null; | 468 _sub = null; |
| 471 } | 469 } |
| 472 | 470 |
| 473 void abandon() { | 471 void abandon() { |
| 474 unobserve(); | 472 unobserve(); |
| 475 _valueBinding.cancel(); | 473 _valueBinding.cancel(); |
| 476 terminators.clear(); | 474 terminators.clear(); |
| 477 inputs.dispose(); | 475 inputs.dispose(); |
| 478 } | 476 } |
| 477 | |
| 478 static void _unbindAllRecursively(Node node) { | |
| 479 var nodeExt = _mdv(node); | |
| 480 nodeExt._templateInstance = null; | |
| 481 if (node is Element && (node as Element).isTemplate) { | |
|
justinfagnani
2013/06/26 20:01:13
this cast is unfortunate
| |
| 482 // Make sure we stop observing when we remove an element. | |
| 483 var templateIterator = nodeExt._templateIterator; | |
| 484 if (templateIterator != null) { | |
| 485 templateIterator.abandon(); | |
| 486 nodeExt._templateIterator = null; | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 _Bindings._nodeOrCustom(node).unbindAll(); | |
| 491 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { | |
| 492 _unbindAllRecursively(c); | |
| 493 } | |
| 494 } | |
| 479 } | 495 } |
| OLD | NEW |