1 | var Value = require('./value')
|
2 | var LazyWatcher = require('./lib/lazy-watcher')
|
3 | var isSame = require('./lib/is-same')
|
4 | var resolve = require('./resolve')
|
5 | var isObservable = require('./is-observable')
|
6 | var forEachPair = require('./for-each-pair')
|
7 | var addLookupMethods = require('./lib/add-lookup-methods')
|
8 |
|
9 | module.exports = Dict
|
10 |
|
11 | function Dict (defaultValues, opts) {
|
12 | var object = Object.create({})
|
13 | var sources = {}
|
14 | var objectReleases = {}
|
15 | var fixedIndexing = opts && opts.fixedIndexing || false
|
16 |
|
17 | var releases = []
|
18 | var comparer = opts && opts.comparer || null
|
19 |
|
20 | var binder = LazyWatcher(update, listen, unlisten)
|
21 | binder.value = object
|
22 |
|
23 | if (opts && opts.nextTick) binder.nextTick = true
|
24 | if (opts && opts.idle) binder.idle = true
|
25 |
|
26 | if (defaultValues) {
|
27 | forEachPair(defaultValues, put)
|
28 | }
|
29 |
|
30 | var observable = function MutantDictionary (listener) {
|
31 | if (!listener) {
|
32 | return binder.getValue()
|
33 | }
|
34 | return binder.addListener(listener)
|
35 | }
|
36 |
|
37 | addLookupMethods(observable, sources)
|
38 |
|
39 | observable.put = function (key, valueOrObs) {
|
40 | valueOrObs = getObsValue(valueOrObs)
|
41 | put(key, valueOrObs)
|
42 | binder.broadcast()
|
43 | return valueOrObs
|
44 | }
|
45 |
|
46 | observable.clear = function () {
|
47 | Object.keys(sources).forEach(function (key) {
|
48 | tryInvoke(objectReleases[key])
|
49 | delete sources[key]
|
50 | delete objectReleases[key]
|
51 | delete object[key]
|
52 | })
|
53 | binder.broadcast()
|
54 | }
|
55 |
|
56 | observable.delete = function (key) {
|
57 | tryInvoke(objectReleases[key])
|
58 | delete sources[key]
|
59 | delete objectReleases[key]
|
60 | delete object[key]
|
61 | binder.broadcast()
|
62 | }
|
63 |
|
64 | observable.includes = function (valueOrObs) {
|
65 | return !!~object.indexOf(valueOrObs)
|
66 | }
|
67 |
|
68 | observable.set = function (values) {
|
69 | if (fixedIndexing) {
|
70 | var keys = []
|
71 |
|
72 | forEachPair(values, function (key, value) {
|
73 | keys.push(key)
|
74 | if (sources[key]) {
|
75 | sources[key].set(value)
|
76 | } else {
|
77 | put(key, getObsValue(value))
|
78 | }
|
79 | })
|
80 |
|
81 | Object.keys(sources).forEach(function (key) {
|
82 | if (!keys.includes(key)) {
|
83 | tryInvoke(objectReleases[key])
|
84 | delete sources[key]
|
85 | delete objectReleases[key]
|
86 | delete object[key]
|
87 | }
|
88 | })
|
89 | } else {
|
90 | Object.keys(sources).forEach(function (key) {
|
91 | tryInvoke(objectReleases[key])
|
92 | delete sources[key]
|
93 | delete objectReleases[key]
|
94 | delete object[key]
|
95 | })
|
96 |
|
97 | forEachPair(values, put)
|
98 | binder.broadcast()
|
99 | }
|
100 | }
|
101 |
|
102 | return observable
|
103 |
|
104 |
|
105 |
|
106 | function getObsValue (valueOrObs) {
|
107 | if (fixedIndexing && !isObservable(valueOrObs)) {
|
108 | valueOrObs = Value(valueOrObs)
|
109 | }
|
110 | return valueOrObs
|
111 | }
|
112 |
|
113 | function put (key, valueOrObs) {
|
114 | tryInvoke(objectReleases[key])
|
115 | sources[key] = valueOrObs
|
116 | if (binder.live) {
|
117 | objectReleases[key] = bind(key, valueOrObs)
|
118 | }
|
119 | object[key] = resolve(valueOrObs)
|
120 | }
|
121 |
|
122 | function bind (key, valueOrObs) {
|
123 | return typeof valueOrObs === 'function' ? valueOrObs(updateKey.bind(this, key)) : null
|
124 | }
|
125 |
|
126 | function updateKey (key, value) {
|
127 | object[key] = value
|
128 | binder.broadcast()
|
129 | }
|
130 |
|
131 | function listen () {
|
132 | Object.keys(sources).forEach(function (key) {
|
133 | objectReleases[key] = bind(key, sources[key])
|
134 | })
|
135 |
|
136 | if (opts && opts.onListen) {
|
137 | var release = opts.onListen()
|
138 | if (typeof release === 'function') {
|
139 | releases.push(release)
|
140 | }
|
141 | }
|
142 | }
|
143 |
|
144 | function unlisten () {
|
145 | Object.keys(sources).forEach(function (key) {
|
146 | tryInvoke(objectReleases[key])
|
147 | delete objectReleases[key]
|
148 | })
|
149 |
|
150 | while (releases.length) {
|
151 | tryInvoke(releases.pop())
|
152 | }
|
153 |
|
154 | if (opts && opts.onUnlisten) {
|
155 | opts.onUnlisten()
|
156 | }
|
157 | }
|
158 |
|
159 | function update () {
|
160 | var changed = false
|
161 | Object.keys(sources).forEach(function (key) {
|
162 | var newValue = resolve(sources[key])
|
163 | if (!isSame(newValue, object[key], comparer)) {
|
164 | object[key] = newValue
|
165 | changed = true
|
166 | }
|
167 | })
|
168 | return changed
|
169 | }
|
170 | }
|
171 |
|
172 | function tryInvoke (func) {
|
173 | if (typeof func === 'function') {
|
174 | func()
|
175 | }
|
176 | }
|