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

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

Powered by Google App Engine
This is Rietveld 408576698