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

Side by Side Diff: pkg/mdv/lib/src/bindings.dart

Issue 17770006: [mdv] implement full DOM stability (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merged Created 7 years, 5 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 | Annotate | Revision Log
« no previous file with comments | « pkg/mdv/lib/mdv.dart ('k') | pkg/mdv/pubspec.yaml » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
270 270
271 var instanceRecord = new TemplateInstance( 271 var instanceRecord = new TemplateInstance(
272 fragment.firstChild, fragment.lastChild, model); 272 fragment.firstChild, fragment.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.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) {
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.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) {
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.firstChild; c != null; c = c.nextNode) {
492 _unbindAllRecursively(c);
493 }
494 }
479 } 495 }
OLDNEW
« no previous file with comments | « pkg/mdv/lib/mdv.dart ('k') | pkg/mdv/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698