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 |