git ssb

1+

Matt McKegg / mutant



Tree: 0f95e238e7d990af64891bac02844a39e4c0cac1

Files: 0f95e238e7d990af64891bac02844a39e4c0cac1 / array.js

5864 bytesRaw
1var Value = require('./value')
2var LazyWatcher = require('./lib/lazy-watcher')
3var isSame = require('./lib/is-same')
4var isObservable = require('./is-observable')
5var resolve = require('./resolve')
6var addCollectionMethods = require('./lib/add-collection-methods')
7var forEach = require('./for-each')
8
9module.exports = Array
10
11function Array (defaultValues, opts) {
12 var object = []
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.idle) binder.idle = true
24 if (opts && opts.nextTick) binder.nextTick = true
25
26 if (defaultValues && defaultValues.length) {
27 forEach(defaultValues, add)
28 }
29
30 var observable = function MutantArray (listener) {
31 if (!listener) {
32 return binder.getValue()
33 }
34 return binder.addListener(listener)
35 }
36
37 // getLength, get, indexOf, etc
38 addCollectionMethods(observable, sources)
39
40 observable.push = function (item) {
41 var result = null
42 if (arguments.length === 1) {
43 result = add(item)
44 } else {
45 result = []
46 for (var i = 0; i < arguments.length; i++) {
47 result.push(add(arguments[i]))
48 }
49 }
50 binder.broadcast()
51 return result
52 }
53
54 observable.put = function (index, valueOrObs) {
55 valueOrObs = getObsValue(valueOrObs)
56 sources[index] = valueOrObs
57 object[index] = resolve(valueOrObs)
58 if (binder.live) {
59 tryInvoke(objectReleases[index])
60 objectReleases[index] = bind(valueOrObs)
61 }
62 binder.broadcast()
63 return valueOrObs
64 }
65
66 observable.insert = function (valueOrObs, at) {
67 valueOrObs = getObsValue(valueOrObs)
68 sources.splice(at, 0, valueOrObs)
69 if (binder.live) objectReleases.splice(at, 0, bind(valueOrObs))
70 object.splice(at, 0, resolve(valueOrObs))
71 binder.broadcast()
72 return valueOrObs
73 }
74
75 observable.pop = function () {
76 var result = sources.pop()
77 if (binder.live) tryInvoke(objectReleases.pop())
78 object.pop()
79 binder.broadcast()
80 return result
81 }
82
83 observable.shift = function () {
84 var result = sources.shift()
85 if (binder.live) tryInvoke(objectReleases.shift())
86 object.shift()
87 binder.broadcast()
88 return result
89 }
90
91 observable.clear = function () {
92 objectReleases.forEach(tryInvoke)
93 sources.length = 0
94 objectReleases.length = 0
95 object.length = 0
96 binder.broadcast()
97 }
98
99 observable.delete = function (valueOrObs) {
100 observable.deleteAt(sources.indexOf(valueOrObs))
101 }
102
103 observable.deleteAt = function (index) {
104 if (index >= 0 && index < sources.length) {
105 sources.splice(index, 1)
106 if (binder.live) objectReleases.splice(index, 1).forEach(tryInvoke)
107 object.splice(index, 1)
108 binder.broadcast()
109 }
110 }
111
112 observable.transaction = function (cb) {
113 binder.transaction(observable, cb)
114 }
115
116 observable.set = function (values) {
117 var changed = false
118 if (fixedIndexing) {
119 var length = values && values.length || 0
120 for (var i = 0; i < length; i++) {
121 if (isObservable(values[i])) {
122 if (values[i] !== sources[i]) {
123 tryInvoke(objectReleases[index])
124 sources[i] = values[i]
125 changed = true
126 if (binder.live) {
127 objectReleases[i] = bind(sources[i])
128 }
129 }
130 } else if (sources[i] && sources[i]._type === 'MutantArrayValue') {
131 if (!isSame(sources[i](), values[i], comparer)) {
132 sources[i].set(values[i])
133 changed = true
134 }
135 } else {
136 tryInvoke(objectReleases[index])
137 sources[i] = getObsValue(values[i])
138 changed = true
139 if (binder.live) {
140 objectReleases[i] = bind(sources[i])
141 }
142 }
143 }
144 for (var index = length; index < sources.length; index++) {
145 tryInvoke(objectReleases[index])
146 changed = true
147 }
148
149 if (changed) {
150 objectReleases.length = length
151 sources.length = length
152 object.length = length
153 binder.broadcast()
154 }
155 } else {
156 unlisten()
157 sources.length = 0
158 objectReleases.length = 0
159 object.length = 0
160 forEach(values, add)
161 if (binder.live) {
162 listen()
163 binder.broadcast()
164 }
165 }
166 }
167
168 return observable
169
170 // scoped
171
172 function getObsValue (valueOrObs) {
173 if (fixedIndexing && !isObservable(valueOrObs)) {
174 valueOrObs = Value(valueOrObs)
175 valueOrObs._type = 'MutantArrayValue'
176 }
177 return valueOrObs
178 }
179
180 function add (valueOrObs) {
181 valueOrObs = getObsValue(valueOrObs)
182 sources.push(valueOrObs)
183 object.push(resolve(valueOrObs))
184 if (binder.live) {
185 objectReleases.push(bind(valueOrObs))
186 }
187 return valueOrObs
188 }
189
190 function bind (valueOrObs) {
191 return typeof valueOrObs === 'function' ? valueOrObs(binder.onUpdate) : null
192 }
193
194 function listen () {
195 sources.forEach(function (obs, i) {
196 objectReleases[i] = bind(obs)
197 })
198
199 if (opts && opts.onListen) {
200 var release = opts.onListen()
201 if (typeof release === 'function') {
202 releases.push(release)
203 }
204 }
205 }
206
207 function unlisten () {
208 objectReleases.forEach(tryInvoke)
209 objectReleases.length = 0
210
211 while (releases.length) {
212 tryInvoke(releases.pop())
213 }
214
215 if (opts && opts.onUnlisten) {
216 opts.onUnlisten()
217 }
218 }
219
220 function update () {
221 var changed = false
222 sources.forEach(function (source, i) {
223 var newValue = resolve(source)
224 if (!isSame(newValue, object[i], comparer)) {
225 object[i] = newValue
226 changed = true
227 }
228 })
229 return changed
230 }
231}
232
233function tryInvoke (func) {
234 if (typeof func === 'function') {
235 func()
236 }
237}
238

Built with git-ssb-web