| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library observe.src.compound_binding; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'package:observe/observe.dart'; | |
| 9 | |
| 10 /** The callback used in the [CompoundBinding.combinator] field. */ | |
| 11 typedef Object CompoundBindingCombinator(Map objects); | |
| 12 | |
| 13 /** | |
| 14 * CompoundBinding is an object which knows how to listen to multiple path | |
| 15 * values (registered via [bind]) and invoke its [combinator] when one or more | |
| 16 * of the values have changed and set its [value] property to the return value | |
| 17 * of the function. When any value has changed, all current values are provided | |
| 18 * to the [combinator] in the single `values` argument. | |
| 19 * | |
| 20 * For example: | |
| 21 * | |
| 22 * var binding = new CompoundBinding((values) { | |
| 23 * var combinedValue; | |
| 24 * // compute combinedValue based on the current values which are provided | |
| 25 * return combinedValue; | |
| 26 * }); | |
| 27 * binding.bind('name1', obj1, path1); | |
| 28 * binding.bind('name2', obj2, path2); | |
| 29 * //... | |
| 30 * binding.bind('nameN', objN, pathN); | |
| 31 */ | |
| 32 // TODO(jmesserly): rename to something that indicates it's a computed value? | |
| 33 class CompoundBinding extends ChangeNotifier { | |
| 34 CompoundBindingCombinator _combinator; | |
| 35 | |
| 36 // TODO(jmesserly): ideally these would be String keys, but sometimes we | |
| 37 // use integers. | |
| 38 Map<dynamic, StreamSubscription> _observers = new Map(); | |
| 39 Map _values = new Map(); | |
| 40 Object _value; | |
| 41 | |
| 42 /** | |
| 43 * True if [resolve] is scheduled. You can set this to true if you plan to | |
| 44 * call [resolve] manually, avoiding the need for scheduling an asynchronous | |
| 45 * resolve. | |
| 46 */ | |
| 47 // TODO(jmesserly): I don't like having this public, is the optimization | |
| 48 // really needed? "scheduleMicrotask" in Dart should be pretty cheap. | |
| 49 bool scheduled = false; | |
| 50 | |
| 51 /** | |
| 52 * Creates a new CompoundBinding, optionally proving the [combinator] function | |
| 53 * for computing the value. You can also set [schedule] to true if you plan | |
| 54 * to invoke [resolve] manually after initial construction of the binding. | |
| 55 */ | |
| 56 CompoundBinding([CompoundBindingCombinator combinator]) { | |
| 57 // TODO(jmesserly): this is a tweak to the original code, it seemed to me | |
| 58 // that passing the combinator to the constructor should be equivalent to | |
| 59 // setting it via the property. | |
| 60 // I also added a null check to the combinator setter. | |
| 61 this.combinator = combinator; | |
| 62 } | |
| 63 | |
| 64 CompoundBindingCombinator get combinator => _combinator; | |
| 65 | |
| 66 set combinator(CompoundBindingCombinator combinator) { | |
| 67 _combinator = combinator; | |
| 68 if (combinator != null) _scheduleResolve(); | |
| 69 } | |
| 70 | |
| 71 int get length => _observers.length; | |
| 72 | |
| 73 @reflectable get value => _value; | |
| 74 | |
| 75 @reflectable void set value(newValue) { | |
| 76 _value = notifyPropertyChange(#value, _value, newValue); | |
| 77 } | |
| 78 | |
| 79 void bind(name, model, String path) { | |
| 80 unbind(name); | |
| 81 | |
| 82 // TODO(jmesserly): should we delay observing until we are observed, | |
| 83 // similar to PathObserver? | |
| 84 _observers[name] = new PathObserver(model, path).bindSync((value) { | |
| 85 _values[name] = value; | |
| 86 _scheduleResolve(); | |
| 87 }); | |
| 88 } | |
| 89 | |
| 90 void unbind(name, {bool suppressResolve: false}) { | |
| 91 var binding = _observers.remove(name); | |
| 92 if (binding == null) return; | |
| 93 | |
| 94 binding.cancel(); | |
| 95 _values.remove(name); | |
| 96 if (!suppressResolve) _scheduleResolve(); | |
| 97 } | |
| 98 | |
| 99 // TODO(rafaelw): Is this the right processing model? | |
| 100 // TODO(rafaelw): Consider having a seperate ChangeSummary for | |
| 101 // CompoundBindings so to excess dirtyChecks. | |
| 102 void _scheduleResolve() { | |
| 103 if (scheduled) return; | |
| 104 scheduled = true; | |
| 105 scheduleMicrotask(resolve); | |
| 106 } | |
| 107 | |
| 108 void resolve() { | |
| 109 if (_observers.isEmpty) return; | |
| 110 scheduled = false; | |
| 111 | |
| 112 if (_combinator == null) { | |
| 113 throw new StateError( | |
| 114 'CompoundBinding attempted to resolve without a combinator'); | |
| 115 } | |
| 116 | |
| 117 value = _combinator(_values); | |
| 118 } | |
| 119 | |
| 120 /** | |
| 121 * Closes the observer. | |
| 122 * | |
| 123 * This happens automatically if the [value] property is no longer observed, | |
| 124 * but this can also be called explicitly. | |
| 125 */ | |
| 126 void close() { | |
| 127 for (var binding in _observers.values) { | |
| 128 binding.cancel(); | |
| 129 } | |
| 130 _observers.clear(); | |
| 131 _values.clear(); | |
| 132 value = null; | |
| 133 } | |
| 134 | |
| 135 unobserved() => close(); | |
| 136 } | |
| OLD | NEW |