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 |