OLD | NEW |
| (Empty) |
1 <!-- | |
2 // Copyright 2014 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 <import src="sky-binder.sky" as="binder" /> | |
7 <import src="element-registry.sky" as="registry" /> | |
8 <script> | |
9 function parseAttributeSpec(registration, definition) { | |
10 var spec = definition.getAttribute('attributes'); | |
11 | |
12 if (!spec) | |
13 return; | |
14 | |
15 var attributeTokens = spec.split(','); | |
16 | |
17 for (var i = 0; i < attributeTokens.length; ++i) { | |
18 var parts = attributeTokens[i].split(':'); | |
19 | |
20 if (parts.length != 2) { | |
21 console.error('Invalid attribute spec "' + spec + '", attributes must' + | |
22 ' be {name}:{type}, where type is one of boolean, number or' + | |
23 ' string.'); | |
24 continue; | |
25 } | |
26 | |
27 var name = parts[0].trim(); | |
28 var type = parts[1].trim(); | |
29 | |
30 registration.defineAttribute(name, type); | |
31 } | |
32 } | |
33 | |
34 function parseEventHandlers(registration, definition) { | |
35 var eventHandlers = []; | |
36 var attributes = definition.getAttributes(); | |
37 | |
38 for (var i = 0; i < attributes.length; i++) { | |
39 var attr = attributes[i]; | |
40 var name = attr.name; | |
41 var value = attr.value; | |
42 | |
43 if (name.startsWith('on-')) { | |
44 registration.eventHandlers.set(name.substring(3), value); | |
45 } | |
46 } | |
47 } | |
48 | |
49 class SkyElement extends HTMLElement { | |
50 | |
51 static register() { | |
52 var definition = document.currentScript.parentNode; | |
53 | |
54 if (definition.localName !== 'sky-element') { | |
55 throw new Error('register() calls must be inside a <sky-element>.'); | |
56 } | |
57 | |
58 var tagName = definition.getAttribute('name'); | |
59 if (!tagName) { | |
60 throw new Error('<sky-element> must have a name.'); | |
61 } | |
62 | |
63 var registration = registry.registerElement(tagName); | |
64 | |
65 parseAttributeSpec(registration, definition); | |
66 parseEventHandlers(registration, definition); | |
67 | |
68 registration.template = definition.querySelector('template'); | |
69 | |
70 registration.synthesizeAttributes(this.prototype); | |
71 | |
72 return document.registerElement(tagName, { | |
73 prototype: this.prototype, | |
74 }); | |
75 } | |
76 | |
77 created() { | |
78 // override | |
79 } | |
80 | |
81 attached() { | |
82 // override | |
83 } | |
84 | |
85 detached() { | |
86 // override | |
87 } | |
88 | |
89 attributeChanged(attrName, oldValue, newValue) { | |
90 // override | |
91 } | |
92 | |
93 shadowRootReady() { | |
94 // override | |
95 } | |
96 | |
97 createdCallback() { | |
98 this.isAttached = false; | |
99 this.propertyBindings = null; | |
100 this.dirtyPropertyBindings = null; | |
101 this.created(); | |
102 | |
103 Object.preventExtensions(this); | |
104 | |
105 // Invoke attributeChanged callback when element is first created too. | |
106 var attributes = this.getAttributes(); | |
107 for (var i = 0; i < attributes.length; ++i) { | |
108 var attribute = attributes[i]; | |
109 this.attributeChangedCallback(attribute.name, null, attribute.value); | |
110 } | |
111 | |
112 var registration = registry.getRegistration(this.localName); | |
113 registration.addInstanceEventListeners(this); | |
114 } | |
115 | |
116 attachedCallback() { | |
117 if (!this.shadowRoot) { | |
118 var registration = registry.getRegistration(this.localName); | |
119 if (registration.template) { | |
120 var shadow = this.ensureShadowRoot(); | |
121 var instance = binder.createInstance(registration.template, this); | |
122 shadow.appendChild(instance.fragment); | |
123 this.shadowRootReady(); | |
124 } | |
125 } | |
126 this.attached(); | |
127 this.isAttached = true; | |
128 } | |
129 | |
130 detachedCallback() { | |
131 this.detached(); | |
132 this.isAttached = false; | |
133 } | |
134 | |
135 attributeChangedCallback(name, oldValue, newValue) { | |
136 if (registry.isExpandableAttribute(name)) | |
137 return; | |
138 this.attributeChanged(name, oldValue, newValue); | |
139 var registration = registry.getRegistration(this.localName); | |
140 var converter = registration.attributes.get(name); | |
141 if (converter) { | |
142 this.notifyPropertyChanged(name, converter(oldValue), | |
143 converter(newValue)); | |
144 } | |
145 } | |
146 | |
147 notifyPropertyChanged(name, oldValue, newValue) { | |
148 if (oldValue == newValue) | |
149 return; | |
150 var notifier = Object.getNotifier(this); | |
151 notifier.notify({ | |
152 type: 'update', | |
153 name: name, | |
154 oldValue: oldValue, | |
155 }); | |
156 var handler = this[name + 'Changed']; | |
157 if (typeof handler == 'function') | |
158 handler.call(this, oldValue, newValue); | |
159 this.schedulePropertyBindingUpdate(name); | |
160 } | |
161 | |
162 addPropertyBinding(name, binding) { | |
163 if (!this.propertyBindings) | |
164 this.propertyBindings = new Map(); | |
165 this.propertyBindings.set(name, binding); | |
166 } | |
167 | |
168 getPropertyBinding(name) { | |
169 if (!this.propertyBindings) | |
170 return null; | |
171 return this.propertyBindings.get(name); | |
172 } | |
173 | |
174 schedulePropertyBindingUpdate(name) { | |
175 if (!this.dirtyPropertyBindings) { | |
176 this.dirtyPropertyBindings = new Set(); | |
177 Promise.resolve().then(this.updatePropertyBindings.bind(this)); | |
178 } | |
179 this.dirtyPropertyBindings.add(name); | |
180 } | |
181 | |
182 updatePropertyBindings() { | |
183 for (var name of this.dirtyPropertyBindings) { | |
184 var binding = this.getPropertyBinding(name); | |
185 if (binding) { | |
186 binding.setValue(this[name]); | |
187 binding.discardChanges(); | |
188 } | |
189 } | |
190 this.dirtyPropertyBindings = null; | |
191 } | |
192 }; | |
193 | |
194 module.exports = SkyElement; | |
195 </script> | |
OLD | NEW |