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