Elliott Sprehn 8291177a0c Combine element registries from sky-binder and sky-element.
We now have an element-registry.sky module that exposes API to register
elements with attributes and event handlers. Right now it's an API on
the module itself, eventually the module will export an ElementRegistry
object since we'll need one per module once the custom element registry
is one per module.

R=abarth@chromium.org, ojan@chromium.org

Review URL: https://codereview.chromium.org/856693002
2015-01-15 20:31:32 -08:00

194 lines
4.9 KiB
Plaintext

<!--
// 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>