source: node_modules/minim/lib/primitives/ArrayElement.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 9.0 KB
Line 
1const negate = require('lodash/negate');
2const Element = require('./Element');
3const ArraySlice = require('../ArraySlice');
4
5/**
6 * @class
7 *
8 * @param {Element[]} content
9 * @param meta
10 * @param attributes
11 */
12class ArrayElement extends Element {
13 constructor(content, meta, attributes) {
14 super(content || [], meta, attributes);
15 this.element = 'array';
16 }
17
18 primitive() {
19 return 'array';
20 }
21
22 /**
23 * @returns {Element}
24 */
25 get(index) {
26 return this.content[index];
27 }
28
29 /**
30 * Helper for returning the value of an item
31 * This works for both ArrayElement and ObjectElement instances
32 */
33 getValue(indexOrKey) {
34 const item = this.get(indexOrKey);
35
36 if (item) {
37 return item.toValue();
38 }
39
40 return undefined;
41 }
42
43 /**
44 * @returns {Element}
45 */
46 getIndex(index) {
47 return this.content[index];
48 }
49
50 set(index, value) {
51 this.content[index] = this.refract(value);
52 return this;
53 }
54
55 remove(index) {
56 const removed = this.content.splice(index, 1);
57
58 if (removed.length) {
59 return removed[0];
60 }
61
62 return null;
63 }
64
65 /**
66 * @param callback - Function to execute for each element
67 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
68 */
69 map(callback, thisArg) {
70 return this.content.map(callback, thisArg);
71 }
72
73 /**
74 * Maps and then flattens the results.
75 * @param callback - Function to execute for each element.
76 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
77 * @returns {array}
78 */
79 flatMap(callback, thisArg) {
80 return this
81 .map(callback, thisArg)
82 .reduce((a, b) => a.concat(b), []);
83 }
84
85 /**
86 * Returns an array containing the truthy results of calling the given transformation with each element of this sequence
87 * @param transform - A closure that accepts an element of this array as its argument and returns an optional value.
88 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
89 * @memberof ArrayElement.prototype
90 * @returns An array of the non-undefined results of calling transform with each element of the array
91 */
92 compactMap(transform, thisArg) {
93 const results = [];
94
95 this.forEach((element) => {
96 const result = transform.bind(thisArg)(element);
97
98 if (result) {
99 results.push(result);
100 }
101 });
102
103 return results;
104 }
105
106 /**
107 * @param callback - Function to execute for each element
108 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
109 * @returns {ArraySlice}
110 */
111 filter(callback, thisArg) {
112 return new ArraySlice(this.content.filter(callback, thisArg));
113 }
114
115 /**
116 * @param callback - Function to execute for each element
117 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
118 * @returns {ArraySlice}
119 */
120 reject(callback, thisArg) {
121 return this.filter(negate(callback), thisArg);
122 }
123
124 /**
125 * This is a reduce function specifically for Minim arrays and objects. It
126 * allows for returning normal values or Minim instances, so it converts any
127 * primitives on each step.
128 */
129 reduce(callback, initialValue) {
130 let startIndex;
131 let memo;
132
133 // Allows for defining a starting value of the reduce
134 if (initialValue !== undefined) {
135 startIndex = 0;
136 memo = this.refract(initialValue);
137 } else {
138 startIndex = 1;
139 // Object Element content items are member elements. Because of this,
140 // the memo should start out as the member value rather than the
141 // actual member itself.
142 memo = this.primitive() === 'object' ? this.first.value : this.first;
143 }
144
145 // Sending each function call to the registry allows for passing Minim
146 // instances through the function return. This means you can return
147 // primitive values or return Minim instances and reduce will still work.
148 for (let i = startIndex; i < this.length; i += 1) {
149 const item = this.content[i];
150
151 if (this.primitive() === 'object') {
152 memo = this.refract(callback(memo, item.value, item.key, item, this));
153 } else {
154 memo = this.refract(callback(memo, item, i, this));
155 }
156 }
157
158 return memo;
159 }
160
161 /**
162 * @callback forEachCallback
163 * @param {Element} currentValue
164 * @param {NumberElement} index
165 */
166
167 /**
168 * @param {forEachCallback} callback - Function to execute for each element
169 * @param thisArg - Value to use as this (i.e the reference Object) when executing callback
170 * @memberof ArrayElement.prototype
171 */
172 forEach(callback, thisArg) {
173 this.content.forEach((item, index) => {
174 callback.bind(thisArg)(item, this.refract(index));
175 });
176 }
177
178 /**
179 * @returns {Element}
180 */
181 shift() {
182 return this.content.shift();
183 }
184
185 /**
186 * @param value
187 */
188 unshift(value) {
189 this.content.unshift(this.refract(value));
190 }
191
192 /**
193 * @param value
194 */
195 push(value) {
196 this.content.push(this.refract(value));
197 return this;
198 }
199
200 /**
201 * @param value
202 */
203 add(value) {
204 this.push(value);
205 }
206
207 /**
208 * Recusively search all descendents using a condition function.
209 * @returns {Element[]}
210 */
211 findElements(condition, givenOptions) {
212 const options = givenOptions || {};
213 const recursive = !!options.recursive;
214 const results = options.results === undefined ? [] : options.results;
215
216 // The forEach method for Object Elements returns value, key, and member.
217 // This passes those along to the condition function below.
218 this.forEach((item, keyOrIndex, member) => {
219 // We use duck-typing here to support any registered class that
220 // may contain other elements.
221 if (recursive && (item.findElements !== undefined)) {
222 item.findElements(condition, {
223 results,
224 recursive,
225 });
226 }
227
228 if (condition(item, keyOrIndex, member)) {
229 results.push(item);
230 }
231 });
232
233 return results;
234 }
235
236 /**
237 * Recusively search all descendents using a condition function.
238 * @param condition
239 * @returns {ArraySlice}
240 */
241 find(condition) {
242 return new ArraySlice(this.findElements(condition, { recursive: true }));
243 }
244
245 /**
246 * @param {string} element
247 * @returns {ArraySlice}
248 */
249 findByElement(element) {
250 return this.find(item => item.element === element);
251 }
252
253 /**
254 * @param {string} className
255 * @returns {ArraySlice}
256 * @memberof ArrayElement.prototype
257 */
258 findByClass(className) {
259 return this.find(item => item.classes.includes(className));
260 }
261
262 /**
263 * Search the tree recursively and find the element with the matching ID
264 * @param {string} id
265 * @returns {Element}
266 * @memberof ArrayElement.prototype
267 */
268 getById(id) {
269 return this.find(item => item.id.toValue() === id).first;
270 }
271
272 /**
273 * Looks for matching children using deep equality
274 * @param value
275 * @returns {boolean}
276 */
277 includes(value) {
278 return this.content.some(element => element.equals(value));
279 }
280
281 /**
282 * Looks for matching children using deep equality
283 * @param value
284 * @returns {boolean}
285 * @see includes
286 * @deprecated method was replaced by includes
287 */
288 contains(value) {
289 return this.includes(value);
290 }
291
292 // Fantasy Land
293
294 /**
295 * @returns {ArrayElement} An empty array element
296 */
297 empty() {
298 return new this.constructor([]);
299 }
300
301 ['fantasy-land/empty']() {
302 return this.empty();
303 }
304
305 /**
306 * @param {ArrayElement} other
307 * @returns {ArrayElement}
308 */
309 concat(other) {
310 return new this.constructor(this.content.concat(other.content));
311 }
312
313 ['fantasy-land/concat'](other) {
314 return this.concat(other);
315 }
316
317 ['fantasy-land/map'](transform) {
318 return new this.constructor(this.map(transform));
319 }
320
321 ['fantasy-land/chain'](transform) {
322 return this
323 .map(element => transform(element), this)
324 .reduce((a, b) => a.concat(b), this.empty());
325 }
326
327 ['fantasy-land/filter'](callback) {
328 return new this.constructor(this.content.filter(callback));
329 }
330
331 ['fantasy-land/reduce'](transform, initialValue) {
332 return this.content.reduce(transform, initialValue);
333 }
334
335 /**
336 * Returns the length of the collection
337 * @type number
338 */
339 get length() {
340 return this.content.length;
341 }
342
343 /**
344 * Returns whether the collection is empty
345 * @type boolean
346 */
347 get isEmpty() {
348 return this.content.length === 0;
349 }
350
351 /**
352 * Return the first item in the collection
353 * @type Element
354 */
355 get first() {
356 return this.getIndex(0);
357 }
358
359 /**
360 * Return the second item in the collection
361 * @type Element
362 */
363 get second() {
364 return this.getIndex(1);
365 }
366
367 /**
368 * Return the last item in the collection
369 * @type Element
370 */
371 get last() {
372 return this.getIndex(this.length - 1);
373 }
374}
375
376/**
377 * @returns {ArrayElement} An empty array element
378 */
379ArrayElement.empty = function empty() {
380 return new this();
381};
382
383ArrayElement['fantasy-land/empty'] = ArrayElement.empty;
384
385if (typeof Symbol !== 'undefined') {
386 ArrayElement.prototype[Symbol.iterator] = function symbol() {
387 return this.content[Symbol.iterator]();
388 };
389}
390
391module.exports = ArrayElement;
Note: See TracBrowser for help on using the repository browser.