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

Side by Side Diff: tools/dom/src/TemplateBindings.dart

Issue 14634011: move top-level internal methods into a class (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 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
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 html; 5 part of html;
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/toolkitchen/mdv 8 // https://github.com/toolkitchen/mdv
9 // The code mostly comes from src/template_element.js 9 // The code mostly comes from src/template_element.js
10 10
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 binding.cancel(); 239 binding.cancel();
240 } 240 }
241 _bindings.clear(); 241 _bindings.clear();
242 _values.clear(); 242 _values.clear();
243 243
244 _disposed = true; 244 _disposed = true;
245 value = null; 245 value = null;
246 } 246 }
247 } 247 }
248 248
249 Stream<Event> _getStreamForInputType(InputElement element) {
250 switch (element.type) {
251 case 'checkbox':
252 return element.onClick;
253 case 'radio':
254 case 'select-multiple':
255 case 'select-one':
256 return element.onChange;
257 default:
258 return element.onInput;
259 }
260 }
261
262 abstract class _InputBinding { 249 abstract class _InputBinding {
263 final InputElement element; 250 final InputElement element;
264 PathObserver binding; 251 PathObserver binding;
265 StreamSubscription _pathSub; 252 StreamSubscription _pathSub;
266 StreamSubscription _eventSub; 253 StreamSubscription _eventSub;
267 254
268 _InputBinding(this.element, model, String path) { 255 _InputBinding(this.element, model, String path) {
269 binding = new PathObserver(model, path); 256 binding = new PathObserver(model, path);
270 _pathSub = binding.bindSync(valueChanged); 257 _pathSub = binding.bindSync(valueChanged);
271 _eventSub = _getStreamForInputType(element).listen(updateBinding); 258 _eventSub = _getStreamForInputType(element).listen(updateBinding);
272 } 259 }
273 260
274 void valueChanged(newValue); 261 void valueChanged(newValue);
275 262
276 void updateBinding(e); 263 void updateBinding(e);
277 264
278 void unbind() { 265 void unbind() {
279 binding = null; 266 binding = null;
280 _pathSub.cancel(); 267 _pathSub.cancel();
281 _eventSub.cancel(); 268 _eventSub.cancel();
282 } 269 }
270
271
272 static Stream<Event> _getStreamForInputType(InputElement element) {
273 switch (element.type) {
274 case 'checkbox':
275 return element.onClick;
276 case 'radio':
277 case 'select-multiple':
278 case 'select-one':
279 return element.onChange;
280 default:
281 return element.onInput;
282 }
283 }
283 } 284 }
284 285
285 class _ValueBinding extends _InputBinding { 286 class _ValueBinding extends _InputBinding {
286 _ValueBinding(element, model, path) : super(element, model, path); 287 _ValueBinding(element, model, path) : super(element, model, path);
287 288
288 void valueChanged(value) { 289 void valueChanged(value) {
289 element.value = value == null ? '' : '$value'; 290 element.value = value == null ? '' : '$value';
290 } 291 }
291 292
292 void updateBinding(e) { 293 void updateBinding(e) {
293 binding.value = element.value; 294 binding.value = element.value;
294 } 295 }
295 } 296 }
296 297
297 // TODO(jmesserly): not sure what kind of boolean conversion rules to
298 // apply for template data-binding. HTML attributes are true if they're present.
299 // However Dart only treats "true" as true. Since this is HTML we'll use
300 // something closer to the HTML rules: null (missing) and false are false,
301 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
302 bool _templateBooleanConversion(value) => null != value && false != value;
303
304 class _CheckedBinding extends _InputBinding { 298 class _CheckedBinding extends _InputBinding {
305 _CheckedBinding(element, model, path) : super(element, model, path); 299 _CheckedBinding(element, model, path) : super(element, model, path);
306 300
307 void valueChanged(value) { 301 void valueChanged(value) {
308 element.checked = _templateBooleanConversion(value); 302 element.checked = _Bindings._toBoolean(value);
309 } 303 }
310 304
311 void updateBinding(e) { 305 void updateBinding(e) {
312 binding.value = element.checked; 306 binding.value = element.checked;
313 307
314 // Only the radio button that is getting checked gets an event. We 308 // Only the radio button that is getting checked gets an event. We
315 // therefore find all the associated radio buttons and update their 309 // therefore find all the associated radio buttons and update their
316 // CheckedBinding manually. 310 // CheckedBinding manually.
317 if (element is InputElement && element.type == 'radio') { 311 if (element is InputElement && element.type == 'radio') {
318 for (var r in _getAssociatedRadioButtons(element)) { 312 for (var r in _getAssociatedRadioButtons(element)) {
319 var checkedBinding = r._checkedBinding; 313 var checkedBinding = r._checkedBinding;
320 if (checkedBinding != null) { 314 if (checkedBinding != null) {
321 // Set the value directly to avoid an infinite call stack. 315 // Set the value directly to avoid an infinite call stack.
322 checkedBinding.binding.value = false; 316 checkedBinding.binding.value = false;
323 } 317 }
324 } 318 }
325 } 319 }
326 } 320 }
321
322 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
323 // Returns an array containing all radio buttons other than |element| that
324 // have the same |name|, either in the form that |element| belongs to or,
325 // if no form, in the document tree to which |element| belongs.
326 //
327 // This implementation is based upon the HTML spec definition of a
328 // "radio button group":
329 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state. html#radio-button-group
330 //
331 static Iterable _getAssociatedRadioButtons(element) {
332 if (!_isNodeInDocument(element)) return [];
333 if (element.form != null) {
334 return element.form.nodes.where((el) {
335 return el != element &&
336 el is InputElement &&
337 el.type == 'radio' &&
338 el.name == element.name;
339 });
340 } else {
341 var radios = element.document.queryAll(
342 'input[type="radio"][name="${element.name}"]');
343 return radios.where((el) => el != element && el.form == null);
344 }
345 }
346
347 // TODO(jmesserly): polyfill document.contains API instead of doing it here
348 static bool _isNodeInDocument(Node node) {
349 // On non-IE this works:
350 // return node.document.contains(node);
351 var document = node.document;
352 if (node == document || node.parentNode == document) return true;
353 return document.documentElement.contains(node);
354 }
327 } 355 }
328 356
329 // TODO(jmesserly): polyfill document.contains API instead of doing it here 357 class _Bindings {
330 bool _isNodeInDocument(Node node) { 358 // TODO(jmesserly): not sure what kind of boolean conversion rules to
331 // On non-IE this works: 359 // apply for template data-binding. HTML attributes are true if they're
332 // return node.document.contains(node); 360 // present. However Dart only treats "true" as true. Since this is HTML we'll
333 var document = node.document; 361 // use something closer to the HTML rules: null (missing) and false are false,
334 if (node == document || node.parentNode == document) return true; 362 // everything else is true. See: https://github.com/toolkitchen/mdv/issues/59
335 return document.documentElement.contains(node); 363 static bool _toBoolean(value) => null != value && false != value;
336 } 364
337 365 static Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
338 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. 366 var clone = node.clone(false); // Shallow clone.
339 // Returns an array containing all radio buttons other than |element| that 367 if (clone is Element && clone.isTemplate) {
340 // have the same |name|, either in the form that |element| belongs to or, 368 TemplateElement.decorate(clone, node);
341 // if no form, in the document tree to which |element| belongs. 369 if (syntax != null) {
342 // 370 clone.attributes.putIfAbsent('syntax', () => syntax);
343 // This implementation is based upon the HTML spec definition of a 371 }
344 // "radio button group": 372 }
345 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.ht ml#radio-button-group 373
346 // 374 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
347 Iterable _getAssociatedRadioButtons(element) { 375 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
348 if (!_isNodeInDocument(element)) return []; 376 }
349 if (element.form != null) { 377 return clone;
350 return element.form.nodes.where((el) { 378 }
351 return el != element && 379
352 el is InputElement && 380 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html# dfn-template-contents-owner
353 el.type == 'radio' && 381 static Document _getTemplateContentsOwner(Document doc) {
354 el.name == element.name; 382 if (doc.window == null) {
355 }); 383 return doc;
356 } else { 384 }
357 var radios = element.document.queryAll( 385 var d = doc._templateContentsOwner;
358 'input[type="radio"][name="${element.name}"]'); 386 if (d == null) {
359 return radios.where((el) => el != element && el.form == null); 387 // TODO(arv): This should either be a Document or HTMLDocument depending
360 } 388 // on doc.
361 } 389 d = doc.implementation.createHtmlDocument('');
362 390 while (d.$dom_lastChild != null) {
363 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { 391 d.$dom_lastChild.remove();
364 var clone = node.clone(false); // Shallow clone. 392 }
365 if (clone is Element && clone.isTemplate) { 393 doc._templateContentsOwner = d;
366 TemplateElement.decorate(clone, node); 394 }
367 if (syntax != null) { 395 return d;
368 clone.attributes.putIfAbsent('syntax', () => syntax); 396 }
369 } 397
370 } 398 static Element _cloneAndSeperateAttributeTemplate(Element templateElement) {
371 399 var clone = templateElement.clone(false);
372 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { 400 var attributes = templateElement.attributes;
373 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); 401 for (var name in attributes.keys.toList()) {
374 } 402 switch (name) {
375 return clone; 403 case 'template':
376 } 404 case 'repeat':
377 405 case 'bind':
378 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#df n-template-contents-owner 406 case 'ref':
379 Document _getTemplateContentsOwner(Document doc) { 407 clone.attributes.remove(name);
380 if (doc.window == null) { 408 break;
381 return doc; 409 default:
382 } 410 attributes.remove(name);
383 var d = doc._templateContentsOwner; 411 break;
384 if (d == null) { 412 }
385 // TODO(arv): This should either be a Document or HTMLDocument depending 413 }
386 // on doc. 414
387 d = doc.implementation.createHtmlDocument(''); 415 return clone;
388 while (d.$dom_lastChild != null) { 416 }
389 d.$dom_lastChild.remove(); 417
390 } 418 static void _liftNonNativeChildrenIntoContent(Element templateElement) {
391 doc._templateContentsOwner = d; 419 var content = templateElement.content;
392 } 420
393 return d; 421 if (!templateElement._isAttributeTemplate) {
394 } 422 var child;
395 423 while ((child = templateElement.$dom_firstChild) != null) {
396 Element _cloneAndSeperateAttributeTemplate(Element templateElement) { 424 content.append(child);
397 var clone = templateElement.clone(false); 425 }
398 var attributes = templateElement.attributes; 426 return;
399 for (var name in attributes.keys.toList()) { 427 }
400 switch (name) { 428
401 case 'template': 429 // For attribute templates we copy the whole thing into the content and
402 case 'repeat': 430 // we move the non template attributes into the content.
403 case 'bind': 431 //
404 case 'ref': 432 // <tr foo template>
405 clone.attributes.remove(name); 433 //
406 break; 434 // becomes
407 default: 435 //
408 attributes.remove(name); 436 // <tr template>
409 break; 437 // + #document-fragment
410 } 438 // + <tr foo>
411 } 439 //
412 440 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement);
413 return clone;
414 }
415
416 void _liftNonNativeTemplateChildrenIntoContent(Element templateElement) {
417 var content = templateElement.content;
418
419 if (!templateElement._isAttributeTemplate) {
420 var child; 441 var child;
421 while ((child = templateElement.$dom_firstChild) != null) { 442 while ((child = templateElement.$dom_firstChild) != null) {
422 content.append(child); 443 newRoot.append(child);
423 } 444 }
424 return; 445 content.append(newRoot);
425 } 446 }
426 447
427 // For attribute templates we copy the whole thing into the content and 448 static void _bootstrapTemplatesRecursivelyFrom(Node node) {
428 // we move the non template attributes into the content. 449 void bootstrap(template) {
429 // 450 if (!TemplateElement.decorate(template)) {
430 // <tr foo template> 451 _bootstrapTemplatesRecursivelyFrom(template.content);
431 // 452 }
432 // becomes 453 }
433 // 454
434 // <tr template> 455 // Need to do this first as the contents may get lifted if |node| is
435 // + #document-fragment 456 // template.
436 // + <tr foo> 457 // TODO(jmesserly): node is DocumentFragment or Element
437 // 458 var descendents = (node as dynamic).queryAll(_allTemplatesSelectors);
438 var newRoot = _cloneAndSeperateAttributeTemplate(templateElement); 459 if (node is Element && node.isTemplate) bootstrap(node);
439 var child; 460
440 while ((child = templateElement.$dom_firstChild) != null) { 461 descendents.forEach(bootstrap);
441 newRoot.append(child); 462 }
442 } 463
443 content.append(newRoot); 464 static final String _allTemplatesSelectors = 'template, option[template], ' +
444 } 465 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", ");
445 466
446 void _bootstrapTemplatesRecursivelyFrom(Node node) { 467 static void _addBindings(Node node, model, [CustomBindingSyntax syntax]) {
447 void bootstrap(template) { 468 if (node is Element) {
448 if (!TemplateElement.decorate(template)) { 469 _addAttributeBindings(node, model, syntax);
449 _bootstrapTemplatesRecursivelyFrom(template.content); 470 } else if (node is Text) {
450 } 471 _parseAndBind(node, 'text', node.text, model, syntax);
451 } 472 }
452 473
453 // Need to do this first as the contents may get lifted if |node| is 474 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
454 // template. 475 _addBindings(c, model, syntax);
455 // TODO(jmesserly): node is DocumentFragment or Element 476 }
456 var templateDescendents = (node as dynamic).queryAll(_allTemplatesSelectors); 477 }
457 if (node is Element && node.isTemplate) bootstrap(node); 478
458 479 static void _addAttributeBindings(Element element, model, syntax) {
459 templateDescendents.forEach(bootstrap); 480 element.attributes.forEach((name, value) {
460 } 481 if (value == '' && (name == 'bind' || name == 'repeat')) {
461 482 value = '{{}}';
462 final String _allTemplatesSelectors = 'template, option[template], ' + 483 }
463 Element._TABLE_TAGS.keys.map((k) => "$k[template]").join(", "); 484 _parseAndBind(element, name, value, model, syntax);
464 485 });
465 void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { 486 }
466 if (node is Element) { 487
467 _addAttributeBindings(node, model, syntax); 488 static void _parseAndBind(Node node, String name, String text, model,
468 } else if (node is Text) { 489 CustomBindingSyntax syntax) {
469 _parseAndBind(node, 'text', node.text, model, syntax); 490
470 } 491 var tokens = _parseMustacheTokens(text);
471 492 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
472 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) { 493 return;
473 _addBindings(c, model, syntax); 494 }
474 } 495
475 } 496 if (tokens.length == 1 && tokens[0].isBinding) {
476 497 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
477 498 return;
478 void _addAttributeBindings(Element element, model, syntax) { 499 }
479 element.attributes.forEach((name, value) { 500
480 if (value == '' && (name == 'bind' || name == 'repeat')) { 501 var replacementBinding = new CompoundBinding();
481 value = '{{}}';
482 }
483 _parseAndBind(element, name, value, model, syntax);
484 });
485 }
486
487 void _parseAndBind(Node node, String name, String text, model,
488 CustomBindingSyntax syntax) {
489
490 var tokens = _parseMustacheTokens(text);
491 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
492 return;
493 }
494
495 if (tokens.length == 1 && tokens[0].isBinding) {
496 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
497 return;
498 }
499
500 var replacementBinding = new CompoundBinding();
501 for (var i = 0; i < tokens.length; i++) {
502 var token = tokens[i];
503 if (token.isBinding) {
504 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
505 }
506 }
507
508 replacementBinding.combinator = (values) {
509 var newValue = new StringBuffer();
510
511 for (var i = 0; i < tokens.length; i++) { 502 for (var i = 0; i < tokens.length; i++) {
512 var token = tokens[i]; 503 var token = tokens[i];
513 if (token.isText) { 504 if (token.isBinding) {
514 newValue.write(token.value); 505 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
506 }
507 }
508
509 replacementBinding.combinator = (values) {
510 var newValue = new StringBuffer();
511
512 for (var i = 0; i < tokens.length; i++) {
513 var token = tokens[i];
514 if (token.isText) {
515 newValue.write(token.value);
516 } else {
517 var value = values[i];
518 if (value != null) {
519 newValue.write(value);
520 }
521 }
522 }
523
524 return newValue.toString();
525 };
526
527 _nodeOrCustom(node).bind(name, replacementBinding, 'value');
528 }
529
530 static void _bindOrDelegate(node, name, model, String path,
531 CustomBindingSyntax syntax) {
532
533 if (syntax != null) {
534 var delegateBinding = syntax.getBinding(model, path, name, node);
535 if (delegateBinding != null) {
536 model = delegateBinding;
537 path = 'value';
538 }
539 }
540
541 _nodeOrCustom(node).bind(name, model, path);
542 }
543
544 /**
545 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
546 * the node. This is used so nodes can override [Node.bind], [Node.unbind],
547 * and [Node.unbindAll] like InputElement does.
548 */
549 // TODO(jmesserly): remove this when we can extend Element for real.
550 static _nodeOrCustom(node) => node is Element ? node.xtag : node;
551
552 static List<_BindingToken> _parseMustacheTokens(String s) {
553 var result = [];
554 var length = s.length;
555 var index = 0, lastIndex = 0;
556 while (lastIndex < length) {
557 index = s.indexOf('{{', lastIndex);
558 if (index < 0) {
559 result.add(new _BindingToken(s.substring(lastIndex)));
560 break;
515 } else { 561 } else {
516 var value = values[i]; 562 // There is a non-empty text run before the next path token.
517 if (value != null) { 563 if (index > 0 && lastIndex < index) {
518 newValue.write(value); 564 result.add(new _BindingToken(s.substring(lastIndex, index)));
519 } 565 }
520 } 566 lastIndex = index + 2;
521 } 567 index = s.indexOf('}}', lastIndex);
522 568 if (index < 0) {
523 return newValue.toString(); 569 var text = s.substring(lastIndex - 2);
524 }; 570 if (result.length > 0 && result.last.isText) {
525 571 result.last.value += text;
526 _nodeOrCustom(node).bind(name, replacementBinding, 'value'); 572 } else {
573 result.add(new _BindingToken(text));
574 }
575 break;
576 }
577
578 var value = s.substring(lastIndex, index).trim();
579 result.add(new _BindingToken(value, isBinding: true));
580 lastIndex = index + 2;
581 }
582 }
583 return result;
584 }
585
586 static void _addTemplateInstanceRecord(fragment, model) {
587 if (fragment.$dom_firstChild == null) {
588 return;
589 }
590
591 var instanceRecord = new TemplateInstance(
592 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
593
594 var node = instanceRecord.firstNode;
595 while (node != null) {
596 node._templateInstance = instanceRecord;
597 node = node.nextNode;
598 }
599 }
600
601 static void _removeAllBindingsRecursively(Node node) {
602 _nodeOrCustom(node).unbindAll();
603 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
604 _removeAllBindingsRecursively(c);
605 }
606 }
607
608 static void _removeChild(Node parent, Node child) {
609 child._templateInstance = null;
610 if (child is Element && child.isTemplate) {
611 // Make sure we stop observing when we remove an element.
612 var templateIterator = child._templateIterator;
613 if (templateIterator != null) {
614 templateIterator.abandon();
615 child._templateIterator = null;
616 }
617 }
618 child.remove();
619 _removeAllBindingsRecursively(child);
620 }
527 } 621 }
528 622
529 void _bindOrDelegate(node, name, model, String path,
530 CustomBindingSyntax syntax) {
531
532 if (syntax != null) {
533 var delegateBinding = syntax.getBinding(model, path, name, node);
534 if (delegateBinding != null) {
535 model = delegateBinding;
536 path = 'value';
537 }
538 }
539
540 _nodeOrCustom(node).bind(name, model, path);
541 }
542
543 /**
544 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
545 * the node. This is used so nodes can override [Node.bind], [Node.unbind],
546 * and [Node.unbindAll] like InputElement does.
547 */
548 // TODO(jmesserly): remove this when we can extend Element for real.
549 _nodeOrCustom(node) => node is Element ? node.xtag : node;
550
551 class _BindingToken { 623 class _BindingToken {
552 final String value; 624 final String value;
553 final bool isBinding; 625 final bool isBinding;
554 626
555 _BindingToken(this.value, {this.isBinding: false}); 627 _BindingToken(this.value, {this.isBinding: false});
556 628
557 bool get isText => !isBinding; 629 bool get isText => !isBinding;
558 } 630 }
559 631
560 List<_BindingToken> _parseMustacheTokens(String s) {
561 var result = [];
562 var length = s.length;
563 var index = 0, lastIndex = 0;
564 while (lastIndex < length) {
565 index = s.indexOf('{{', lastIndex);
566 if (index < 0) {
567 result.add(new _BindingToken(s.substring(lastIndex)));
568 break;
569 } else {
570 // There is a non-empty text run before the next path token.
571 if (index > 0 && lastIndex < index) {
572 result.add(new _BindingToken(s.substring(lastIndex, index)));
573 }
574 lastIndex = index + 2;
575 index = s.indexOf('}}', lastIndex);
576 if (index < 0) {
577 var text = s.substring(lastIndex - 2);
578 if (result.length > 0 && result.last.isText) {
579 result.last.value += text;
580 } else {
581 result.add(new _BindingToken(text));
582 }
583 break;
584 }
585
586 var value = s.substring(lastIndex, index).trim();
587 result.add(new _BindingToken(value, isBinding: true));
588 lastIndex = index + 2;
589 }
590 }
591 return result;
592 }
593
594 void _addTemplateInstanceRecord(fragment, model) {
595 if (fragment.$dom_firstChild == null) {
596 return;
597 }
598
599 var instanceRecord = new TemplateInstance(
600 fragment.$dom_firstChild, fragment.$dom_lastChild, model);
601
602 var node = instanceRecord.firstNode;
603 while (node != null) {
604 node._templateInstance = instanceRecord;
605 node = node.nextNode;
606 }
607 }
608
609 void _removeAllBindingsRecursively(Node node) {
610 _nodeOrCustom(node).unbindAll();
611 for (var c = node.$dom_firstChild; c != null; c = c.nextNode) {
612 _removeAllBindingsRecursively(c);
613 }
614 }
615
616 void _removeTemplateChild(Node parent, Node child) {
617 child._templateInstance = null;
618 if (child is Element && child.isTemplate) {
619 // Make sure we stop observing when we remove an element.
620 var templateIterator = child._templateIterator;
621 if (templateIterator != null) {
622 templateIterator.abandon();
623 child._templateIterator = null;
624 }
625 }
626 child.remove();
627 _removeAllBindingsRecursively(child);
628 }
629
630
631 class _TemplateIterator { 632 class _TemplateIterator {
632 final Element _templateElement; 633 final Element _templateElement;
633 final List<Node> terminators = []; 634 final List<Node> terminators = [];
634 final CompoundBinding inputs; 635 final CompoundBinding inputs;
635 List iteratedValue; 636 List iteratedValue;
636 637
637 StreamSubscription _sub; 638 StreamSubscription _sub;
638 StreamSubscription _valueBinding; 639 StreamSubscription _valueBinding;
639 640
640 _TemplateIterator(this._templateElement) 641 _TemplateIterator(this._templateElement)
641 : inputs = new CompoundBinding(resolveInputs) { 642 : inputs = new CompoundBinding(resolveInputs) {
642 643
643 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); 644 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
644 } 645 }
645 646
646 static Object resolveInputs(Map values) { 647 static Object resolveInputs(Map values) {
647 if (values.containsKey('if') && !_templateBooleanConversion(values['if'])) { 648 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) {
648 return null; 649 return null;
649 } 650 }
650 651
651 if (values.containsKey('repeat')) { 652 if (values.containsKey('repeat')) {
652 return values['repeat']; 653 return values['repeat'];
653 } 654 }
654 655
655 if (values.containsKey('bind')) { 656 if (values.containsKey('bind')) {
656 return [values['bind']]; 657 return [values['bind']];
657 } 658 }
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
698 699
699 void removeInstanceAt(int index) { 700 void removeInstanceAt(int index) {
700 var previousTerminator = getTerminatorAt(index - 1); 701 var previousTerminator = getTerminatorAt(index - 1);
701 var terminator = getTerminatorAt(index); 702 var terminator = getTerminatorAt(index);
702 terminators.removeAt(index); 703 terminators.removeAt(index);
703 704
704 var parent = _templateElement.parentNode; 705 var parent = _templateElement.parentNode;
705 while (terminator != previousTerminator) { 706 while (terminator != previousTerminator) {
706 var node = terminator; 707 var node = terminator;
707 terminator = node.previousNode; 708 terminator = node.previousNode;
708 _removeTemplateChild(parent, node); 709 _Bindings._removeChild(parent, node);
709 } 710 }
710 } 711 }
711 712
712 void removeAllInstances() { 713 void removeAllInstances() {
713 if (terminators.length == 0) return; 714 if (terminators.length == 0) return;
714 715
715 var previousTerminator = _templateElement; 716 var previousTerminator = _templateElement;
716 var terminator = getTerminatorAt(terminators.length - 1); 717 var terminator = getTerminatorAt(terminators.length - 1);
717 terminators.length = 0; 718 terminators.length = 0;
718 719
719 var parent = _templateElement.parentNode; 720 var parent = _templateElement.parentNode;
720 while (terminator != previousTerminator) { 721 while (terminator != previousTerminator) {
721 var node = terminator; 722 var node = terminator;
722 terminator = node.previousNode; 723 terminator = node.previousNode;
723 _removeTemplateChild(parent, node); 724 _Bindings._removeChild(parent, node);
724 } 725 }
725 } 726 }
726 727
727 void clear() { 728 void clear() {
728 unobserve(); 729 unobserve();
729 removeAllInstances(); 730 removeAllInstances();
730 iteratedValue = null; 731 iteratedValue = null;
731 } 732 }
732 733
733 getInstanceModel(model, syntax) { 734 getInstanceModel(model, syntax) {
(...skipping 21 matching lines...) Expand all
755 } 756 }
756 757
757 for (var addIndex = splice.index; 758 for (var addIndex = splice.index;
758 addIndex < splice.index + splice.addedCount; 759 addIndex < splice.index + splice.addedCount;
759 addIndex++) { 760 addIndex++) {
760 761
761 var model = getInstanceModel(iteratedValue[addIndex], syntax); 762 var model = getInstanceModel(iteratedValue[addIndex], syntax);
762 763
763 var fragment = getInstanceFragment(syntax); 764 var fragment = getInstanceFragment(syntax);
764 765
765 _addBindings(fragment, model, syntax); 766 _Bindings._addBindings(fragment, model, syntax);
766 _addTemplateInstanceRecord(fragment, model); 767 _Bindings._addTemplateInstanceRecord(fragment, model);
767 768
768 insertInstanceAt(addIndex, fragment); 769 insertInstanceAt(addIndex, fragment);
769 } 770 }
770 } 771 }
771 } 772 }
772 773
773 void unobserve() { 774 void unobserve() {
774 if (_sub == null) return; 775 if (_sub == null) return;
775 _sub.cancel(); 776 _sub.cancel();
776 _sub = null; 777 _sub = null;
777 } 778 }
778 779
779 void abandon() { 780 void abandon() {
780 unobserve(); 781 unobserve();
781 _valueBinding.cancel(); 782 _valueBinding.cancel();
782 inputs.dispose(); 783 inputs.dispose();
783 } 784 }
784 } 785 }
OLDNEW
« no previous file with comments | « sdk/lib/html/dartium/html_dartium.dart ('k') | tools/dom/templates/html/impl/impl_Element.darttemplate » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698