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