OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of $LIBRARYNAME; | 5 part of $LIBRARYNAME; |
6 | 6 |
7 /** | 7 /** |
8 * Lazy implementation of the child nodes of an element that does not request | 8 * Lazy implementation of the child nodes of an element that does not request |
9 * the actual child nodes of an element until strictly necessary greatly | 9 * the actual child nodes of an element until strictly necessary greatly |
10 * improving performance for the typical cases where it is not required. | 10 * improving performance for the typical cases where it is not required. |
11 */ | 11 */ |
12 class _ChildNodeListLazy implements List { | 12 class _ChildNodeListLazy extends ListBase<Node> { |
13 final Node _this; | 13 final Node _this; |
14 | 14 |
15 _ChildNodeListLazy(this._this); | 15 _ChildNodeListLazy(this._this); |
16 | 16 |
17 | 17 |
18 $if DART2JS | 18 $if DART2JS |
19 Node get first { | 19 Node get first { |
20 Node result = JS('Node|Null', '#.firstChild', _this); | 20 Node result = JS('Node|Null', '#.firstChild', _this); |
21 if (result == null) throw new StateError("No elements"); | 21 if (result == null) throw new StateError("No elements"); |
22 return result; | 22 return result; |
(...skipping 21 matching lines...) Expand all Loading... |
44 return result; | 44 return result; |
45 } | 45 } |
46 Node get single { | 46 Node get single { |
47 int l = this.length; | 47 int l = this.length; |
48 if (l == 0) throw new StateError("No elements"); | 48 if (l == 0) throw new StateError("No elements"); |
49 if (l > 1) throw new StateError("More than one element"); | 49 if (l > 1) throw new StateError("More than one element"); |
50 return _this.$dom_firstChild; | 50 return _this.$dom_firstChild; |
51 } | 51 } |
52 $endif | 52 $endif |
53 | 53 |
54 Node min([int compare(Node a, Node b)]) { | |
55 return IterableMixinWorkaround.min(this, compare); | |
56 } | |
57 | |
58 Node max([int compare(Node a, Node b)]) { | |
59 return IterableMixinWorkaround.max(this, compare); | |
60 } | |
61 | |
62 void add(Node value) { | 54 void add(Node value) { |
63 _this.append(value); | 55 _this.append(value); |
64 } | 56 } |
65 | 57 |
66 void addAll(Iterable<Node> iterable) { | 58 void addAll(Iterable<Node> iterable) { |
67 if (iterable is _ChildNodeListLazy) { | 59 if (iterable is _ChildNodeListLazy) { |
68 if (!identical(iterable._this, _this)) { | 60 if (!identical(iterable._this, _this)) { |
69 // Optimized route for copying between nodes. | 61 // Optimized route for copying between nodes. |
70 for (var i = 0, len = iterable.length; i < len; ++i) { | 62 for (var i = 0, len = iterable.length; i < len; ++i) { |
71 // Should use $dom_firstChild, Bug 8886. | 63 // Should use $dom_firstChild, Bug 8886. |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 } | 99 } |
108 | 100 |
109 void remove(Object object) { | 101 void remove(Object object) { |
110 if (object is! Node) return; | 102 if (object is! Node) return; |
111 Node node = object; | 103 Node node = object; |
112 if (!identical(_this, node.parentNode)) return; | 104 if (!identical(_this, node.parentNode)) return; |
113 _this.$dom_removeChild(node); | 105 _this.$dom_removeChild(node); |
114 } | 106 } |
115 | 107 |
116 void removeAll(Iterable elements) { | 108 void removeAll(Iterable elements) { |
117 IterableMixinWorkaround.removeAll(this, elements); | 109 // This is not using the default removeAll from ListBase because |
| 110 // DOM nodes can be efficiently removed in constant time. |
| 111 for (var element in elements) { |
| 112 remove(element); |
| 113 } |
| 114 } |
| 115 |
| 116 void _filter(bool test(Node node), bool removeMatching) { |
| 117 // This implementation of removeWhere/retainWhere is more efficient |
| 118 // than the default in ListBase. Child nodes can be removed in constant |
| 119 // time. |
| 120 Node child = _this.$dom_firstChild; |
| 121 while (child != null) { |
| 122 Node nextChild = child.nextSibling; |
| 123 if (test(child) == removeMatching) { |
| 124 _this.$dom_removeChild(child); |
| 125 } |
| 126 child = nextChild; |
| 127 } |
118 } | 128 } |
119 | 129 |
120 void retainAll(Iterable elements) { | 130 void retainAll(Iterable elements) { |
121 IterableMixinWorkaround.retainAll(this, elements); | 131 Set retainSet = (elements is Set) ? elements : elements.toSet(); |
| 132 _filter(retainSet.contains, false); |
122 } | 133 } |
123 | 134 |
124 void removeWhere(bool test(Node node)) { | 135 void removeWhere(bool test(Node node)) { |
125 IterableMixinWorkaround.removeWhere(this, test); | 136 _filter(test, true); |
126 } | 137 } |
127 | 138 |
128 void retainWhere(bool test(Node node)) { | 139 void retainWhere(bool test(Node node)) { |
129 IterableMixinWorkaround.retainWhere(this, test); | 140 _filter(test, false); |
130 } | 141 } |
131 | 142 |
132 void clear() { | 143 void clear() { |
133 _this.text = ''; | 144 _this.text = ''; |
134 } | 145 } |
135 | 146 |
136 void operator []=(int index, Node value) { | 147 void operator []=(int index, Node value) { |
137 _this.$dom_replaceChild(value, this[index]); | 148 _this.$dom_replaceChild(value, this[index]); |
138 } | 149 } |
139 | 150 |
140 Iterator<Node> get iterator => _this.$dom_childNodes.iterator; | 151 Iterator<Node> get iterator => _this.$dom_childNodes.iterator; |
141 | 152 |
142 // TODO(jacobr): We can implement these methods much more efficiently by | |
143 // looking up the nodeList only once instead of once per iteration. | |
144 bool contains(Node element) => IterableMixinWorkaround.contains(this, element)
; | |
145 | |
146 void forEach(void f(Node element)) => IterableMixinWorkaround.forEach(this, f)
; | |
147 | |
148 dynamic reduce(dynamic initialValue, | |
149 dynamic combine(dynamic previousValue, Node element)) { | |
150 return IterableMixinWorkaround.reduce(this, initialValue, combine); | |
151 } | |
152 | |
153 dynamic fold(dynamic initialValue, | |
154 dynamic combine(dynamic previousValue, Node element)) { | |
155 return IterableMixinWorkaround.fold(this, initialValue, combine); | |
156 } | |
157 | |
158 String join([String separator]) { | |
159 return IterableMixinWorkaround.joinList(this, separator); | |
160 } | |
161 | |
162 Iterable map(f(Node element)) { | |
163 return IterableMixinWorkaround.mapList(this, f); | |
164 } | |
165 | |
166 Iterable<Node> where(bool f(Node element)) { | |
167 return IterableMixinWorkaround.where(this, f); | |
168 } | |
169 | |
170 Iterable expand(Iterable f(Node element)) { | |
171 return IterableMixinWorkaround.expand(this, f); | |
172 } | |
173 | |
174 bool every(bool f(Node element)) => IterableMixinWorkaround.every(this, f); | |
175 | |
176 bool any(bool f(Node element)) => IterableMixinWorkaround.any(this, f); | |
177 | |
178 List<Node> toList({ bool growable: true }) => | 153 List<Node> toList({ bool growable: true }) => |
179 new List<Node>.from(this, growable: growable); | 154 new List<Node>.from(this, growable: growable); |
180 Set<Node> toSet() => new Set<Node>.from(this); | 155 Set<Node> toSet() => new Set<Node>.from(this); |
181 | 156 |
182 bool get isEmpty => this.length == 0; | 157 bool get isEmpty => this.length == 0; |
183 | 158 |
184 // From List<Node>: | 159 // From List<Node>: |
185 | 160 |
186 Iterable<Node> take(int n) { | |
187 return IterableMixinWorkaround.takeList(this, n); | |
188 } | |
189 | |
190 Iterable<Node> takeWhile(bool test(Node value)) { | |
191 return IterableMixinWorkaround.takeWhile(this, test); | |
192 } | |
193 | |
194 Iterable<Node> skip(int n) { | |
195 return IterableMixinWorkaround.skipList(this, n); | |
196 } | |
197 | |
198 Iterable<Node> skipWhile(bool test(Node value)) { | |
199 return IterableMixinWorkaround.skipWhile(this, test); | |
200 } | |
201 | |
202 Node firstWhere(bool test(Node value), {Node orElse()}) { | |
203 return IterableMixinWorkaround.firstWhere(this, test, orElse); | |
204 } | |
205 | |
206 Node lastWhere(bool test(Node value), {Node orElse()}) { | |
207 return IterableMixinWorkaround.lastWhereList(this, test, orElse); | |
208 } | |
209 | |
210 Node singleWhere(bool test(Node value)) { | |
211 return IterableMixinWorkaround.singleWhere(this, test); | |
212 } | |
213 | |
214 Node elementAt(int index) { | |
215 return this[index]; | |
216 } | |
217 | |
218 Iterable<Node> get reversed { | |
219 return IterableMixinWorkaround.reversedList(this); | |
220 } | |
221 | |
222 // TODO(jacobr): this could be implemented for child node lists. | 161 // TODO(jacobr): this could be implemented for child node lists. |
223 // The exception we throw here is misleading. | 162 // The exception we throw here is misleading. |
224 void sort([int compare(Node a, Node b)]) { | 163 void sort([int compare(Node a, Node b)]) { |
225 throw new UnsupportedError("Cannot sort immutable List."); | 164 throw new UnsupportedError("Cannot sort immutable List."); |
226 } | 165 } |
227 | 166 |
228 int indexOf(Node element, [int start = 0]) => | |
229 Lists.indexOf(this, element, start, this.length); | |
230 | |
231 int lastIndexOf(Node element, [int start = 0]) => | |
232 Lists.lastIndexOf(this, element, start); | |
233 | |
234 // FIXME: implement these. | 167 // FIXME: implement these. |
235 void setRange(int start, int rangeLength, List<Node> from, [int startFrom]) { | 168 void setRange(int start, int rangeLength, List<Node> from, [int startFrom]) { |
236 throw new UnsupportedError( | 169 throw new UnsupportedError( |
237 "Cannot setRange on immutable List."); | 170 "Cannot setRange on immutable List."); |
238 } | 171 } |
239 void removeRange(int start, int rangeLength) { | 172 void removeRange(int start, int rangeLength) { |
240 throw new UnsupportedError( | 173 throw new UnsupportedError( |
241 "Cannot removeRange on immutable List."); | 174 "Cannot removeRange on immutable List."); |
242 } | 175 } |
243 void insertRange(int start, int rangeLength, [Node initialValue]) { | 176 void insertRange(int start, int rangeLength, [Node initialValue]) { |
(...skipping 19 matching lines...) Expand all Loading... |
263 // TODO(jacobr): benchmark whether this is more efficient or whether caching | 196 // TODO(jacobr): benchmark whether this is more efficient or whether caching |
264 // a local copy of $dom_childNodes is more efficient. | 197 // a local copy of $dom_childNodes is more efficient. |
265 int get length => _this.$dom_childNodes.length; | 198 int get length => _this.$dom_childNodes.length; |
266 | 199 |
267 void set length(int value) { | 200 void set length(int value) { |
268 throw new UnsupportedError( | 201 throw new UnsupportedError( |
269 "Cannot set length on immutable List."); | 202 "Cannot set length on immutable List."); |
270 } | 203 } |
271 | 204 |
272 Node operator[](int index) => _this.$dom_childNodes[index]; | 205 Node operator[](int index) => _this.$dom_childNodes[index]; |
273 | |
274 Map<int, Node> asMap() => IterableMixinWorkaround.asMapList(this); | |
275 } | 206 } |
276 | 207 |
277 $(ANNOTATIONS)class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { | 208 $(ANNOTATIONS)class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |
278 List<Node> get nodes { | 209 List<Node> get nodes { |
279 return new _ChildNodeListLazy(this); | 210 return new _ChildNodeListLazy(this); |
280 } | 211 } |
281 | 212 |
282 void set nodes(Collection<Node> value) { | 213 void set nodes(Collection<Node> value) { |
283 // Copy list first since we don't want liveness during iteration. | 214 // Copy list first since we don't want liveness during iteration. |
284 // TODO(jacobr): there is a better way to do this. | 215 // TODO(jacobr): there is a better way to do this. |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
417 */ | 348 */ |
418 Stream<Node> get onModelChanged { | 349 Stream<Node> get onModelChanged { |
419 if (_modelChangedStream == null) { | 350 if (_modelChangedStream == null) { |
420 // Ensure the model is cached locally to minimize change notifications. | 351 // Ensure the model is cached locally to minimize change notifications. |
421 _model = model; | 352 _model = model; |
422 _modelChangedStream = new StreamController.broadcast(); | 353 _modelChangedStream = new StreamController.broadcast(); |
423 } | 354 } |
424 return _modelChangedStream.stream; | 355 return _modelChangedStream.stream; |
425 } | 356 } |
426 | 357 |
427 /** | 358 /** |
428 * Print out a String representation of this Node. | 359 * Print out a String representation of this Node. |
429 */ | 360 */ |
430 String toString() => localName == null ? | 361 String toString() => localName == null ? |
431 (nodeValue == null ? super.toString() : nodeValue) : localName; | 362 (nodeValue == null ? super.toString() : nodeValue) : localName; |
432 | 363 |
433 $!MEMBERS | 364 $!MEMBERS |
434 } | 365 } |
OLD | NEW |