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

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

Powered by Google App Engine
This is Rietveld 408576698