Chromium Code Reviews| 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; | |
| 260 | 263 |
| 261 void insert() { | 264 void insert() { |
| 262 if (isFinal) { | 265 if (isFinal) { |
| 263 action(new ChangeNotification(null, exp())); | 266 invokeCallback(); |
| 264 } else if (stopper != null) { | 267 } else if (stopper != null) { |
| 265 throw new StateError('binding already attached'); | 268 throw new StateError('binding already attached'); |
| 266 } else { | 269 } else { |
| 267 stopper = watchAndInvoke(exp, action, 'generic-binding'); | 270 stopper = registerAndInvoke(); |
| 268 } | 271 } |
| 269 } | 272 } |
| 270 | 273 |
| 271 void remove() { | 274 void remove() { |
| 272 if (!isFinal) { | 275 if (!isFinal) { |
| 273 stopper(); | 276 stopper(); |
| 274 stopper = null; | 277 stopper = null; |
| 275 } | 278 } |
| 276 } | 279 } |
| 280 | |
| 281 /** Invokes the action associated with this binding. */ | |
| 282 void invokeCallback(); | |
| 283 | |
| 284 /** | |
| 285 * Registers the watcher and invokes the action associated with this binding. | |
| 286 */ | |
| 287 ChangeUnobserver registerAndInvoke(); | |
| 288 } | |
| 289 | |
| 290 /** Represents a generic data binding and a corresponding action. */ | |
| 291 class GenericBinding extends Binding { | |
| 292 final ChangeObserver action; | |
| 293 | |
| 294 GenericBinding(exp, this.action, bool isFinal) : super(exp, isFinal); | |
| 295 | |
| 296 void invokeCallback() => action(new ChangeNotification(null, exp())); | |
| 297 | |
| 298 ChangeUnobserver registerAndInvoke() => | |
| 299 watchAndInvoke(exp, action, 'generic-binding', debugLocation); | |
| 277 } | 300 } |
| 278 | 301 |
| 279 /** Represents a binding to a style attribute. */ | 302 /** Represents a binding to a style attribute. */ |
| 280 class StyleAttrBinding extends TemplateItem { | 303 class StyleAttrBinding extends Binding { |
| 281 final exp; | |
| 282 final Element elem; | 304 final Element elem; |
| 283 final bool isFinal; | |
| 284 ChangeUnobserver stopper; | |
| 285 | 305 |
| 286 StyleAttrBinding(this.elem, this.exp, this.isFinal); | 306 StyleAttrBinding(this.elem, exp, bool isFinal) : super(exp, isFinal); |
| 287 | 307 |
| 288 void insert() { | 308 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 | 309 |
| 298 void remove() { | 310 ChangeUnobserver registerAndInvoke() => bindStyle(elem, exp, debugLocation); |
| 299 if (!isFinal) { | |
| 300 stopper(); | |
| 301 stopper = null; | |
| 302 } | |
| 303 } | |
| 304 } | 311 } |
| 305 | 312 |
| 306 /** Represents a binding to a class attribute. */ | 313 /** Represents a binding to a class attribute. */ |
| 307 class ClassAttrBinding extends TemplateItem { | 314 class ClassAttrBinding extends Binding { |
| 308 final Element elem; | 315 final Element elem; |
| 309 final exp; | |
| 310 final bool isFinal; | |
| 311 ChangeUnobserver stopper; | |
| 312 | 316 |
| 313 ClassAttrBinding(this.elem, this.exp, this.isFinal); | 317 ClassAttrBinding(this.elem, exp, bool isFinal) : super(exp, isFinal); |
| 314 | 318 |
| 315 void insert() { | 319 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 | 320 |
| 325 void remove() { | 321 ChangeUnobserver registerAndInvoke() => |
| 326 if (!isFinal) { | 322 bindCssClasses(elem, exp, debugLocation); |
| 327 stopper(); | |
| 328 stopper = null; | |
| 329 } | |
| 330 } | |
| 331 } | 323 } |
| 332 | 324 |
| 333 /** | 325 /** |
| 334 * Represents a one-way binding between a dart getter expression and a DOM | 326 * 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. | 327 * property, or conversely between a DOM property value and a dart property. |
| 336 */ | 328 */ |
| 337 class DomPropertyBinding extends TemplateItem { | 329 class DomPropertyBinding extends Binding { |
| 338 /** Value updated by this binding. */ | 330 /** Value updated by this binding. */ |
| 339 final Setter setter; | 331 final Setter setter; |
| 340 | 332 |
| 341 /** | 333 /** |
| 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 | 334 * 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. | 335 * values. If so, the value assigned to the attribute needs to be sanitized. |
| 350 */ | 336 */ |
| 351 final bool isUrl; | 337 final bool isUrl; |
| 352 | 338 |
| 353 final bool isFinal; | 339 /** |
| 354 | 340 * Creates a DOM property binding, where [getter] reads the value of the |
| 355 ChangeUnobserver stopper; | 341 * binding, either from a Dart expression or from a DOM property (which is |
| 356 | 342 * internally also a Dart expression). |
| 357 DomPropertyBinding(this.getter, this.setter, this.isUrl, this.isFinal); | 343 */ |
| 344 DomPropertyBinding(Getter getter, this.setter, this.isUrl, bool isFinal) | |
| 345 : super(getter, isFinal); | |
| 358 | 346 |
| 359 void _safeSetter(value) { | 347 void _safeSetter(value) { |
| 360 setter(isUrl ? sanitizeUri(value) : value); | 348 setter(isUrl ? sanitizeUri(value) : value); |
| 361 } | 349 } |
| 350 void invokeCallback() => _safeSetter(exp()); | |
| 362 | 351 |
| 363 void insert() { | 352 ChangeUnobserver registerAndInvoke() => |
| 364 if (isFinal) { | 353 watchAndInvoke(exp, (e) => _safeSetter(e.newValue), |
| 365 _safeSetter(getter()); | 354 '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 } | 355 } |
| 381 | 356 |
| 382 /** Represents a component added within a template. */ | 357 /** Represents a component added within a template. */ |
| 383 class ComponentItem extends TemplateItem { | 358 class ComponentItem extends TemplateItem { |
| 384 /** An autogenerated component. */ | 359 /** An autogenerated component. */ |
| 385 final component; | 360 final component; |
| 386 | 361 |
| 387 ComponentItem(this.component); | 362 ComponentItem(this.component); |
| 388 | 363 |
| 389 void create() { | 364 void create() { |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 413 | 388 |
| 414 Template(this.node); | 389 Template(this.node); |
| 415 | 390 |
| 416 /** Associate the event listener while this template is visible. */ | 391 /** Associate the event listener while this template is visible. */ |
| 417 void listen(Stream<Event> stream, EventListener listener) { | 392 void listen(Stream<Event> stream, EventListener listener) { |
| 418 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); | 393 children.add(new Listener(stream, (e) { listener(e); dispatch(); })); |
| 419 } | 394 } |
| 420 | 395 |
| 421 /** Run [action] when [exp] changes (while this template is visible). */ | 396 /** Run [action] when [exp] changes (while this template is visible). */ |
| 422 void bind(exp, ChangeObserver action, bool isFinal) { | 397 void bind(exp, ChangeObserver action, bool isFinal) { |
| 423 children.add(new Binding(exp, action, isFinal)); | 398 children.add(new GenericBinding(exp, action, isFinal)); |
| 424 } | 399 } |
| 425 | 400 |
| 426 /** Create and bind a [Node] to [exp] while this template is visible. */ | 401 /** Create and bind a [Node] to [exp] while this template is visible. */ |
| 427 Node contentBind(Function exp, isFinal) { | 402 Node contentBind(Function exp, isFinal) { |
| 428 var bindNode = new Text(''); | 403 var bindNode = new Text(''); |
| 429 children.add(new Binding(() => '${exp()}', (e) { | 404 children.add(new GenericBinding(() => '${exp()}', (e) { |
| 430 bindNode = updateBinding(exp(), bindNode, e.newValue); | 405 bindNode = updateBinding(exp(), bindNode, e.newValue); |
| 431 }, isFinal)); | 406 }, isFinal)); |
| 432 return bindNode; | 407 return bindNode; |
| 433 } | 408 } |
| 434 | 409 |
| 435 /** Bind [exp] to `elem.class` while this template is visible. */ | 410 /** Bind [exp] to `elem.class` while this template is visible. */ |
| 436 void bindClass(elem, exp, isFinal) { | 411 void bindClass(elem, exp, isFinal) { |
| 437 children.add(new ClassAttrBinding(elem, exp, isFinal)); | 412 children.add(new ClassAttrBinding(elem, exp, isFinal)); |
| 438 } | 413 } |
| 439 | 414 |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 669 node.nodes.clear(); | 644 node.nodes.clear(); |
| 670 nodes.clear(); | 645 nodes.clear(); |
| 671 } | 646 } |
| 672 | 647 |
| 673 void remove() { | 648 void remove() { |
| 674 _removeInternal(); | 649 _removeInternal(); |
| 675 stopper(); | 650 stopper(); |
| 676 stopper = null; | 651 stopper = null; |
| 677 } | 652 } |
| 678 } | 653 } |
| 654 | |
| 655 String _readCurrentStackTrace() { | |
| 656 try { | |
| 657 throw ""; | |
| 658 } catch (e, trace) { | |
| 659 return trace.toString(); | |
|
Jennifer Messerly
2013/07/11 21:27:23
use http://pub.dartlang.org/packages/stack_trace ?
Siggi Cherem (dart-lang)
2013/07/11 22:07:01
Oh, it used to depend on dart:io, but seems to hav
| |
| 660 } | |
| 661 } | |
| 662 | |
| OLD | NEW |