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 /** Collects several code emitters for the template tool. */ | 5 /** Collects several code emitters for the template tool. */ |
6 // TODO(sigmund): add visitor that applies all emitters on a component | 6 // TODO(sigmund): add visitor that applies all emitters on a component |
7 // TODO(sigmund): add support for conditionals, so context is changed at that | 7 // TODO(sigmund): add support for conditionals, so context is changed at that |
8 // point. | 8 // point. |
9 library emitters; | 9 library emitters; |
10 | 10 |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 /** Generates a field for any data-bound content node. */ | 175 /** Generates a field for any data-bound content node. */ |
176 class ContentFieldEmitter extends Emitter<TextInfo> { | 176 class ContentFieldEmitter extends Emitter<TextInfo> { |
177 ContentFieldEmitter(TextInfo info) : super(info); | 177 ContentFieldEmitter(TextInfo info) : super(info); |
178 | 178 |
179 void emitDeclarations(Context context) { | 179 void emitDeclarations(Context context) { |
180 context.declarations.add('var ${info.identifier};'); | 180 context.declarations.add('var ${info.identifier};'); |
181 } | 181 } |
182 | 182 |
183 void emitCreated(Context context) { | 183 void emitCreated(Context context) { |
184 context.createdMethod.add( | 184 context.createdMethod.add( |
185 '${info.identifier} = autogenerated.nodeForBinding(${info.binding});'); | 185 "${info.identifier} = new autogenerated.Text('');"); |
186 } | 186 } |
187 | 187 |
188 void emitRemoved(Context context) { | 188 void emitRemoved(Context context) { |
189 context.removedMethod.add("${info.identifier} = null;"); | 189 context.removedMethod.add("${info.identifier} = null;"); |
190 } | 190 } |
191 } | 191 } |
192 | 192 |
193 | 193 |
194 /** | 194 /** |
195 * Generates event listeners attached to a node and code that attaches/detaches | 195 * Generates event listeners attached to a node and code that attaches/detaches |
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
319 void _emitStyleAttributeInserted( | 319 void _emitStyleAttributeInserted( |
320 String stoppers, AttributeInfo attr, CodePrinter printer) { | 320 String stoppers, AttributeInfo attr, CodePrinter printer) { |
321 var id = info.identifier; | 321 var id = info.identifier; |
322 var binding = attr.boundValue; | 322 var binding = attr.boundValue; |
323 printer.add('$stoppers.add(autogenerated.bindStyle($id, () => $binding));'); | 323 printer.add('$stoppers.add(autogenerated.bindStyle($id, () => $binding));'); |
324 } | 324 } |
325 | 325 |
326 void _emitSimpleAttributeInserted( | 326 void _emitSimpleAttributeInserted( |
327 String stoppers, String name, AttributeInfo attr, CodePrinter printer) { | 327 String stoppers, String name, AttributeInfo attr, CodePrinter printer) { |
328 var binding = attr.boundValue; | 328 var binding = attr.boundValue; |
329 var setter = _findDomSetter(info.node, name); | 329 var field = _findDomField(info, name); |
330 printer.add('$stoppers.add(autogenerated.watchAndInvoke(() => $binding, ' | 330 printer.add('$stoppers.add(autogenerated.watchAndInvoke(() => $binding, ' |
331 '(__e) { ${info.identifier}.$setter = __e.newValue; }));'); | 331 '(__e) { ${info.identifier}.$field = __e.newValue; }));'); |
| 332 |
| 333 if (attr.customTwoWayBinding) { |
| 334 printer.add('$stoppers.add(autogenerated.watchAndInvoke(') |
| 335 .add('() => ${info.identifier}.$field, ') |
| 336 .add('(__e) { $binding = __e.newValue; }));'); |
| 337 } |
332 } | 338 } |
333 | 339 |
334 void _emitTextAttributeInserted( | 340 void _emitTextAttributeInserted( |
335 String stoppers, String name, AttributeInfo attr, CodePrinter printer) { | 341 String stoppers, String name, AttributeInfo attr, CodePrinter printer) { |
336 var textContent = attr.textContent.map(escapeDartString); | 342 var textContent = attr.textContent.map(escapeDartString); |
337 var setter = _findDomSetter(info.node, name); | 343 var setter = _findDomField(info, name); |
338 var content = new StringBuffer(); | 344 var content = new StringBuffer(); |
339 var binding; | 345 var binding; |
340 if (attr.bindings.length == 1) { | 346 if (attr.bindings.length == 1) { |
341 binding = attr.boundValue; | 347 binding = attr.boundValue; |
342 content.add(textContent[0]); | 348 content.add(textContent[0]); |
343 content.add('\${__e.newValue}'); | 349 content.add('\${__e.newValue}'); |
344 content.add(textContent[1]); | 350 content.add(textContent[1]); |
345 } else { | 351 } else { |
346 // TODO(jmesserly): we could probably do something faster than a list | 352 // TODO(jmesserly): we could probably do something faster than a list |
347 // for watching on multiple bindings. | 353 // for watching on multiple bindings. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
382 * | 388 * |
383 * And the component has been defined as: | 389 * And the component has been defined as: |
384 * | 390 * |
385 * <element name="x-hello" extends="div" constructor="HelloComponent"> | 391 * <element name="x-hello" extends="div" constructor="HelloComponent"> |
386 * <template>Hello, <content>!</template> | 392 * <template>Hello, <content>!</template> |
387 * <script type="application/dart"></script> | 393 * <script type="application/dart"></script> |
388 * </element> | 394 * </element> |
389 * | 395 * |
390 * This will ensure that the Dart HelloComponent for `x-hello` is created and | 396 * This will ensure that the Dart HelloComponent for `x-hello` is created and |
391 * attached to the appropriate DOM node. | 397 * attached to the appropriate DOM node. |
392 * | |
393 * Also, this copies values from the scope into the object at component creation | |
394 * time, for example: | |
395 * | |
396 * <x-foo data-value="bar:baz"> | |
397 * | |
398 * This will set the "bar" property of FooComponent to be "baz". | |
399 */ | 398 */ |
400 class ComponentInstanceEmitter extends Emitter<ElementInfo> { | 399 class ComponentInstanceEmitter extends Emitter<ElementInfo> { |
401 ComponentInstanceEmitter(ElementInfo info) : super(info); | 400 ComponentInstanceEmitter(ElementInfo info) : super(info); |
402 | 401 |
403 void emitCreated(Context context) { | 402 void emitCreated(Context context) { |
404 var component = info.component; | 403 var component = info.component; |
405 if (component == null) return; | 404 if (component == null) return; |
406 | 405 |
407 var id = info.identifier; | 406 var id = info.identifier; |
408 context.createdMethod.add( | 407 context.createdMethod.add('new ${component.constructor}.forElement($id)'); |
409 'var component$id = new ${component.constructor}.forElement($id);'); | |
410 | 408 |
| 409 // Note: this feature is deprecated and will be removed. |
411 info.values.forEach((name, value) { | 410 info.values.forEach((name, value) { |
412 context.createdMethod.add('component$id.$name = $value;'); | 411 context.createdMethod.add('..$name = $value'); |
413 }); | 412 }); |
414 | 413 |
415 context.createdMethod.add('component$id.created_autogenerated();') | 414 context.createdMethod.add('..created_autogenerated()') |
416 .add('component$id.created();') | 415 .add('..created()') |
417 .add('component$id.composeChildren();'); | 416 .add('..composeChildren();'); |
418 } | 417 } |
419 | 418 |
420 void emitInserted(Context context) { | 419 void emitInserted(Context context) { |
421 if (info.component == null) return; | 420 if (info.component == null) return; |
422 | 421 |
423 // Note: watchers are intentionally hooked up after inserted() has run, | 422 // Note: watchers are intentionally hooked up after inserted() has run, |
424 // in case it makes any changes to the data. | 423 // in case it makes any changes to the data. |
425 var id = info.identifier; | 424 var id = info.identifier; |
426 context.insertedMethod.add('$id.xtag.inserted();') | 425 context.insertedMethod.add('$id.xtag..inserted()') |
427 .add('$id.xtag.inserted_autogenerated();'); | 426 .add('..inserted_autogenerated();'); |
428 } | 427 } |
429 | 428 |
430 void emitRemoved(Context context) { | 429 void emitRemoved(Context context) { |
431 if (info.component == null) return; | 430 if (info.component == null) return; |
432 | 431 |
433 var id = info.identifier; | 432 var id = info.identifier; |
434 context.removedMethod.add('$id.xtag.removed_autogenerated();') | 433 context.removedMethod.add('$id.xtag..removed_autogenerated()') |
435 .add('$id.xtag.removed();'); | 434 .add('..removed();'); |
436 } | 435 } |
437 } | 436 } |
438 | 437 |
439 /** | 438 /** |
440 * Emitter of template conditionals like `<template instantiate="if test">` or | 439 * Emitter of template conditionals like `<template instantiate="if test">` or |
441 * `<td template instantiate="if test">`. | 440 * `<td template instantiate="if test">`. |
442 * | 441 * |
443 * For a template element, we leave the (childless) template element in the | 442 * For a template element, we leave the (childless) template element in the |
444 * tree and use it as a reference point for child insertion. This matches | 443 * tree and use it as a reference point for child insertion. This matches |
445 * native MDV behavior. | 444 * native MDV behavior. |
(...skipping 465 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
911 } | 910 } |
912 return 'new autogenerated.$constructor'; | 911 return 'new autogenerated.$constructor'; |
913 } | 912 } |
914 | 913 |
915 /** | 914 /** |
916 * Finds the correct expression to set an HTML attribute through the DOM. | 915 * Finds the correct expression to set an HTML attribute through the DOM. |
917 * It is important for correctness to use the DOM setter if it is available. | 916 * It is important for correctness to use the DOM setter if it is available. |
918 * Otherwise changes will not be applied. This is most easily observed with | 917 * Otherwise changes will not be applied. This is most easily observed with |
919 * "InputElement.value", ".checked", etc. | 918 * "InputElement.value", ".checked", etc. |
920 */ | 919 */ |
921 String _findDomSetter(Element node, String name) { | 920 String _findDomField(ElementInfo info, String name) { |
922 var typeName = typeForHtmlTag(node.tagName); | 921 var typeName = typeForHtmlTag(info.baseTagName); |
923 while (typeName != null) { | 922 while (typeName != null) { |
924 var fields = htmlElementFields[typeName]; | 923 var fields = htmlElementFields[typeName]; |
925 if (fields != null) { | 924 if (fields != null) { |
926 var setter = fields[name]; | 925 var field = fields[name]; |
927 if (setter != null) return setter; | 926 if (field != null) return field; |
928 } | 927 } |
929 typeName = htmlElementExtends[typeName]; | 928 typeName = htmlElementExtends[typeName]; |
930 } | 929 } |
931 // If we didn't find a DOM setter, use the attributes map instead. | 930 // If we didn't find a DOM setter, and this is a component, set a property on |
932 return 'attributes["$name"]'; | 931 // the component. |
| 932 if (info.component != null && !name.startsWith('data-')) { |
| 933 return 'xtag.$name'; |
| 934 } |
| 935 return "attributes['$name']"; |
933 } | 936 } |
OLD | NEW |