OLD | NEW |
1 <!-- | 1 <!-- |
2 // Copyright 2015 The Chromium Authors. All rights reserved. | 2 // Copyright 2015 The Chromium Authors. All rights reserved. |
3 // Use of this source code is governed by a BSD-style license that can be | 3 // Use of this source code is governed by a BSD-style license that can be |
4 // found in the LICENSE file. | 4 // found in the LICENSE file. |
5 --> | 5 --> |
6 <script> | 6 <script> |
7 import "dart:mirrors"; | 7 import "dart:mirrors"; |
8 import "dart:sky"; | 8 import "dart:sky"; |
9 | 9 |
| 10 typedef dynamic _Converter(String value); |
| 11 |
| 12 final Map<String, _Converter> _kAttributeConverters = { |
| 13 'boolean': (String value) { |
| 14 return value == 'true'; |
| 15 }, |
| 16 'number': (String value) { |
| 17 return double.parse(value); |
| 18 }, |
| 19 'string': (String value) { |
| 20 return value == null ? '' : value; |
| 21 }, |
| 22 }; |
| 23 |
10 class _Registration { | 24 class _Registration { |
11 Element template; | 25 final Element template; |
| 26 final Map<String, _Converter> attributes = new Map(); |
| 27 |
12 _Registration(this.template); | 28 _Registration(this.template); |
| 29 |
| 30 void parseAttributeSpec(definition) { |
| 31 String spec = definition.getAttribute('attributes'); |
| 32 if (spec == null) |
| 33 return; |
| 34 |
| 35 for (String token in spec.split(',')) { |
| 36 List<String> parts = token.split(':'); |
| 37 |
| 38 if (parts.length != 2) { |
| 39 window.console.error( |
| 40 'Invalid attribute spec "${spec}", attributes must' |
| 41 ' be {name}:{type}, where type is one of boolean, number or' |
| 42 ' string.'); |
| 43 continue; |
| 44 } |
| 45 |
| 46 var name = parts[0].trim(); |
| 47 var type = parts[1].trim(); |
| 48 |
| 49 defineAttribute(name, type); |
| 50 } |
| 51 } |
| 52 |
| 53 void defineAttribute(String name, String type) { |
| 54 _Converter converter = _kAttributeConverters[type]; |
| 55 |
| 56 if (converter == null) { |
| 57 window.console.error( |
| 58 'Invalid attribute type "${type}", type must be one of boolean,' |
| 59 ' number or string.'); |
| 60 return; |
| 61 } |
| 62 |
| 63 attributes[name] = converter; |
| 64 } |
13 } | 65 } |
14 | 66 |
15 final Map<String, _Registration> _registery = new Map<String, _Registration>(); | 67 final Map<String, _Registration> _registery = new Map<String, _Registration>(); |
16 | 68 |
17 class Tagname { | 69 class Tagname { |
18 final String name; | 70 final String name; |
19 const Tagname(this.name); | 71 const Tagname(this.name); |
20 } | 72 } |
21 | 73 |
22 String _getTagName(Type type) { | 74 String _getTagName(Type type) { |
23 return reflectClass(type).metadata.firstWhere( | 75 return reflectClass(type).metadata.firstWhere( |
24 (i) => i.reflectee is Tagname).reflectee.name; | 76 (i) => i.reflectee is Tagname).reflectee.name; |
25 } | 77 } |
26 | 78 |
27 abstract class SkyElement extends Element { | 79 abstract class SkyElement extends Element { |
28 // Override these functions to receive lifecycle notifications. | 80 // Override these functions to receive lifecycle notifications. |
29 void created() {} | 81 void created() {} |
30 void attached() {} | 82 void attached() {} |
31 void detached() {} | 83 void detached() {} |
32 void attributeChanged(String attrName, String oldValue, String newValue) {} | 84 void attributeChanged(String attrName, String oldValue, String newValue) {} |
33 void shadowRootReady() {} | 85 void shadowRootReady() {} |
34 | 86 |
35 String get tagName => _getTagName(runtimeType); | 87 String get tagName => _getTagName(runtimeType); |
| 88 _Registration _registration; |
36 | 89 |
37 SkyElement() { | 90 SkyElement() { |
38 created(); | 91 _registration = _registery[tagName]; |
39 | |
40 // Invoke attributeChanged callback when element is first created too. | 92 // Invoke attributeChanged callback when element is first created too. |
| 93 // TODO(abarth): Is this necessary? We shouldn't have any attribute yet... |
41 for (Attr attribute in getAttributes()) | 94 for (Attr attribute in getAttributes()) |
42 attributeChangedCallback(attribute.name, null, attribute.value); | 95 attributeChangedCallback(attribute.name, null, attribute.value); |
43 } | 96 } |
44 | 97 |
45 attachedCallback() { | 98 attachedCallback() { |
46 if (shadowRoot == null) { | 99 if (shadowRoot == null) { |
47 var registration = _registery[tagName]; | 100 if (_registration.template != null) { |
48 if (registration.template != null) { | |
49 ShadowRoot shadow = ensureShadowRoot(); | 101 ShadowRoot shadow = ensureShadowRoot(); |
50 Node content = registration.template.content; | 102 Node content = _registration.template.content; |
51 shadow.appendChild(document.importNode(content, deep: true)); | 103 shadow.appendChild(document.importNode(content, deep: true)); |
52 shadowRootReady(); | 104 shadowRootReady(); |
53 } | 105 } |
54 } | 106 } |
55 attached(); | 107 attached(); |
56 } | 108 } |
57 | 109 |
58 detachedCallback() { | 110 detachedCallback() { |
59 detached(); | 111 detached(); |
60 } | 112 } |
61 | 113 |
62 attributeChangedCallback(name, oldValue, newValue) { | 114 attributeChangedCallback(name, oldValue, newValue) { |
63 attributeChanged(name, oldValue, newValue); | 115 attributeChanged(name, oldValue, newValue); |
| 116 |
| 117 _Converter converter = _registration.attributes[name]; |
| 118 if (converter == null) |
| 119 return; |
| 120 Symbol callback = new Symbol('${name}Changed'); |
| 121 InstanceMirror mirror = reflect(this); |
| 122 if (mirror.type.instanceMembers.containsKey(callback)) |
| 123 mirror.invoke(callback, [converter(oldValue), converter(newValue)]); |
| 124 } |
| 125 |
| 126 noSuchMethod(Invocation invocation) { |
| 127 String name = MirrorSystem.getName(invocation.memberName); |
| 128 if (name.endsWith('=')) |
| 129 name = name.substring(0, name.length - 1); |
| 130 _Converter converter = _registration.attributes[name]; |
| 131 if (converter != null) { |
| 132 if (invocation.isGetter) { |
| 133 return converter(getAttribute(name)); |
| 134 } else if (invocation.isSetter) { |
| 135 setAttribute(name, invocation.positionalArguments[0].toString()); |
| 136 return; |
| 137 } |
| 138 } |
| 139 return super.noSuchMethod(invocation); |
64 } | 140 } |
65 } | 141 } |
66 | 142 |
67 void register(Element script, Type type) { | 143 void register(Element script, Type type) { |
68 Element definition = script.parentNode; | 144 Element definition = script.parentNode; |
69 | 145 |
70 if (definition.tagName != 'sky-element') | 146 if (definition.tagName != 'sky-element') |
71 throw new UnsupportedError('register() calls must be inside a <sky-element>.
'); | 147 throw new UnsupportedError('register() calls must be inside a <sky-element>.
'); |
72 | 148 |
73 ClassMirror mirror = reflectClass(type); | 149 ClassMirror mirror = reflectClass(type); |
74 if (!mirror.isSubclassOf(reflectClass(SkyElement))) | 150 if (!mirror.isSubclassOf(reflectClass(SkyElement))) |
75 throw new UnsupportedError('@Tagname can only be used on descendants of SkyE
lement'); | 151 throw new UnsupportedError('@Tagname can only be used on descendants of SkyE
lement'); |
76 | 152 |
77 String tagName = _getTagName(type); | 153 String tagName = _getTagName(type); |
78 Element template = definition.querySelector('template'); | 154 Element template = definition.querySelector('template'); |
79 | 155 |
80 document.registerElement(tagName, type); | 156 document.registerElement(tagName, type); |
81 _registery[tagName] = new _Registration(template); | 157 _registery[tagName] = new _Registration(template) |
| 158 ..parseAttributeSpec(definition); |
82 } | 159 } |
83 </script> | 160 </script> |
OLD | NEW |