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 |
|
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 | }
|