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

Side by Side Diff: pkg/custom_element/lib/custom_element.dart

Issue 20886002: add custom_element package (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: remove unused dev dep Created 7 years, 4 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
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 * Custom Elements let authors define their own elements. Authors associate code
7 * with custom tag names, and then use those custom tag names as they would any
8 * standard tag. See <www.polymer-project.org/platform/custom-elements.html>
9 * for more information.
10 */
11 library custom_element;
12
13 import 'dart:async';
14 import 'dart:html';
15 import 'package:mdv/mdv.dart' as mdv;
16 import 'package:meta/meta.dart';
17 import 'src/custom_tag_name.dart';
18
19 // TODO(jmesserly): replace with a real custom element polyfill.
20 // This is just something temporary.
21 /**
22 * *Warning*: this implementation is a work in progress. It only implements
23 * the specification partially.
24 *
25 * Registers a custom HTML element with [localName] and the associated
26 * constructor. This will ensure the element is detected and
27 *
28 * See the specification at:
29 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html>
30 */
31 void registerCustomElement(String localName, CustomElement create()) {
32 if (_customElements == null) {
33 _customElements = {};
34 CustomElement.templateCreated.add(_createElements);
35 // TODO(jmesserly): use MutationObserver to watch for inserts?
36 }
37
38 if (!isCustomTag(localName)) {
39 throw new ArgumentError('$localName is not a valid custom element name, '
40 'it should have at least one dash and not be a reserved name.');
41 }
42
43 if (_customElements.containsKey(localName)) {
44 throw new ArgumentError('custom element $localName already registered.');
45 }
46
47 // TODO(jmesserly): validate this is a valid tag name, not a selector.
48 _customElements[localName] = create;
49
50 // Initialize elements already on the page.
51 for (var query in [localName, '[is=$localName]']) {
52 for (var element in document.queryAll(query)) {
53 _initCustomElement(element, create);
54 }
55 }
56 }
57
58 /**
59 * The base class for all Dart web components. In addition to the [Element]
60 * interface, it also provides lifecycle methods:
61 * - [created]
62 * - [inserted]
63 * - [attributeChanged]
64 * - [removed]
65 */
66 class CustomElement implements Element {
67 /** The web component element wrapped by this class. */
68 Element _host;
69 List _shadowRoots;
70
71 /**
72 * Shadow roots generated by dwc for each custom element, indexed by the
73 * custom element tag name.
74 */
75 Map<String, dynamic> _generatedRoots = {};
76
77 /**
78 * Temporary property until components extend [Element]. An element can
79 * only be associated with one host, and it is an error to use a web component
80 * without an associated host element.
81 */
82 Element get host {
83 if (_host == null) throw new StateError('host element has not been set.');
84 return _host;
85 }
86
87 set host(Element value) {
88 if (value == null) {
89 throw new ArgumentError('host must not be null.');
90 }
91 // TODO(jmesserly): xtag used to return "null" if unset, now it checks for
92 // "this". Temporarily allow both.
93 var xtag = value.xtag;
94 if (xtag != null && xtag != value) {
95 throw new ArgumentError('host must not have its xtag property set.');
96 }
97 if (_host != null) {
98 throw new StateError('host can only be set once.');
99 }
100
101 value.xtag = this;
102 _host = value;
103 }
104
105 /**
106 * **Note**: This is an implementation helper and should not need to be called
107 * from your code.
108 *
109 * Creates the [ShadowRoot] backing this component.
110 */
111 createShadowRoot([String componentName]) {
112 var root = host.createShadowRoot();
113 if (componentName != null) {
114 _generatedRoots[componentName] = root;
115 }
116 return root;
117 }
118
119 getShadowRoot(String componentName) => _generatedRoots[componentName];
120
121
122 /**
123 * *Warning*: This is an implementation helper for Custom Elements and
124 * should not be used in your code.
125 *
126 * Clones the template, instantiates custom elements and hooks events, then
127 * returns it.
128 */
129 DocumentFragment cloneTemplate(DocumentFragment shadowTemplate) {
130 var result = shadowTemplate.clone(true);
131 // TODO(jmesserly): should bindModel ensure this happens?
132 TemplateElement.bootstrap(result);
133 if (_templateCreated != null) {
134 for (var callback in _templateCreated) callback(result);
135 }
136 return result;
137 }
138
139 // TODO(jmesserly): ideally this would be a stream, but they don't allow
140 // reentrancy.
141 static Set<DocumentFragmentCreated> _templateCreated;
142
143 /**
144 * *Warning*: This is an implementation helper for Custom Elements and
145 * should not be used in your code.
146 *
147 * This event is fired whenever a template is instantiated via
148 * [cloneTemplate] or via [Element.createInstance]
149 */
150 // TODO(jmesserly): This is a hack, and is neccesary for the polyfill
151 // because custom elements are not upgraded during clone()
152 static Set<DocumentFragmentCreated> get templateCreated {
153 if (_templateCreated == null) {
154 _templateCreated = new Set<DocumentFragmentCreated>();
155 mdv.instanceCreated.listen((value) {
156 for (var callback in _templateCreated) callback(value);
157 });
158 }
159 return _templateCreated;
160 }
161 /**
162 * Invoked when this component gets created.
163 * Note that [root] will be a [ShadowRoot] if the browser supports Shadow DOM.
164 */
165 void created() {}
166
167 /** Invoked when this component gets inserted in the DOM tree. */
168 void inserted() {}
169
170 /** Invoked when this component is removed from the DOM tree. */
171 void removed() {}
172
173 // TODO(jmesserly): how do we implement this efficiently?
174 // See https://github.com/dart-lang/web-ui/issues/37
175 /** Invoked when any attribute of the component is modified. */
176 void attributeChanged(String name, String oldValue, String newValue) {}
177
178 get model => host.model;
179
180 void set model(newModel) {
181 host.model = newModel;
182 }
183
184 get templateInstance => host.templateInstance;
185 get isTemplate => host.isTemplate;
186 get ref => host.ref;
187 get content => host.content;
188 DocumentFragment createInstance(model, BindingDelegate delegate) =>
189 host.createInstance(model, delegate);
190 createBinding(String name, model, String path) =>
191 host.createBinding(name, model, path);
192 void bind(String name, model, String path) => host.bind(name, model, path);
193 void unbind(String name) => host.unbind(name);
194 void unbindAll() => host.unbindAll();
195 get bindings => host.bindings;
196 BindingDelegate get bindingDelegate => host.bindingDelegate;
197 set bindingDelegate(BindingDelegate value) { host.bindingDelegate = value; }
198
199 // TODO(jmesserly): this forwarding is temporary until Dart supports
200 // subclassing Elements.
201 // TODO(jmesserly): we were missing the setter for title, are other things
202 // missing setters?
203
204 List<Node> get nodes => host.nodes;
205
206 set nodes(Iterable<Node> value) { host.nodes = value; }
207
208 /**
209 * Replaces this node with another node.
210 */
211 Node replaceWith(Node otherNode) { host.replaceWith(otherNode); }
212
213 /**
214 * Removes this node from the DOM.
215 */
216 void remove() => host.remove();
217
218 Node get nextNode => host.nextNode;
219
220 String get nodeName => host.nodeName;
221
222 Document get document => host.document;
223
224 Node get previousNode => host.previousNode;
225
226 String get text => host.text;
227
228 set text(String v) { host.text = v; }
229
230 bool contains(Node other) => host.contains(other);
231
232 bool hasChildNodes() => host.hasChildNodes();
233
234 Node insertBefore(Node newChild, Node refChild) =>
235 host.insertBefore(newChild, refChild);
236
237 Node insertAllBefore(Iterable<Node> newChild, Node refChild) =>
238 host.insertAllBefore(newChild, refChild);
239
240 Map<String, String> get attributes => host.attributes;
241 set attributes(Map<String, String> value) {
242 host.attributes = value;
243 }
244
245 List<Element> get elements => host.children;
246
247 set elements(List<Element> value) {
248 host.children = value;
249 }
250
251 List<Element> get children => host.children;
252
253 set children(List<Element> value) {
254 host.children = value;
255 }
256
257 Set<String> get classes => host.classes;
258
259 set classes(Iterable<String> value) {
260 host.classes = value;
261 }
262
263 CssRect get contentEdge => host.contentEdge;
264 CssRect get paddingEdge => host.paddingEdge;
265 CssRect get borderEdge => host.borderEdge;
266 CssRect get marginEdge => host.marginEdge;
267 Point get documentOffset => host.documentOffset;
268 Point offsetTo(Element parent) => host.offsetTo(parent);
269
270 Map<String, String> getNamespacedAttributes(String namespace) =>
271 host.getNamespacedAttributes(namespace);
272
273 CssStyleDeclaration getComputedStyle([String pseudoElement])
274 => host.getComputedStyle(pseudoElement);
275
276 Element clone(bool deep) => host.clone(deep);
277
278 Element get parent => host.parent;
279
280 Node get parentNode => host.parentNode;
281
282 String get nodeValue => host.nodeValue;
283
284 @deprecated
285 // TODO(sigmund): restore the old return type and call host.on when
286 // dartbug.com/8131 is fixed.
287 dynamic get on { throw new UnsupportedError('on is deprecated'); }
288
289 String get contentEditable => host.contentEditable;
290 set contentEditable(String v) { host.contentEditable = v; }
291
292 String get dir => host.dir;
293 set dir(String v) { host.dir = v; }
294
295 bool get draggable => host.draggable;
296 set draggable(bool v) { host.draggable = v; }
297
298 bool get hidden => host.hidden;
299 set hidden(bool v) { host.hidden = v; }
300
301 String get id => host.id;
302 set id(String v) { host.id = v; }
303
304 String get innerHTML => host.innerHtml;
305
306 void set innerHTML(String v) {
307 host.innerHtml = v;
308 }
309
310 String get innerHtml => host.innerHtml;
311 void set innerHtml(String v) {
312 host.innerHtml = v;
313 }
314
315 bool get isContentEditable => host.isContentEditable;
316
317 String get lang => host.lang;
318 set lang(String v) { host.lang = v; }
319
320 String get outerHtml => host.outerHtml;
321
322 bool get spellcheck => host.spellcheck;
323 set spellcheck(bool v) { host.spellcheck = v; }
324
325 int get tabIndex => host.tabIndex;
326 set tabIndex(int i) { host.tabIndex = i; }
327
328 String get title => host.title;
329
330 set title(String value) { host.title = value; }
331
332 bool get translate => host.translate;
333 set translate(bool v) { host.translate = v; }
334
335 String get dropzone => host.dropzone;
336 set dropzone(String v) { host.dropzone = v; }
337
338 void click() { host.click(); }
339
340 InputMethodContext getInputContext() => host.getInputContext();
341
342 Element insertAdjacentElement(String where, Element element) =>
343 host.insertAdjacentElement(where, element);
344
345 void insertAdjacentHtml(String where, String html) {
346 host.insertAdjacentHtml(where, html);
347 }
348
349 void insertAdjacentText(String where, String text) {
350 host.insertAdjacentText(where, text);
351 }
352
353 Map<String, String> get dataset => host.dataset;
354
355 set dataset(Map<String, String> value) {
356 host.dataset = value;
357 }
358
359 Element get nextElementSibling => host.nextElementSibling;
360
361 Element get offsetParent => host.offsetParent;
362
363 Element get previousElementSibling => host.previousElementSibling;
364
365 CssStyleDeclaration get style => host.style;
366
367 String get tagName => host.tagName;
368
369 String get pseudo => host.pseudo;
370
371 void set pseudo(String value) {
372 host.pseudo = value;
373 }
374
375 // Note: we are not polyfilling the shadow root here. This will be fixed when
376 // we migrate to the JS Shadow DOM polyfills. You can still use getShadowRoot
377 // to retrieve a node that behaves as the shadow root when Shadow DOM is not
378 // enabled.
379 ShadowRoot get shadowRoot => host.shadowRoot;
380
381 void blur() { host.blur(); }
382
383 void focus() { host.focus(); }
384
385 void scrollByLines(int lines) {
386 host.scrollByLines(lines);
387 }
388
389 void scrollByPages(int pages) {
390 host.scrollByPages(pages);
391 }
392
393 void scrollIntoView([ScrollAlignment alignment]) {
394 host.scrollIntoView(alignment);
395 }
396
397 bool matches(String selectors) => host.matches(selectors);
398
399 @deprecated
400 void requestFullScreen(int flags) { requestFullscreen(); }
401
402 void requestFullscreen() { host.requestFullscreen(); }
403
404 void requestPointerLock() { host.requestPointerLock(); }
405
406 Element query(String selectors) => host.query(selectors);
407
408 ElementList queryAll(String selectors) => host.queryAll(selectors);
409
410 HtmlCollection get $dom_children => host.$dom_children;
411
412 int get $dom_childElementCount => host.$dom_childElementCount;
413
414 String get className => host.className;
415 set className(String value) { host.className = value; }
416
417 @deprecated
418 int get clientHeight => client.height;
419
420 @deprecated
421 int get clientLeft => client.left;
422
423 @deprecated
424 int get clientTop => client.top;
425
426 @deprecated
427 int get clientWidth => client.width;
428
429 Rect get client => host.client;
430
431 Element get $dom_firstElementChild => host.$dom_firstElementChild;
432
433 Element get $dom_lastElementChild => host.$dom_lastElementChild;
434
435 @deprecated
436 int get offsetHeight => offset.height;
437
438 @deprecated
439 int get offsetLeft => offset.left;
440
441 @deprecated
442 int get offsetTop => offset.top;
443
444 @deprecated
445 int get offsetWidth => offset.width;
446
447 Rect get offset => host.offset;
448
449 int get scrollHeight => host.scrollHeight;
450
451 int get scrollLeft => host.scrollLeft;
452
453 int get scrollTop => host.scrollTop;
454
455 set scrollLeft(int value) { host.scrollLeft = value; }
456
457 set scrollTop(int value) { host.scrollTop = value; }
458
459 int get scrollWidth => host.scrollWidth;
460
461 String $dom_getAttribute(String name) =>
462 host.$dom_getAttribute(name);
463
464 String $dom_getAttributeNS(String namespaceUri, String localName) =>
465 host.$dom_getAttributeNS(namespaceUri, localName);
466
467 String $dom_setAttributeNS(
468 String namespaceUri, String localName, String value) {
469 host.$dom_setAttributeNS(namespaceUri, localName, value);
470 }
471
472 bool $dom_hasAttributeNS(String namespaceUri, String localName) =>
473 host.$dom_hasAttributeNS(namespaceUri, localName);
474
475 void $dom_removeAttributeNS(String namespaceUri, String localName) =>
476 host.$dom_removeAttributeNS(namespaceUri, localName);
477
478 Rect getBoundingClientRect() => host.getBoundingClientRect();
479
480 List<Rect> getClientRects() => host.getClientRects();
481
482 List<Node> getElementsByClassName(String name) =>
483 host.getElementsByClassName(name);
484
485 List<Node> $dom_getElementsByTagName(String name) =>
486 host.$dom_getElementsByTagName(name);
487
488 bool $dom_hasAttribute(String name) =>
489 host.$dom_hasAttribute(name);
490
491 List<Node> $dom_querySelectorAll(String selectors) =>
492 host.$dom_querySelectorAll(selectors);
493
494 void $dom_removeAttribute(String name) =>
495 host.$dom_removeAttribute(name);
496
497 void $dom_setAttribute(String name, String value) =>
498 host.$dom_setAttribute(name, value);
499
500 get $dom_attributes => host.$dom_attributes;
501
502 List<Node> get $dom_childNodes => host.$dom_childNodes;
503
504 Node get firstChild => host.firstChild;
505
506 Node get lastChild => host.lastChild;
507
508 String get localName => host.localName;
509 String get $dom_localName => host.$dom_localName;
510
511 String get namespaceUri => host.namespaceUri;
512 String get $dom_namespaceUri => host.$dom_namespaceUri;
513
514 int get nodeType => host.nodeType;
515
516 void $dom_addEventListener(String type, EventListener listener,
517 [bool useCapture]) {
518 host.$dom_addEventListener(type, listener, useCapture);
519 }
520
521 bool dispatchEvent(Event event) => host.dispatchEvent(event);
522
523 Node $dom_removeChild(Node oldChild) => host.$dom_removeChild(oldChild);
524
525 void $dom_removeEventListener(String type, EventListener listener,
526 [bool useCapture]) {
527 host.$dom_removeEventListener(type, listener, useCapture);
528 }
529
530 Node $dom_replaceChild(Node newChild, Node oldChild) =>
531 host.$dom_replaceChild(newChild, oldChild);
532
533 get xtag => host.xtag;
534
535 set xtag(value) { host.xtag = value; }
536
537 Node append(Node e) => host.append(e);
538
539 void appendText(String text) => host.appendText(text);
540
541 void appendHtml(String html) => host.appendHtml(html);
542
543 void $dom_scrollIntoView([bool alignWithTop]) {
544 if (alignWithTop == null) {
545 host.$dom_scrollIntoView();
546 } else {
547 host.$dom_scrollIntoView(alignWithTop);
548 }
549 }
550
551 void $dom_scrollIntoViewIfNeeded([bool centerIfNeeded]) {
552 if (centerIfNeeded == null) {
553 host.$dom_scrollIntoViewIfNeeded();
554 } else {
555 host.$dom_scrollIntoViewIfNeeded(centerIfNeeded);
556 }
557 }
558
559 String get regionOverset => host.regionOverset;
560
561 List<Range> getRegionFlowRanges() => host.getRegionFlowRanges();
562
563 // TODO(jmesserly): rename "created" to "onCreated".
564 void onCreated() => created();
565
566 Stream<Event> get onAbort => host.onAbort;
567 Stream<Event> get onBeforeCopy => host.onBeforeCopy;
568 Stream<Event> get onBeforeCut => host.onBeforeCut;
569 Stream<Event> get onBeforePaste => host.onBeforePaste;
570 Stream<Event> get onBlur => host.onBlur;
571 Stream<Event> get onChange => host.onChange;
572 Stream<MouseEvent> get onClick => host.onClick;
573 Stream<MouseEvent> get onContextMenu => host.onContextMenu;
574 Stream<Event> get onCopy => host.onCopy;
575 Stream<Event> get onCut => host.onCut;
576 Stream<Event> get onDoubleClick => host.onDoubleClick;
577 Stream<MouseEvent> get onDrag => host.onDrag;
578 Stream<MouseEvent> get onDragEnd => host.onDragEnd;
579 Stream<MouseEvent> get onDragEnter => host.onDragEnter;
580 Stream<MouseEvent> get onDragLeave => host.onDragLeave;
581 Stream<MouseEvent> get onDragOver => host.onDragOver;
582 Stream<MouseEvent> get onDragStart => host.onDragStart;
583 Stream<MouseEvent> get onDrop => host.onDrop;
584 Stream<Event> get onError => host.onError;
585 Stream<Event> get onFocus => host.onFocus;
586 Stream<Event> get onInput => host.onInput;
587 Stream<Event> get onInvalid => host.onInvalid;
588 Stream<KeyboardEvent> get onKeyDown => host.onKeyDown;
589 Stream<KeyboardEvent> get onKeyPress => host.onKeyPress;
590 Stream<KeyboardEvent> get onKeyUp => host.onKeyUp;
591 Stream<Event> get onLoad => host.onLoad;
592 Stream<MouseEvent> get onMouseDown => host.onMouseDown;
593 Stream<MouseEvent> get onMouseMove => host.onMouseMove;
594 Stream<Event> get onFullscreenChange => host.onFullscreenChange;
595 Stream<Event> get onFullscreenError => host.onFullscreenError;
596 Stream<Event> get onPaste => host.onPaste;
597 Stream<Event> get onReset => host.onReset;
598 Stream<Event> get onScroll => host.onScroll;
599 Stream<Event> get onSearch => host.onSearch;
600 Stream<Event> get onSelect => host.onSelect;
601 Stream<Event> get onSelectStart => host.onSelectStart;
602 Stream<Event> get onSubmit => host.onSubmit;
603 Stream<MouseEvent> get onMouseOut => host.onMouseOut;
604 Stream<MouseEvent> get onMouseOver => host.onMouseOver;
605 Stream<MouseEvent> get onMouseUp => host.onMouseUp;
606 Stream<TouchEvent> get onTouchCancel => host.onTouchCancel;
607 Stream<TouchEvent> get onTouchEnd => host.onTouchEnd;
608 Stream<TouchEvent> get onTouchEnter => host.onTouchEnter;
609 Stream<TouchEvent> get onTouchLeave => host.onTouchLeave;
610 Stream<TouchEvent> get onTouchMove => host.onTouchMove;
611 Stream<TouchEvent> get onTouchStart => host.onTouchStart;
612 Stream<TransitionEvent> get onTransitionEnd => host.onTransitionEnd;
613
614 // TODO(sigmund): do the normal forwarding when dartbug.com/7919 is fixed.
615 Stream<WheelEvent> get onMouseWheel {
616 throw new UnsupportedError('onMouseWheel is not supported');
617 }
618 }
619
620
621 typedef DocumentFragmentCreated(DocumentFragment fragment);
622
623 Map<String, Function> _customElements;
624
625 void _createElements(Node node) {
626 for (var c = node.firstChild; c != null; c = c.nextNode) {
627 _createElements(c);
628 }
629 if (node is Element) {
630 var ctor = _customElements[node.localName];
631 if (ctor == null) {
632 var attr = node.attributes['is'];
633 if (attr != null) ctor = _customElements[attr];
634 }
635 if (ctor != null) _initCustomElement(node, ctor);
636 }
637 }
638
639 void _initCustomElement(Element node, CustomElement ctor()) {
640 CustomElement element = ctor();
641 element.host = node;
642
643 // TODO(jmesserly): replace lifecycle stuff with a proper polyfill.
644 element.created();
645
646 _registerLifecycleInsert(element);
647 }
648
649 void _registerLifecycleInsert(CustomElement element) {
650 runAsync(() {
651 // TODO(jmesserly): bottom up or top down insert?
652 var node = element.host;
653
654 // TODO(jmesserly): need a better check to see if the node has been removed.
655 if (node.parentNode == null) return;
656
657 _registerLifecycleRemove(element);
658 element.inserted();
659 });
660 }
661
662 void _registerLifecycleRemove(CustomElement element) {
663 // TODO(jmesserly): need fallback or polyfill for MutationObserver.
664 if (!MutationObserver.supported) return;
665
666 new MutationObserver((records, observer) {
667 var node = element.host;
668 for (var record in records) {
669 for (var removed in record.removedNodes) {
670 if (identical(node, removed)) {
671 observer.disconnect();
672 element.removed();
673 return;
674 }
675 }
676 }
677 }).observe(element.parentNode, childList: true);
678 }
OLDNEW
« no previous file with comments | « no previous file | pkg/custom_element/lib/src/custom_tag_name.dart » ('j') | pkg/custom_element/test/analyzer_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698