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