Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(329)

Side by Side Diff: samples/webcomponents/shadow_polyfill.dart

Issue 10964030: A variety of cleanups to bring our samples up-to-date. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « samples/webcomponents/list_map.dart ('k') | utils/css/tree.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Polyfill script for custom elements. To use this script, your app must
7 * create a CustomElementsManager with the appropriate lookup function before
8 * doing any DOM queries or modifications.
9 * Currently, all custom elements must be registered with the polyfill. To
10 * register custom elements, provide the appropriate lookup function to your
11 * CustomElementsManager.
12 *
13 * This script only works at present in dart2js, but it should work in dartium
14 * soon (pending MutationObservers). The script does an HTTP request, so
15 * to test using locally defined custom elements you must run chrome with the
16 * flag -allow-file-access-from-files.
17 */
18
19 #library('webcomponents');
20
21 #import('dart:html');
22
23 #source('list_map.dart');
24
25 // typedefs
26 typedef WebComponent WebComponentFactory (ShadowRoot shadowRoot, Element elt);
27 typedef WebComponentFactory RegistryLookupFunction(String tagName);
28
29 // Globals
30 const int REQUEST_DONE = 4;
31 CustomElementsManager _manager;
32 CustomElementsManager get manager => _manager;
33
34 void initializeComponents(RegistryLookupFunction lookup) {
35 _manager = new CustomElementsManager._internal(lookup);
36 manager._loadComponents();
37 }
38
39 /** A Dart wrapper for a web component. */
40 abstract class WebComponent {
41 /** The web component element wrapped by this class. */
42 abstract Element get element;
43
44 /** Invoked when this component gets created. */
45 abstract void created();
46
47 /** Invoked when this component gets inserted in the DOM tree. */
48 abstract void inserted();
49
50 /** Invoked when any attribute of the component is modified. */
51 abstract void attributeChanged(
52 String name, String oldValue, String newValue);
53
54 /** Invoked when this component is removed from the DOM tree. */
55 abstract void removed();
56 }
57
58 /** Loads and manages the custom elements on a page. */
59 class CustomElementsManager {
60
61 /**
62 * Maps tag names to our internal dart representation of the custom element.
63 */
64 Map<String, _CustomDeclaration> _customDeclarations;
65
66 // TODO(samhop): evaluate possibility of using vsm's trick of storing
67 // arbitrary Dart objects directly on DOM objects rather than this map.
68 /** Maps DOM elements to the user-defiend corresponding dart objects. */
69 ListMap<Element, WebComponent> _customElements;
70
71 RegistryLookupFunction _lookup;
72
73 MutationObserver _insertionObserver;
74
75 CustomElementsManager._internal(this._lookup) {
76 // TODO(samhop): check for ShadowDOM support
77 _customDeclarations = <String, _CustomDeclaration>{};
78 // We use a ListMap because DOM objects aren't hashable right now.
79 // TODO(samhop): DOM objects (and everything else) should be hashable
80 _customElements = new ListMap<Element, WebComponent>();
81 initializeInsertedRemovedCallbacks(document);
82 }
83
84 /**
85 * Locate all external component files, load each of them, and expand
86 * declarations.
87 */
88 void _loadComponents() {
89 queryAll('link[rel=components]').forEach((link) => _load(link.href));
90 _expandDeclarations();
91 }
92
93 /**
94 * Load the document at the given url and parse it to extract
95 * custom element declarations.
96 */
97 void _load(String url) {
98 var request = new HttpRequest();
99 // We use a blocking request here because no Dart is allowed to run
100 // until DOM content is loaded.
101 // TODO(samhop): give a decent timeout message if custom elements fail to
102 // load
103 request.open('GET', url, async: false);
104 request.on.readyStateChange.add((Event e) {
105 if (request.readyState == REQUEST_DONE) {
106 if (request.status >= 200 && request.status < 300
107 || request.status == 304 || request.status == 0) {
108 var declarations = _parse(request.response);
109 declarations.forEach((declaration) {
110 _customDeclarations[declaration.name] = declaration;
111 });
112 } else {
113 window.console.error(
114 'Unable to load component: Status ${request.status}'
115 ' - ${request.statusText}');
116 }
117 }
118 });
119 request.send();
120 }
121
122 /** Parse the given string of HTML to extract the custom declarations. */
123 List<_CustomDeclaration> _parse(String toParse) {
124 var declarations = new DocumentFragment.html(toParse);
125 var newDeclarations = [];
126 declarations.queryAll('element').forEach((element) {
127 newDeclarations.add(new _CustomDeclaration(element));
128 });
129 return newDeclarations;
130 }
131
132 /**
133 * Look for all custom elements under [root] and expand them appropriately.
134 * This calls the [created] event on a webcomponent, but it will not insert
135 * the component in the DOM tree (and hence it won't call [inserted].
136 */
137 List expandDeclarations(Element root) =>
138 _expandDeclarations(root, insert: false);
139
140 /** Look for all custom elements uses and expand them appropriately. */
141 List _expandDeclarations([Element root, bool insert = true]) {
142 var newCustomElements = [];
143 var target;
144 var rootUnderTemplate = false;
145 if (root == null) {
146 target = document;
147 } else {
148 target = root;
149 rootUnderTemplate = root.matchesSelector('template *');
150 }
151 for (var declaration in _customDeclarations.getValues()) {
152 var selector = '${declaration.extendz}[is=${declaration.name}]';
153 var all = target.queryAll(selector);
154 // templates are innert and should not be expanded.
155 var activeElements = all.filter(
156 (e) => !e.matchesSelector('template *'));
157 if (root != null && root.matchesSelector(selector)
158 && !rootUnderTemplate) {
159 activeElements.add(root);
160 }
161 for (var e in activeElements) {
162 var component = _customElements[e];
163 if (component == null) {
164 component = declaration.morph(e);
165 newCustomElements.add(component);
166 }
167 if (insert) {
168 component.inserted();
169 }
170 }
171 }
172 return newCustomElements;
173 }
174
175
176 /** Fire the [removed] event on all web components under [root]. */
177 void _removeComponents(Element root) {
178 for (var decl in _customDeclarations.getValues()) {
179 for (var e in root.queryAll('${decl.extendz}[is=${decl.name}]')) {
180 if (_customElements[e] != null) _customElements[e].removed();
181 }
182 if (root.matchesSelector('${decl.extendz}[is=${decl.name}]')) {
183 if (_customElements[root] != null) _customElements[root].removed();
184 }
185 }
186 }
187
188 /**
189 * Expands the given html string into a custom element.
190 * Assumes only one element use in htmlString (or only returns the
191 * first one) and assumes corresponding custom element is already
192 * registered.
193 */
194 WebComponent expandHtml(String htmlString) {
195 return expandElement(new Element.html(htmlString));
196 }
197
198 /** Expand [element], assuming it is a webcomponent. */
199 WebComponent expandElement(Element element) {
200 var declaration = _customDeclarations[element.attributes['is']];
201 if (declaration == null) throw 'No such custom element declaration';
202 return declaration.morph(element);
203 }
204
205 WebComponent operator [](Element element) => _customElements[element];
206
207 // Initializes management of inserted and removed
208 // callbacks for WebComponents below root in the DOM. We need one of these
209 // for every shadow subtree, since mutation observers can't see across
210 // shadow boundaries.
211 void initializeInsertedRemovedCallbacks(Element root) {
212 _insertionObserver = new MutationObserver((mutations, observer) {
213 for (var mutation in mutations) {
214 // TODO(samhop): remove this test if it turns out that it always passes
215 if (mutation.type == 'childList') {
216 for (var node in mutation.addedNodes) {
217 if (node is Element) _expandDeclarations(node);
218 }
219 for (var node in mutation.removedNodes) {
220 if (node is Element) _removeComponents(node);
221 }
222 }
223 }
224 });
225 _insertionObserver.observe(root, childList: true, subtree: true);
226 }
227 }
228
229 bool _hasShadowRoot;
230
231 /**
232 * True if the browser supports the [ShadowRoot] element and it is enabled.
233 * See the [Shadow DOM spec](http://www.w3.org/TR/shadow-dom/) for more
234 * information about the ShadowRoot.
235 */
236 bool get hasShadowRoot {
237 if (_hasShadowRoot == null) {
238 try {
239 // TODO(jmesserly): it'd be nice if we could check this without causing
240 // an exception to be thrown.
241 new ShadowRoot(new DivElement());
242 _hasShadowRoot = true;
243 } catch (e) {
244 _hasShadowRoot = false;
245 // Hide <template> elements.
246 // TODO(jmesserly): This is a workaround because we don't distribute
247 // children correctly. It's not actually the right fix.
248 var style = new Element.html(
249 @'<style type="text/css">template { display: none; }</style>');
250 document.head.nodes.add(style);
251 }
252 }
253 return _hasShadowRoot;
254 }
255
256 class _CustomDeclaration {
257 String name;
258 String extendz;
259 Element template;
260 bool applyAuthorStyles;
261
262 _CustomDeclaration(Element element) {
263 name = element.attributes['name'];
264 applyAuthorStyles = element.attributes.containsKey('apply-author-styles');
265 if (name == null) {
266 // TODO(samhop): friendlier errors
267 window.console.error('name attribute is required');
268 return;
269 }
270 extendz = element.attributes['extends'];
271 if (extendz == null || extendz.length == 0) {
272 window.console.error('extends attribute is required');
273 return;
274 }
275 template = element.query('template');
276 }
277
278 int hashCode() {
279 return name.hashCode();
280 }
281
282 operator ==(other) {
283 if (other is! _CustomDeclaration) {
284 return false;
285 } else {
286 return other.name == name &&
287 other.extendz == extendz &&
288 other.template == template;
289 }
290 }
291
292 // TODO(samhop): better docs
293 /**
294 * Modify the DOM for e, return a new Dart object corresponding to it.
295 * Returns null if this custom declaration has no template element.
296 */
297 WebComponent morph(Element e) {
298 if (template == null) {
299 return null;
300 }
301
302 var shadowRoot;
303 if (hasShadowRoot) {
304 shadowRoot = new ShadowRoot(e);
305 shadowRoot.resetStyleInheritance = false;
306 if (applyAuthorStyles) {
307 shadowRoot.applyAuthorStyles = true;
308 }
309 } else {
310 // Remove the old ShadowRoot, if any
311 // TODO(jmesserly): can we avoid morphing the same node twice?
312 shadowRoot = e.query('.shadowroot');
313 if (shadowRoot != null && shadowRoot.parent == e) shadowRoot.remove();
314
315 // TODO(jmesserly): distribute children to insertion points.
316 shadowRoot = new Element.html('<div class="shadowroot"></div>');
317 e.nodes.add(shadowRoot);
318 }
319
320 template.nodes.forEach((node) => shadowRoot.nodes.add(node.clone(true)));
321 var newCustomElement = manager._lookup(this.name)(shadowRoot, e);
322 manager._customElements[e] = newCustomElement;
323 manager._expandDeclarations(shadowRoot, insert: false);
324 newCustomElement.created();
325 manager._expandDeclarations(shadowRoot, insert: true);
326
327 // TODO(samhop): investigate refactoring/redesigning the API so that
328 // components which don't need their attributes observed don't have an
329 // observer created, for perf reasons.
330 var attributeObserver = new MutationObserver((mutations, observer) {
331 for (var mutation in mutations) {
332 if (mutation.type == 'attributes') {
333 var attrName = mutation.attributeName;
334 Element element = mutation.target;
335 newCustomElement.attributeChanged(attrName,
336 mutation.oldValue, element.attributes[attrName]);
337 }
338 }
339 });
340 attributeObserver.observe(e, attributes: true, attributeOldValue: true);
341
342 // Listen for all insertions and deletions on the DOM so that we can
343 // catch custom elements being inserted and call the appropriate callbacks.
344 manager.initializeInsertedRemovedCallbacks(shadowRoot);
345 return newCustomElement;
346 }
347 }
OLDNEW
« no previous file with comments | « samples/webcomponents/list_map.dart ('k') | utils/css/tree.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698