blob: ecbc9667e0574db0b2738384946845e6bcbb0583 [file] [log] [blame]
<!--
// 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>