| OLD | NEW |
| 1 <!-- | 1 <!-- |
| 2 // Copyright 2015 The Chromium Authors. All rights reserved. | 2 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 // Use of this source code is governed by a BSD-style license that can be | 3 // Use of this source code is governed by a BSD-style license that can be |
| 4 // found in the LICENSE file. | 4 // found in the LICENSE file. |
| 5 --> | 5 --> |
| 6 <script> | 6 <script> |
| 7 import "dart:mirrors"; | 7 import "dart:mirrors"; |
| 8 import "dart:sky"; | 8 import "dart:sky"; |
| 9 | 9 |
| 10 typedef dynamic _Converter(String value); |
| 11 |
| 12 final Map<String, _Converter> _kAttributeConverters = { |
| 13 'boolean': (String value) { |
| 14 return value == 'true'; |
| 15 }, |
| 16 'number': (String value) { |
| 17 return double.parse(value); |
| 18 }, |
| 19 'string': (String value) { |
| 20 return value == null ? '' : value; |
| 21 }, |
| 22 }; |
| 23 |
| 10 class _Registration { | 24 class _Registration { |
| 11 Element template; | 25 final Element template; |
| 26 final Map<String, _Converter> attributes = new Map(); |
| 27 |
| 12 _Registration(this.template); | 28 _Registration(this.template); |
| 29 |
| 30 void parseAttributeSpec(definition) { |
| 31 String spec = definition.getAttribute('attributes'); |
| 32 if (spec == null) |
| 33 return; |
| 34 |
| 35 for (String token in spec.split(',')) { |
| 36 List<String> parts = token.split(':'); |
| 37 |
| 38 if (parts.length != 2) { |
| 39 window.console.error( |
| 40 'Invalid attribute spec "${spec}", attributes must' |
| 41 ' be {name}:{type}, where type is one of boolean, number or' |
| 42 ' string.'); |
| 43 continue; |
| 44 } |
| 45 |
| 46 var name = parts[0].trim(); |
| 47 var type = parts[1].trim(); |
| 48 |
| 49 defineAttribute(name, type); |
| 50 } |
| 51 } |
| 52 |
| 53 void defineAttribute(String name, String type) { |
| 54 _Converter converter = _kAttributeConverters[type]; |
| 55 |
| 56 if (converter == null) { |
| 57 window.console.error( |
| 58 'Invalid attribute type "${type}", type must be one of boolean,' |
| 59 ' number or string.'); |
| 60 return; |
| 61 } |
| 62 |
| 63 attributes[name] = converter; |
| 64 } |
| 13 } | 65 } |
| 14 | 66 |
| 15 final Map<String, _Registration> _registery = new Map<String, _Registration>(); | 67 final Map<String, _Registration> _registery = new Map<String, _Registration>(); |
| 16 | 68 |
| 17 class Tagname { | 69 class Tagname { |
| 18 final String name; | 70 final String name; |
| 19 const Tagname(this.name); | 71 const Tagname(this.name); |
| 20 } | 72 } |
| 21 | 73 |
| 22 String _getTagName(Type type) { | 74 String _getTagName(Type type) { |
| 23 return reflectClass(type).metadata.firstWhere( | 75 return reflectClass(type).metadata.firstWhere( |
| 24 (i) => i.reflectee is Tagname).reflectee.name; | 76 (i) => i.reflectee is Tagname).reflectee.name; |
| 25 } | 77 } |
| 26 | 78 |
| 27 abstract class SkyElement extends Element { | 79 abstract class SkyElement extends Element { |
| 28 // Override these functions to receive lifecycle notifications. | 80 // Override these functions to receive lifecycle notifications. |
| 29 void created() {} | 81 void created() {} |
| 30 void attached() {} | 82 void attached() {} |
| 31 void detached() {} | 83 void detached() {} |
| 32 void attributeChanged(String attrName, String oldValue, String newValue) {} | 84 void attributeChanged(String attrName, String oldValue, String newValue) {} |
| 33 void shadowRootReady() {} | 85 void shadowRootReady() {} |
| 34 | 86 |
| 35 String get tagName => _getTagName(runtimeType); | 87 String get tagName => _getTagName(runtimeType); |
| 88 _Registration _registration; |
| 36 | 89 |
| 37 SkyElement() { | 90 SkyElement() { |
| 38 created(); | 91 _registration = _registery[tagName]; |
| 39 | |
| 40 // Invoke attributeChanged callback when element is first created too. | 92 // Invoke attributeChanged callback when element is first created too. |
| 93 // TODO(abarth): Is this necessary? We shouldn't have any attribute yet... |
| 41 for (Attr attribute in getAttributes()) | 94 for (Attr attribute in getAttributes()) |
| 42 attributeChangedCallback(attribute.name, null, attribute.value); | 95 attributeChangedCallback(attribute.name, null, attribute.value); |
| 43 } | 96 } |
| 44 | 97 |
| 45 attachedCallback() { | 98 attachedCallback() { |
| 46 if (shadowRoot == null) { | 99 if (shadowRoot == null) { |
| 47 var registration = _registery[tagName]; | 100 if (_registration.template != null) { |
| 48 if (registration.template != null) { | |
| 49 ShadowRoot shadow = ensureShadowRoot(); | 101 ShadowRoot shadow = ensureShadowRoot(); |
| 50 Node content = registration.template.content; | 102 Node content = _registration.template.content; |
| 51 shadow.appendChild(document.importNode(content, deep: true)); | 103 shadow.appendChild(document.importNode(content, deep: true)); |
| 52 shadowRootReady(); | 104 shadowRootReady(); |
| 53 } | 105 } |
| 54 } | 106 } |
| 55 attached(); | 107 attached(); |
| 56 } | 108 } |
| 57 | 109 |
| 58 detachedCallback() { | 110 detachedCallback() { |
| 59 detached(); | 111 detached(); |
| 60 } | 112 } |
| 61 | 113 |
| 62 attributeChangedCallback(name, oldValue, newValue) { | 114 attributeChangedCallback(name, oldValue, newValue) { |
| 63 attributeChanged(name, oldValue, newValue); | 115 attributeChanged(name, oldValue, newValue); |
| 116 |
| 117 _Converter converter = _registration.attributes[name]; |
| 118 if (converter == null) |
| 119 return; |
| 120 Symbol callback = new Symbol('${name}Changed'); |
| 121 InstanceMirror mirror = reflect(this); |
| 122 if (mirror.type.instanceMembers.containsKey(callback)) |
| 123 mirror.invoke(callback, [converter(oldValue), converter(newValue)]); |
| 124 } |
| 125 |
| 126 noSuchMethod(Invocation invocation) { |
| 127 String name = MirrorSystem.getName(invocation.memberName); |
| 128 if (name.endsWith('=')) |
| 129 name = name.substring(0, name.length - 1); |
| 130 _Converter converter = _registration.attributes[name]; |
| 131 if (converter != null) { |
| 132 if (invocation.isGetter) { |
| 133 return converter(getAttribute(name)); |
| 134 } else if (invocation.isSetter) { |
| 135 setAttribute(name, invocation.positionalArguments[0].toString()); |
| 136 return; |
| 137 } |
| 138 } |
| 139 return super.noSuchMethod(invocation); |
| 64 } | 140 } |
| 65 } | 141 } |
| 66 | 142 |
| 67 void register(Element script, Type type) { | 143 void register(Element script, Type type) { |
| 68 Element definition = script.parentNode; | 144 Element definition = script.parentNode; |
| 69 | 145 |
| 70 if (definition.tagName != 'sky-element') | 146 if (definition.tagName != 'sky-element') |
| 71 throw new UnsupportedError('register() calls must be inside a <sky-element>.
'); | 147 throw new UnsupportedError('register() calls must be inside a <sky-element>.
'); |
| 72 | 148 |
| 73 ClassMirror mirror = reflectClass(type); | 149 ClassMirror mirror = reflectClass(type); |
| 74 if (!mirror.isSubclassOf(reflectClass(SkyElement))) | 150 if (!mirror.isSubclassOf(reflectClass(SkyElement))) |
| 75 throw new UnsupportedError('@Tagname can only be used on descendants of SkyE
lement'); | 151 throw new UnsupportedError('@Tagname can only be used on descendants of SkyE
lement'); |
| 76 | 152 |
| 77 String tagName = _getTagName(type); | 153 String tagName = _getTagName(type); |
| 78 Element template = definition.querySelector('template'); | 154 Element template = definition.querySelector('template'); |
| 79 | 155 |
| 80 document.registerElement(tagName, type); | 156 document.registerElement(tagName, type); |
| 81 _registery[tagName] = new _Registration(template); | 157 _registery[tagName] = new _Registration(template) |
| 158 ..parseAttributeSpec(definition); |
| 82 } | 159 } |
| 83 </script> | 160 </script> |
| OLD | NEW |