| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, 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 // TODO(jacobr): use Lists.dart to remove some of the duplicated functionality. | |
| 6 class _ChildrenElementList implements ElementList { | |
| 7 // Raw Element. | |
| 8 final _ElementJs _element; | |
| 9 final _HTMLCollectionJs _childElements; | |
| 10 | |
| 11 _ChildrenElementList._wrap(_ElementJs element) | |
| 12 : _childElements = element._children, | |
| 13 _element = element; | |
| 14 | |
| 15 List<Element> _toList() { | |
| 16 final output = new List(_childElements.length); | |
| 17 for (int i = 0, len = _childElements.length; i < len; i++) { | |
| 18 output[i] = _childElements[i]; | |
| 19 } | |
| 20 return output; | |
| 21 } | |
| 22 | |
| 23 _ElementJs get first() { | |
| 24 return _element._firstElementChild; | |
| 25 } | |
| 26 | |
| 27 void forEach(void f(Element element)) { | |
| 28 for (_ElementJs element in _childElements) { | |
| 29 f(element); | |
| 30 } | |
| 31 } | |
| 32 | |
| 33 Collection<Element> filter(bool f(Element element)) { | |
| 34 List<Element> output = <Element>[]; | |
| 35 forEach((Element element) { | |
| 36 if (f(element)) { | |
| 37 output.add(element); | |
| 38 } | |
| 39 }); | |
| 40 return output; | |
| 41 } | |
| 42 | |
| 43 bool every(bool f(Element element)) { | |
| 44 for(Element element in this) { | |
| 45 if (!f(element)) { | |
| 46 return false; | |
| 47 } | |
| 48 }; | |
| 49 return true; | |
| 50 } | |
| 51 | |
| 52 bool some(bool f(Element element)) { | |
| 53 for(Element element in this) { | |
| 54 if (f(element)) { | |
| 55 return true; | |
| 56 } | |
| 57 }; | |
| 58 return false; | |
| 59 } | |
| 60 | |
| 61 bool isEmpty() { | |
| 62 return _element._firstElementChild !== null; | |
| 63 } | |
| 64 | |
| 65 int get length() { | |
| 66 return _childElements.length; | |
| 67 } | |
| 68 | |
| 69 _ElementJs operator [](int index) { | |
| 70 return _childElements[index]; | |
| 71 } | |
| 72 | |
| 73 void operator []=(int index, _ElementJs value) { | |
| 74 _element._replaceChild(value, _childElements.item(index)); | |
| 75 } | |
| 76 | |
| 77 void set length(int newLength) { | |
| 78 // TODO(jacobr): remove children when length is reduced. | |
| 79 throw const UnsupportedOperationException(''); | |
| 80 } | |
| 81 | |
| 82 Element add(_ElementJs value) { | |
| 83 _element._appendChild(value); | |
| 84 return value; | |
| 85 } | |
| 86 | |
| 87 Element addLast(_ElementJs value) => add(value); | |
| 88 | |
| 89 Iterator<Element> iterator() => _toList().iterator(); | |
| 90 | |
| 91 void addAll(Collection<_ElementJs> collection) { | |
| 92 for (_ElementJs element in collection) { | |
| 93 _element._appendChild(element); | |
| 94 } | |
| 95 } | |
| 96 | |
| 97 void sort(int compare(Element a, Element b)) { | |
| 98 throw const UnsupportedOperationException('TODO(jacobr): should we impl?'); | |
| 99 } | |
| 100 | |
| 101 void copyFrom(List<Object> src, int srcStart, int dstStart, int count) { | |
| 102 throw 'Not impl yet. todo(jacobr)'; | |
| 103 } | |
| 104 | |
| 105 void setRange(int start, int length, List from, [int startFrom = 0]) { | |
| 106 throw const NotImplementedException(); | |
| 107 } | |
| 108 | |
| 109 void removeRange(int start, int length) { | |
| 110 throw const NotImplementedException(); | |
| 111 } | |
| 112 | |
| 113 void insertRange(int start, int length, [initialValue = null]) { | |
| 114 throw const NotImplementedException(); | |
| 115 } | |
| 116 | |
| 117 List getRange(int start, int length) { | |
| 118 throw const NotImplementedException(); | |
| 119 } | |
| 120 | |
| 121 int indexOf(Element element, [int start = 0]) { | |
| 122 return _Lists.indexOf(this, element, start, this.length); | |
| 123 } | |
| 124 | |
| 125 int lastIndexOf(Element element, [int start = null]) { | |
| 126 if (start === null) start = length - 1; | |
| 127 return _Lists.lastIndexOf(this, element, start); | |
| 128 } | |
| 129 | |
| 130 void clear() { | |
| 131 // It is unclear if we want to keep non element nodes? | |
| 132 _element.text = ''; | |
| 133 } | |
| 134 | |
| 135 Element removeLast() { | |
| 136 final last = this.last(); | |
| 137 if (last != null) { | |
| 138 _element._removeChild(last); | |
| 139 } | |
| 140 return last; | |
| 141 } | |
| 142 | |
| 143 Element last() { | |
| 144 return _element.lastElementChild; | |
| 145 } | |
| 146 } | |
| 147 | |
| 148 class ElementAttributeMap implements Map<String, String> { | |
| 149 | |
| 150 final _ElementJs _element; | |
| 151 | |
| 152 ElementAttributeMap._wrap(this._element); | |
| 153 | |
| 154 bool containsValue(String value) { | |
| 155 final attributes = _element.attributes; | |
| 156 for (int i = 0, len = attributes.length; i < len; i++) { | |
| 157 if(value == attributes.item(i).value) { | |
| 158 return true; | |
| 159 } | |
| 160 } | |
| 161 return false; | |
| 162 } | |
| 163 | |
| 164 bool containsKey(String key) { | |
| 165 return _element._hasAttribute(key); | |
| 166 } | |
| 167 | |
| 168 String operator [](String key) { | |
| 169 return _element._getAttribute(key); | |
| 170 } | |
| 171 | |
| 172 void operator []=(String key, String value) { | |
| 173 _element._setAttribute(key, value); | |
| 174 } | |
| 175 | |
| 176 String putIfAbsent(String key, String ifAbsent()) { | |
| 177 if (!containsKey(key)) { | |
| 178 this[key] = ifAbsent(); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 String remove(String key) { | |
| 183 _element._removeAttribute(key); | |
| 184 } | |
| 185 | |
| 186 void clear() { | |
| 187 final attributes = _element._attributes; | |
| 188 for (int i = attributes.length - 1; i >= 0; i--) { | |
| 189 remove(attributes.item(i).name); | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 void forEach(void f(String key, String value)) { | |
| 194 final attributes = _element.attributes; | |
| 195 for (int i = 0, len = attributes.length; i < len; i++) { | |
| 196 final item = attributes.item(i); | |
| 197 f(item.name, item.value); | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 Collection<String> getKeys() { | |
| 202 // TODO(jacobr): generate a lazy collection instead. | |
| 203 final attributes = _element.attributes; | |
| 204 final keys = new List<String>(attributes.length); | |
| 205 for (int i = 0, len = attributes.length; i < len; i++) { | |
| 206 keys[i] = attributes.item(i).name; | |
| 207 } | |
| 208 return keys; | |
| 209 } | |
| 210 | |
| 211 Collection<String> getValues() { | |
| 212 // TODO(jacobr): generate a lazy collection instead. | |
| 213 final attributes = _element.attributes; | |
| 214 final values = new List<String>(attributes.length); | |
| 215 for (int i = 0, len = attributes.length; i < len; i++) { | |
| 216 values[i] = attributes.item(i).value; | |
| 217 } | |
| 218 return values; | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * The number of {key, value} pairs in the map. | |
| 223 */ | |
| 224 int get length() { | |
| 225 return _element._attributes.length; | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * Returns true if there is no {key, value} pair in the map. | |
| 230 */ | |
| 231 bool isEmpty() { | |
| 232 return length == 0; | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 class _SimpleClientRect implements ClientRect { | |
| 237 final num left; | |
| 238 final num top; | |
| 239 final num width; | |
| 240 final num height; | |
| 241 num get right() => left + width; | |
| 242 num get bottom() => top + height; | |
| 243 | |
| 244 const _SimpleClientRect(this.left, this.top, this.width, this.height); | |
| 245 | |
| 246 bool operator ==(ClientRect other) { | |
| 247 return other !== null && left == other.left && top == other.top | |
| 248 && width == other.width && height == other.height; | |
| 249 } | |
| 250 | |
| 251 String toString() => "($left, $top, $width, $height)"; | |
| 252 } | |
| 253 | |
| 254 // TODO(jacobr): we cannot currently be lazy about calculating the client | |
| 255 // rects as we must perform all measurement queries at a safe point to avoid | |
| 256 // triggering unneeded layouts. | |
| 257 /** | |
| 258 * All your element measurement needs in one place | |
| 259 * @domName none | |
| 260 */ | |
| 261 class _ElementRectImpl implements ElementRect { | |
| 262 final ClientRect client; | |
| 263 final ClientRect offset; | |
| 264 final ClientRect scroll; | |
| 265 | |
| 266 // TODO(jacobr): should we move these outside of ElementRect to avoid the | |
| 267 // overhead of computing them every time even though they are rarely used. | |
| 268 final _ClientRectJs _boundingClientRect; | |
| 269 final _ClientRectListJs _clientRects; | |
| 270 | |
| 271 _ElementRectImpl(_ElementJs element) : | |
| 272 client = new _SimpleClientRect(element._clientLeft, | |
| 273 element._clientTop, | |
| 274 element._clientWidth, | |
| 275 element._clientHeight), | |
| 276 offset = new _SimpleClientRect(element._offsetLeft, | |
| 277 element._offsetTop, | |
| 278 element._offsetWidth, | |
| 279 element._offsetHeight), | |
| 280 scroll = new _SimpleClientRect(element._scrollLeft, | |
| 281 element._scrollTop, | |
| 282 element._scrollWidth, | |
| 283 element._scrollHeight), | |
| 284 _boundingClientRect = element.getBoundingClientRect(), | |
| 285 _clientRects = element.getClientRects(); | |
| 286 | |
| 287 _ClientRectJs get bounding() => _boundingClientRect; | |
| 288 | |
| 289 // TODO(jacobr): cleanup. | |
| 290 List<ClientRect> get clientRects() { | |
| 291 final out = new List(_clientRects.length); | |
| 292 for (num i = 0; i < _clientRects.length; i++) { | |
| 293 out[i] = _clientRects.item(i); | |
| 294 } | |
| 295 return out; | |
| 296 } | |
| 297 } | |
| 298 | |
| 299 final _START_TAG_REGEXP = const RegExp('<(\\w+)'); | |
| 300 | |
| 301 class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { | |
| 302 | |
| 303 static final _CUSTOM_PARENT_TAG_MAP = const { | |
| 304 'body' : 'html', | |
| 305 'head' : 'html', | |
| 306 'caption' : 'table', | |
| 307 'td': 'tr', | |
| 308 'colgroup': 'table', | |
| 309 'col' : 'colgroup', | |
| 310 'tr' : 'tbody', | |
| 311 'tbody' : 'table', | |
| 312 'tfoot' : 'table', | |
| 313 'thead' : 'table', | |
| 314 'track' : 'audio', | |
| 315 }; | |
| 316 | |
| 317 /** @domName Document.createElement */ | |
| 318 factory Element.html(String html) { | |
| 319 // TODO(jacobr): this method can be made more robust and performant. | |
| 320 // 1) Cache the dummy parent elements required to use innerHTML rather than | |
| 321 // creating them every call. | |
| 322 // 2) Verify that the html does not contain leading or trailing text nodes. | |
| 323 // 3) Verify that the html does not contain both <head> and <body> tags. | |
| 324 // 4) Detatch the created element from its dummy parent. | |
| 325 String parentTag = 'div'; | |
| 326 String tag; | |
| 327 final match = _START_TAG_REGEXP.firstMatch(html); | |
| 328 if (match !== null) { | |
| 329 tag = match.group(1).toLowerCase(); | |
| 330 if (_CUSTOM_PARENT_TAG_MAP.containsKey(tag)) { | |
| 331 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | |
| 332 } | |
| 333 } | |
| 334 _ElementJs temp = _document._createElement(parentTag); | |
| 335 temp.innerHTML = html; | |
| 336 | |
| 337 if (temp._childElementCount == 1) { | |
| 338 return temp._firstElementChild; | |
| 339 } else if (parentTag == 'html' && temp._childElementCount == 2) { | |
| 340 // Work around for edge case in WebKit and possibly other browsers where | |
| 341 // both body and head elements are created even though the inner html | |
| 342 // only contains a head or body element. | |
| 343 return temp.elements[tag == 'head' ? 0 : 1]; | |
| 344 } else { | |
| 345 throw new IllegalArgumentException('HTML had ${temp._childElementCount} '
+ | |
| 346 'top level elements but 1 expected'); | |
| 347 } | |
| 348 } | |
| 349 | |
| 350 /** @domName Document.createElement */ | |
| 351 factory Element.tag(String tag) { | |
| 352 return _document._createElement(tag); | |
| 353 } | |
| 354 | |
| 355 // TODO(jacobr): caching these may hurt performance. | |
| 356 ElementAttributeMap _elementAttributeMap; | |
| 357 _CssClassSet _cssClassSet; | |
| 358 _DataAttributeMap _dataAttributes; | |
| 359 | |
| 360 // TODO(jacobr): remove these methods and let them be generated automatically | |
| 361 // once dart supports defining fields with the same name in an interface and | |
| 362 // its parent interface. | |
| 363 String get title() native "return this.parentNode.title;"; | |
| 364 void set title(String value) native "this.parentNode.title = value;"; | |
| 365 | |
| 366 /** | |
| 367 * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute, | |
| 368 * Element.removeAttribute | |
| 369 */ | |
| 370 Map<String, String> get attributes() { | |
| 371 if (_elementAttributeMap === null) { | |
| 372 _elementAttributeMap = new ElementAttributeMap._wrap(this); | |
| 373 } | |
| 374 return _elementAttributeMap; | |
| 375 } | |
| 376 | |
| 377 void set attributes(Map<String, String> value) { | |
| 378 Map<String, String> attributes = this.attributes; | |
| 379 attributes.clear(); | |
| 380 for (String key in value.getKeys()) { | |
| 381 attributes[key] = value[key]; | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 void set elements(Collection<Element> value) { | |
| 386 final elements = this.elements; | |
| 387 elements.clear(); | |
| 388 elements.addAll(value); | |
| 389 } | |
| 390 | |
| 391 /** | |
| 392 * @domName childElementCount, firstElementChild, lastElementChild, | |
| 393 * children, Node.nodes.add | |
| 394 */ | |
| 395 ElementList get elements() => new _ChildrenElementList._wrap(this); | |
| 396 | |
| 397 /** @domName querySelector, Document.getElementById */ | |
| 398 Element query(String selectors) native "return this.querySelector(selectors);"
; | |
| 399 | |
| 400 /** | |
| 401 * @domName querySelectorAll, getElementsByClassName, getElementsByTagName, | |
| 402 * getElementsByTagNameNS | |
| 403 */ | |
| 404 ElementList queryAll(String selectors) native "return this.querySelectorAll(se
lectors);"; | |
| 405 | |
| 406 /** @domName className, classList */ | |
| 407 Set<String> get classes() { | |
| 408 if (_cssClassSet === null) { | |
| 409 _cssClassSet = new _CssClassSet(this); | |
| 410 } | |
| 411 return _cssClassSet; | |
| 412 } | |
| 413 | |
| 414 void set classes(Collection<String> value) { | |
| 415 _CssClassSet classSet = classes; | |
| 416 classSet.clear(); | |
| 417 classSet.addAll(value); | |
| 418 } | |
| 419 | |
| 420 Map<String, String> get dataAttributes() { | |
| 421 if (_dataAttributes === null) { | |
| 422 _dataAttributes = new _DataAttributeMap(attributes); | |
| 423 } | |
| 424 return _dataAttributes; | |
| 425 } | |
| 426 | |
| 427 void set dataAttributes(Map<String, String> value) { | |
| 428 Map<String, String> dataAttributes = this.dataAttributes; | |
| 429 dataAttributes.clear(); | |
| 430 for (String key in value.getKeys()) { | |
| 431 dataAttributes[key] = value[key]; | |
| 432 } | |
| 433 } | |
| 434 | |
| 435 bool matchesSelector(String selectors) native "return this.webkitMatchesSelect
or(selectors)"; | |
| 436 | |
| 437 /** | |
| 438 * @domName getClientRects, getBoundingClientRect, clientHeight, clientWidth, | |
| 439 * clientTop, clientLeft, offsetHeight, offsetWidth, offsetTop, offsetLeft, | |
| 440 * scrollHeight, scrollWidth, scrollTop, scrollLeft | |
| 441 */ | |
| 442 Future<ElementRect> get rect() { | |
| 443 return _createMeasurementFuture( | |
| 444 () => new _ElementRectImpl(this), | |
| 445 new Completer<ElementRect>()); | |
| 446 } | |
| 447 | |
| 448 /** @domName Window.getComputedStyle */ | |
| 449 Future<CSSStyleDeclaration> get computedStyle() { | |
| 450 // TODO(jacobr): last param should be null, see b/5045788 | |
| 451 return getComputedStyle(''); | |
| 452 } | |
| 453 | |
| 454 /** @domName Window.getComputedStyle */ | |
| 455 Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) { | |
| 456 return _createMeasurementFuture(() => | |
| 457 _window._getComputedStyle(this, pseudoElement), | |
| 458 new Completer<CSSStyleDeclaration>()); | |
| 459 } | |
| 460 | |
| 461 _ElementJs clone(bool deep) native; | |
| 462 $!MEMBERS | |
| 463 } | |
| OLD | NEW |