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 <script> | |
8 // TODO(esprehn): It would be nice if these were exposed by the platform so | |
9 // the framework didn't need to hard code a list. | |
10 var globalAttributesNames = new Set([ | |
11 'class', | |
12 'contenteditable', | |
13 'dir', | |
14 'id', | |
15 'lang', | |
16 'spellcheck', | |
17 'tabindex', | |
18 'style', | |
19 ]); | |
20 | |
21 function isExpandableAttribute(name) { | |
22 return name.startsWith('data-') || name.startsWith('on-'); | |
23 } | |
24 | |
25 function isGlobalAttribute(name) { | |
26 if (isExpandableAttribute(name)) | |
27 return true; | |
28 return globalAttributesNames.has(name); | |
29 } | |
30 | |
31 var attributeConverters = { | |
32 boolean: function(value) { | |
33 if (typeof value == 'string') | |
34 return value == 'true'; | |
35 return !!value; | |
36 }, | |
37 number: function(value) { | |
38 return Number(value); | |
39 }, | |
40 string: function(value) { | |
41 if (value === null) | |
42 return ''; | |
43 return String(value); | |
44 }, | |
45 }; | |
46 | |
47 function eventHandlerCallback(event) { | |
48 var element = event.currentTarget; | |
49 var registration = getRegistration(element.localName); | |
50 var method = registration.eventHandlers.get(event.type); | |
51 var handler = element[method]; | |
52 if (!(typeof handler == 'function')) { | |
53 throw new Error('Element ' + element.localName + | |
54 ' specifies invalid event handler "' + method + '"'); | |
55 } | |
56 handler.call(element, event); | |
57 } | |
58 | |
59 class ElementRegistration { | |
60 constructor(tagName) { | |
61 this.tagName = tagName; | |
62 this.attributes = new Map(); | |
63 this.eventHandlers = new Map(); | |
64 this.template = null; | |
65 Object.preventExtensions(this); | |
66 } | |
67 | |
68 allowsAttribute(name) { | |
69 if (isGlobalAttribute(name)) | |
70 return true; | |
71 if (this.attributes.has(name)) | |
72 return true; | |
73 return false; | |
74 } | |
75 | |
76 defineAttribute(name, type) { | |
77 var converter = attributeConverters[type]; | |
78 | |
79 if (!converter) { | |
80 console.error('Invalid attribute type "' + type + '", type must be one' | |
81 + ' of boolean, number or string.'); | |
82 return; | |
83 } | |
84 | |
85 this.attributes.set(name, converter); | |
86 } | |
87 | |
88 synthesizeAttributes(prototype) { | |
89 this.attributes.forEach(function(converter, name) { | |
90 Object.defineProperty(prototype, name, { | |
91 get: function() { | |
92 return converter(this.getAttribute(name)); | |
93 }, | |
94 set: function(newValue) { | |
95 this.setAttribute(name, converter(newValue)); | |
96 }, | |
97 enumerable: true, | |
98 configurable: true, | |
99 }); | |
100 }); | |
101 } | |
102 | |
103 addInstanceEventListeners(instance) { | |
104 for (var eventName of this.eventHandlers.keys()) { | |
105 instance.addEventListener(eventName, eventHandlerCallback); | |
106 } | |
107 } | |
108 } | |
109 | |
110 var registrations = new Map(); | |
111 | |
112 function registerElement(tagName, attributes) { | |
113 if (registrations.has(tagName)) | |
114 throw new Error('tagName "' + tagName + '" registered twice.'); | |
115 var registration = new ElementRegistration(tagName); | |
116 for (var name in attributes) { | |
117 registration.defineAttribute(name, attributes[name]); | |
118 } | |
119 registrations.set(tagName, registration); | |
120 return registration; | |
121 } | |
122 | |
123 function getRegistration(tagName) { | |
124 return registrations.get(tagName); | |
125 } | |
126 | |
127 function checkAttribute(tagName, attrName) { | |
128 var registration = getRegistration(tagName); | |
129 | |
130 if (!registration) | |
131 return isGlobalAttribute(attrName); | |
132 | |
133 return registration.allowsAttribute(attrName); | |
134 } | |
135 | |
136 registerElement('img', { | |
137 'width': 'number', | |
138 'height': 'number', | |
139 // TODO(esprehn): Sky probably doesn't want the crossorign attr. | |
140 'crossorigin': 'string', | |
141 'src': 'string', | |
142 }); | |
143 | |
144 registerElement('import', { | |
145 'as': 'string', | |
146 'src': 'string', | |
147 'async': 'boolean', | |
148 }); | |
149 | |
150 registerElement('a', { | |
151 'href': 'string', | |
152 'async': 'boolean', | |
153 }); | |
154 | |
155 registerElement('content', { | |
156 'select': 'string', | |
157 }); | |
158 | |
159 registerElement('style', { | |
160 'media': 'string', | |
161 }); | |
162 | |
163 registerElement('template', { | |
164 'if': 'string', | |
165 'repeat': 'string', | |
166 }); | |
167 | |
168 registerElement('iframe', { | |
169 'src': 'string', | |
170 }); | |
171 | |
172 module.exports = { | |
173 registerElement: registerElement, | |
174 getRegistration: getRegistration, | |
175 checkAttribute: checkAttribute, | |
176 isGlobalAttribute: isGlobalAttribute, | |
177 isExpandableAttribute: isExpandableAttribute, | |
178 }; | |
179 </script> | |
OLD | NEW |