Files: 073f2ca837054c3b04113dd573e1a9e8b0c6ed29 / set.js
3944 bytesRaw
1 | var LazyWatcher = require('./lib/lazy-watcher') |
2 | |
3 | module.exports = Set |
4 | |
5 | function Set (defaultValues, opts) { |
6 | var instance = new ProtoSet(defaultValues, opts) |
7 | var observable = instance.MutantSet.bind(instance) |
8 | observable.add = instance.add.bind(instance) |
9 | observable.clear = instance.clear.bind(instance) |
10 | observable.delete = instance.delete.bind(instance) |
11 | observable.has = instance.has.bind(instance) |
12 | observable.set = instance.set.bind(instance) |
13 | observable.get = instance.get.bind(instance) |
14 | observable.getLength = instance.getLength.bind(instance) |
15 | return observable |
16 | } |
17 | |
18 | // optimise memory usage |
19 | function ProtoSet (defaultValues, opts) { |
20 | var self = this |
21 | self.object = [] |
22 | self.sources = [] |
23 | self.releases = [] |
24 | self.binder = LazyWatcher.call(self, self._update, self._listen, self._unlisten) |
25 | self.binder.value = this.object |
26 | |
27 | if (opts && opts.nextTick) { |
28 | self.binder.nextTick = true |
29 | } |
30 | |
31 | if (defaultValues && defaultValues.length) { |
32 | defaultValues.forEach(function (valueOrObs) { |
33 | if (!~self.sources.indexOf(valueOrObs)) { |
34 | self.sources.push(valueOrObs) |
35 | } |
36 | }) |
37 | this._update() |
38 | } |
39 | } |
40 | |
41 | ProtoSet.prototype.MutantSet = function (listener) { |
42 | if (!listener) { |
43 | return this.binder.getValue() |
44 | } |
45 | return this.binder.addListener(listener) |
46 | } |
47 | |
48 | ProtoSet.prototype.add = function (valueOrObs) { |
49 | if (!~this.sources.indexOf(valueOrObs)) { |
50 | this.sources.push(valueOrObs) |
51 | if (this.binder.live) { |
52 | this.releases[this.sources.length - 1] = this._bind(valueOrObs) |
53 | } |
54 | this.binder.onUpdate() |
55 | } |
56 | } |
57 | |
58 | ProtoSet.prototype.clear = function () { |
59 | this.releases.forEach(tryInvoke) |
60 | this.sources.length = 0 |
61 | this.releases.length = 0 |
62 | this.binder.onUpdate() |
63 | } |
64 | |
65 | ProtoSet.prototype.delete = function (valueOrObs) { |
66 | var index = this.sources.indexOf(valueOrObs) |
67 | if (~index) { |
68 | this.sources.splice(index, 1) |
69 | this.releases.splice(index, 1).forEach(tryInvoke) |
70 | this.binder.onUpdate() |
71 | } |
72 | } |
73 | |
74 | ProtoSet.prototype.has = function (valueOrObs) { |
75 | return !!~this.object.indexOf(valueOrObs) |
76 | } |
77 | |
78 | ProtoSet.prototype.set = function (values) { |
79 | var self = this |
80 | var changed = false |
81 | |
82 | if (Array.isArray(values)) { |
83 | for (var i = this.sources.length - 1; i >= 0; i -= 1) { |
84 | if (!~values.indexOf(this.sources[i])) { |
85 | changed = true |
86 | self.sources.splice(i, 1) |
87 | } |
88 | } |
89 | values.forEach(function (value) { |
90 | if (!~self.sources.indexOf(value)) { |
91 | changed = true |
92 | self.sources.push(value) |
93 | } |
94 | }) |
95 | } else { |
96 | if (self.sources.length > 0) { |
97 | self.sources.length = 0 |
98 | changed = true |
99 | } |
100 | } |
101 | |
102 | if (changed) { |
103 | self.binder.onUpdate() |
104 | } |
105 | } |
106 | |
107 | ProtoSet.prototype.get = function (index) { |
108 | return this.sources[index] |
109 | } |
110 | |
111 | ProtoSet.prototype.getLength = function () { |
112 | return this.sources.length |
113 | } |
114 | |
115 | ProtoSet.prototype._bind = function (valueOrObs) { |
116 | return typeof valueOrObs === 'function' ? valueOrObs(this.binder.onUpdate) : null |
117 | } |
118 | |
119 | ProtoSet.prototype._listen = function () { |
120 | var self = this |
121 | self.sources.forEach(function (obs, i) { |
122 | self.releases[i] = self._bind(obs) |
123 | }) |
124 | } |
125 | |
126 | ProtoSet.prototype._unlisten = function () { |
127 | this.releases.forEach(tryInvoke) |
128 | this.releases.length = 0 |
129 | } |
130 | |
131 | ProtoSet.prototype._update = function () { |
132 | var currentValues = this.object.map(get) |
133 | var newValues = this.sources.map(resolve) |
134 | currentValues.filter(notIncluded, newValues).forEach(removeFrom, this.object) |
135 | newValues.filter(notIncluded, currentValues).forEach(addTo, this.object) |
136 | return true |
137 | } |
138 | |
139 | function get (value) { |
140 | return value |
141 | } |
142 | |
143 | function resolve (source) { |
144 | return typeof source === 'function' ? source() : source |
145 | } |
146 | |
147 | function notIncluded (value) { |
148 | return !~this.indexOf(value) |
149 | } |
150 | |
151 | function removeFrom (item) { |
152 | var index = this.indexOf(item) |
153 | if (~index) { |
154 | this.splice(index, 1) |
155 | } |
156 | } |
157 | |
158 | function addTo (item) { |
159 | this.push(item) |
160 | } |
161 | |
162 | function tryInvoke (func) { |
163 | if (typeof func === 'function') { |
164 | func() |
165 | } |
166 | } |
167 |
Built with git-ssb-web