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