flutter_flutter/framework/sky-element.sky
Adam Barth b6f511c6a9 Port Sky widgets demo to Dart
This CL updates sky-box, sky-button, sky-checkbox, sky-input, and sky-radio to
work in Dart. We don't have a data binding system yet, so there's a bit more
plumbing in the code.

This CL adds support for sky-element@attributes, which lets you specify which
attributes your element supports. We use this information to synthesize getters
and setters for those attributes and to dispatch to mumbleChanged methods when
the attributes change.

I've also wrapped the widgets demo itself in a sky-scrollable so the whole
thing scrolls.

R=eseidel@chromium.org

Review URL: https://codereview.chromium.org/946813005
2015-02-23 08:30:36 -08:00

161 lines
4.5 KiB
Plaintext

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