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 'tbody': 'table', |
| 309 'colgroup': 'table', |
| 310 'col' : 'colgroup', |
| 311 'tr' : 'tbody', |
| 312 'tbody' : 'table', |
| 313 'tfoot' : 'table', |
| 314 'thead' : 'table', |
| 315 'track' : 'audio', |
| 316 }; |
| 317 |
| 318 /** @domName Document.createElement */ |
| 319 factory Element.html(String html) { |
| 320 // TODO(jacobr): this method can be made more robust and performant. |
| 321 // 1) Cache the dummy parent elements required to use innerHTML rather than |
| 322 // creating them every call. |
| 323 // 2) Verify that the html does not contain leading or trailing text nodes. |
| 324 // 3) Verify that the html does not contain both <head> and <body> tags. |
| 325 // 4) Detatch the created element from its dummy parent. |
| 326 String parentTag = 'div'; |
| 327 String tag; |
| 328 final match = _START_TAG_REGEXP.firstMatch(html); |
| 329 if (match !== null) { |
| 330 tag = match.group(1).toLowerCase(); |
| 331 if (_CUSTOM_PARENT_TAG_MAP.containsKey(tag)) { |
| 332 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| 333 } |
| 334 } |
| 335 _ElementJs temp = _document._createElement(parentTag); |
| 336 temp.innerHTML = html; |
| 337 |
| 338 if (temp._childElementCount == 1) { |
| 339 return temp._firstElementChild; |
| 340 } else if (parentTag == 'html' && temp._childElementCount == 2) { |
| 341 // Work around for edge case in WebKit and possibly other browsers where |
| 342 // both body and head elements are created even though the inner html |
| 343 // only contains a head or body element. |
| 344 return temp.elements[tag == 'head' ? 0 : 1]; |
| 345 } else { |
| 346 throw new IllegalArgumentException('HTML had ${temp._childElementCount} '
+ |
| 347 'top level elements but 1 expected'); |
| 348 } |
| 349 } |
| 350 |
| 351 /** @domName Document.createElement */ |
| 352 factory Element.tag(String tag) { |
| 353 return _document._createElement(tag); |
| 354 } |
| 355 |
| 356 // TODO(jacobr): caching these may hurt performance. |
| 357 ElementAttributeMap _elementAttributeMap; |
| 358 _CssClassSet _cssClassSet; |
| 359 _DataAttributeMap _dataAttributes; |
| 360 |
| 361 // TODO(jacobr): remove these methods and let them be generated automatically |
| 362 // once dart supports defining fields with the same name in an interface and |
| 363 // its parent interface. |
| 364 String get title() native "return this.parentNode.title;"; |
| 365 void set title(String value) native "this.parentNode.title = value;"; |
| 366 |
| 367 /** |
| 368 * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute, |
| 369 * Element.removeAttribute |
| 370 */ |
| 371 Map<String, String> get attributes() { |
| 372 if (_elementAttributeMap === null) { |
| 373 _elementAttributeMap = new ElementAttributeMap._wrap(this); |
| 374 } |
| 375 return _elementAttributeMap; |
| 376 } |
| 377 |
| 378 void set attributes(Map<String, String> value) { |
| 379 Map<String, String> attributes = this.attributes; |
| 380 attributes.clear(); |
| 381 for (String key in value.getKeys()) { |
| 382 attributes[key] = value[key]; |
| 383 } |
| 384 } |
| 385 |
| 386 void set elements(Collection<Element> value) { |
| 387 final elements = this.elements; |
| 388 elements.clear(); |
| 389 elements.addAll(value); |
| 390 } |
| 391 |
| 392 /** |
| 393 * @domName childElementCount, firstElementChild, lastElementChild, |
| 394 * children, Node.nodes.add |
| 395 */ |
| 396 ElementList get elements() => new _ChildrenElementList._wrap(this); |
| 397 |
| 398 /** @domName querySelector, Document.getElementById */ |
| 399 Element query(String selectors) native "return this.querySelector(selectors);"
; |
| 400 |
| 401 /** |
| 402 * @domName querySelectorAll, getElementsByClassName, getElementsByTagName, |
| 403 * getElementsByTagNameNS |
| 404 */ |
| 405 ElementList queryAll(String selectors) native "return this.querySelectorAll(se
lectors);"; |
| 406 |
| 407 /** @domName className, classList */ |
| 408 Set<String> get classes() { |
| 409 if (_cssClassSet === null) { |
| 410 _cssClassSet = new _CssClassSet(this); |
| 411 } |
| 412 return _cssClassSet; |
| 413 } |
| 414 |
| 415 void set classes(Collection<String> value) { |
| 416 _CssClassSet classSet = classes; |
| 417 classSet.clear(); |
| 418 classSet.addAll(value); |
| 419 } |
| 420 |
| 421 Map<String, String> get dataAttributes() { |
| 422 if (_dataAttributes === null) { |
| 423 _dataAttributes = new _DataAttributeMap(attributes); |
| 424 } |
| 425 return _dataAttributes; |
| 426 } |
| 427 |
| 428 void set dataAttributes(Map<String, String> value) { |
| 429 Map<String, String> dataAttributes = this.dataAttributes; |
| 430 dataAttributes.clear(); |
| 431 for (String key in value.getKeys()) { |
| 432 dataAttributes[key] = value[key]; |
| 433 } |
| 434 } |
| 435 |
| 436 bool matchesSelector(String selectors) native "return this.webkitMatchesSelect
or(selectors)"; |
| 437 |
| 438 /** |
| 439 * @domName getClientRects, getBoundingClientRect, clientHeight, clientWidth, |
| 440 * clientTop, clientLeft, offsetHeight, offsetWidth, offsetTop, offsetLeft, |
| 441 * scrollHeight, scrollWidth, scrollTop, scrollLeft |
| 442 */ |
| 443 Future<ElementRect> get rect() { |
| 444 return _createMeasurementFuture( |
| 445 () => new _ElementRectImpl(this), |
| 446 new Completer<ElementRect>()); |
| 447 } |
| 448 |
| 449 /** @domName Window.getComputedStyle */ |
| 450 Future<CSSStyleDeclaration> get computedStyle() { |
| 451 // TODO(jacobr): last param should be null, see b/5045788 |
| 452 return getComputedStyle(''); |
| 453 } |
| 454 |
| 455 /** @domName Window.getComputedStyle */ |
| 456 Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) { |
| 457 return _createMeasurementFuture(() => |
| 458 _window._getComputedStyle(this, pseudoElement), |
| 459 new Completer<CSSStyleDeclaration>()); |
| 460 } |
| 461 |
| 462 _ElementJs clone(bool deep) native; |
| 463 $!MEMBERS |
| 464 } |
OLD | NEW |