OLD | NEW |
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:html'; | 9 import 'dart:html'; |
10 import 'dart:uri'; | 10 import 'dart:uri'; |
11 import 'package:web_ui/safe_html.dart'; | 11 import 'package:web_ui/safe_html.dart'; |
12 import 'package:web_ui/watcher.dart'; | 12 import 'package:web_ui/watcher.dart'; |
| 13 import 'package:web_ui/observe.dart'; |
| 14 export 'src/utils.dart' show setImmediate; |
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 |
22 /** | 24 /** |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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(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 }); | 145 }); |
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(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 } |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
230 | 232 |
231 void remove() { | 233 void remove() { |
232 _subscription.cancel(); | 234 _subscription.cancel(); |
233 _subscription = null; | 235 _subscription = null; |
234 } | 236 } |
235 } | 237 } |
236 | 238 |
237 /** Represents a generic data binding and a corresponding action. */ | 239 /** Represents a generic data binding and a corresponding action. */ |
238 class Binding extends TemplateItem { | 240 class Binding extends TemplateItem { |
239 final exp; | 241 final exp; |
240 final ValueWatcher action; | 242 final ChangeObserver action; |
241 WatcherDisposer stopper; | 243 ChangeUnobserver stopper; |
242 | 244 |
243 Binding(this.exp, this.action); | 245 Binding(this.exp, this.action); |
244 | 246 |
245 void create() {} | 247 void create() {} |
246 void insert() { | 248 void insert() { |
247 if (stopper != null) throw new StateError('binding already attached'); | 249 if (stopper != null) throw new StateError('binding already attached'); |
248 stopper = watchAndInvoke(exp, action); | 250 stopper = watchAndInvoke(exp, action); |
249 } | 251 } |
250 void remove() { | 252 void remove() { |
251 stopper(); | 253 stopper(); |
252 stopper = null; | 254 stopper = null; |
253 } | 255 } |
254 } | 256 } |
255 | 257 |
256 /** Represents a binding to a style attribute. */ | 258 /** Represents a binding to a style attribute. */ |
257 class StyleAttrBinding extends TemplateItem { | 259 class StyleAttrBinding extends TemplateItem { |
258 final exp; | 260 final exp; |
259 final Element elem; | 261 final Element elem; |
260 WatcherDisposer stopper; | 262 ChangeUnobserver stopper; |
261 | 263 |
262 StyleAttrBinding(this.elem, this.exp); | 264 StyleAttrBinding(this.elem, this.exp); |
263 | 265 |
264 void create() {} | 266 void create() {} |
265 void insert() { | 267 void insert() { |
266 if (stopper != null) throw new StateError('style binding already attached'); | 268 if (stopper != null) throw new StateError('style binding already attached'); |
267 stopper = bindStyle(elem, exp); | 269 stopper = bindStyle(elem, exp); |
268 } | 270 } |
269 void remove() { | 271 void remove() { |
270 stopper(); | 272 stopper(); |
271 stopper = null; | 273 stopper = null; |
272 } | 274 } |
273 } | 275 } |
274 | 276 |
275 /** Represents a binding to a class attribute. */ | 277 /** Represents a binding to a class attribute. */ |
276 class ClassAttrBinding extends TemplateItem { | 278 class ClassAttrBinding extends TemplateItem { |
277 final Element elem; | 279 final Element elem; |
278 final exp; | 280 final exp; |
279 WatcherDisposer stopper; | 281 ChangeUnobserver stopper; |
280 | 282 |
281 ClassAttrBinding(this.elem, this.exp); | 283 ClassAttrBinding(this.elem, this.exp); |
282 | 284 |
283 void create() {} | 285 void create() {} |
284 void insert() { | 286 void insert() { |
285 if (stopper != null) throw new StateError('class binding already attached'); | 287 if (stopper != null) throw new StateError('class binding already attached'); |
286 stopper = bindCssClasses(elem, exp); | 288 stopper = bindCssClasses(elem, exp); |
287 } | 289 } |
288 void remove() { | 290 void remove() { |
289 stopper(); | 291 stopper(); |
(...skipping 14 matching lines...) Expand all Loading... |
304 * or from a DOM property (which is internally also a Dart expression). | 306 * or from a DOM property (which is internally also a Dart expression). |
305 */ | 307 */ |
306 final Getter getter; | 308 final Getter getter; |
307 | 309 |
308 /** | 310 /** |
309 * Whether this is a binding that assigns a DOM attribute accepting URL | 311 * Whether this is a binding that assigns a DOM attribute accepting URL |
310 * values. If so, the value assigned to the attribute needs to be sanitized. | 312 * values. If so, the value assigned to the attribute needs to be sanitized. |
311 */ | 313 */ |
312 final bool isUrl; | 314 final bool isUrl; |
313 | 315 |
314 WatcherDisposer stopper; | 316 ChangeUnobserver stopper; |
315 | 317 |
316 DomPropertyBinding(this.getter, this.setter, this.isUrl); | 318 DomPropertyBinding(this.getter, this.setter, this.isUrl); |
317 | 319 |
318 void create() {} | 320 void create() {} |
319 void insert() { | 321 void insert() { |
320 if (stopper != null) throw new StateError('data binding already attached.'); | 322 if (stopper != null) throw new StateError('data binding already attached.'); |
321 stopper = watchAndInvoke(getter, (e) { | 323 stopper = watchAndInvoke(getter, (e) { |
322 setter(isUrl ? sanitizeUri(e.newValue) : e.newValue); | 324 setter(isUrl ? sanitizeUri(e.newValue) : e.newValue); |
323 }); | 325 }); |
324 } | 326 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
368 final List<Node> nodes = []; | 370 final List<Node> nodes = []; |
369 | 371 |
370 Template(this.node); | 372 Template(this.node); |
371 | 373 |
372 /** Associate the event listener while this template is visible. */ | 374 /** Associate the event listener while this template is visible. */ |
373 void listen(Stream<Event> stream, EventListener listener) { | 375 void listen(Stream<Event> stream, EventListener listener) { |
374 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); | 376 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); |
375 } | 377 } |
376 | 378 |
377 /** Run [action] when [exp] changes (while this template is visible). */ | 379 /** Run [action] when [exp] changes (while this template is visible). */ |
378 void bind(exp, ValueWatcher action) { | 380 void bind(exp, ChangeObserver action) { |
379 children.add(new Binding(exp, action)); | 381 children.add(new Binding(exp, action)); |
380 } | 382 } |
381 | 383 |
382 /** Create and bind a [Node] to [exp] while this template is visible. */ | 384 /** Create and bind a [Node] to [exp] while this template is visible. */ |
383 Node contentBind(Function exp) { | 385 Node contentBind(Function exp) { |
384 var bindNode = new Text(''); | 386 var bindNode = new Text(''); |
385 children.add(new Binding(() => '${exp()}', (e) { | 387 children.add(new Binding(() => '${exp()}', (e) { |
386 bindNode = updateBinding(exp(), bindNode, e.newValue); | 388 bindNode = updateBinding(exp(), bindNode, e.newValue); |
387 })); | 389 })); |
388 return bindNode; | 390 return bindNode; |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
481 // </tr> | 483 // </tr> |
482 // | 484 // |
483 // We can't necessarily rely on child position because of possible mutation, | 485 // We can't necessarily rely on child position because of possible mutation, |
484 // unless we're willing to say that "if" requires a fixed number of children. | 486 // unless we're willing to say that "if" requires a fixed number of children. |
485 // If that's the case, we need a way to check for this error case and alert the | 487 // If that's the case, we need a way to check for this error case and alert the |
486 // developer. | 488 // developer. |
487 abstract class PlaceholderTemplate extends Template { | 489 abstract class PlaceholderTemplate extends Template { |
488 /** Expression watch by this template (condition or loop expression). */ | 490 /** Expression watch by this template (condition or loop expression). */ |
489 final exp; | 491 final exp; |
490 | 492 |
491 WatcherDisposer stopper; | 493 ChangeUnobserver stopper; |
492 | 494 |
493 PlaceholderTemplate(Node reference, this.exp) | 495 PlaceholderTemplate(Node reference, this.exp) |
494 : super(reference); | 496 : super(reference); |
495 | 497 |
496 void create() {} | 498 void create() {} |
497 | 499 |
498 void insert() { | 500 void insert() { |
499 super.create(); | 501 super.create(); |
500 if (nodes.length > 0) { | 502 if (nodes.length > 0) { |
501 var parent = node.parentNode; | 503 var parent = node.parentNode; |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
582 } | 584 } |
583 | 585 |
584 /** | 586 /** |
585 * A template loop of the form `<td template iterate="x in list ">`. Unlike | 587 * A template loop of the form `<td template iterate="x in list ">`. Unlike |
586 * [LoopTemplate], here we insert children directly then node annotated with the | 588 * [LoopTemplate], here we insert children directly then node annotated with the |
587 * template attribute. | 589 * template attribute. |
588 */ | 590 */ |
589 class LoopTemplateInAttribute extends Template { | 591 class LoopTemplateInAttribute extends Template { |
590 final LoopIterationSetup iterSetup; | 592 final LoopIterationSetup iterSetup; |
591 final exp; | 593 final exp; |
592 WatcherDisposer stopper; | 594 ChangeUnobserver stopper; |
593 | 595 |
594 LoopTemplateInAttribute(Node node, this.exp, this.iterSetup) : super(node); | 596 LoopTemplateInAttribute(Node node, this.exp, this.iterSetup) : super(node); |
595 | 597 |
596 void create() {} | 598 void create() {} |
597 | 599 |
598 void insert() { | 600 void insert() { |
599 stopper = watchAndInvoke(exp, (e) { | 601 stopper = watchAndInvoke(exp, (e) { |
600 _removeInternal(); | 602 _removeInternal(); |
601 for (var x in e.newValue) { | 603 for (var x in e.newValue) { |
602 iterSetup(x, this); | 604 iterSetup(x, this); |
603 } | 605 } |
604 super.create(); | 606 super.create(); |
605 node.nodes.addAll(nodes); | 607 node.nodes.addAll(nodes); |
606 super.insert(); | 608 super.insert(); |
607 }); | 609 }); |
608 } | 610 } |
609 | 611 |
610 void _removeInternal() { | 612 void _removeInternal() { |
611 super.remove(); | 613 super.remove(); |
612 node.nodes.clear(); | 614 node.nodes.clear(); |
613 nodes.clear(); | 615 nodes.clear(); |
614 } | 616 } |
615 | 617 |
616 void remove() { | 618 void remove() { |
617 _removeInternal(); | 619 _removeInternal(); |
618 stopper(); | 620 stopper(); |
619 stopper = null; | 621 stopper = null; |
620 } | 622 } |
621 } | 623 } |
OLD | NEW |