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.observable_map; | |
6 | |
7 import 'dart:collection'; | |
8 import 'package:observe/observe.dart'; | |
9 | |
10 | |
11 // TODO(jmesserly): this needs to be faster. We currently require multiple | |
12 // lookups per key to get the old value. | |
13 // TODO(jmesserly): this doesn't implement the precise interfaces like | |
14 // LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the | |
15 // backing store. | |
16 | |
17 // TODO(jmesserly): should we summarize map changes like we do for list changes? | |
18 class MapChangeRecord<K, V> extends ChangeRecord { | |
19 // TODO(jmesserly): we could store this more compactly if it matters, with | |
20 // subtypes for inserted and removed. | |
21 | |
22 /// The map key that changed. | |
23 final K key; | |
24 | |
25 /// The previous value associated with this key. | |
26 final V oldValue; | |
27 | |
28 /// The new value associated with this key. | |
29 final V newValue; | |
30 | |
31 /// True if this key was inserted. | |
32 final bool isInsert; | |
33 | |
34 /// True if this key was removed. | |
35 final bool isRemove; | |
36 | |
37 MapChangeRecord(this.key, this.oldValue, this.newValue) | |
38 : isInsert = false, isRemove = false; | |
39 | |
40 MapChangeRecord.insert(this.key, this.newValue) | |
41 : isInsert = true, isRemove = false, oldValue = null; | |
42 | |
43 MapChangeRecord.remove(this.key, this.oldValue) | |
44 : isInsert = false, isRemove = true, newValue = null; | |
45 | |
46 String toString() { | |
47 var kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set'; | |
48 return '#<MapChangeRecord $kind $key from: $oldValue to: $newValue>'; | |
49 } | |
50 } | |
51 | |
52 /// Represents an observable map of model values. If any items are added, | |
53 /// removed, or replaced, then observers that are listening to [changes] | |
54 /// will be notified. | |
55 class ObservableMap<K, V> extends ChangeNotifier implements Map<K, V> { | |
56 final Map<K, V> _map; | |
57 | |
58 /// Creates an observable map. | |
59 ObservableMap() : _map = new HashMap<K, V>(); | |
60 | |
61 /// Creates a new observable map using a [LinkedHashMap]. | |
62 ObservableMap.linked() : _map = new LinkedHashMap<K, V>(); | |
63 | |
64 /// Creates a new observable map using a [SplayTreeMap]. | |
65 ObservableMap.sorted() : _map = new SplayTreeMap<K, V>(); | |
66 | |
67 /// Creates an observable map that contains all key value pairs of [other]. | |
68 /// It will attempt to use the same backing map type if the other map is a | |
69 /// [LinkedHashMap], [SplayTreeMap], or [HashMap]. Otherwise it defaults to | |
70 /// [HashMap]. | |
71 /// | |
72 /// Note this will perform a shallow conversion. If you want a deep conversion | |
73 /// you should use [toObservable]. | |
74 factory ObservableMap.from(Map<K, V> other) { | |
75 return new ObservableMap<K, V>.createFromType(other)..addAll(other); | |
76 } | |
77 | |
78 /// Like [ObservableMap.from], but creates an empty map. | |
79 factory ObservableMap.createFromType(Map<K, V> other) { | |
80 ObservableMap result; | |
81 if (other is SplayTreeMap) { | |
82 result = new ObservableMap<K, V>.sorted(); | |
83 } else if (other is LinkedHashMap) { | |
84 result = new ObservableMap<K, V>.linked(); | |
85 } else { | |
86 result = new ObservableMap<K, V>(); | |
87 } | |
88 return result; | |
89 } | |
90 | |
91 @reflectable Iterable<K> get keys => _map.keys; | |
92 | |
93 @reflectable Iterable<V> get values => _map.values; | |
94 | |
95 @reflectable int get length =>_map.length; | |
96 | |
97 @reflectable bool get isEmpty => length == 0; | |
98 | |
99 @reflectable bool get isNotEmpty => !isEmpty; | |
100 | |
101 @reflectable bool containsValue(Object value) => _map.containsValue(value); | |
102 | |
103 @reflectable bool containsKey(Object key) => _map.containsKey(key); | |
104 | |
105 @reflectable V operator [](Object key) => _map[key]; | |
106 | |
107 @reflectable void operator []=(K key, V value) { | |
108 if (!hasObservers) { | |
109 _map[key] = value; | |
110 return; | |
111 } | |
112 | |
113 int len = _map.length; | |
114 V oldValue = _map[key]; | |
115 | |
116 _map[key] = value; | |
117 | |
118 if (len != _map.length) { | |
119 notifyPropertyChange(#length, len, _map.length); | |
120 notifyChange(new MapChangeRecord.insert(key, value)); | |
121 _notifyKeysValuesChanged(); | |
122 } else if (oldValue != value) { | |
123 notifyChange(new MapChangeRecord(key, oldValue, value)); | |
124 _notifyValuesChanged(); | |
125 } | |
126 } | |
127 | |
128 void addAll(Map<K, V> other) { | |
129 other.forEach((K key, V value) { this[key] = value; }); | |
130 } | |
131 | |
132 V putIfAbsent(K key, V ifAbsent()) { | |
133 int len = _map.length; | |
134 V result = _map.putIfAbsent(key, ifAbsent); | |
135 if (hasObservers && len != _map.length) { | |
136 notifyPropertyChange(#length, len, _map.length); | |
137 notifyChange(new MapChangeRecord.insert(key, result)); | |
138 _notifyKeysValuesChanged(); | |
139 } | |
140 return result; | |
141 } | |
142 | |
143 V remove(Object key) { | |
144 int len = _map.length; | |
145 V result = _map.remove(key); | |
146 if (hasObservers && len != _map.length) { | |
147 notifyChange(new MapChangeRecord.remove(key, result)); | |
148 notifyPropertyChange(#length, len, _map.length); | |
149 _notifyKeysValuesChanged(); | |
150 } | |
151 return result; | |
152 } | |
153 | |
154 void clear() { | |
155 int len = _map.length; | |
156 if (hasObservers && len > 0) { | |
157 _map.forEach((key, value) { | |
158 notifyChange(new MapChangeRecord.remove(key, value)); | |
159 }); | |
160 notifyPropertyChange(#length, len, 0); | |
161 _notifyKeysValuesChanged(); | |
162 } | |
163 _map.clear(); | |
164 } | |
165 | |
166 void forEach(void f(K key, V value)) => _map.forEach(f); | |
167 | |
168 String toString() => Maps.mapToString(this); | |
169 | |
170 // Note: we don't really have a reasonable old/new value to use here. | |
171 // But this should fix "keys" and "values" in templates with minimal overhead. | |
172 void _notifyKeysValuesChanged() { | |
173 notifyChange(new PropertyChangeRecord(this, #keys, null, null)); | |
174 _notifyValuesChanged(); | |
175 } | |
176 | |
177 void _notifyValuesChanged() { | |
178 notifyChange(new PropertyChangeRecord(this, #values, null, null)); | |
179 } | |
180 } | |
OLD | NEW |