| 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:collection'; | 9 import 'dart:collection'; |
| 10 import 'dart:html'; | 10 import 'dart:html'; |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 140 * * binding classes with a string: | 140 * * binding classes with a string: |
| 141 * | 141 * |
| 142 * bindCssClasses(e, () => "${class1 != null ? class1 : ''} " | 142 * bindCssClasses(e, () => "${class1 != null ? class1 : ''} " |
| 143 * "${class2 != null ? class2 : ''}"); | 143 * "${class2 != null ? class2 : ''}"); |
| 144 * | 144 * |
| 145 * * binding classes separately: | 145 * * binding classes separately: |
| 146 * | 146 * |
| 147 * bindCssClasses(e, () => class1); | 147 * bindCssClasses(e, () => class1); |
| 148 * bindCssClasses(e, () => class2); | 148 * bindCssClasses(e, () => class2); |
| 149 */ | 149 */ |
| 150 ChangeUnobserver bindCssClasses(Element elem, dynamic exp()) { | 150 ChangeUnobserver bindCssClasses(Element elem, dynamic exp(), |
| 151 [String debugLocation]) { |
| 151 return watchAndInvoke(exp, (e) { | 152 return watchAndInvoke(exp, (e) { |
| 152 if (e.changes != null) { | 153 if (e.changes != null) { |
| 153 for (var change in e.changes) changeCssClasses(elem, change); | 154 for (var change in e.changes) changeCssClasses(elem, change); |
| 154 } else { | 155 } else { |
| 155 updateCssClass(elem, false, e.oldValue); | 156 updateCssClass(elem, false, e.oldValue); |
| 156 updateCssClass(elem, true, e.newValue); | 157 updateCssClass(elem, true, e.newValue); |
| 157 } | 158 } |
| 158 }, 'css-class-bind'); | 159 }, 'css-class-bind', debugLocation); |
| 159 } | 160 } |
| 160 | 161 |
| 161 /** Bind the result of [exp] to the style attribute in [elem]. */ | 162 /** Bind the result of [exp] to the style attribute in [elem]. */ |
| 162 ChangeUnobserver bindStyle(Element elem, Map<String, String> exp()) { | 163 ChangeUnobserver bindStyle(Element elem, Map<String, String> exp(), |
| 164 [String debugLocation]) { |
| 163 return watchAndInvoke(exp, (e) => updateStyle(elem, e.oldValue, e.newValue), | 165 return watchAndInvoke(exp, (e) => updateStyle(elem, e.oldValue, e.newValue), |
| 164 'css-style-bind'); | 166 'css-style-bind', debugLocation); |
| 165 } | 167 } |
| 166 | 168 |
| 167 /** | 169 /** |
| 168 * Changes the style properties from [oldValue] to [newValue]. A runtime error | 170 * Changes the style properties from [oldValue] to [newValue]. A runtime error |
| 169 * is reported if [newValue] is not a `String` or `Map<String, String>`. | 171 * is reported if [newValue] is not a `String` or `Map<String, String>`. |
| 170 */ | 172 */ |
| 171 void updateStyle(Element elem, oldValue, newValue) { | 173 void updateStyle(Element elem, oldValue, newValue) { |
| 172 if (newValue is! String && newValue is! Map<String, String>) { | 174 if (newValue is! String && newValue is! Map<String, String>) { |
| 173 elem.style.cssText = ''; | 175 elem.style.cssText = ''; |
| 174 throw new ArgumentError("style must be a String or Map<String, String>."); | 176 throw new ArgumentError("style must be a String or Map<String, String>."); |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 242 void insert() { | 244 void insert() { |
| 243 _subscription = eventStream.listen(listener); | 245 _subscription = eventStream.listen(listener); |
| 244 } | 246 } |
| 245 | 247 |
| 246 void remove() { | 248 void remove() { |
| 247 _subscription.cancel(); | 249 _subscription.cancel(); |
| 248 _subscription = null; | 250 _subscription = null; |
| 249 } | 251 } |
| 250 } | 252 } |
| 251 | 253 |
| 252 /** Represents a generic data binding and a corresponding action. */ | 254 /** Common logic for all bindings. */ |
| 253 class Binding extends TemplateItem { | 255 abstract class Binding extends TemplateItem { |
| 254 final exp; | 256 final exp; |
| 255 final ChangeObserver action; | |
| 256 final bool isFinal; | 257 final bool isFinal; |
| 258 final String debugLocation; |
| 257 ChangeUnobserver stopper; | 259 ChangeUnobserver stopper; |
| 258 | 260 |
| 259 Binding(this.exp, this.action, this.isFinal); | 261 Binding(this.exp, this.isFinal) |
| 262 : debugLocation = verboseDebugMessages && readCurrentStackTrace != null |
| 263 ? readCurrentStackTrace() : null; |
| 260 | 264 |
| 261 void insert() { | 265 void insert() { |
| 262 if (isFinal) { | 266 if (isFinal) { |
| 263 action(new ChangeNotification(null, exp())); | 267 invokeCallback(); |
| 264 } else if (stopper != null) { | 268 } else if (stopper != null) { |
| 265 throw new StateError('binding already attached'); | 269 throw new StateError('binding already attached'); |
| 266 } else { | 270 } else { |
| 267 stopper = watchAndInvoke(exp, action, 'generic-binding'); | 271 stopper = registerAndInvoke(); |
| 268 } | 272 } |
| 269 } | 273 } |
| 270 | 274 |
| 271 void remove() { | 275 void remove() { |
| 272 if (!isFinal) { | 276 if (!isFinal) { |
| 273 stopper(); | 277 stopper(); |
| 274 stopper = null; | 278 stopper = null; |
| 275 } | 279 } |
| 276 } | 280 } |
| 281 |
| 282 /** Invokes the action associated with this binding. */ |
| 283 void invokeCallback(); |
| 284 |
| 285 /** |
| 286 * Registers the watcher and invokes the action associated with this binding. |
| 287 */ |
| 288 ChangeUnobserver registerAndInvoke(); |
| 289 } |
| 290 |
| 291 /** Represents a generic data binding and a corresponding action. */ |
| 292 class GenericBinding extends Binding { |
| 293 final ChangeObserver action; |
| 294 |
| 295 GenericBinding(exp, this.action, bool isFinal) : super(exp, isFinal); |
| 296 |
| 297 void invokeCallback() => action(new ChangeNotification(null, exp())); |
| 298 |
| 299 ChangeUnobserver registerAndInvoke() => |
| 300 watchAndInvoke(exp, action, 'generic-binding', debugLocation); |
| 277 } | 301 } |
| 278 | 302 |
| 279 /** Represents a binding to a style attribute. */ | 303 /** Represents a binding to a style attribute. */ |
| 280 class StyleAttrBinding extends TemplateItem { | 304 class StyleAttrBinding extends Binding { |
| 281 final exp; | |
| 282 final Element elem; | 305 final Element elem; |
| 283 final bool isFinal; | |
| 284 ChangeUnobserver stopper; | |
| 285 | 306 |
| 286 StyleAttrBinding(this.elem, this.exp, this.isFinal); | 307 StyleAttrBinding(this.elem, exp, bool isFinal) : super(exp, isFinal); |
| 287 | 308 |
| 288 void insert() { | 309 void invokeCallback() => updateStyle(elem, null, exp()); |
| 289 if (isFinal) { | |
| 290 updateStyle(elem, null, exp()); | |
| 291 } else if (stopper != null) { | |
| 292 throw new StateError('style binding already attached'); | |
| 293 } else { | |
| 294 stopper = bindStyle(elem, exp); | |
| 295 } | |
| 296 } | |
| 297 | 310 |
| 298 void remove() { | 311 ChangeUnobserver registerAndInvoke() => bindStyle(elem, exp, debugLocation); |
| 299 if (!isFinal) { | |
| 300 stopper(); | |
| 301 stopper = null; | |
| 302 } | |
| 303 } | |
| 304 } | 312 } |
| 305 | 313 |
| 306 /** Represents a binding to a class attribute. */ | 314 /** Represents a binding to a class attribute. */ |
| 307 class ClassAttrBinding extends TemplateItem { | 315 class ClassAttrBinding extends Binding { |
| 308 final Element elem; | 316 final Element elem; |
| 309 final exp; | |
| 310 final bool isFinal; | |
| 311 ChangeUnobserver stopper; | |
| 312 | 317 |
| 313 ClassAttrBinding(this.elem, this.exp, this.isFinal); | 318 ClassAttrBinding(this.elem, exp, bool isFinal) : super(exp, isFinal); |
| 314 | 319 |
| 315 void insert() { | 320 void invokeCallback() => updateCssClass(elem, true, exp()); |
| 316 if (isFinal) { | |
| 317 updateCssClass(elem, true, exp()); | |
| 318 } else if (stopper != null) { | |
| 319 throw new StateError('class binding already attached'); | |
| 320 } else { | |
| 321 stopper = bindCssClasses(elem, exp); | |
| 322 } | |
| 323 } | |
| 324 | 321 |
| 325 void remove() { | 322 ChangeUnobserver registerAndInvoke() => |
| 326 if (!isFinal) { | 323 bindCssClasses(elem, exp, debugLocation); |
| 327 stopper(); | |
| 328 stopper = null; | |
| 329 } | |
| 330 } | |
| 331 } | 324 } |
| 332 | 325 |
| 333 /** | 326 /** |
| 334 * Represents a one-way binding between a dart getter expression and a DOM | 327 * Represents a one-way binding between a dart getter expression and a DOM |
| 335 * property, or conversely between a DOM property value and a dart property. | 328 * property, or conversely between a DOM property value and a dart property. |
| 336 */ | 329 */ |
| 337 class DomPropertyBinding extends TemplateItem { | 330 class DomPropertyBinding extends Binding { |
| 338 /** Value updated by this binding. */ | 331 /** Value updated by this binding. */ |
| 339 final Setter setter; | 332 final Setter setter; |
| 340 | 333 |
| 341 /** | 334 /** |
| 342 * Getter that reads the value of the binding, either from a Dart expression | |
| 343 * or from a DOM property (which is internally also a Dart expression). | |
| 344 */ | |
| 345 final Getter getter; | |
| 346 | |
| 347 /** | |
| 348 * Whether this is a binding that assigns a DOM attribute accepting URL | 335 * Whether this is a binding that assigns a DOM attribute accepting URL |
| 349 * values. If so, the value assigned to the attribute needs to be sanitized. | 336 * values. If so, the value assigned to the attribute needs to be sanitized. |
| 350 */ | 337 */ |
| 351 final bool isUrl; | 338 final bool isUrl; |
| 352 | 339 |
| 353 final bool isFinal; | 340 /** |
| 354 | 341 * Creates a DOM property binding, where [getter] reads the value of the |
| 355 ChangeUnobserver stopper; | 342 * binding, either from a Dart expression or from a DOM property (which is |
| 356 | 343 * internally also a Dart expression). |
| 357 DomPropertyBinding(this.getter, this.setter, this.isUrl, this.isFinal); | 344 */ |
| 345 DomPropertyBinding(Getter getter, this.setter, this.isUrl, bool isFinal) |
| 346 : super(getter, isFinal); |
| 358 | 347 |
| 359 void _safeSetter(value) { | 348 void _safeSetter(value) { |
| 360 setter(isUrl ? sanitizeUri(value) : value); | 349 setter(isUrl ? sanitizeUri(value) : value); |
| 361 } | 350 } |
| 351 void invokeCallback() => _safeSetter(exp()); |
| 362 | 352 |
| 363 void insert() { | 353 ChangeUnobserver registerAndInvoke() => |
| 364 if (isFinal) { | 354 watchAndInvoke(exp, (e) => _safeSetter(e.newValue), |
| 365 _safeSetter(getter()); | 355 'dom-property-binding', debugLocation); |
| 366 } else if (stopper != null) { | |
| 367 throw new StateError('data binding already attached.'); | |
| 368 } else { | |
| 369 stopper = watchAndInvoke(getter, (e) => _safeSetter(e.newValue), | |
| 370 'dom-property-binding'); | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 void remove() { | |
| 375 if (!isFinal) { | |
| 376 stopper(); | |
| 377 stopper = null; | |
| 378 } | |
| 379 } | |
| 380 } | 356 } |
| 381 | 357 |
| 382 /** Represents a component added within a template. */ | 358 /** Represents a component added within a template. */ |
| 383 class ComponentItem extends TemplateItem { | 359 class ComponentItem extends TemplateItem { |
| 384 /** An autogenerated component. */ | 360 /** An autogenerated component. */ |
| 385 final component; | 361 final component; |
| 386 | 362 |
| 387 ComponentItem(this.component); | 363 ComponentItem(this.component); |
| 388 | 364 |
| 389 void create() { | 365 void create() { |
| (...skipping 23 matching lines...) Expand all Loading... |
| 413 | 389 |
| 414 Template(this.node); | 390 Template(this.node); |
| 415 | 391 |
| 416 /** Associate the event listener while this template is visible. */ | 392 /** Associate the event listener while this template is visible. */ |
| 417 void listen(Stream<Event> stream, EventListener listener) { | 393 void listen(Stream<Event> stream, EventListener listener) { |
| 418 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); | 394 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); |
| 419 } | 395 } |
| 420 | 396 |
| 421 /** Run [action] when [exp] changes (while this template is visible). */ | 397 /** Run [action] when [exp] changes (while this template is visible). */ |
| 422 void bind(exp, ChangeObserver action, bool isFinal) { | 398 void bind(exp, ChangeObserver action, bool isFinal) { |
| 423 children.add(new Binding(exp, action, isFinal)); | 399 children.add(new GenericBinding(exp, action, isFinal)); |
| 424 } | 400 } |
| 425 | 401 |
| 426 /** Create and bind a [Node] to [exp] while this template is visible. */ | 402 /** Create and bind a [Node] to [exp] while this template is visible. */ |
| 427 Node contentBind(Function exp, isFinal) { | 403 Node contentBind(Function exp, isFinal) { |
| 428 var bindNode = new Text(''); | 404 var bindNode = new Text(''); |
| 429 children.add(new Binding(() => '${exp()}', (e) { | 405 children.add(new GenericBinding(() => '${exp()}', (e) { |
| 430 bindNode = updateBinding(exp(), bindNode, e.newValue); | 406 bindNode = updateBinding(exp(), bindNode, e.newValue); |
| 431 }, isFinal)); | 407 }, isFinal)); |
| 432 return bindNode; | 408 return bindNode; |
| 433 } | 409 } |
| 434 | 410 |
| 435 /** Bind [exp] to `elem.class` while this template is visible. */ | 411 /** Bind [exp] to `elem.class` while this template is visible. */ |
| 436 void bindClass(elem, exp, isFinal) { | 412 void bindClass(elem, exp, isFinal) { |
| 437 children.add(new ClassAttrBinding(elem, exp, isFinal)); | 413 children.add(new ClassAttrBinding(elem, exp, isFinal)); |
| 438 } | 414 } |
| 439 | 415 |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 669 node.nodes.clear(); | 645 node.nodes.clear(); |
| 670 nodes.clear(); | 646 nodes.clear(); |
| 671 } | 647 } |
| 672 | 648 |
| 673 void remove() { | 649 void remove() { |
| 674 _removeInternal(); | 650 _removeInternal(); |
| 675 stopper(); | 651 stopper(); |
| 676 stopper = null; | 652 stopper = null; |
| 677 } | 653 } |
| 678 } | 654 } |
| OLD | NEW |