| 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 |
| 76 getEventHandler(eventName) { |
| 77 return this.definition.getAttribute('on-' + eventName); |
| 78 } |
| 79 |
| 80 getAttributeNames() { |
| 81 // TODO(esprehn): We can replace this method with |
| 82 // Array.from(registration.attributes) once we turn that on. |
| 83 var names = [] |
| 84 this.attributes.forEach(function(converter, name) { |
| 85 names.push(name); |
| 86 }); |
| 87 return names; |
| 88 } |
| 89 |
| 90 synthesizeAttributes(prototype) { |
| 91 this.attributes.forEach(function(converter, name) { |
| 92 Object.defineProperty(prototype, name, { |
| 93 get: function() { |
| 94 return converter(this.getAttribute(name)); |
| 95 }, |
| 96 set: function(newValue) { |
| 97 this.setAttribute(name, converter(newValue)); |
| 98 }, |
| 99 enumerable: true, |
| 100 configurable: true, |
| 101 }); |
| 102 }); |
| 103 } |
| 104 } |
| 105 |
| 106 var registrations = new Map(); |
| 107 |
| 63 class SkyElement extends HTMLElement { | 108 class SkyElement extends HTMLElement { |
| 64 | 109 |
| 65 static register() { | 110 static register() { |
| 66 var wrapper = document.currentScript.parentNode; | 111 var definition = document.currentScript.parentNode; |
| 67 | 112 |
| 68 if (wrapper.localName !== 'sky-element') | 113 if (definition.localName !== 'sky-element') { |
| 69 throw new Error('No <sky-element>.'); | 114 throw new Error('register() calls must be inside a <sky-element>.'); |
| 115 } |
| 70 | 116 |
| 71 var tagName = wrapper.getAttribute('name'); | 117 var registration = new ElementRegistration(definition); |
| 72 if (!tagName) | 118 |
| 119 if (!registration.tagName) { |
| 73 throw new Error('<sky-element> must have a name.'); | 120 throw new Error('<sky-element> must have a name.'); |
| 121 } |
| 74 | 122 |
| 75 var template = wrapper.querySelector('template'); | 123 if (registrations.has(registration.tagName)) { |
| 76 if (template) | 124 throw new Error('Duplicate registration for tag name: ' + |
| 77 templates.set(tagName, template); | 125 registration.tagName); |
| 126 } |
| 78 | 127 |
| 79 defineReflectedAttributes(this, tagName, | 128 registration.synthesizeAttributes(this.prototype); |
| 80 wrapper.getAttribute('attributes')); | |
| 81 | 129 |
| 82 return document.registerElement(tagName, { | 130 // TODO(esprehn): Combine the two element registries here and in sky binder. |
| 131 binder.registerElement(registration.tagName, { |
| 132 attributeNames: registration.getAttributeNames(), |
| 133 }); |
| 134 |
| 135 registrations.set(registration.tagName, registration); |
| 136 return document.registerElement(registration.tagName, { |
| 83 prototype: this.prototype, | 137 prototype: this.prototype, |
| 84 }); | 138 }); |
| 85 } | 139 } |
| 86 | 140 |
| 87 created() { | 141 created() { |
| 88 // override | 142 // override |
| 89 } | 143 } |
| 90 | 144 |
| 91 attached() { | 145 attached() { |
| 92 // override | 146 // override |
| (...skipping 16 matching lines...) Expand all Loading... |
| 109 this.created(); | 163 this.created(); |
| 110 | 164 |
| 111 Object.preventExtensions(this); | 165 Object.preventExtensions(this); |
| 112 | 166 |
| 113 // Invoke attributeChanged callback when element is first created too. | 167 // Invoke attributeChanged callback when element is first created too. |
| 114 var attributes = this.getAttributes(); | 168 var attributes = this.getAttributes(); |
| 115 for (var i = 0; i < attributes.length; ++i) { | 169 for (var i = 0; i < attributes.length; ++i) { |
| 116 var attribute = attributes[i]; | 170 var attribute = attributes[i]; |
| 117 this.attributeChangedCallback(attribute.name, null, attribute.value); | 171 this.attributeChangedCallback(attribute.name, null, attribute.value); |
| 118 } | 172 } |
| 173 |
| 174 var registration = registrations.get(this.localName); |
| 175 for (var i = 0; i < registration.eventHandlers.length; ++i) { |
| 176 var eventName = registration.eventHandlers[i]; |
| 177 this.addEventListener(eventName, eventHandlerCallback); |
| 178 } |
| 119 } | 179 } |
| 120 | 180 |
| 121 attachedCallback() { | 181 attachedCallback() { |
| 122 if (!this.shadowRoot) { | 182 if (!this.shadowRoot) { |
| 123 var template = templates.get(this.localName); | 183 var registration = registrations.get(this.localName); |
| 124 if (template) { | 184 if (registration.template) { |
| 125 var shadow = this.ensureShadowRoot(); | 185 var shadow = this.ensureShadowRoot(); |
| 126 var instance = binder.createInstance(template, this); | 186 var instance = binder.createInstance(registration.template, this); |
| 127 shadow.appendChild(instance.fragment); | 187 shadow.appendChild(instance.fragment); |
| 128 this.shadowRootReady(); | 188 this.shadowRootReady(); |
| 129 } | 189 } |
| 130 } | 190 } |
| 131 this.attached(); | 191 this.attached(); |
| 132 this.isAttached = true; | 192 this.isAttached = true; |
| 133 } | 193 } |
| 134 | 194 |
| 135 detachedCallback() { | 195 detachedCallback() { |
| 136 this.detached(); | 196 this.detached(); |
| 137 this.isAttached = false; | 197 this.isAttached = false; |
| 138 } | 198 } |
| 139 | 199 |
| 140 attributeChangedCallback(name, oldValue, newValue) { | 200 attributeChangedCallback(name, oldValue, newValue) { |
| 141 this.attributeChanged(name, oldValue, newValue); | 201 this.attributeChanged(name, oldValue, newValue); |
| 142 var handler = this[name + 'AttributeChanged']; | 202 var registration = registrations.get(this.localName); |
| 143 if (typeof handler == 'function') | 203 var converter = registration.attributes.get(name); |
| 144 handler.call(this, oldValue, newValue); | 204 if (converter) { |
| 205 this.notifyPropertyChanged(name, converter(oldValue), |
| 206 converter(newValue)); |
| 207 } |
| 145 } | 208 } |
| 146 | 209 |
| 147 notifyPropertyChanged(name, oldValue, newValue) { | 210 notifyPropertyChanged(name, oldValue, newValue) { |
| 148 var notifier = Object.getNotifier(this); | 211 var notifier = Object.getNotifier(this); |
| 149 notifier.notify({ | 212 notifier.notify({ |
| 150 type: 'update', | 213 type: 'update', |
| 151 name: name, | 214 name: name, |
| 152 oldValue: oldValue, | 215 oldValue: oldValue, |
| 153 }); | 216 }); |
| 154 var handler = this[name + 'Changed']; | 217 var handler = this[name + 'Changed']; |
| 155 if (typeof handler == 'function') | 218 if (typeof handler == 'function') |
| 156 handler.call(this, oldValue, newValue); | 219 handler.call(this, oldValue, newValue); |
| 157 } | 220 } |
| 158 }; | 221 }; |
| 159 | 222 |
| 160 module.exports = SkyElement; | 223 module.exports = SkyElement; |
| 161 </script> | 224 </script> |
| OLD | NEW |