| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 mdv; | 5 part of mdv; |
| 6 | 6 |
| 7 // This code is a port of Model-Driven-Views: | 7 // This code is a port of Model-Driven-Views: |
| 8 // https://github.com/polymer-project/mdv | 8 // https://github.com/polymer-project/mdv |
| 9 // The code mostly comes from src/template_element.js | 9 // The code mostly comes from src/template_element.js |
| 10 | 10 |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 183 final bool isBinding; | 183 final bool isBinding; |
| 184 | 184 |
| 185 _BindingToken(this.value, {this.isBinding: false}); | 185 _BindingToken(this.value, {this.isBinding: false}); |
| 186 | 186 |
| 187 bool get isText => !isBinding; | 187 bool get isText => !isBinding; |
| 188 } | 188 } |
| 189 | 189 |
| 190 class _TemplateIterator { | 190 class _TemplateIterator { |
| 191 final Element _templateElement; | 191 final Element _templateElement; |
| 192 final List<Node> terminators = []; | 192 final List<Node> terminators = []; |
| 193 final CompoundBinding inputs; | 193 CompoundBinding inputs; |
| 194 List iteratedValue; | 194 List iteratedValue; |
| 195 Object _lastValue; | 195 bool closed = false; |
| 196 | 196 |
| 197 StreamSubscription _sub; | 197 StreamSubscription _sub; |
| 198 StreamSubscription _valueBinding; | |
| 199 | 198 |
| 200 _TemplateIterator(this._templateElement) | 199 _TemplateIterator(this._templateElement) { |
| 201 : inputs = new CompoundBinding(resolveInputs) { | 200 inputs = new CompoundBinding(resolveInputs); |
| 202 | |
| 203 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); | |
| 204 } | 201 } |
| 205 | 202 |
| 206 static Object resolveInputs(Map values) { | 203 void resolveInputs(Map values) { |
| 204 if (closed) return; |
| 205 |
| 207 if (values.containsKey('if') && !_toBoolean(values['if'])) { | 206 if (values.containsKey('if') && !_toBoolean(values['if'])) { |
| 208 return null; | 207 valueChanged(null); |
| 208 } else if (values.containsKey('repeat')) { |
| 209 valueChanged(values['repeat']); |
| 210 } else if (values.containsKey('bind') || values.containsKey('if')) { |
| 211 valueChanged([values['bind']]); |
| 212 } else { |
| 213 valueChanged(null); |
| 209 } | 214 } |
| 210 | |
| 211 if (values.containsKey('repeat')) { | |
| 212 return values['repeat']; | |
| 213 } | |
| 214 | |
| 215 if (values.containsKey('bind') || values.containsKey('if')) { | |
| 216 return [values['bind']]; | |
| 217 } | |
| 218 | |
| 219 return null; | |
| 220 } | 215 } |
| 221 | 216 |
| 222 void valueChanged(value) { | 217 void valueChanged(value) { |
| 223 // TODO(jmesserly): should PathObserver do this for us? | 218 if (value is! List) value = null; |
| 224 var oldValue = _lastValue; | |
| 225 _lastValue = value; | |
| 226 | 219 |
| 227 if (value is! List) { | 220 var oldValue = iteratedValue; |
| 228 value = []; | |
| 229 } | |
| 230 | |
| 231 unobserve(); | 221 unobserve(); |
| 232 iteratedValue = value; | 222 iteratedValue = value; |
| 233 | 223 |
| 234 if (value is Observable) { | 224 if (iteratedValue is Observable) { |
| 235 _sub = value.changes.listen(_handleChanges); | 225 _sub = iteratedValue.changes.listen(_handleChanges); |
| 236 } | 226 } |
| 237 | 227 |
| 238 int addedCount = iteratedValue.length; | 228 var splices = calculateSplices( |
| 239 var removedCount = oldValue is List ? (oldValue as List).length : 0; | 229 iteratedValue != null ? iteratedValue : [], |
| 240 if (addedCount == 0 && removedCount == 0) return; // nothing to do. | 230 oldValue != null ? oldValue : []); |
| 241 | 231 |
| 242 _handleChanges([new ListChangeRecord(0, addedCount: addedCount, | 232 if (splices.length > 0) _handleChanges(splices); |
| 243 removedCount: removedCount)]); | 233 |
| 234 if (inputs.length == 0) { |
| 235 close(); |
| 236 _mdv(_templateElement)._templateIterator = null; |
| 237 } |
| 244 } | 238 } |
| 245 | 239 |
| 246 Node getTerminatorAt(int index) { | 240 Node getTerminatorAt(int index) { |
| 247 if (index == -1) return _templateElement; | 241 if (index == -1) return _templateElement; |
| 248 var terminator = terminators[index]; | 242 var terminator = terminators[index]; |
| 249 if (terminator is Element && (terminator as Element).isTemplate && | 243 if (terminator is Element && (terminator as Element).isTemplate && |
| 250 !identical(terminator, _templateElement)) { | 244 !identical(terminator, _templateElement)) { |
| 251 var subIterator = _mdv(terminator)._templateIterator; | 245 var subIterator = _mdv(terminator)._templateIterator; |
| 252 if (subIterator != null) { | 246 if (subIterator != null) { |
| 253 return subIterator.getTerminatorAt(subIterator.terminators.length - 1); | 247 return subIterator.getTerminatorAt(subIterator.terminators.length - 1); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 305 return delegate.getInstanceModel(_templateElement, model); | 299 return delegate.getInstanceModel(_templateElement, model); |
| 306 } | 300 } |
| 307 return model; | 301 return model; |
| 308 } | 302 } |
| 309 | 303 |
| 310 DocumentFragment getInstanceFragment(model, BindingDelegate delegate) { | 304 DocumentFragment getInstanceFragment(model, BindingDelegate delegate) { |
| 311 return _templateElement.createInstance(model, delegate); | 305 return _templateElement.createInstance(model, delegate); |
| 312 } | 306 } |
| 313 | 307 |
| 314 void _handleChanges(Iterable<ChangeRecord> splices) { | 308 void _handleChanges(Iterable<ChangeRecord> splices) { |
| 309 if (closed) return; |
| 310 |
| 315 splices = splices.where((s) => s is ListChangeRecord); | 311 splices = splices.where((s) => s is ListChangeRecord); |
| 316 | 312 |
| 317 var template = _templateElement; | 313 var template = _templateElement; |
| 318 var delegate = template.bindingDelegate; | 314 var delegate = template.bindingDelegate; |
| 319 | 315 |
| 320 if (template.parentNode == null || template.document.window == null) { | 316 if (template.parentNode == null || template.document.window == null) { |
| 321 abandon(); | 317 close(); |
| 322 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, | 318 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, |
| 323 // but I think that's a no-op because only nodes are used as keys. | 319 // but I think that's a no-op because only nodes are used as keys. |
| 324 // See https://github.com/Polymer/mdv/pull/114. | 320 // See https://github.com/Polymer/mdv/pull/114. |
| 325 return; | 321 return; |
| 326 } | 322 } |
| 327 | 323 |
| 328 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right | 324 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right |
| 329 // now. See http://dartbug.com/4161. | 325 // now. See http://dartbug.com/4161. |
| 330 var instanceCache = new IdentityMap(); | 326 var instanceCache = new IdentityMap(); |
| 331 var removeDelta = 0; | 327 var removeDelta = 0; |
| (...skipping 29 matching lines...) Expand all Loading... |
| 361 instanceNodes.forEach(_unbindAllRecursively); | 357 instanceNodes.forEach(_unbindAllRecursively); |
| 362 } | 358 } |
| 363 } | 359 } |
| 364 | 360 |
| 365 void unobserve() { | 361 void unobserve() { |
| 366 if (_sub == null) return; | 362 if (_sub == null) return; |
| 367 _sub.cancel(); | 363 _sub.cancel(); |
| 368 _sub = null; | 364 _sub = null; |
| 369 } | 365 } |
| 370 | 366 |
| 371 void abandon() { | 367 void close() { |
| 368 if (closed) return; |
| 369 |
| 372 unobserve(); | 370 unobserve(); |
| 373 _valueBinding.cancel(); | |
| 374 terminators.clear(); | 371 terminators.clear(); |
| 375 inputs.dispose(); | 372 inputs.dispose(); |
| 373 closed = true; |
| 376 } | 374 } |
| 377 | 375 |
| 378 static void _unbindAllRecursively(Node node) { | 376 static void _unbindAllRecursively(Node node) { |
| 379 var nodeExt = _mdv(node); | 377 var nodeExt = _mdv(node); |
| 380 nodeExt._templateInstance = null; | 378 nodeExt._templateInstance = null; |
| 381 if (node is Element && (node as Element).isTemplate) { | 379 if (node is Element && (node as Element).isTemplate) { |
| 382 // Make sure we stop observing when we remove an element. | 380 // Make sure we stop observing when we remove an element. |
| 383 var templateIterator = nodeExt._templateIterator; | 381 var templateIterator = nodeExt._templateIterator; |
| 384 if (templateIterator != null) { | 382 if (templateIterator != null) { |
| 385 templateIterator.abandon(); | 383 templateIterator.close(); |
| 386 nodeExt._templateIterator = null; | 384 nodeExt._templateIterator = null; |
| 387 } | 385 } |
| 388 } | 386 } |
| 389 | 387 |
| 390 _nodeOrCustom(node).unbindAll(); | 388 _nodeOrCustom(node).unbindAll(); |
| 391 for (var c = node.firstChild; c != null; c = c.nextNode) { | 389 for (var c = node.firstChild; c != null; c = c.nextNode) { |
| 392 _unbindAllRecursively(c); | 390 _unbindAllRecursively(c); |
| 393 } | 391 } |
| 394 } | 392 } |
| 395 } | 393 } |
| OLD | NEW |