| 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 |