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 |