mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This CL makes the list view in the stocks app more consistent with the material spec by using a colored circle instead of color-coding the percentage change. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/932103004
165 lines
4.5 KiB
Plaintext
165 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) {
|
|
try {
|
|
return double.parse(value);
|
|
} catch(_) {
|
|
return 0.0;
|
|
}
|
|
},
|
|
'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>
|