Index: sky/framework/sky-element.sky |
diff --git a/sky/framework/sky-element.sky b/sky/framework/sky-element.sky |
index 2fdc62ad65dcf10e6f0301995ed6ca375e637f41..e1d0a0ea715da04f9ea53e51ecceeb5be2b74d6f 100644 |
--- a/sky/framework/sky-element.sky |
+++ b/sky/framework/sky-element.sky |
@@ -7,9 +7,61 @@ |
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 { |
- Element template; |
+ 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>(); |
@@ -33,21 +85,21 @@ abstract class SkyElement extends Element { |
void shadowRootReady() {} |
String get tagName => _getTagName(runtimeType); |
+ _Registration _registration; |
SkyElement() { |
- created(); |
- |
+ _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) { |
- var registration = _registery[tagName]; |
- if (registration.template != null) { |
+ if (_registration.template != null) { |
ShadowRoot shadow = ensureShadowRoot(); |
- Node content = registration.template.content; |
+ Node content = _registration.template.content; |
shadow.appendChild(document.importNode(content, deep: true)); |
shadowRootReady(); |
} |
@@ -61,6 +113,30 @@ abstract class SkyElement extends Element { |
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); |
} |
} |
@@ -78,6 +154,7 @@ void register(Element script, Type type) { |
Element template = definition.querySelector('template'); |
document.registerElement(tagName, type); |
- _registery[tagName] = new _Registration(template); |
+ _registery[tagName] = new _Registration(template) |
+ ..parseAttributeSpec(definition); |
} |
</script> |