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 observe; | 5 part of observe; |
6 | 6 |
7 // TODO(jmesserly): this needs to be faster. We currently require multiple | 7 // TODO(jmesserly): this needs to be faster. We currently require multiple |
8 // lookups per key to get the old value. | 8 // lookups per key to get the old value. |
9 // TODO(jmesserly): this doesn't implement the precise interfaces like | 9 // TODO(jmesserly): this doesn't implement the precise interfaces like |
10 // LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the | 10 // LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the |
11 // backing store. | 11 // backing store. |
12 | 12 |
13 // TODO(jmesserly): should we summarize map changes like we do for list changes? | 13 // TODO(jmesserly): should we summarize map changes like we do for list changes? |
14 class MapChangeRecord extends ChangeRecord { | 14 class MapChangeRecord<K, V> extends ChangeRecord { |
| 15 // TODO(jmesserly): we could store this more compactly if it matters, with |
| 16 // subtypes for inserted and removed. |
| 17 |
15 /** The map key that changed. */ | 18 /** The map key that changed. */ |
16 final key; | 19 final K key; |
17 | 20 |
18 // TODO(jmesserly): we could store this more compactly if it matters. | 21 /** The previous value associated with this key. */ |
| 22 final V oldValue; |
| 23 |
| 24 /** The new value associated with this key. */ |
| 25 final V newValue; |
| 26 |
19 /** True if this key was inserted. */ | 27 /** True if this key was inserted. */ |
20 final bool isInsert; | 28 final bool isInsert; |
21 | 29 |
22 /** True if this key was removed. */ | 30 /** True if this key was removed. */ |
23 final bool isRemove; | 31 final bool isRemove; |
24 | 32 |
25 MapChangeRecord(this.key, {this.isInsert: false, this.isRemove: false}) { | 33 MapChangeRecord(this.key, this.oldValue, this.newValue) |
26 if (isInsert && isRemove) { | 34 : isInsert = false, isRemove = false; |
27 throw new ArgumentError( | |
28 '$key cannot be inserted and removed in the same change'); | |
29 } | |
30 } | |
31 | 35 |
32 // Use == on the key, to match equality semantics of most Maps. | 36 MapChangeRecord.insert(this.key, this.newValue) |
| 37 : isInsert = true, isRemove = false; |
| 38 |
| 39 MapChangeRecord.remove(this.key, this.oldValue) |
| 40 : isInsert = false, isRemove = true; |
| 41 |
| 42 /// *Deprecated* compare [key]s instead. |
| 43 @deprecated |
33 bool changes(otherKey) => key == otherKey; | 44 bool changes(otherKey) => key == otherKey; |
34 | 45 |
35 String toString() { | 46 String toString() { |
36 var kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set'; | 47 var kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set'; |
37 return '#<MapChangeRecord $kind $key>'; | 48 return '#<MapChangeRecord $kind $key from: $oldValue to: $newValue>'; |
38 } | 49 } |
39 } | 50 } |
40 | 51 |
41 /** | 52 /** |
42 * Represents an observable map of model values. If any items are added, | 53 * Represents an observable map of model values. If any items are added, |
43 * removed, or replaced, then observers that are listening to [changes] | 54 * removed, or replaced, then observers that are listening to [changes] |
44 * will be notified. | 55 * will be notified. |
45 */ | 56 */ |
46 class ObservableMap<K, V> extends ChangeNotifierBase implements Map<K, V> { | 57 class ObservableMap<K, V> extends ChangeNotifier implements Map<K, V> { |
47 final Map<K, V> _map; | 58 final Map<K, V> _map; |
48 | 59 |
49 /** Creates an observable map. */ | 60 /** Creates an observable map. */ |
50 ObservableMap() : _map = new HashMap<K, V>(); | 61 ObservableMap() : _map = new HashMap<K, V>(); |
51 | 62 |
52 /** Creates a new observable map using a [LinkedHashMap]. */ | 63 /** Creates a new observable map using a [LinkedHashMap]. */ |
53 ObservableMap.linked() : _map = new LinkedHashMap<K, V>(); | 64 ObservableMap.linked() : _map = new LinkedHashMap<K, V>(); |
54 | 65 |
55 /** Creates a new observable map using a [SplayTreeMap]. */ | 66 /** Creates a new observable map using a [SplayTreeMap]. */ |
56 ObservableMap.sorted() : _map = new SplayTreeMap<K, V>(); | 67 ObservableMap.sorted() : _map = new SplayTreeMap<K, V>(); |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
96 | 107 |
97 @reflectable V operator [](Object key) => _map[key]; | 108 @reflectable V operator [](Object key) => _map[key]; |
98 | 109 |
99 @reflectable void operator []=(K key, V value) { | 110 @reflectable void operator []=(K key, V value) { |
100 int len = _map.length; | 111 int len = _map.length; |
101 V oldValue = _map[key]; | 112 V oldValue = _map[key]; |
102 _map[key] = value; | 113 _map[key] = value; |
103 if (hasObservers) { | 114 if (hasObservers) { |
104 if (len != _map.length) { | 115 if (len != _map.length) { |
105 notifyPropertyChange(#length, len, _map.length); | 116 notifyPropertyChange(#length, len, _map.length); |
106 notifyChange(new MapChangeRecord(key, isInsert: true)); | 117 notifyChange(new MapChangeRecord.insert(key, value)); |
107 } else if (!identical(oldValue, value)) { | 118 } else if (oldValue != value) { |
108 notifyChange(new MapChangeRecord(key)); | 119 notifyChange(new MapChangeRecord(key, oldValue, value)); |
109 } | 120 } |
110 } | 121 } |
111 } | 122 } |
112 | 123 |
113 void addAll(Map<K, V> other) { | 124 void addAll(Map<K, V> other) { |
114 other.forEach((K key, V value) { this[key] = value; }); | 125 other.forEach((K key, V value) { this[key] = value; }); |
115 } | 126 } |
116 | 127 |
117 V putIfAbsent(K key, V ifAbsent()) { | 128 V putIfAbsent(K key, V ifAbsent()) { |
118 int len = _map.length; | 129 int len = _map.length; |
119 V result = _map.putIfAbsent(key, ifAbsent); | 130 V result = _map.putIfAbsent(key, ifAbsent); |
120 if (hasObservers && len != _map.length) { | 131 if (hasObservers && len != _map.length) { |
121 notifyPropertyChange(#length, len, _map.length); | 132 notifyPropertyChange(#length, len, _map.length); |
122 notifyChange(new MapChangeRecord(key, isInsert: true)); | 133 notifyChange(new MapChangeRecord.insert(key, result)); |
123 } | 134 } |
124 return result; | 135 return result; |
125 } | 136 } |
126 | 137 |
127 V remove(Object key) { | 138 V remove(Object key) { |
128 int len = _map.length; | 139 int len = _map.length; |
129 V result = _map.remove(key); | 140 V result = _map.remove(key); |
130 if (hasObservers && len != _map.length) { | 141 if (hasObservers && len != _map.length) { |
131 notifyChange(new MapChangeRecord(key, isRemove: true)); | 142 notifyChange(new MapChangeRecord.remove(key, result)); |
132 notifyPropertyChange(#length, len, _map.length); | 143 notifyPropertyChange(#length, len, _map.length); |
133 } | 144 } |
134 return result; | 145 return result; |
135 } | 146 } |
136 | 147 |
137 void clear() { | 148 void clear() { |
138 int len = _map.length; | 149 int len = _map.length; |
139 if (hasObservers && len > 0) { | 150 if (hasObservers && len > 0) { |
140 _map.forEach((key, value) { | 151 _map.forEach((key, value) { |
141 notifyChange(new MapChangeRecord(key, isRemove: true)); | 152 notifyChange(new MapChangeRecord.remove(key, value)); |
142 }); | 153 }); |
143 notifyPropertyChange(#length, len, 0); | 154 notifyPropertyChange(#length, len, 0); |
144 } | 155 } |
145 _map.clear(); | 156 _map.clear(); |
146 } | 157 } |
147 | 158 |
148 void forEach(void f(K key, V value)) => _map.forEach(f); | 159 void forEach(void f(K key, V value)) => _map.forEach(f); |
149 | 160 |
150 String toString() => Maps.mapToString(this); | 161 String toString() => Maps.mapToString(this); |
151 } | 162 } |
OLD | NEW |