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

Side by Side Diff: lib/templating.dart

Issue 12225039: Support for observable models, fixes #259 (Closed) Base URL: https://github.com/dart-lang/web-ui.git@master
Patch Set: Created 7 years, 10 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
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 /** Common utility functions used by code generated by the dwc compiler. */ 5 /** Common utility functions used by code generated by the dwc compiler. */
6 library templating; 6 library templating;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:collection';
9 import 'dart:html'; 10 import 'dart:html';
10 import 'dart:uri'; 11 import 'dart:uri';
11 import 'package:web_ui/safe_html.dart'; 12 import 'package:web_ui/safe_html.dart';
13 import 'package:web_ui/observe.dart';
12 import 'package:web_ui/watcher.dart'; 14 import 'package:web_ui/watcher.dart';
15 export 'src/utils.dart' show setImmediate;
Siggi Cherem (dart-lang) 2013/02/13 19:28:54 unused import
Jennifer Messerly 2013/02/14 00:38:09 It's an export, not an import :) ...but not import
13 16
14 /** 17 /**
15 * Take the value of a bound expression and creates an HTML node with its value. 18 * Take the value of a bound expression and creates an HTML node with its value.
16 * Normally bindings are associated with text nodes, unless [binding] has the 19 * Normally bindings are associated with text nodes, unless [binding] has the
17 * [SafeHtml] type, in which case an html element is created for it. 20 * [SafeHtml] type, in which case an html element is created for it.
18 */ 21 */
19 Node nodeForBinding(binding) => binding is SafeHtml 22 Node nodeForBinding(binding) => binding is SafeHtml
20 ? new Element.html(binding.toString()) : new Text(binding.toString()); 23 ? new Element.html(binding.toString()) : new Text(binding.toString());
21 24
22 /** 25 /**
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 * * binding classes with a string: 132 * * binding classes with a string:
130 * 133 *
131 * bindCssClasses(e, () => "${class1 != null ? class1 : ''} " 134 * bindCssClasses(e, () => "${class1 != null ? class1 : ''} "
132 * "${class2 != null ? class2 : ''}"); 135 * "${class2 != null ? class2 : ''}");
133 * 136 *
134 * * binding classes separately: 137 * * binding classes separately:
135 * 138 *
136 * bindCssClasses(e, () => class1); 139 * bindCssClasses(e, () => class1);
137 * bindCssClasses(e, () => class2); 140 * bindCssClasses(e, () => class2);
138 */ 141 */
139 WatcherDisposer bindCssClasses(Element elem, dynamic exp()) { 142 ChangeUnobserver bindCssClasses(Element elem, dynamic exp()) {
140 return watchAndInvoke(exp, (e) { 143 return watchAndInvoke(_observeList(exp), (e) {
141 updateCssClass(elem, false, e.oldValue); 144 updateCssClass(elem, false, e.oldValue);
142 updateCssClass(elem, true, e.newValue); 145 updateCssClass(elem, true, e.newValue);
143 }, 'css-class-bind'); 146 }, 'css-class-bind');
144 } 147 }
145 148
146 /** Bind the result of [exp] to the style attribute in [elem]. */ 149 /** Bind the result of [exp] to the style attribute in [elem]. */
147 WatcherDisposer bindStyle(Element elem, Map<String, String> exp()) { 150 ChangeUnobserver bindStyle(Element elem, Map<String, String> exp()) {
148 return watchAndInvoke(exp, (e) { 151 return watchAndInvoke(_observeMap(exp), (e) {
149 if (e.oldValue is Map<String, String>) { 152 if (e.oldValue is Map<String, String>) {
150 var props = e.newValue; 153 var props = e.newValue;
151 if (props is! Map<String, String>) props = const {}; 154 if (props is! Map<String, String>) props = const {};
152 for (var property in e.oldValue.keys) { 155 for (var property in e.oldValue.keys) {
153 if (!props.containsKey(property)) { 156 if (!props.containsKey(property)) {
154 // Value will not be overwritten with new setting. Remove. 157 // Value will not be overwritten with new setting. Remove.
155 elem.style.removeProperty(property); 158 elem.style.removeProperty(property);
156 } 159 }
157 } 160 }
158 } 161 }
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
226 229
227 void remove() { 230 void remove() {
228 _subscription.cancel(); 231 _subscription.cancel();
229 _subscription = null; 232 _subscription = null;
230 } 233 }
231 } 234 }
232 235
233 /** Represents a generic data binding and a corresponding action. */ 236 /** Represents a generic data binding and a corresponding action. */
234 class Binding extends TemplateItem { 237 class Binding extends TemplateItem {
235 final exp; 238 final exp;
236 final ValueWatcher action; 239 final ChangeObserver action;
237 WatcherDisposer stopper; 240 ChangeUnobserver stopper;
238 241
239 Binding(this.exp, this.action); 242 Binding(this.exp, this.action);
240 243
241 void insert() { 244 void insert() {
242 if (stopper != null) throw new StateError('binding already attached'); 245 if (stopper != null) throw new StateError('binding already attached');
243 stopper = watchAndInvoke(exp, action, 'generic-binding'); 246 stopper = watchAndInvoke(exp, action, 'generic-binding');
244 } 247 }
245 248
246 void remove() { 249 void remove() {
247 stopper(); 250 stopper();
248 stopper = null; 251 stopper = null;
249 } 252 }
250 } 253 }
251 254
252 /** Represents a binding to a style attribute. */ 255 /** Represents a binding to a style attribute. */
253 class StyleAttrBinding extends TemplateItem { 256 class StyleAttrBinding extends TemplateItem {
254 final exp; 257 final exp;
255 final Element elem; 258 final Element elem;
256 WatcherDisposer stopper; 259 ChangeUnobserver stopper;
257 260
258 StyleAttrBinding(this.elem, this.exp); 261 StyleAttrBinding(this.elem, this.exp);
259 262
260 void insert() { 263 void insert() {
261 if (stopper != null) throw new StateError('style binding already attached'); 264 if (stopper != null) throw new StateError('style binding already attached');
262 stopper = bindStyle(elem, exp); 265 stopper = bindStyle(elem, exp);
263 } 266 }
264 267
265 void remove() { 268 void remove() {
266 stopper(); 269 stopper();
267 stopper = null; 270 stopper = null;
268 } 271 }
269 } 272 }
270 273
271 /** Represents a binding to a class attribute. */ 274 /** Represents a binding to a class attribute. */
272 class ClassAttrBinding extends TemplateItem { 275 class ClassAttrBinding extends TemplateItem {
273 final Element elem; 276 final Element elem;
274 final exp; 277 final exp;
275 WatcherDisposer stopper; 278 ChangeUnobserver stopper;
276 279
277 ClassAttrBinding(this.elem, this.exp); 280 ClassAttrBinding(this.elem, this.exp);
278 281
279 void insert() { 282 void insert() {
280 if (stopper != null) throw new StateError('class binding already attached'); 283 if (stopper != null) throw new StateError('class binding already attached');
281 stopper = bindCssClasses(elem, exp); 284 stopper = bindCssClasses(elem, exp);
282 } 285 }
283 286
284 void remove() { 287 void remove() {
285 stopper(); 288 stopper();
(...skipping 14 matching lines...) Expand all
300 * or from a DOM property (which is internally also a Dart expression). 303 * or from a DOM property (which is internally also a Dart expression).
301 */ 304 */
302 final Getter getter; 305 final Getter getter;
303 306
304 /** 307 /**
305 * Whether this is a binding that assigns a DOM attribute accepting URL 308 * Whether this is a binding that assigns a DOM attribute accepting URL
306 * values. If so, the value assigned to the attribute needs to be sanitized. 309 * values. If so, the value assigned to the attribute needs to be sanitized.
307 */ 310 */
308 final bool isUrl; 311 final bool isUrl;
309 312
310 WatcherDisposer stopper; 313 ChangeUnobserver stopper;
311 314
312 DomPropertyBinding(this.getter, this.setter, this.isUrl); 315 DomPropertyBinding(this.getter, this.setter, this.isUrl);
313 316
314 void insert() { 317 void insert() {
315 if (stopper != null) throw new StateError('data binding already attached.'); 318 if (stopper != null) throw new StateError('data binding already attached.');
316 stopper = watchAndInvoke(getter, (e) { 319 stopper = watchAndInvoke(getter, (e) {
317 setter(isUrl ? sanitizeUri(e.newValue) : e.newValue); 320 setter(isUrl ? sanitizeUri(e.newValue) : e.newValue);
318 }, 'dom-property-binding'); 321 }, 'dom-property-binding');
319 } 322 }
320 323
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 final List<Node> nodes = []; 367 final List<Node> nodes = [];
365 368
366 Template(this.node); 369 Template(this.node);
367 370
368 /** Associate the event listener while this template is visible. */ 371 /** Associate the event listener while this template is visible. */
369 void listen(Stream<Event> stream, EventListener listener) { 372 void listen(Stream<Event> stream, EventListener listener) {
370 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); 373 children.add(new Listener(stream, (e) { listener(e); dispatch(); }));
371 } 374 }
372 375
373 /** Run [action] when [exp] changes (while this template is visible). */ 376 /** Run [action] when [exp] changes (while this template is visible). */
374 void bind(exp, ValueWatcher action) { 377 void bind(exp, ChangeObserver action) {
375 children.add(new Binding(exp, action)); 378 children.add(new Binding(exp, action));
376 } 379 }
377 380
378 /** Create and bind a [Node] to [exp] while this template is visible. */ 381 /** Create and bind a [Node] to [exp] while this template is visible. */
379 Node contentBind(Function exp) { 382 Node contentBind(Function exp) {
380 var bindNode = new Text(''); 383 var bindNode = new Text('');
381 children.add(new Binding(() => '${exp()}', (e) { 384 children.add(new Binding(() => '${exp()}', (e) {
382 bindNode = updateBinding(exp(), bindNode, e.newValue); 385 bindNode = updateBinding(exp(), bindNode, e.newValue);
383 })); 386 }));
384 return bindNode; 387 return bindNode;
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
469 // </tr> 472 // </tr>
470 // 473 //
471 // We can't necessarily rely on child position because of possible mutation, 474 // We can't necessarily rely on child position because of possible mutation,
472 // unless we're willing to say that "if" requires a fixed number of children. 475 // unless we're willing to say that "if" requires a fixed number of children.
473 // If that's the case, we need a way to check for this error case and alert the 476 // If that's the case, we need a way to check for this error case and alert the
474 // developer. 477 // developer.
475 abstract class PlaceholderTemplate extends Template { 478 abstract class PlaceholderTemplate extends Template {
476 /** Expression watch by this template (condition or loop expression). */ 479 /** Expression watch by this template (condition or loop expression). */
477 final exp; 480 final exp;
478 481
479 WatcherDisposer stopper; 482 ChangeUnobserver stopper;
480 483
481 PlaceholderTemplate(Node reference, this.exp) 484 PlaceholderTemplate(Node reference, this.exp)
482 : super(reference); 485 : super(reference);
483 486
484 // Delay creating the template body until this is inserted. 487 // Delay creating the template body until this is inserted.
485 void create() {} 488 void create() {}
486 489
487 void insert() { 490 void insert() {
488 super.create(); 491 super.create();
489 if (nodes.length > 0) { 492 if (nodes.length > 0) {
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 }, 'conditional-binding'); 540 }, 'conditional-binding');
538 } 541 }
539 542
540 void remove() { 543 void remove() {
541 super.remove(); 544 super.remove();
542 stopper(); 545 stopper();
543 stopper = null; 546 stopper = null;
544 } 547 }
545 } 548 }
546 549
550 _observeList(exp) {
551 if (!useObservers) return exp;
552
553 // TODO(jmesserly): implement detailed change records in Observable* types,
554 // so we can observe the list itself and react to all changes.
555 // For now we call .toList which causes us to depend on all items. This
556 // also works for Iterables.
557 return () {
558 var x = exp();
559 return x is Iterable ? x.toList() : x;
560 };
561 }
562
563 _observeMap(exp) {
564 if (!useObservers) return exp;
565
566 // TODO(jmesserly): this has similar issues as _observeList. Ideally
567 // observers can observe all changes to the resulting map, so we don't need a
568 // copy here.
569 return () {
570 var x = exp();
571 return x is Map ? new LinkedHashMap.from(x) : x;
572 };
573 }
574
547 /** Function to set up the contents of a loop template. */ 575 /** Function to set up the contents of a loop template. */
548 typedef void LoopIterationSetup(loopVariable, Template template); 576 typedef void LoopIterationSetup(loopVariable, Template template);
549 577
550 /** A template loop of the form `<template iterate="x in list ">`. */ 578 /** A template loop of the form `<template iterate="x in list ">`. */
551 class LoopTemplate extends PlaceholderTemplate { 579 class LoopTemplate extends PlaceholderTemplate {
552 final LoopIterationSetup iterSetup; 580 final LoopIterationSetup iterSetup;
553 581
554 LoopTemplate(Node reference, exp, this.iterSetup) : super(reference, exp); 582 LoopTemplate(Node reference, exp, this.iterSetup) : super(reference, exp);
555 583
556 void insert() { 584 void insert() {
557 stopper = watchAndInvoke(exp, (e) { 585 stopper = watchAndInvoke(_observeList(exp), (e) {
558 super.remove(); 586 super.remove();
559 for (var x in e.newValue) { 587 for (var x in e.newValue) {
560 iterSetup(x, this); 588 iterSetup(x, this);
561 } 589 }
562 super.insert(); 590 super.insert();
563 }, 'loop-binding'); 591 }, 'loop-binding');
564 } 592 }
565 593
566 void remove() { 594 void remove() {
567 super.remove(); 595 super.remove();
568 stopper(); 596 stopper();
569 stopper = null; 597 stopper = null;
570 } 598 }
571 } 599 }
572 600
573 /** 601 /**
574 * A template loop of the form `<td template iterate="x in list ">`. Unlike 602 * A template loop of the form `<td template iterate="x in list ">`. Unlike
575 * [LoopTemplate], here we insert children directly then node annotated with the 603 * [LoopTemplate], here we insert children directly then node annotated with the
576 * template attribute. 604 * template attribute.
577 */ 605 */
578 class LoopTemplateInAttribute extends Template { 606 class LoopTemplateInAttribute extends Template {
579 final LoopIterationSetup iterSetup; 607 final LoopIterationSetup iterSetup;
580 final exp; 608 final exp;
581 WatcherDisposer stopper; 609 ChangeUnobserver stopper;
582 610
583 LoopTemplateInAttribute(Node node, this.exp, this.iterSetup) : super(node); 611 LoopTemplateInAttribute(Node node, this.exp, this.iterSetup) : super(node);
584 612
585 // Delay creating the template body until this is inserted. 613 // Delay creating the template body until this is inserted.
586 void create() {} 614 void create() {}
587 615
588 void insert() { 616 void insert() {
589 stopper = watchAndInvoke(exp, (e) { 617 stopper = watchAndInvoke(_observeList(exp), (e) {
590 _removeInternal(); 618 _removeInternal();
591 for (var x in e.newValue) { 619 for (var x in e.newValue) {
592 iterSetup(x, this); 620 iterSetup(x, this);
593 } 621 }
594 super.create(); 622 super.create();
595 node.nodes.addAll(nodes); 623 node.nodes.addAll(nodes);
596 super.insert(); 624 super.insert();
597 }, 'loop-attribute-binding'); 625 }, 'loop-attribute-binding');
598 } 626 }
599 627
600 void _removeInternal() { 628 void _removeInternal() {
601 super.remove(); 629 super.remove();
602 node.nodes.clear(); 630 node.nodes.clear();
603 nodes.clear(); 631 nodes.clear();
604 } 632 }
605 633
606 void remove() { 634 void remove() {
607 _removeInternal(); 635 _removeInternal();
608 stopper(); 636 stopper();
609 stopper = null; 637 stopper = null;
610 } 638 }
611 } 639 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698