Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!-- | 1 <!-- |
| 2 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 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 <import src="sky-binder.sky" as="binder" /> | 6 <import src="sky-binder.sky" as="binder" /> |
| 7 <script> | 7 <script> |
| 8 var templates = new Map(); | |
| 9 | |
| 10 var attributeConverters = { | 8 var attributeConverters = { |
| 11 boolean: function(value) { | 9 boolean: function(value) { |
| 12 if (typeof value == 'string') | 10 if (typeof value == 'string') |
| 13 return value == 'true'; | 11 return value == 'true'; |
| 14 return !!value; | 12 return !!value; |
| 15 }, | 13 }, |
| 16 number: function(value) { | 14 number: function(value) { |
| 17 return Number(value); | 15 return Number(value); |
| 18 }, | 16 }, |
| 19 string: function(value) { | 17 string: function(value) { |
| 20 if (value === null) | 18 if (value === null) |
| 21 return ''; | 19 return ''; |
| 22 return String(value); | 20 return String(value); |
| 23 }, | 21 }, |
| 24 }; | 22 }; |
| 25 | 23 |
| 26 function defineReflectedAttribute(prototype, converter, name) { | 24 function parseAttributeSpec(spec) { |
| 27 Object.defineProperty(prototype, name, { | 25 var attributes = new Map(); |
| 28 get: function() { | 26 var attributeTokens = (spec || '').split(','); |
| 29 return converter(this.getAttribute(name)); | |
| 30 }, | |
| 31 set: function(newValue) { | |
| 32 this.setAttribute(name, converter(newValue)); | |
| 33 }, | |
| 34 enumerable: true, | |
| 35 configurable: true, | |
| 36 }); | |
| 37 | |
| 38 prototype[name + 'AttributeChanged'] = function(oldValue, newValue) { | |
| 39 this.notifyPropertyChanged(name, converter(oldValue), converter(newValue)); | |
| 40 }; | |
| 41 } | |
| 42 | |
| 43 function defineReflectedAttributes(elementClass, tagName, list) { | |
| 44 var attributeTokens = (list || '').split(','); | |
| 45 var attributeNames = []; | |
| 46 var prototype = elementClass.prototype; | |
| 47 | 27 |
| 48 for (var i = 0; i < attributeTokens.length; ++i) { | 28 for (var i = 0; i < attributeTokens.length; ++i) { |
| 49 var parts = attributeTokens[i].split(':'); | 29 var parts = attributeTokens[i].split(':'); |
| 50 var name = parts[0].trim(); | 30 var name = parts[0].trim(); |
| 51 var type = (parts[1] || '').trim(); | 31 var type = (parts[1] || '').trim(); |
| 52 var converter = attributeConverters[type] || attributeConverters.string; | 32 var converter = attributeConverters[type] || attributeConverters.string; |
| 53 | 33 |
| 54 attributeNames.push(name); | 34 attributes.set(name, converter); |
| 55 defineReflectedAttribute(prototype, converter, name); | |
| 56 } | 35 } |
| 57 | 36 |
| 58 binder.registerElement(tagName, { | 37 return attributes; |
| 59 attributeNames: attributeNames, | |
| 60 }); | |
| 61 } | 38 } |
| 62 | 39 |
| 40 function collectEventHandlers(definition) { | |
| 41 var eventHandlers = []; | |
| 42 var attributes = definition.getAttributes(); | |
| 43 | |
| 44 for (var i = 0; i < attributes.length; i++) { | |
| 45 var attr = attributes[i]; | |
| 46 var name = attr.name; | |
| 47 var value = attr.value; | |
| 48 | |
| 49 if (name.startsWith('on-')) { | |
| 50 eventHandlers.push(name.substring(3)); | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 return eventHandlers; | |
| 55 } | |
| 56 | |
| 57 function eventHandlerCallback(event) { | |
| 58 var element = event.currentTarget; | |
| 59 var registration = registrations.get(element.localName); | |
| 60 var method = registration.getEventHandler(event.type); | |
| 61 var handler = element[method]; | |
| 62 if (handler instanceof Function) | |
| 63 return handler.call(element, event); | |
| 64 } | |
| 65 | |
| 66 class ElementRegistration { | |
| 67 constructor(definition) { | |
| 68 this.definition = definition; | |
| 69 this.tagName = definition.getAttribute('name'); | |
| 70 this.attributes = parseAttributeSpec(definition.getAttribute('attributes')); | |
| 71 this.eventHandlers = collectEventHandlers(definition); | |
| 72 this.template = definition.querySelector('template'); | |
| 73 Object.preventExtensions(this); | |
| 74 } | |
| 75 getEventHandler(eventName) { | |
|
ojan
2015/01/13 01:02:46
Nit: I'd put an extra line break after each method
| |
| 76 return this.definition.getAttribute('on-' + eventName); | |
| 77 } | |
| 78 getAttributeNames() { | |
| 79 // TODO(esprehn): We can replace this method with | |
| 80 // Array.from(registration.attributes) once we turn that on. | |
| 81 var names = [] | |
| 82 this.attributes.forEach(function(converter, name) { | |
| 83 names.push(name); | |
| 84 }); | |
| 85 return names; | |
| 86 } | |
| 87 synthesizeAttributes(prototype) { | |
| 88 this.attributes.forEach(function(converter, name) { | |
| 89 Object.defineProperty(prototype, name, { | |
| 90 get: function() { | |
| 91 return converter(this.getAttribute(name)); | |
| 92 }, | |
| 93 set: function(newValue) { | |
| 94 this.setAttribute(name, converter(newValue)); | |
| 95 }, | |
| 96 enumerable: true, | |
| 97 configurable: true, | |
| 98 }); | |
| 99 }); | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 var registrations = new Map(); | |
| 104 | |
| 63 class SkyElement extends HTMLElement { | 105 class SkyElement extends HTMLElement { |
| 64 | 106 |
| 65 static register() { | 107 static register() { |
| 66 var wrapper = document.currentScript.parentNode; | 108 var definition = document.currentScript.parentNode; |
| 67 | 109 |
| 68 if (wrapper.localName !== 'sky-element') | 110 if (definition.localName !== 'sky-element') { |
| 69 throw new Error('No <sky-element>.'); | 111 throw new Error('register() calls must be inside a <sky-element>.'); |
| 112 } | |
| 70 | 113 |
| 71 var tagName = wrapper.getAttribute('name'); | 114 var registration = new ElementRegistration(definition); |
| 72 if (!tagName) | 115 |
| 116 if (!registration.tagName) { | |
| 73 throw new Error('<sky-element> must have a name.'); | 117 throw new Error('<sky-element> must have a name.'); |
| 118 } | |
| 74 | 119 |
| 75 var template = wrapper.querySelector('template'); | 120 if (registrations.has(registration.tagName)) { |
| 76 if (template) | 121 throw new Error('Duplicate registration for tag name: ' + |
| 77 templates.set(tagName, template); | 122 registration.tagName); |
| 123 } | |
| 78 | 124 |
| 79 defineReflectedAttributes(this, tagName, | 125 registration.synthesizeAttributes(this.prototype); |
| 80 wrapper.getAttribute('attributes')); | |
| 81 | 126 |
| 82 return document.registerElement(tagName, { | 127 // TODO(esprehn): Combine the two element registries here and in sky binder. |
| 128 binder.registerElement(registration.tagName, { | |
| 129 attributeNames: registration.getAttributeNames(), | |
| 130 }); | |
| 131 | |
| 132 registrations.set(registration.tagName, registration); | |
| 133 return document.registerElement(registration.tagName, { | |
| 83 prototype: this.prototype, | 134 prototype: this.prototype, |
| 84 }); | 135 }); |
| 85 } | 136 } |
| 86 | 137 |
| 87 created() { | 138 created() { |
| 88 // override | 139 // override |
| 89 } | 140 } |
| 90 | 141 |
| 91 attached() { | 142 attached() { |
| 92 // override | 143 // override |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 109 this.created(); | 160 this.created(); |
| 110 | 161 |
| 111 Object.preventExtensions(this); | 162 Object.preventExtensions(this); |
| 112 | 163 |
| 113 // Invoke attributeChanged callback when element is first created too. | 164 // Invoke attributeChanged callback when element is first created too. |
| 114 var attributes = this.getAttributes(); | 165 var attributes = this.getAttributes(); |
| 115 for (var i = 0; i < attributes.length; ++i) { | 166 for (var i = 0; i < attributes.length; ++i) { |
| 116 var attribute = attributes[i]; | 167 var attribute = attributes[i]; |
| 117 this.attributeChangedCallback(attribute.name, null, attribute.value); | 168 this.attributeChangedCallback(attribute.name, null, attribute.value); |
| 118 } | 169 } |
| 170 | |
| 171 var registration = registrations.get(this.localName); | |
| 172 for (var i = 0; i < registration.eventHandlers.length; ++i) { | |
| 173 var eventName = registration.eventHandlers[i]; | |
| 174 this.addEventListener(eventName, eventHandlerCallback); | |
| 175 } | |
| 119 } | 176 } |
| 120 | 177 |
| 121 attachedCallback() { | 178 attachedCallback() { |
| 122 if (!this.shadowRoot) { | 179 if (!this.shadowRoot) { |
| 123 var template = templates.get(this.localName); | 180 var registration = registrations.get(this.localName); |
| 124 if (template) { | 181 if (registration.template) { |
| 125 var shadow = this.ensureShadowRoot(); | 182 var shadow = this.ensureShadowRoot(); |
| 126 var instance = binder.createInstance(template, this); | 183 var instance = binder.createInstance(registration.template, this); |
| 127 shadow.appendChild(instance.fragment); | 184 shadow.appendChild(instance.fragment); |
| 128 this.shadowRootReady(); | 185 this.shadowRootReady(); |
| 129 } | 186 } |
| 130 } | 187 } |
| 131 this.attached(); | 188 this.attached(); |
| 132 this.isAttached = true; | 189 this.isAttached = true; |
| 133 } | 190 } |
| 134 | 191 |
| 135 detachedCallback() { | 192 detachedCallback() { |
| 136 this.detached(); | 193 this.detached(); |
| 137 this.isAttached = false; | 194 this.isAttached = false; |
| 138 } | 195 } |
| 139 | 196 |
| 140 attributeChangedCallback(name, oldValue, newValue) { | 197 attributeChangedCallback(name, oldValue, newValue) { |
| 141 this.attributeChanged(name, oldValue, newValue); | 198 this.attributeChanged(name, oldValue, newValue); |
| 142 var handler = this[name + 'AttributeChanged']; | 199 var registration = registrations.get(this.localName); |
| 143 if (typeof handler == 'function') | 200 var converter = registration.attributes.get(name); |
| 144 handler.call(this, oldValue, newValue); | 201 if (converter) { |
| 202 this.notifyPropertyChanged(name, converter(oldValue), | |
| 203 converter(newValue)); | |
| 204 } | |
| 145 } | 205 } |
| 146 | 206 |
| 147 notifyPropertyChanged(name, oldValue, newValue) { | 207 notifyPropertyChanged(name, oldValue, newValue) { |
| 148 var notifier = Object.getNotifier(this); | 208 var notifier = Object.getNotifier(this); |
| 149 notifier.notify({ | 209 notifier.notify({ |
| 150 type: 'update', | 210 type: 'update', |
| 151 name: name, | 211 name: name, |
| 152 oldValue: oldValue, | 212 oldValue: oldValue, |
| 153 }); | 213 }); |
| 154 var handler = this[name + 'Changed']; | 214 var handler = this[name + 'Changed']; |
| 155 if (typeof handler == 'function') | 215 if (typeof handler == 'function') |
| 156 handler.call(this, oldValue, newValue); | 216 handler.call(this, oldValue, newValue); |
| 157 } | 217 } |
| 158 }; | 218 }; |
| 159 | 219 |
| 160 module.exports = SkyElement; | 220 module.exports = SkyElement; |
| 161 </script> | 221 </script> |
| OLD | NEW |