blob: e1d0a0ea715da04f9ea53e51ecceeb5be2b74d6f [file] [log] [blame]
<!--
// Copyright 2015 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.
-->
<script>
import "dart:mirrors";
import "dart:sky";
typedef dynamic _Converter(String value);
final Map<String, _Converter> _kAttributeConverters = {
'boolean': (String value) {
return value == 'true';
},
'number': (String value) {
return double.parse(value);
},
'string': (String value) {
return value == null ? '' : value;
},
};
class _Registration {
final Element template;
final Map<String, _Converter> attributes = new Map();
_Registration(this.template);
void parseAttributeSpec(definition) {
String spec = definition.getAttribute('attributes');
if (spec == null)
return;
for (String token in spec.split(',')) {
List<String> parts = token.split(':');
if (parts.length != 2) {
window.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();
defineAttribute(name, type);
}
}
void defineAttribute(String name, String type) {
_Converter converter = _kAttributeConverters[type];
if (converter == null) {
window.console.error(
'Invalid attribute type "${type}", type must be one of boolean,'
' number or string.');
return;
}
attributes[name] = converter;
}
}
final Map<String, _Registration> _registery = new Map<String, _Registration>();
class Tagname {
final String name;
const Tagname(this.name);
}
String _getTagName(Type type) {
return reflectClass(type).metadata.firstWhere(
(i) => i.reflectee is Tagname).reflectee.name;
}
abstract class SkyElement extends Element {
// Override these functions to receive lifecycle notifications.
void created() {}
void attached() {}
void detached() {}
void attributeChanged(String attrName, String oldValue, String newValue) {}
void shadowRootReady() {}
String get tagName => _getTagName(runtimeType);
_Registration _registration;
SkyElement() {
_registration = _registery[tagName];
// Invoke attributeChanged callback when element is first created too.
// TODO(abarth): Is this necessary? We shouldn't have any attribute yet...
for (Attr attribute in getAttributes())
attributeChangedCallback(attribute.name, null, attribute.value);
}
attachedCallback() {
if (shadowRoot == null) {
if (_registration.template != null) {
ShadowRoot shadow = ensureShadowRoot();
Node content = _registration.template.content;
shadow.appendChild(document.importNode(content, deep: true));
shadowRootReady();
}
}
attached();
}
detachedCallback() {
detached();
}
attributeChangedCallback(name, oldValue, newValue) {
attributeChanged(name, oldValue, newValue);
_Converter converter = _registration.attributes[name];
if (converter == null)
return;
Symbol callback = new Symbol('${name}Changed');
InstanceMirror mirror = reflect(this);
if (mirror.type.instanceMembers.containsKey(callback))
mirror.invoke(callback, [converter(oldValue), converter(newValue)]);
}
noSuchMethod(Invocation invocation) {
String name = MirrorSystem.getName(invocation.memberName);
if (name.endsWith('='))
name = name.substring(0, name.length - 1);
_Converter converter = _registration.attributes[name];
if (converter != null) {
if (invocation.isGetter) {
return converter(getAttribute(name));
} else if (invocation.isSetter) {
setAttribute(name, invocation.positionalArguments[0].toString());
return;
}
}
return super.noSuchMethod(invocation);
}
}
void register(Element script, Type type) {
Element definition = script.parentNode;
if (definition.tagName != 'sky-element')
throw new UnsupportedError('register() calls must be inside a <sky-element>.');
ClassMirror mirror = reflectClass(type);
if (!mirror.isSubclassOf(reflectClass(SkyElement)))
throw new UnsupportedError('@Tagname can only be used on descendants of SkyElement');
String tagName = _getTagName(type);
Element template = definition.querySelector('template');
document.registerElement(tagName, type);
_registery[tagName] = new _Registration(template)
..parseAttributeSpec(definition);
}
</script>