| <!-- |
| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| --> |
| <import src="sky-binder.sky" as="binder" /> |
| <import src="element-registry.sky" as="registry" /> |
| <script> |
| function parseAttributeSpec(registration, definition) { |
| var spec = definition.getAttribute('attributes'); |
| |
| if (!spec) |
| return; |
| |
| var attributeTokens = spec.split(','); |
| |
| for (var i = 0; i < attributeTokens.length; ++i) { |
| var parts = attributeTokens[i].split(':'); |
| |
| if (parts.length != 2) { |
| console.error('Invalid attribute spec "' + spec + '", attributes must' + |
| ' be {name}:{type}, where type is one of boolean, number or' + |
| ' string.'); |
| continue; |
| } |
| |
| var name = parts[0].trim(); |
| var type = parts[1].trim(); |
| |
| registration.defineAttribute(name, type); |
| } |
| } |
| |
| function parseEventHandlers(registration, definition) { |
| var eventHandlers = []; |
| var attributes = definition.getAttributes(); |
| |
| for (var i = 0; i < attributes.length; i++) { |
| var attr = attributes[i]; |
| var name = attr.name; |
| var value = attr.value; |
| |
| if (name.startsWith('on-')) { |
| registration.eventHandlers.set(name.substring(3), value); |
| } |
| } |
| } |
| |
| class SkyElement extends HTMLElement { |
| |
| static register() { |
| var definition = document.currentScript.parentNode; |
| |
| if (definition.localName !== 'sky-element') { |
| throw new Error('register() calls must be inside a <sky-element>.'); |
| } |
| |
| var tagName = definition.getAttribute('name'); |
| if (!tagName) { |
| throw new Error('<sky-element> must have a name.'); |
| } |
| |
| var registration = registry.registerElement(tagName); |
| |
| parseAttributeSpec(registration, definition); |
| parseEventHandlers(registration, definition); |
| |
| registration.template = definition.querySelector('template'); |
| |
| registration.synthesizeAttributes(this.prototype); |
| |
| return document.registerElement(tagName, { |
| prototype: this.prototype, |
| }); |
| } |
| |
| created() { |
| // override |
| } |
| |
| attached() { |
| // override |
| } |
| |
| detached() { |
| // override |
| } |
| |
| attributeChanged(attrName, oldValue, newValue) { |
| // override |
| } |
| |
| shadowRootReady() { |
| // override |
| } |
| |
| createdCallback() { |
| this.isAttached = false; |
| this.propertyBindings = null; |
| this.dirtyPropertyBindings = null; |
| this.created(); |
| |
| Object.preventExtensions(this); |
| |
| // Invoke attributeChanged callback when element is first created too. |
| var attributes = this.getAttributes(); |
| for (var i = 0; i < attributes.length; ++i) { |
| var attribute = attributes[i]; |
| this.attributeChangedCallback(attribute.name, null, attribute.value); |
| } |
| |
| var registration = registry.getRegistration(this.localName); |
| registration.addInstanceEventListeners(this); |
| } |
| |
| attachedCallback() { |
| if (!this.shadowRoot) { |
| var registration = registry.getRegistration(this.localName); |
| if (registration.template) { |
| var shadow = this.ensureShadowRoot(); |
| var instance = binder.createInstance(registration.template, this); |
| shadow.appendChild(instance.fragment); |
| this.shadowRootReady(); |
| } |
| } |
| this.attached(); |
| this.isAttached = true; |
| } |
| |
| detachedCallback() { |
| this.detached(); |
| this.isAttached = false; |
| } |
| |
| attributeChangedCallback(name, oldValue, newValue) { |
| this.attributeChanged(name, oldValue, newValue); |
| var registration = registry.getRegistration(this.localName); |
| var converter = registration.attributes.get(name); |
| if (converter) { |
| this.notifyPropertyChanged(name, converter(oldValue), |
| converter(newValue)); |
| } |
| } |
| |
| notifyPropertyChanged(name, oldValue, newValue) { |
| if (oldValue == newValue) |
| return; |
| var notifier = Object.getNotifier(this); |
| notifier.notify({ |
| type: 'update', |
| name: name, |
| oldValue: oldValue, |
| }); |
| var handler = this[name + 'Changed']; |
| if (typeof handler == 'function') |
| handler.call(this, oldValue, newValue); |
| this.schedulePropertyBindingUpdate(name); |
| } |
| |
| addPropertyBinding(name, binding) { |
| if (!this.propertyBindings) |
| this.propertyBindings = new Map(); |
| this.propertyBindings.set(name, binding); |
| } |
| |
| getPropertyBinding(name) { |
| if (!this.propertyBindings) |
| return null; |
| return this.propertyBindings.get(name); |
| } |
| |
| schedulePropertyBindingUpdate(name) { |
| if (!this.dirtyPropertyBindings) { |
| this.dirtyPropertyBindings = new Set(); |
| Promise.resolve().then(this.updatePropertyBindings.bind(this)); |
| } |
| this.dirtyPropertyBindings.add(name); |
| } |
| |
| updatePropertyBindings() { |
| for (var name of this.dirtyPropertyBindings) { |
| var binding = this.getPropertyBinding(name); |
| if (binding) { |
| binding.setValue(this[name]); |
| binding.discardChanges(); |
| } |
| } |
| this.dirtyPropertyBindings = null; |
| } |
| }; |
| |
| module.exports = SkyElement; |
| </script> |