// 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. // TODO(jacobr): use Lists.dart to remove some of the duplicated functionality. class _ChildrenElementList implements ElementList { // Raw Element. final _element; final _childElements; _ChildrenElementList._wrap(var element) : _childElements = element.children, _element = element; List _toList() { final output = new List(_childElements.length); for (int i = 0, len = _childElements.length; i < len; i++) { output[i] = LevelDom.wrapElement(_childElements[i]); } return output; } Element get first() { return LevelDom.wrapElement(_element.firstElementChild); } void forEach(void f(Element element)) { for (var element in _childElements) { f(LevelDom.wrapElement(element)); } } Collection filter(bool f(Element element)) { List output = new List(); forEach((Element element) { if (f(element)) { output.add(element); } }); return output; } bool every(bool f(Element element)) { for(Element element in this) { if (!f(element)) { return false; } }; return true; } bool some(bool f(Element element)) { for(Element element in this) { if (f(element)) { return true; } }; return false; } bool isEmpty() { return _element.firstElementChild !== null; } int get length() { return _childElements.length; } Element operator [](int index) { return LevelDom.wrapElement(_childElements[index]); } void operator []=(int index, Element value) { _element.replaceChild(LevelDom.unwrap(value), _childElements.item(index)); } void set length(int newLength) { // TODO(jacobr): remove children when length is reduced. throw const UnsupportedOperationException(''); } Element add(Element value) { _element.appendChild(LevelDom.unwrap(value)); return value; } Element addLast(Element value) => add(value); Iterator iterator() => _toList().iterator(); void addAll(Collection collection) { for (Element element in collection) { _element.appendChild(LevelDom.unwrap(element)); } } void sort(int compare(Element a, Element b)) { throw const UnsupportedOperationException('TODO(jacobr): should we impl?'); } void copyFrom(List src, int srcStart, int dstStart, int count) { throw 'Not impl yet. todo(jacobr)'; } void setRange(int start, int length, List from, [int startFrom = 0]) { throw const NotImplementedException(); } void removeRange(int start, int length) { throw const NotImplementedException(); } void insertRange(int start, int length, [initialValue = null]) { throw const NotImplementedException(); } List getRange(int start, int length) { throw const NotImplementedException(); } int indexOf(Element element, [int start = 0]) { return _Lists.indexOf(this, element, start, this.length); } int lastIndexOf(Element element, [int start = null]) { if (start === null) start = length - 1; return _Lists.lastIndexOf(this, element, start); } void clear() { // It is unclear if we want to keep non element nodes? _element.textContent = ''; } Element removeLast() { final last = this.last(); if (last != null) { _element.removeChild(LevelDom.unwrap(last)); } return last; } Element last() { return LevelDom.wrapElement(_element.lastElementChild); } } class FrozenElementList implements ElementList { final _ptr; FrozenElementList._wrap(this._ptr); Element get first() { return this[0]; } void forEach(void f(Element element)) { final length = _ptr.length; for (var i = 0; i < length; i++) { f(LevelDom.wrapElement(_ptr[i])); } } Collection filter(bool f(Element element)) { throw 'Not impl yet. todo(jacobr)'; } bool every(bool f(Element element)) { throw 'Not impl yet. todo(jacobr)'; } bool some(bool f(Element element)) { throw 'Not impl yet. todo(jacobr)'; } bool isEmpty() { return _ptr.length == 0; } int get length() { return _ptr.length; } Element operator [](int index) { return LevelDom.wrapElement(_ptr[index]); } void operator []=(int index, Element value) { throw const UnsupportedOperationException(''); } void set length(int newLength) { throw const UnsupportedOperationException(''); } void add(Element value) { throw const UnsupportedOperationException(''); } void addLast(Element value) { throw const UnsupportedOperationException(''); } Iterator iterator() => new FrozenElementListIterator(this); void addAll(Collection collection) { throw const UnsupportedOperationException(''); } void sort(int compare(Element a, Element b)) { throw const UnsupportedOperationException(''); } void copyFrom(List src, int srcStart, int dstStart, int count) { throw 'Not impl yet. todo(jacobr)'; } void setRange(int start, int length, List from, [int startFrom = 0]) { throw const NotImplementedException(); } void removeRange(int start, int length) { throw const NotImplementedException(); } void insertRange(int start, int length, [initialValue = null]) { throw const NotImplementedException(); } List getRange(int start, int length) { throw const NotImplementedException(); } int indexOf(Element element, [int start = 0]) { throw 'Not impl yet. todo(jacobr)'; } int lastIndexOf(Element element, [int start = null]) { throw 'Not impl yet. todo(jacobr)'; } void clear() { throw 'Not impl yet. todo(jacobr)'; } Element removeLast() { throw 'Not impl yet. todo(jacobr)'; } Element last() { return this[length-1]; } } class FrozenElementListIterator implements Iterator { final FrozenElementList _list; int _index = 0; FrozenElementListIterator(this._list); /** * Gets the next element in the iteration. Throws a * [NoMoreElementsException] if no element is left. */ Element next() { if (!hasNext()) { throw const NoMoreElementsException(); } return _list[_index++]; } /** * Returns whether the [Iterator] has elements left. */ bool hasNext() => _index < _list.length; } class ElementAttributeMap implements Map { final _element; ElementAttributeMap._wrap(this._element); bool containsValue(String value) { final attributes = _element.attributes; for (int i = 0, len = attributes.length; i < len; i++) { if(value == attributes.item(i).value) { return true; } } return false; } bool containsKey(String key) { return _element.hasAttribute(key); } String operator [](String key) { return _element.getAttribute(key); } void operator []=(String key, String value) { _element.setAttribute(key, value); } String putIfAbsent(String key, String ifAbsent()) { if (!containsKey(key)) { this[key] = ifAbsent(); } } String remove(String key) { _element.removeAttribute(key); } void clear() { final attributes = _element.attributes; for (int i = attributes.length - 1; i >= 0; i--) { _element.removeAttribute(attributes.item(i).name); } } void forEach(void f(String key, String value)) { final attributes = _element.attributes; for (int i = 0, len = attributes.length; i < len; i++) { final item = attributes.item(i); f(item.name, item.value); } } Collection getKeys() { // TODO(jacobr): generate a lazy collection instead. final attributes = _element.attributes; final keys = new List(attributes.length); for (int i = 0, len = attributes.length; i < len; i++) { keys[i] = attributes.item(i).name; } return keys; } Collection getValues() { // TODO(jacobr): generate a lazy collection instead. final attributes = _element.attributes; final values = new List(attributes.length); for (int i = 0, len = attributes.length; i < len; i++) { values[i] = attributes.item(i).value; } return values; } /** * The number of {key, value} pairs in the map. */ int get length() { return _element.attributes.length; } /** * Returns true if there is no {key, value} pair in the map. */ bool isEmpty() { return !_element.hasAttributes(); } } class ElementEventsImplementation extends EventsImplementation implements ElementEvents { ElementEventsImplementation._wrap(_ptr) : super._wrap(_ptr); EventListenerList get abort() => _get("abort"); EventListenerList get beforeCopy() => _get("beforecopy"); EventListenerList get beforeCut() => _get("beforecut"); EventListenerList get beforePaste() => _get("beforepaste"); EventListenerList get blur() => _get("blur"); EventListenerList get change() => _get("change"); EventListenerList get click() => _get("click"); EventListenerList get contextMenu() => _get("contextmenu"); EventListenerList get copy() => _get("copy"); EventListenerList get cut() => _get("cut"); EventListenerList get dblClick() => _get("dblclick"); EventListenerList get drag() => _get("drag"); EventListenerList get dragEnd() => _get("dragend"); EventListenerList get dragEnter() => _get("dragenter"); EventListenerList get dragLeave() => _get("dragleave"); EventListenerList get dragOver() => _get("dragover"); EventListenerList get dragStart() => _get("dragstart"); EventListenerList get drop() => _get("drop"); EventListenerList get error() => _get("error"); EventListenerList get focus() => _get("focus"); EventListenerList get input() => _get("input"); EventListenerList get invalid() => _get("invalid"); EventListenerList get keyDown() => _get("keydown"); EventListenerList get keyPress() => _get("keypress"); EventListenerList get keyUp() => _get("keyup"); EventListenerList get load() => _get("load"); EventListenerList get mouseDown() => _get("mousedown"); EventListenerList get mouseMove() => _get("mousemove"); EventListenerList get mouseOut() => _get("mouseout"); EventListenerList get mouseOver() => _get("mouseover"); EventListenerList get mouseUp() => _get("mouseup"); EventListenerList get mouseWheel() => _get("mousewheel"); EventListenerList get paste() => _get("paste"); EventListenerList get reset() => _get("reset"); EventListenerList get scroll() => _get("scroll"); EventListenerList get search() => _get("search"); EventListenerList get select() => _get("select"); EventListenerList get selectStart() => _get("selectstart"); EventListenerList get submit() => _get("submit"); EventListenerList get touchCancel() => _get("touchcancel"); EventListenerList get touchEnd() => _get("touchend"); EventListenerList get touchLeave() => _get("touchleave"); EventListenerList get touchMove() => _get("touchmove"); EventListenerList get touchStart() => _get("touchstart"); EventListenerList get transitionEnd() => _get("webkitTransitionEnd"); EventListenerList get fullscreenChange() => _get("webkitfullscreenchange"); } class SimpleClientRect implements ClientRect { final num left; final num top; final num width; final num height; num get right() => left + width; num get bottom() => top + height; const SimpleClientRect(this.left, this.top, this.width, this.height); bool operator ==(ClientRect other) { return other !== null && left == other.left && top == other.top && width == other.width && height == other.height; } String toString() => "($left, $top, $width, $height)"; } // TODO(jacobr): we cannot currently be lazy about calculating the client // rects as we must perform all measurement queries at a safe point to avoid // triggering unneeded layouts. /** * All your element measurement needs in one place * @domName none */ class ElementRectWrappingImplementation implements ElementRect { final ClientRect client; final ClientRect offset; final ClientRect scroll; // TODO(jacobr): should we move these outside of ElementRect to avoid the // overhead of computing them every time even though they are rarely used. // This should be type dom.ClientRect but that fails on dartium. b/5522629 final _boundingClientRect; // an exception due to a dartium bug. final _clientRects; // TODO(jacobr): should be dom.ClientRectList ElementRectWrappingImplementation(dom.HTMLElement element) : client = new SimpleClientRect(element.clientLeft, element.clientTop, element.clientWidth, element.clientHeight), offset = new SimpleClientRect(element.offsetLeft, element.offsetTop, element.offsetWidth, element.offsetHeight), scroll = new SimpleClientRect(element.scrollLeft, element.scrollTop, element.scrollWidth, element.scrollHeight), _boundingClientRect = element.getBoundingClientRect(), _clientRects = element.getClientRects(); ClientRect get bounding() => LevelDom.wrapClientRect(_boundingClientRect); List get clientRects() { final out = new List(_clientRects.length); for (num i = 0; i < _clientRects.length; i++) { out[i] = LevelDom.wrapClientRect(_clientRects.item(i)); } return out; } } final _START_TAG_REGEXP = const RegExp('<(\\w+)'); /** @domName Element, HTMLElement */ class ElementWrappingImplementation extends NodeWrappingImplementation implements Element { static final _CUSTOM_PARENT_TAG_MAP = const { 'body' : 'html', 'head' : 'html', 'caption' : 'table', 'td': 'tr', 'tbody': 'table', 'colgroup': 'table', 'col' : 'colgroup', 'tr' : 'tbody', 'tbody' : 'table', 'tfoot' : 'table', 'thead' : 'table', 'track' : 'audio', }; /** @domName Document.createElement */ factory ElementWrappingImplementation.html(String html) { // TODO(jacobr): this method can be made more robust and performant. // 1) Cache the dummy parent elements required to use innerHTML rather than // creating them every call. // 2) Verify that the html does not contain leading or trailing text nodes. // 3) Verify that the html does not contain both and tags. // 4) Detatch the created element from its dummy parent. String parentTag = 'div'; String tag; final match = _START_TAG_REGEXP.firstMatch(html); if (match !== null) { tag = match.group(1).toLowerCase(); if (_CUSTOM_PARENT_TAG_MAP.containsKey(tag)) { parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; } } // TODO(jacobr): make type dom.HTMLElement when dartium allows it. var temp = dom.document.createElement(parentTag); temp.innerHTML = html; if (temp.childElementCount == 1) { return LevelDom.wrapElement(temp.firstElementChild); } else if (parentTag == 'html' && temp.childElementCount == 2) { // Work around for edge case in WebKit and possibly other browsers where // both body and head elements are created even though the inner html // only contains a head or body element. return LevelDom.wrapElement(temp.children.item(tag == 'head' ? 0 : 1)); } else { throw new IllegalArgumentException('HTML had ${temp.childElementCount} ' + 'top level elements but 1 expected'); } } /** @domName Document.createElement */ factory ElementWrappingImplementation.tag(String tag) { return LevelDom.wrapElement(dom.document.createElement(tag)); } ElementWrappingImplementation._wrap(ptr) : super._wrap(ptr); ElementAttributeMap _elementAttributeMap; ElementList _elements; _CssClassSet _cssClassSet; _DataAttributeMap _dataAttributes; /** * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute, * Element.removeAttribute */ Map get attributes() { if (_elementAttributeMap === null) { _elementAttributeMap = new ElementAttributeMap._wrap(_ptr); } return _elementAttributeMap; } void set attributes(Map value) { Map attributes = this.attributes; attributes.clear(); for (String key in value.getKeys()) { attributes[key] = value[key]; } } void set elements(Collection value) { final elements = this.elements; elements.clear(); elements.addAll(value); } /** * @domName childElementCount, firstElementChild, lastElementChild, * children, appendChild */ ElementList get elements() { if (_elements == null) { _elements = new _ChildrenElementList._wrap(_ptr); } return _elements; } /** @domName className, classList */ Set get classes() { if (_cssClassSet === null) { _cssClassSet = new _CssClassSet(_ptr); } return _cssClassSet; } void set classes(Collection value) { _CssClassSet classSet = classes; classSet.clear(); classSet.addAll(value); } Map get dataAttributes() { if (_dataAttributes === null) { _dataAttributes = new _DataAttributeMap(attributes); } return _dataAttributes; } void set dataAttributes(Map value) { Map dataAttributes = this.dataAttributes; dataAttributes.clear(); for (String key in value.getKeys()) { dataAttributes[key] = value[key]; } } String get contentEditable() => _ptr.contentEditable; void set contentEditable(String value) { _ptr.contentEditable = value; } String get dir() => _ptr.dir; void set dir(String value) { _ptr.dir = value; } bool get draggable() => _ptr.draggable; void set draggable(bool value) { _ptr.draggable = value; } Element get firstElementChild() => LevelDom.wrapElement(_ptr.firstElementChild); bool get hidden() => _ptr.hidden; void set hidden(bool value) { _ptr.hidden = value; } String get id() => _ptr.id; void set id(String value) { _ptr.id = value; } String get innerHTML() => _ptr.innerHTML; void set innerHTML(String value) { _ptr.innerHTML = value; } bool get isContentEditable() => _ptr.isContentEditable; String get lang() => _ptr.lang; void set lang(String value) { _ptr.lang = value; } Element get lastElementChild() => LevelDom.wrapElement(_ptr.lastElementChild); Element get nextElementSibling() => LevelDom.wrapElement(_ptr.nextElementSibling); Element get offsetParent() => LevelDom.wrapElement(_ptr.offsetParent); String get outerHTML() => _ptr.outerHTML; Element get previousElementSibling() => LevelDom.wrapElement(_ptr.previousElementSibling); bool get spellcheck() => _ptr.spellcheck; void set spellcheck(bool value) { _ptr.spellcheck = value; } CSSStyleDeclaration get style() => LevelDom.wrapCSSStyleDeclaration(_ptr.style); int get tabIndex() => _ptr.tabIndex; void set tabIndex(int value) { _ptr.tabIndex = value; } String get tagName() => _ptr.tagName; String get title() => _ptr.title; void set title(String value) { _ptr.title = value; } String get webkitdropzone() => _ptr.webkitdropzone; void set webkitdropzone(String value) { _ptr.webkitdropzone = value; } void blur() { _ptr.blur(); } bool contains(Node element) { return _ptr.contains(LevelDom.unwrap(element)); } void focus() { _ptr.focus(); } Element insertAdjacentElement([String where = null, Element element = null]) { return LevelDom.wrapElement(_ptr.insertAdjacentElement(where, LevelDom.unwrap(element))); } void insertAdjacentHTML([String position_OR_where = null, String text = null]) { _ptr.insertAdjacentHTML(position_OR_where, text); } void insertAdjacentText([String where = null, String text = null]) { _ptr.insertAdjacentText(where, text); } /** @domName querySelector, Document.getElementById */ Element query(String selectors) { // TODO(jacobr): scope fix. return LevelDom.wrapElement(_ptr.querySelector(selectors)); } /** * @domName querySelectorAll, getElementsByClassName, getElementsByTagName, * getElementsByTagNameNS */ ElementList queryAll(String selectors) { // TODO(jacobr): scope fix. return new FrozenElementList._wrap(_ptr.querySelectorAll(selectors)); } void scrollByLines([int lines = null]) { _ptr.scrollByLines(lines); } void scrollByPages([int pages = null]) { _ptr.scrollByPages(pages); } /** @domName scrollIntoView, scrollIntoViewIfNeeded */ void scrollIntoView([bool centerIfNeeded = null]) { _ptr.scrollIntoViewIfNeeded(centerIfNeeded); } bool matchesSelector([String selectors = null]) { return _ptr.webkitMatchesSelector(selectors); } void set scrollLeft(int value) { _ptr.scrollLeft = value; } void set scrollTop(int value) { _ptr.scrollTop = value; } /** * @domName getClientRects, getBoundingClientRect, clientHeight, clientWidth, * clientTop, clientLeft, offsetHeight, offsetWidth, offsetTop, offsetLeft, * scrollHeight, scrollWidth, scrollTop, scrollLeft */ Future get rect() { return _createMeasurementFuture( () => new ElementRectWrappingImplementation(_ptr), new Completer()); } /** @domName Window.getComputedStyle */ Future get computedStyle() { // TODO(jacobr): last param should be null, see b/5045788 return getComputedStyle(''); } /** @domName Window.getComputedStyle */ Future getComputedStyle(String pseudoElement) { return _createMeasurementFuture(() => LevelDom.wrapCSSStyleDeclaration( dom.window.getComputedStyle(_ptr, pseudoElement)), new Completer()); } ElementEvents get on() { if (_on === null) { _on = new ElementEventsImplementation._wrap(_ptr); } return _on; } Element clone(bool deep) => super.clone(deep); }