mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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
161 lines
4.5 KiB
Plaintext
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>
|