| // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| class _FilteredElementList implements List { |
| final Node _node; |
| final List<Node> _childNodes; |
| |
| _FilteredElementList(Node node): _childNodes = node.nodes, _node = node; |
| |
| // We can't memoize this, since it's possible that children will be messed |
| // with externally to this class. |
| // |
| // TODO(nweiz): Do we really need to copy the list to make the types work out? |
| List<Element> get _filtered => |
| new List.from(_childNodes.filter((n) => n is Element)); |
| |
| void forEach(void f(Element element)) { |
| _filtered.forEach(f); |
| } |
| |
| void operator []=(int index, Element value) { |
| this[index].replaceWith(value); |
| } |
| |
| void set length(int newLength) { |
| final len = this.length; |
| if (newLength >= len) { |
| return; |
| } else if (newLength < 0) { |
| throw new ArgumentError("Invalid list length"); |
| } |
| |
| removeRange(newLength - 1, len - newLength); |
| } |
| |
| void add(Element value) { |
| _childNodes.add(value); |
| } |
| |
| void addAll(Collection<Element> collection) { |
| collection.forEach(add); |
| } |
| |
| void addLast(Element value) { |
| add(value); |
| } |
| |
| bool contains(Element element) { |
| return element is Element && _childNodes.contains(element); |
| } |
| |
| void sort([Comparator<Element> compare = Comparable.compare]) { |
| throw new UnsupportedError('TODO(jacobr): should we impl?'); |
| } |
| |
| void setRange(int start, int rangeLength, List from, [int startFrom = 0]) { |
| throw new UnimplementedError(); |
| } |
| |
| void removeRange(int start, int rangeLength) { |
| _filtered.getRange(start, rangeLength).forEach((el) => el.remove()); |
| } |
| |
| void insertRange(int start, int rangeLength, [initialValue = null]) { |
| throw new UnimplementedError(); |
| } |
| |
| void clear() { |
| // Currently, ElementList#clear clears even non-element nodes, so we follow |
| // that behavior. |
| _childNodes.clear(); |
| } |
| |
| Element removeLast() { |
| final result = this.last; |
| if (result != null) { |
| result.remove(); |
| } |
| return result; |
| } |
| |
| Collection map(f(Element element)) => _filtered.map(f); |
| Collection<Element> filter(bool f(Element element)) => _filtered.filter(f); |
| bool every(bool f(Element element)) => _filtered.every(f); |
| bool some(bool f(Element element)) => _filtered.some(f); |
| bool get isEmpty => _filtered.isEmpty; |
| int get length => _filtered.length; |
| Element operator [](int index) => _filtered[index]; |
| Iterator<Element> iterator() => _filtered.iterator(); |
| List<Element> getRange(int start, int rangeLength) => |
| _filtered.getRange(start, rangeLength); |
| int indexOf(Element element, [int start = 0]) => |
| _filtered.indexOf(element, start); |
| |
| int lastIndexOf(Element element, [int start = null]) { |
| if (start == null) start = length - 1; |
| return _filtered.lastIndexOf(element, start); |
| } |
| |
| Element get last => _filtered.last; |
| } |
| |
| Future<CSSStyleDeclaration> _emptyStyleFuture() { |
| return _createMeasurementFuture(() => new Element.tag('div').style, |
| new Completer<CSSStyleDeclaration>()); |
| } |
| |
| class EmptyElementRect implements ElementRect { |
| final ClientRect client = const _SimpleClientRect(0, 0, 0, 0); |
| final ClientRect offset = const _SimpleClientRect(0, 0, 0, 0); |
| final ClientRect scroll = const _SimpleClientRect(0, 0, 0, 0); |
| final ClientRect bounding = const _SimpleClientRect(0, 0, 0, 0); |
| final List<ClientRect> clientRects = const <ClientRect>[]; |
| |
| const EmptyElementRect(); |
| } |
| |
| class _FrozenCssClassSet extends _CssClassSet { |
| _FrozenCssClassSet() : super(null); |
| |
| void _write(Set s) { |
| throw new UnsupportedError( |
| 'frozen class set cannot be modified'); |
| } |
| Set<String> _read() => new Set<String>(); |
| |
| bool get frozen => true; |
| } |
| |
| class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |
| factory $CLASSNAME() => _$(CLASSNAME)FactoryProvider.createDocumentFragment(); |
| |
| factory $CLASSNAME.html(String html) => |
| _$(CLASSNAME)FactoryProvider.createDocumentFragment_html(html); |
| |
| factory $CLASSNAME.svg(String svg) => |
| new _$(CLASSNAME)FactoryProvider.createDocumentFragment_svg(svg); |
| |
| List<Element> _elements; |
| |
| List<Element> get elements { |
| if (_elements == null) { |
| _elements = new _FilteredElementList(this); |
| } |
| return _elements; |
| } |
| |
| // TODO: The type of value should be Collection<Element>. See http://b/5392897 |
| void set elements(value) { |
| // Copy list first since we don't want liveness during iteration. |
| List copy = new List.from(value); |
| final elements = this.elements; |
| elements.clear(); |
| elements.addAll(copy); |
| } |
| |
| Element query(String selectors) => $dom_querySelector(selectors); |
| |
| List<Element> queryAll(String selectors) => |
| new _FrozenElementList._wrap($dom_querySelectorAll(selectors)); |
| |
| String get innerHTML { |
| final e = new Element.tag("div"); |
| e.nodes.add(this.clone(true)); |
| return e.innerHTML; |
| } |
| |
| String get outerHTML => innerHTML; |
| |
| // TODO(nweiz): Do we want to support some variant of innerHTML for XML and/or |
| // SVG strings? |
| void set innerHTML(String value) { |
| this.nodes.clear(); |
| |
| final e = new Element.tag("div"); |
| e.innerHTML = value; |
| |
| // Copy list first since we don't want liveness during iteration. |
| List nodes = new List.from(e.nodes); |
| this.nodes.addAll(nodes); |
| } |
| |
| Node _insertAdjacentNode(String where, Node node) { |
| switch (where.toLowerCase()) { |
| case "beforebegin": return null; |
| case "afterend": return null; |
| case "afterbegin": |
| var first = this.nodes.length > 0 ? this.nodes[0] : null; |
| this.insertBefore(node, first); |
| return node; |
| case "beforeend": |
| this.nodes.add(node); |
| return node; |
| default: |
| throw new ArgumentError("Invalid position ${where}"); |
| } |
| } |
| |
| Element insertAdjacentElement(String where, Element element) |
| => this._insertAdjacentNode(where, element); |
| |
| void insertAdjacentText(String where, String text) { |
| this._insertAdjacentNode(where, new Text(text)); |
| } |
| |
| void insertAdjacentHTML(String where, String text) { |
| this._insertAdjacentNode(where, new DocumentFragment.html(text)); |
| } |
| |
| void addText(String text) { |
| this.insertAdjacentText('beforeend', text); |
| } |
| |
| void addHTML(String text) { |
| this.insertAdjacentHTML('beforeend', text); |
| } |
| |
| Future<ElementRect> get rect { |
| return _createMeasurementFuture(() => const EmptyElementRect(), |
| new Completer<ElementRect>()); |
| } |
| |
| // If we can come up with a semi-reasonable default value for an Element |
| // getter, we'll use it. In general, these return the same values as an |
| // element that has no parent. |
| String get contentEditable => "false"; |
| bool get isContentEditable => false; |
| bool get draggable => false; |
| bool get hidden => false; |
| bool get spellcheck => false; |
| bool get translate => false; |
| int get tabIndex => -1; |
| String get id => ""; |
| String get title => ""; |
| String get tagName => ""; |
| String get webkitdropzone => ""; |
| String get webkitRegionOverflow => ""; |
| Element get $m_firstElementChild { |
| if (elements.length > 0) { |
| return elements[0]; |
| } |
| return null; |
| } |
| Element get $m_lastElementChild() => elements.last; |
| Element get nextElementSibling => null; |
| Element get previousElementSibling => null; |
| Element get offsetParent => null; |
| Element get parent => null; |
| Map<String, String> get attributes => const {}; |
| CssClassSet get classes => new _FrozenCssClassSet(); |
| Map<String, String> get dataAttributes => const {}; |
| CSSStyleDeclaration get style => new Element.tag('div').style; |
| Future<CSSStyleDeclaration> get computedStyle => |
| _emptyStyleFuture(); |
| Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) => |
| _emptyStyleFuture(); |
| bool matchesSelector(String selectors) => false; |
| |
| // Imperative Element methods are made into no-ops, as they are on parentless |
| // elements. |
| void blur() {} |
| void focus() {} |
| void click() {} |
| void scrollByLines(int lines) {} |
| void scrollByPages(int pages) {} |
| void scrollIntoView([bool centerIfNeeded]) {} |
| void webkitRequestFullScreen(int flags) {} |
| void webkitRequestFullscreen() {} |
| |
| // Setters throw errors rather than being no-ops because we aren't going to |
| // retain the values that were set, and erroring out seems clearer. |
| void set attributes(Map<String, String> value) { |
| throw new UnsupportedError( |
| "Attributes can't be set for document fragments."); |
| } |
| |
| void set classes(Collection<String> value) { |
| throw new UnsupportedError( |
| "Classes can't be set for document fragments."); |
| } |
| |
| void set dataAttributes(Map<String, String> value) { |
| throw new UnsupportedError( |
| "Data attributes can't be set for document fragments."); |
| } |
| |
| void set contentEditable(String value) { |
| throw new UnsupportedError( |
| "Content editable can't be set for document fragments."); |
| } |
| |
| String get dir { |
| throw new UnsupportedError( |
| "Document fragments don't support text direction."); |
| } |
| |
| void set dir(String value) { |
| throw new UnsupportedError( |
| "Document fragments don't support text direction."); |
| } |
| |
| void set draggable(bool value) { |
| throw new UnsupportedError( |
| "Draggable can't be set for document fragments."); |
| } |
| |
| void set hidden(bool value) { |
| throw new UnsupportedError( |
| "Hidden can't be set for document fragments."); |
| } |
| |
| void set id(String value) { |
| throw new UnsupportedError( |
| "ID can't be set for document fragments."); |
| } |
| |
| String get lang { |
| throw new UnsupportedError( |
| "Document fragments don't support language."); |
| } |
| |
| void set lang(String value) { |
| throw new UnsupportedError( |
| "Document fragments don't support language."); |
| } |
| |
| void set scrollLeft(int value) { |
| throw new UnsupportedError( |
| "Document fragments don't support scrolling."); |
| } |
| |
| void set scrollTop(int value) { |
| throw new UnsupportedError( |
| "Document fragments don't support scrolling."); |
| } |
| |
| void set spellcheck(bool value) { |
| throw new UnsupportedError( |
| "Spellcheck can't be set for document fragments."); |
| } |
| |
| void set translate(bool value) { |
| throw new UnsupportedError( |
| "Spellcheck can't be set for document fragments."); |
| } |
| |
| void set tabIndex(int value) { |
| throw new UnsupportedError( |
| "Tab index can't be set for document fragments."); |
| } |
| |
| void set title(String value) { |
| throw new UnsupportedError( |
| "Title can't be set for document fragments."); |
| } |
| |
| void set webkitdropzone(String value) { |
| throw new UnsupportedError( |
| "WebKit drop zone can't be set for document fragments."); |
| } |
| |
| void set webkitRegionOverflow(String value) { |
| throw new UnsupportedError( |
| "WebKit region overflow can't be set for document fragments."); |
| } |
| |
| $!MEMBERS |
| } |