UNPKG

7.82 kBJavaScriptView Raw
1
2/*
3 TODO:
4 sort reverse
5 test array methods
6 figure out a way to not update removed items
7*/
8
9const Observer = {
10
11 splice () {
12 const self = this;
13
14 let startIndex = arguments[0];
15 let deleteCount = arguments[1];
16 let addCount = arguments.length > 2 ? arguments.length - 2 : 0;
17
18 if (typeof startIndex !== 'number' || typeof deleteCount !== 'number') {
19 return [];
20 }
21
22 // handle negative startIndex
23 if (startIndex < 0) {
24 startIndex = self.length + startIndex;
25 startIndex = startIndex > 0 ? startIndex : 0;
26 } else {
27 startIndex = startIndex < self.length ? startIndex : self.length;
28 }
29
30 // handle negative deleteCount
31 if (deleteCount < 0) {
32 deleteCount = 0;
33 } else if (deleteCount > (self.length - startIndex)) {
34 deleteCount = self.length - startIndex;
35 }
36
37 let totalCount = self.$meta.length;
38 let argumentIndex = 2;
39 let argumentsCount = arguments.length - argumentIndex;
40 const result = self.slice(startIndex, deleteCount);
41
42 let updateCount = (totalCount - 1) - startIndex;
43
44 const promises = [];
45
46 const length = self.length + addCount - deleteCount;
47
48 if (self.length !== length) {
49 promises.push(self.$meta.listener.bind(null, self, self.$meta.path.slice(0, -1), 'length'));
50 }
51
52 if (updateCount > 0) {
53 let value;
54 let index = startIndex;
55
56 while (updateCount--) {
57 const key = index++;
58
59 if (argumentsCount && argumentIndex < argumentsCount) {
60 value = arguments[argumentIndex++];
61 } else {
62 value = self.$meta[index];
63 }
64
65 self.$meta[key] = Observer.create(value, self.$meta.listener, self.$meta.path + key);
66 promises.push(self.$meta.listener.bind(null, self.$meta[key], self.$meta.path + key, key));
67 }
68
69 }
70
71
72 if (addCount > 0) {
73 while (addCount--) {
74 const key = self.length;
75
76 if (key in this === false) {
77 Object.defineProperty(this, key, Observer.descriptor(key));
78 }
79
80 self.$meta[key] = Observer.create(arguments[argumentIndex++], self.$meta.listener, self.$meta.path + key);
81 promises.push(self.$meta.listener.bind(null, self.$meta[key], self.$meta.path + key, key));
82
83 }
84 }
85
86 if (deleteCount > 0) {
87 while (deleteCount--) {
88 self.$meta.length--;
89 self.length--;
90 const key = self.length;
91 promises.push(self.$meta.listener.bind(null, undefined, self.$meta.path + key, key));
92 }
93 }
94
95 Promise.resolve().then(function () {
96 promises.reduce(function (promise, item) {
97 return promise.then(item);
98 }, Promise.resolve());
99 }).catch(console.error);
100
101 return result;
102 },
103
104 arrayProperties () {
105 const self = this;
106
107 return {
108 push: {
109 value: function () {
110 if (!arguments.length) return this.length;
111
112 for (let i = 0, l = arguments.length; i < l; i++) {
113 self.splice.call(this, this.length, 0, arguments[i]);
114 }
115
116 return this.length;
117 }
118 },
119 unshift: {
120 value: function () {
121 if (!arguments.length) return this.length;
122
123 for (let i = 0, l = arguments.length; i < l; i++) {
124 self.splice.call(this, 0, 0, arguments[i]);
125 }
126
127 return this.length;
128 }
129 },
130 pop: {
131 value: function () {
132 if (!this.length) return;
133 const result = self.splice.call(this, this.length-1, 1);
134 return result[0];
135 }
136 },
137 shift: {
138 value: function () {
139 if (!this.length) return;
140 const result = self.splice.call(this, 0, 1);
141 return result[0];
142 }
143 },
144 splice: {
145 value: self.splice
146 }
147 };
148 },
149
150 objectProperties () {
151 const self = this;
152
153 return {
154 $get: {
155 value: function (key) {
156 return this.$meta[key];
157 }
158 },
159 $set: {
160 value: function (key, value) {
161 if (value !== this.$meta[key]) {
162
163 if (key in this === false) {
164 Object.defineProperty(this, key, self.descriptor(key));
165 }
166
167 this.$meta[key] = self.create(value, this.$meta.listener, this.$meta.path + key);
168 this.$meta.listener(this.$meta[key], this.$meta.path + key, key, this);
169 }
170 }
171 },
172 $remove: {
173 value: function (key) {
174 if (key in this) {
175 if (this.constructor === Array) {
176 return self.splice.call(this, key, 1);
177 } else {
178 let result = this[key];
179 delete this.$meta[key];
180 delete this[key];
181 this.$meta.listener(undefined, this.$meta.path + key, key);
182 return result;
183 }
184 }
185 }
186 }
187 };
188 },
189
190 descriptor (key) {
191 const self = this;
192
193 return {
194 enumerable: true,
195 configurable: true,
196 get: function () {
197 return this.$meta[key];
198 },
199 set: function (value) {
200 if (value !== this.$meta[key]) {
201 this.$meta[key] = self.create(value, this.$meta.listener, this.$meta.path + key);
202 this.$meta.listener(this.$meta[key], this.$meta.path + key, key, this);
203 }
204 }
205 };
206 },
207
208 create (source, listener, path) {
209 // const self = this;
210
211 if (!source || source.constructor !== Object && source.constructor !== Array) {
212 return source;
213 }
214
215 path = path ? path + '.' : '';
216
217 const type = source.constructor;
218 const target = source.constructor();
219 const descriptors = {};
220
221 descriptors.$meta = {
222 value: source.constructor()
223 };
224
225 descriptors.$meta.value.path = path;
226 descriptors.$meta.value.listener = listener;
227
228 if (type === Array) {
229 for (let key = 0, length = source.length; key < length; key++) {
230 descriptors.$meta.value[key] = this.create(source[key], listener, path + key);
231 descriptors[key] = this.descriptor(key);
232 }
233 }
234
235 if (type === Object) {
236 for (let key in source) {
237 descriptors.$meta.value[key] = this.create(source[key], listener, path + key);
238 descriptors[key] = this.descriptor(key);
239 }
240 }
241
242 Object.defineProperties(target, descriptors);
243 Object.defineProperties(target, this.objectProperties(source, listener, path));
244
245 if (type === Array) {
246 Object.defineProperties(target, this.arrayProperties(source, listener, path));
247 }
248
249 return target;
250 }
251
252};
253
254export default Observer;