1 | # scour.js
|
2 |
|
3 |
|
4 |
|
5 | > Traverse objects and arrays immutably
|
6 |
|
7 | Scour is a general-purpose library for dealing with JSON trees.<br>
|
8 | As a simple utility with a broad purpose, it can be used to solve many problems. Use it to:
|
9 |
|
10 | - Manage your [Redux] datastore.
|
11 | - Provide a model layer to access data in your single-page app. [→](#models)
|
12 | - Navigate a large JSON tree easily.
|
13 | - Rejoice in having a lightweight alternative to [Immutable.js]. ([Compare](docs/comparison.md))
|
14 |
|
15 | [![Status](https://travis-ci.org/rstacruz/scour.svg?branch=master)](https://travis-ci.org/rstacruz/scour "See test builds")
|
16 |
|
17 | ## Install
|
18 |
|
19 | ```sh
|
20 | npm install --save-exact scourjs
|
21 | ```
|
22 |
|
23 | ```js
|
24 | window.scour // non commonjs
|
25 | const scour = require('scourjs') // commonjs/node
|
26 | import scour from 'scourjs' // es6 modules
|
27 | ```
|
28 |
|
29 | ## Features
|
30 |
|
31 | Calling `scour(object)` returns a wrapper that you can use to traverse `object`.
|
32 | Use [get()](#get) to retrieve values.
|
33 |
|
34 | ```js
|
35 | data =
|
36 | { users:
|
37 | { 1: { name: 'john' },
|
38 | 2: { name: 'shane', confirmed: true },
|
39 | 3: { name: 'barry', confirmed: true } } }
|
40 | ```
|
41 |
|
42 | ```js
|
43 | scour(data).get('users', '1', 'name') // => 'john'
|
44 | ```
|
45 |
|
46 | <br>
|
47 |
|
48 | ### Traversal
|
49 | Use [go()](#go) to dig into the structure. It will return another `scour`
|
50 | wrapper scoped to that object.
|
51 |
|
52 | ```js
|
53 | data =
|
54 | { users:
|
55 | { admins:
|
56 | { bob: { logged_in: true },
|
57 | sue: { logged_in: false } } } }
|
58 | ```
|
59 |
|
60 | ```js
|
61 | users = scour(data).go('users') // => [scour (admins)]
|
62 | admins = scour(data).go('users', 'admins') // => [scour (bob, sue)]
|
63 |
|
64 | admins.go('bob').get('logged_in') // => true
|
65 | ```
|
66 |
|
67 | <br>
|
68 |
|
69 | ### Chaining
|
70 |
|
71 | `scour()` provides a wrapper that can be used to chain methods. This is inspired by [Underscore] and [Lodash].
|
72 |
|
73 | ```js
|
74 | scour(data)
|
75 | .go('users')
|
76 | .filter({ admin: true })
|
77 | .value
|
78 | ```
|
79 |
|
80 | [Underscore]: http://underscorejs.org/
|
81 | [Lodash]: http://lodash.com/
|
82 |
|
83 | <br>
|
84 |
|
85 | ### Immutable modifications
|
86 |
|
87 | Use [set()](#set) to update values. Scout treats all data as immutable, so this
|
88 | doesn't modify your original `data`, but gets you a new one with the
|
89 | modifications made.
|
90 |
|
91 | ```js
|
92 | data = scour(data)
|
93 | .set(['users', '1', 'updated_at'], +new Date())
|
94 | .value
|
95 |
|
96 | // => { users:
|
97 | // { 1: { name: 'john', updated_at: 1450667171188 },
|
98 | // 2: { name: 'shane', confirmed: true },
|
99 | // 3: { name: 'barry', confirmed: true } } }
|
100 | ```
|
101 |
|
102 | <br>
|
103 |
|
104 | ### Advanced traversing
|
105 |
|
106 | Use [filter()] to filter results with advanced querying.
|
107 |
|
108 | ```js
|
109 | users = scour(data).go('users')
|
110 |
|
111 | users
|
112 | .filter({ confirmed: true })
|
113 | .at(0)
|
114 | .get('name') // => 'shane'
|
115 | ```
|
116 |
|
117 | <br>
|
118 |
|
119 | ### Models
|
120 |
|
121 | Use [use()](#use) to add your own methods to certain keypaths. This makes them behave like models.<br>
|
122 | See [a detailed example](docs/extensions_example.md) to learn more.
|
123 |
|
124 | ##### Sample data
|
125 |
|
126 |
|
127 |
|
128 | ```js
|
129 | data =
|
130 | { artists:
|
131 | { 1: { first_name: 'Louie', last_name: 'Armstrong' },
|
132 | 2: { first_name: 'Miles', last_name: 'Davis' } } }
|
133 | ```
|
134 |
|
135 | ##### Your models
|
136 |
|
137 |
|
138 |
|
139 | ```js
|
140 | Root = {
|
141 | artists () { return this.go('artists') }
|
142 | }
|
143 |
|
144 | Artist = {
|
145 | fullname () {
|
146 | return this.get('first_name') + ' ' + this.get('last_name')
|
147 | }
|
148 | }
|
149 | ```
|
150 |
|
151 | ##### Using with scour
|
152 |
|
153 |
|
154 |
|
155 | ```js
|
156 | db = scour(data)
|
157 | .use({
|
158 | '': Root,
|
159 | 'artists.*': Artist
|
160 | })
|
161 |
|
162 | db.artists().find({ name: 'Miles' }).fullname()
|
163 | //=> 'Miles Davis'
|
164 | ```
|
165 |
|
166 | <br>
|
167 |
|
168 | ## API
|
169 |
|
170 |
|
171 |
|
172 | ### scour
|
173 |
|
174 | > `scour(object)`
|
175 |
|
176 | Returns a scour instance wrapping `object`.
|
177 |
|
178 | ```js
|
179 | scour(obj)
|
180 | ```
|
181 |
|
182 | It can be called on any Object or Array. (In fact, it can be called on
|
183 | anything, but is only generally useful for Objects and Arrays.)
|
184 |
|
185 | ```js
|
186 | data = { menu: { visible: true, position: 'left' } }
|
187 | scour(data).get('menu.visible')
|
188 |
|
189 | list = [ { id: 2 }, { id: 5 }, { id: 12 } ]
|
190 | scour(list).get('0.id')
|
191 | ```
|
192 |
|
193 | __Chaining__:
|
194 | You can use it to start method chains. In fact, the intended use is to keep
|
195 | your root [scour] object around, and chain from this.
|
196 |
|
197 | ```js
|
198 | db = scour({ menu: { visible: true, position: 'left' } })
|
199 |
|
200 | // Elsewhere:
|
201 | menu = db.go('menu')
|
202 | menu.get('visible')
|
203 | ```
|
204 |
|
205 | __Properties__:
|
206 | It the [root], [value] and [keypath] properties.
|
207 |
|
208 | ```js
|
209 | s = scour(obj)
|
210 | s.root // => [scour object]
|
211 | s.value // => raw data (that is, `obj`)
|
212 | s.keypath // => string array
|
213 | ```
|
214 |
|
215 | __Accessing the value:__
|
216 | You can access the raw data using [value].
|
217 |
|
218 | ```js
|
219 | db = scour(data)
|
220 | db.value // => same as `data`
|
221 | db.go('users').value // => same as `data.users`
|
222 | ```
|
223 |
|
224 | ## Chaining methods
|
225 |
|
226 | These methods are used to traverse nested structures. All these
|
227 | methods return [scour] instances, making them suitable for chaining.
|
228 |
|
229 | #### On null values
|
230 | Note that `undefined`, `false` and `null` values are still [scour]-wrapped
|
231 | when returned from [go()], [at()] and [find()].
|
232 |
|
233 | ```js
|
234 | list = [ { name: 'Homer' }, { name: 'Bart' } ]
|
235 |
|
236 | scour(list).at(4) // => [ scour undefined ]
|
237 | scour(list).at(4).value // => undefined
|
238 | ```
|
239 |
|
240 | This is done so that you can chain methods safely even when something is null.
|
241 | This behavior is consistent with what you'd expect with jQuery.
|
242 |
|
243 | ```js
|
244 | data = { users: { ... } }
|
245 | db = scour(data)
|
246 |
|
247 | db.go('blogposts').map((post) => post.get('title'))
|
248 | // => []
|
249 | ```
|
250 |
|
251 | ### go
|
252 |
|
253 | > `go(keypath...)`
|
254 |
|
255 | Navigates down to a given `keypath`. Always returns a [scour] instance.
|
256 | Rules [on null values] apply.
|
257 |
|
258 | ```js
|
259 | data =
|
260 | { users:
|
261 | { 12: { name: 'steve', last: 'jobs' },
|
262 | 23: { name: 'bill', last: 'gates' } } }
|
263 |
|
264 | scour(data).go('users') // => [scour (users)]
|
265 | scour(data).go('users', '12') // => [scour (name, last)]
|
266 | scour(data).go('users', '12').get('name') // => 'steve'
|
267 | ```
|
268 |
|
269 | __Dot notation:__
|
270 | Keypaths can be given in dot notation or as an array. These statements are
|
271 | equivalent.
|
272 |
|
273 | ```js
|
274 | scour(data).go('users.12')
|
275 | scour(data).go('users', '12')
|
276 | scour(data).go(['users', '12'])
|
277 | ```
|
278 |
|
279 | __Non-objects:__
|
280 | If you use it on a non-object or non-array value, it will still be
|
281 | returned as a [scour] instance. This is not likely what you want; use
|
282 | [get()] instead.
|
283 |
|
284 | ```js
|
285 | attr = scour(data).go('users', '12', 'name')
|
286 | attr // => [scour object]
|
287 | attr.value // => 'steve'
|
288 | attr.keypath // => ['users', '12', 'name']
|
289 | ```
|
290 |
|
291 | ### at
|
292 |
|
293 | > `at(index)`
|
294 |
|
295 | Returns the item at `index`. This differs from `go` as this searches by
|
296 | index, not by key. This returns a the raw value, unlike [getAt()]. Rules
|
297 | [on null values] apply.
|
298 |
|
299 | ```js
|
300 | users =
|
301 | { 12: { name: 'steve' },
|
302 | 23: { name: 'bill' } }
|
303 |
|
304 | scour(users).at(0) // => [scour { name: 'steve' }]
|
305 | scour(users).get(12) // => [scour { name: 'steve' }]
|
306 | ```
|
307 |
|
308 | ### getAt
|
309 |
|
310 | > `getAt(index)`
|
311 |
|
312 | Returns the item at `index`. This differs from `get` as this searches by
|
313 | index, not by key. This returns a the raw value, unlike [at()].
|
314 | *(Since v0.5)*
|
315 |
|
316 | ```js
|
317 | users =
|
318 | { 12: { name: 'steve' },
|
319 | 23: { name: 'bill' } }
|
320 |
|
321 | scour(users).at(0) // => [scour { name: 'steve' }]
|
322 | scour(users).getAt(0) // => { name: 'steve' }
|
323 | ```
|
324 |
|
325 | ### filter
|
326 |
|
327 | > `filter(conditions)`
|
328 |
|
329 | Sifts through the values and returns a set that matches given
|
330 | `conditions`. Supports simple objects, MongoDB-style
|
331 | queries, and functions.
|
332 |
|
333 | ```js
|
334 | scour(data).filter({ name: 'Moe' })
|
335 | scour(data).filter({ name: { $in: ['Larry', 'Curly'] })
|
336 | scour(data).filter((item) => item.get('name') === 'Moe')
|
337 | ```
|
338 |
|
339 | __Filter by object:__
|
340 | If you pass an object as a condition, `filter()` will check if that object
|
341 | coincides with the objects in the collection.
|
342 |
|
343 | ```js
|
344 | scour(data).filter({ name: 'Moe' })
|
345 | ```
|
346 |
|
347 | __Filter by function:__
|
348 | You may pass a function as a parameter. In this case, the `item` being
|
349 | passed to the callback will be a [scour]-wrapped object. The result
|
350 | will also be a [scour]-wrapped object, making it chainable.
|
351 |
|
352 | ```js
|
353 | scour(data)
|
354 | .filter((item, key) => +item.get('price') > 200)
|
355 | .sortBy('price')
|
356 | .first()
|
357 | ```
|
358 |
|
359 | __Advanced queries:__
|
360 | MongoDB-style queries are supported as provided by [sift.js]. For
|
361 | reference, see [MongoDB Query Operators][query-ops].
|
362 |
|
363 | ```js
|
364 | scour(products).filter({ price: { $gt: 200 })
|
365 | scour(articles).filter({ published_at: { $not: null }})
|
366 | ```
|
367 |
|
368 | __Arrays or objects:__
|
369 | Both arrays and array-like objects are supported. In this example below,
|
370 | an object will be used as the input.
|
371 |
|
372 | ```js
|
373 | devices =
|
374 | { 1: { id: 1, name: 'Phone', mobile: true },
|
375 | 2: { id: 2, name: 'Tablet', mobile: true },
|
376 | 3: { id: 3, name: 'Desktop', mobile: false } }
|
377 |
|
378 | scour(devices).filter({ mobile: true }).len()
|
379 | // => 2
|
380 | ```
|
381 |
|
382 | Also see [scour.filter()] for the unwrapped version.
|
383 |
|
384 | [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
|
385 |
|
386 | ### reject
|
387 |
|
388 | > `reject(conditions)`
|
389 |
|
390 | Inverse of [filter()] -- see `filter()` documentation for details.
|
391 |
|
392 | ### find
|
393 |
|
394 | > `find(conditions)`
|
395 |
|
396 | Returns the first value that matches `conditions`. Supports MongoDB-style
|
397 | queries. For reference, see [MongoDB Query Operators][query-ops]. Also
|
398 | see [filter()], as this is functionally-equivalent to the first result of
|
399 | `filter()`. Rules [on null values] apply.
|
400 |
|
401 | [query-ops]: https://docs.mongodb.org/manual/reference/operator/query/
|
402 |
|
403 | ```js
|
404 | scour(data).find({ name: 'john' })
|
405 | scour(data).find({ name: { $in: ['moe', 'larry'] })
|
406 | ```
|
407 |
|
408 | ### first
|
409 |
|
410 | > `first()`
|
411 |
|
412 | Returns the first result as a [scour]-wrapped object. This is equivalent
|
413 | to [at(0)](#at).
|
414 |
|
415 | ### last
|
416 |
|
417 | > `last()`
|
418 |
|
419 | Returns the first result as a [scour]-wrapped object. This is equivalent
|
420 | to `at(len() - 1)`: see [at()] and [len()].
|
421 |
|
422 | ### sortBy
|
423 |
|
424 | > `sortBy(condition)`
|
425 |
|
426 | Sorts a collection. Returns a [scour]-wrapped object suitable for
|
427 | chaining. Like other chainable methods, this works on arrays as well as
|
428 | objects. *(Since v0.8)*
|
429 |
|
430 | ```js
|
431 | data =
|
432 | { 0: { name: 'Wilma' },
|
433 | 1: { name: 'Barney' },
|
434 | 2: { name: 'Fred' } }
|
435 |
|
436 | scour(data).sortBy('name').value
|
437 | // { 1: { name: 'Barney' },
|
438 | // 2: { name: 'Fred' },
|
439 | // 0: { name: 'Wilma' } }
|
440 | ```
|
441 |
|
442 | __Conditions:__
|
443 | The given condition can be a string or a function. When it's given as a
|
444 | function, the `item` being passed is a [scour]-wrapped object, just like
|
445 | in [forEach()] (et al). These two examples below are
|
446 | functionally-equivalent.
|
447 |
|
448 | ```js
|
449 | scour(data).sortBy('name')
|
450 | scour(data).sortBy((item) => item.get('name'))
|
451 | ```
|
452 |
|
453 | You may also define nested keys in dot-notation:
|
454 |
|
455 | ```js
|
456 | scour(data).sortBy('user.name')
|
457 | ```
|
458 |
|
459 | ## Reading methods
|
460 |
|
461 | For retrieving data.
|
462 |
|
463 | ### get
|
464 |
|
465 | > `get(keypath...)`
|
466 |
|
467 | Returns data in a given `keypath`.
|
468 |
|
469 | ```js
|
470 | data =
|
471 | { users:
|
472 | { 12: { name: 'steve' },
|
473 | 23: { name: 'bill' } } }
|
474 |
|
475 | scour(data).get('users') // => same as data.users
|
476 | scour(data).go('users').value // => same as data.users
|
477 | ```
|
478 |
|
479 | __Dot notation:__
|
480 | Like [go()], the `keypath` can be given in dot notation.
|
481 |
|
482 | ```js
|
483 | scour(data).get('books.featured.name')
|
484 | scour(data).get('books', 'featured', 'name')
|
485 | ```
|
486 |
|
487 | ### len
|
488 |
|
489 | > `len()`
|
490 |
|
491 | Returns the length of the object or array. For objects, it returns the
|
492 | number of keys.
|
493 |
|
494 | ```js
|
495 | users =
|
496 | { 12: { name: 'steve' },
|
497 | 23: { name: 'bill' } }
|
498 |
|
499 | names = scour(users).len() // => 2
|
500 | ```
|
501 |
|
502 | ### toArray
|
503 |
|
504 | > `toArray()`
|
505 |
|
506 | Returns an array. If the the value is an object, it returns the values of
|
507 | that object. If the value is an array, it returns it as is. Also aliased
|
508 | as `values()`.
|
509 |
|
510 | ```js
|
511 | users =
|
512 | { 12: { name: 'steve' },
|
513 | 23: { name: 'bill' } }
|
514 |
|
515 | names = scour(users).toArray()
|
516 | // => [ {name: 'steve'}, {name: 'bill'} ]
|
517 | ```
|
518 |
|
519 | ### keys
|
520 |
|
521 | > `keys()`
|
522 |
|
523 | Returns keys. If the value is an array, this returns the array's indices.
|
524 | Also see [toArray()] to retrieve the values instead.
|
525 |
|
526 | ## Writing methods
|
527 |
|
528 | These are methods for modifying an object/array tree immutably.
|
529 | Note that all these functions are immutable--it will not modify existing
|
530 | data, but rather spawn new objects with the modifications done on them.
|
531 |
|
532 | ### set
|
533 |
|
534 | > `set(keypath, value)`
|
535 |
|
536 | Sets values immutably. Returns a copy of the same object ([scour]-wrapped)
|
537 | with the modifications applied.
|
538 |
|
539 | ```js
|
540 | data = { bob: { name: 'Bob' } }
|
541 | db = scour(data)
|
542 | db.set([ 'bob', 'name' ], 'Robert')
|
543 | // db.value == { bob: { name: 'Robert' } }
|
544 | ```
|
545 |
|
546 | __Immutability:__
|
547 | This is an immutable function, and will return a new object. It won't
|
548 | modify your original object.
|
549 |
|
550 | ```js
|
551 | profile = scour({ name: 'John' })
|
552 | profile2 = profile.set('email', 'john@gmail.com')
|
553 |
|
554 | profile.value // => { name: 'John' }
|
555 | profile2.value // => { name: 'John', email: 'john@gmail.com' }
|
556 | ```
|
557 |
|
558 | __Using within a scope:__
|
559 | Be aware that using all writing methods ([set()], [del()], [extend()]) on
|
560 | scoped objects (ie, made with [go()]) will spawn a new [root] object. If
|
561 | you're keeping a reference to the root object, you'll need to update it
|
562 | accordingly.
|
563 |
|
564 | ```js
|
565 | db = scour(data)
|
566 | book = db.go('book')
|
567 | book.root === db // correct so far
|
568 |
|
569 | book = book.set('title', 'IQ84')
|
570 | book = book.del('sale_price')
|
571 | book.root !== db // `root` has been updated
|
572 | ```
|
573 |
|
574 | __Dot notation:__
|
575 | Like [go()] and [get()], the keypath can be given in dot notation or an
|
576 | array.
|
577 |
|
578 | ```js
|
579 | scour(data).set('menu.left.visible', true)
|
580 | scour(data).set(['menu', 'left', 'visible'], true)
|
581 | ```
|
582 |
|
583 | ### del
|
584 |
|
585 | > `del(keypath)`
|
586 |
|
587 | Deletes values immutably. Returns a copy of the same object
|
588 | ([scour]-wrapped) with the modifications applied.
|
589 |
|
590 | Like [set()], the keypath can be given in dot notation or an
|
591 | array.
|
592 |
|
593 | ```js
|
594 | scour(data).del('menu.left.visible')
|
595 | scour(data).del(['menu', 'left', 'visible'])
|
596 | ```
|
597 |
|
598 | See [set()] for more information on working with immutables.
|
599 |
|
600 | ### extend
|
601 |
|
602 | > `extend(objects...)`
|
603 |
|
604 | Extends the data with more values. Returns a [scour]-wrapped object. Just
|
605 | like [Object.assign], you may pass multiple objects to the parameters.
|
606 |
|
607 | ```js
|
608 | data = { a: 1, b: 2 }
|
609 | data2 = scour(data).extend({ c: 3 })
|
610 | ```
|
611 |
|
612 | ```js
|
613 | data2 // => [scour { a: 1, b: 2, c: 3 }]
|
614 | data2.value // => { a: 1, b: 2, c: 3 }
|
615 | ```
|
616 |
|
617 | When used with anything non-object, it will be overridden.
|
618 |
|
619 | ```js
|
620 | data = {}
|
621 | db = scour(data)
|
622 | db = db.go('state').extend({ pressed: true }).root
|
623 |
|
624 | db.value // => { state: { pressed: true } }
|
625 | ```
|
626 |
|
627 | See [set()] for more information on working with immutables.
|
628 |
|
629 | ## Utility methods
|
630 |
|
631 | For stuff.
|
632 |
|
633 | ### use
|
634 |
|
635 | > `use(extensions)`
|
636 |
|
637 | Extends functionality for certain keypaths with custom methods.
|
638 | See [Extensions example] for examples.
|
639 |
|
640 | ```js
|
641 | data =
|
642 | { users:
|
643 | { 12: { name: 'steve', surname: 'jobs' },
|
644 | 23: { name: 'bill', surname: 'gates' } } }
|
645 |
|
646 | extensions = {
|
647 | 'users.*': {
|
648 | fullname () {
|
649 | return this.get('name') + ' ' + this.get('surname')
|
650 | }
|
651 | }
|
652 | }
|
653 |
|
654 | scour(data)
|
655 | .use(extensions)
|
656 | .get('users', 12)
|
657 | .fullname() // => 'bill gates'
|
658 | ```
|
659 |
|
660 | __Extensions format:__
|
661 | The parameter `extension` is an object, with keys being keypath globs, and
|
662 | values being properties to be extended.
|
663 |
|
664 | ```js
|
665 | .use({
|
666 | 'books.*': { ... },
|
667 | 'authors.*': { ... },
|
668 | 'publishers.*': { ... }
|
669 | })
|
670 | ```
|
671 |
|
672 | __Extending root:__
|
673 | To bind properties to the root method, use an empty string as the keypath.
|
674 |
|
675 | ```js
|
676 | .use({
|
677 | '': {
|
678 | users() { return this.go('users') },
|
679 | authors() { return this.go('authors') }
|
680 | }
|
681 | })
|
682 | ```
|
683 |
|
684 | __Keypath filtering:__
|
685 | You can use glob-like `*` and `**` to match parts of a keypath. A `*` will
|
686 | match any one segment, and `**` will match one or many segments. Here are
|
687 | some examples:
|
688 |
|
689 | - `users.*` - will match `users.1`, but not `users.1.photos`
|
690 | - `users.**` - will match `users.1.photos`
|
691 | - `users.*.photos` - will match `users.1.photos`
|
692 | - `**` will match anything
|
693 |
|
694 | __When using outside root:__
|
695 | Any extensions in a scoped object (ie, made with [go()]) will be used relative
|
696 | to it. For instance, if you define an extension to `admins.*` inside
|
697 | `.go('users')`, it will affect `users.
|
698 |
|
699 | ```js
|
700 | data = { users: { john: { } }
|
701 | db = scour(data)
|
702 |
|
703 | users = db.go('users')
|
704 | .use({ '*': { hasName () { return !!this.get('name') } })
|
705 |
|
706 | users.go('john').hasName() // works
|
707 | ```
|
708 |
|
709 | While this is supported, it is *not* recommended: these extensions will not
|
710 | propagate back to the root, and any objects taken from the root will not
|
711 | have those extensions applied to them.
|
712 |
|
713 | ```js
|
714 | users.go('john').hasName() // works
|
715 | db.go('users.john').hasName() // doesn't work
|
716 | ```
|
717 |
|
718 | ### index
|
719 |
|
720 | > `index(keypath, field)`
|
721 |
|
722 | Sets up indices to improve [filter()] performance. *(Since v0.12)*
|
723 |
|
724 | - `keypath` *(String | Array)* - the keypath of the collection.
|
725 | - `field` *(String)* - the name of the field to be indexed.
|
726 |
|
727 | ```js
|
728 | data =
|
729 | { users:
|
730 | { 1: { name: 'John Creamer' },
|
731 | 2: { name: 'Stephane K' } } }
|
732 |
|
733 | db = scour(data).index('users', 'name')
|
734 | db.filter({ name: 'Stephane K' })
|
735 | ```
|
736 |
|
737 | Doing this will add an index in the root (acccessible via
|
738 | `scour().indices`) to make searches faster for certain [filter()] queries.
|
739 | Any writing actions ([set()], [extend()], [del()]) will automatically
|
740 | update the index.
|
741 |
|
742 | See [scour-search] for more information on indexing.
|
743 |
|
744 | [scour-search]: https://github.com/rstacruz/scour-search
|
745 |
|
746 | ### toJSON
|
747 |
|
748 | > `toJSON()`
|
749 |
|
750 | Returns the value for serialization. This allows `JSON.stringify()` to
|
751 | work with `scour`-wrapped objects. The name of this method is a bit
|
752 | confusing, as it doesn't actually return a JSON string — but I'm afraid
|
753 | that it's the way that the JavaScript API for [JSON.stringify] works.
|
754 |
|
755 | [JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior
|
756 |
|
757 | ### equal
|
758 |
|
759 | > `equal(other)`
|
760 |
|
761 | Checks for equality between two Scour-wrapped objects.
|
762 |
|
763 | ```js
|
764 | a = scour(data)
|
765 | b = scour(data)
|
766 |
|
767 | a.equal(b) // => true
|
768 | ```
|
769 |
|
770 | ## Iteration methods
|
771 |
|
772 | These methods are generally useful for collections. These
|
773 | methods can work with either arrays or array-like objects, such as
|
774 | below.
|
775 |
|
776 | ```js
|
777 | subjects =
|
778 | { 1: { id: 1, title: 'Math', level: 101 },
|
779 | 2: { id: 2, title: 'Science', level: 103 },
|
780 | 3: { id: 3, title: 'History', level: 102 } }
|
781 | ```
|
782 |
|
783 | __Values:__
|
784 | For all these functions, The items passed onto the callbacks _is_ a
|
785 | [scour]-wrapped object. Use `item.value` or `this` to access the raw
|
786 | values.
|
787 |
|
788 | ```js
|
789 | scour(subjects).forEach((subject, key) => {
|
790 | console.log(subject.get('title'))
|
791 | })
|
792 | ```
|
793 |
|
794 | __Return values:__
|
795 | For methods that return values (such as [map()], the returned results _is
|
796 | not_ a [scour]-wrapped object, and isn't suitable for chaining.
|
797 |
|
798 | ```js
|
799 | scour(subjects).map((subject, key) => {
|
800 | return subject.get('title') + ' ' + subject.get('level')
|
801 | })
|
802 | // => [ 'Math 101', 'Science 103', 'History 102' ]
|
803 | ```
|
804 |
|
805 | ### forEach
|
806 |
|
807 | > `forEach(function(item, key, index))`
|
808 |
|
809 | Loops through each item. Supports both arrays and objects.
|
810 | The rules specified in [Iteration methods] apply.
|
811 |
|
812 | ```js
|
813 | users =
|
814 | { 12: { name: 'steve' },
|
815 | 23: { name: 'bill' } }
|
816 |
|
817 | scour(users).each((user, key) => {
|
818 | console.log(user.get('name'))
|
819 | })
|
820 | ```
|
821 |
|
822 | The values passed onto the function are:
|
823 |
|
824 | - `item` - the value; always a scour object.
|
825 | - `key` - the key.
|
826 | - `index` - the index.
|
827 |
|
828 | ### each
|
829 |
|
830 | > `each(fn)`
|
831 |
|
832 | Alias for [forEach](#foreach).
|
833 |
|
834 | ### map
|
835 |
|
836 | > `map(function(item, key))`
|
837 |
|
838 | Loops through each item and returns an array based on the iterator's
|
839 | return values. Supports both arrays and objects.
|
840 | The rules specified in [Iteration methods] apply.
|
841 |
|
842 | ```js
|
843 | users =
|
844 | { 12: { name: 'Steve' },
|
845 | 23: { name: 'Bill' } }
|
846 |
|
847 | names = scour(users).map((user, key) => user.get('name'))
|
848 | // => [ 'Steve', 'Bill' ]
|
849 | ```
|
850 |
|
851 | ### mapObject
|
852 |
|
853 | > `mapObject(function(val, key))`
|
854 |
|
855 | Creates a new `Object` with with the results of calling a provided function
|
856 | on every element in this array. Works like [Array#map], but also works on
|
857 | objects as well as arrays, and it returns an object instead.
|
858 | The rules specified in [Iteration methods] apply.
|
859 |
|
860 | See [scour.mapObject()] for details and the non-wrapped version.
|
861 |
|
862 | ### indexedMap
|
863 |
|
864 | > `indexedMap(function(val, key))`
|
865 |
|
866 | Creates a new `Object` with with the results of calling a provided function
|
867 | returning the keys and values for the new object.
|
868 | The rules specified in [Iteration methods] apply.
|
869 |
|
870 | See [scour.indexedMap()] for details and the non-wrapped version.
|
871 |
|
872 | ### reset
|
873 |
|
874 | > `reset(value, options)`
|
875 |
|
876 | Returns a clone with the `value` replaced. The new instance will
|
877 | retain the same properties, so things like [use()] extensions are carried
|
878 | over.
|
879 |
|
880 | ```js
|
881 | db = scour({ name: 'hello' })
|
882 | db.value //=> { name: 'hello' }
|
883 |
|
884 | db = db.reset({})
|
885 | db.value // => {}
|
886 | ```
|
887 |
|
888 | This is useful for, say, using Scour with [Redux] and implementing an
|
889 | action to reset the state back to empty.
|
890 |
|
891 | ## Attributes
|
892 |
|
893 | These attributes are available to [scour] instances.
|
894 |
|
895 | ### value
|
896 |
|
897 | > `value`
|
898 |
|
899 | The raw value being wrapped. You can use this to terminate a chained call.
|
900 |
|
901 | ```js
|
902 | users =
|
903 | [ { name: 'john', admin: true },
|
904 | { name: 'kyle', admin: false } ]
|
905 |
|
906 | scour(users)
|
907 | .filter({ admin: true })
|
908 | .value
|
909 | // => [ { name: 'john', admin: true } ]
|
910 | ```
|
911 |
|
912 | ### root
|
913 |
|
914 | > `root`
|
915 |
|
916 | A reference to the root [scour] instance.
|
917 | Everytime you traverse using [go()], a new [scour] object is spawned that's
|
918 | scoped to a keypath. Each of these [scour] objects have a `root` attribute
|
919 | that's a reference to the top-level [scour] object.
|
920 |
|
921 | ```js
|
922 | db = scour(...)
|
923 |
|
924 | photos = db.go('photos')
|
925 | photos.root // => same as `db`
|
926 | ```
|
927 |
|
928 | This allows you to return to the root when needed.
|
929 |
|
930 | ```js
|
931 | db = scour(...)
|
932 | artist = db.go('artists', '9328')
|
933 | artist.root.go('albums').find({ artist_id: artist.get('id') })
|
934 | ```
|
935 |
|
936 | ### keypath
|
937 |
|
938 | > `keypath`
|
939 |
|
940 | An array of strings representing each step in how deep the current scope is
|
941 | relative to the root. Each time you traverse using [go()], a new [scour]
|
942 | object is spawned.
|
943 |
|
944 | ```js
|
945 | db = scour(...)
|
946 |
|
947 | users = db.go('users')
|
948 | users.keypath // => ['users']
|
949 |
|
950 | admins = users.go('admins')
|
951 | admins.keypath // => ['users', 'admins']
|
952 |
|
953 | user = admins.go('23')
|
954 | user.keypath // => ['users', 'admins', '23']
|
955 | ```
|
956 |
|
957 | ## Utility functions
|
958 |
|
959 | These are utilities that don't need a wrapped object.
|
960 |
|
961 | ### scour.get
|
962 |
|
963 | > `scour.get(object, keypath)`
|
964 |
|
965 | Gets a keypath from an object.
|
966 |
|
967 | ```js
|
968 | data = { users: { bob: { name: 'john' } } }
|
969 |
|
970 | result = get(data, ['users', 'bob', 'name'])
|
971 | // => 'robert'
|
972 | ```
|
973 |
|
974 | This is also available as `require('scourjs/utilities/get')`.
|
975 |
|
976 | ### scour.set
|
977 |
|
978 | > `scour.set(object, keypath, value)`
|
979 |
|
980 | Sets a `keypath` into an `object` immutably.
|
981 |
|
982 | ```js
|
983 | data = { users: { bob: { name: 'john' } } }
|
984 |
|
985 | result = set(data, ['users', 'bob', 'name'], 'robert')
|
986 | // => { users: { bob: { name: 'robert' } } }
|
987 | ```
|
988 |
|
989 | This is also available as `require('scourjs/utilities/set')`.
|
990 |
|
991 | ### scour.del
|
992 |
|
993 | > `scour.del(object, keypath)`
|
994 |
|
995 | Deletes a `keypath` from an `object` immutably.
|
996 |
|
997 | ```js
|
998 | data = { users: { bob: { name: 'robert' } } }
|
999 | result = del(data, ['users', 'bob', 'name'])
|
1000 |
|
1001 | // => { users: { bob: {} } }
|
1002 | ```
|
1003 |
|
1004 | This is also available as `require('scourjs/utilities/del')`.
|
1005 |
|
1006 | ### scour.extendIn
|
1007 |
|
1008 | > `scour.extendIn(object, keypath, extensions...)`
|
1009 |
|
1010 | Extends a `keypath` from an `object` immutably.
|
1011 |
|
1012 | ```js
|
1013 | data = { users: { bob: { name: 'robert' } } }
|
1014 | result = extendIn(data, ['users', 'bob'], { email: 'bob@gmail.com' })
|
1015 |
|
1016 | // => { users: { bob: { name: 'robert', email: 'bob@gmail.com' } } }
|
1017 | ```
|
1018 |
|
1019 | This is also available as `require('scourjs/utilities/extend_in')`.
|
1020 |
|
1021 | ### scour.each
|
1022 |
|
1023 | > `scour.each(iterable, fn)`
|
1024 |
|
1025 | Iterates through `iterable`, either an object or an array. This is an
|
1026 | implementation of [Array#forEach] that also works for objects. The callback
|
1027 | `fn` will be invoked with two parameters: `currentValue` and `key`, just
|
1028 | like `Array#forEach`.
|
1029 |
|
1030 | This is also available as `require('scourjs/utilities/each')`.
|
1031 |
|
1032 | [Array#forEach]: http://devdocs.io/javascript/global_objects/array/foreach
|
1033 |
|
1034 | ### scour.map
|
1035 |
|
1036 | > `scour.map(iterable, fn)`
|
1037 |
|
1038 | Creates a new `Array` with with the results of calling a provided function
|
1039 | on every element in this array. Works like [Array#map], but also works on
|
1040 | objects as well as arrays.
|
1041 |
|
1042 | The callback `fn` will be invoked with two parameters: `currentValue` and
|
1043 | `key`, just like [Array#map].
|
1044 |
|
1045 | This is also available as `require('scourjs/utilities/map')`.
|
1046 |
|
1047 | [Array#map]: http://devdocs.io/javascript/global_objects/array/map
|
1048 |
|
1049 | ### scour.mapObject
|
1050 |
|
1051 | > `scour.mapObject(iterable, fn)`
|
1052 |
|
1053 | Creates a new `Object` with with the results of calling a provided function
|
1054 | on every element in this array. Works like [Array#map], but also works on
|
1055 | objects as well as arrays, and it returns an object instead.
|
1056 |
|
1057 | The callback `fn` will be invoked with two parameters: `currentValue` and
|
1058 | `key`, just like [Array#map].
|
1059 |
|
1060 | ```js
|
1061 | object = { a: 20, b: 30, c: 40 }
|
1062 | result = scour.mapObject(object, (val, key) => {
|
1063 | return '$' + val + '.00'
|
1064 | })
|
1065 |
|
1066 | // => { a: '$20.00', b: '$30.00', c: '$40.00' }
|
1067 | ```
|
1068 |
|
1069 | This is also available as `require('scourjs/utilities/map_object')`.
|
1070 |
|
1071 | ### scour.indexedMap
|
1072 |
|
1073 | > `scour.indexedMap(iterable, fn)`
|
1074 |
|
1075 | Creates a new `Object` with with the results of calling a provided function
|
1076 | returning the keys and values for the new object.
|
1077 |
|
1078 | The callback `fn` will be invoked with two parameters: `currentValue` and
|
1079 | `key`, just like [Array#map].
|
1080 |
|
1081 | The callback `fn` should return an array with two elements: with `result[0]`
|
1082 | being the key, and `result[1]` being the value. These are what the new
|
1083 | object will be constructed with.
|
1084 |
|
1085 | The `iterable` parameter can be an object or an array. This works like
|
1086 | `Array#map`, but also works on objects as well as arrays.
|
1087 |
|
1088 | ```js
|
1089 | list = ['Fred', 'Barney', 'Wilma']
|
1090 |
|
1091 | object = scour.indexedMap(list, (val, key) => {
|
1092 | var newkey = val.substr(0, 1)
|
1093 | return [ newkey, val ]
|
1094 | })
|
1095 |
|
1096 | // => { f: 'Fred', b: 'Barney', w: 'Wilma' }
|
1097 | ```
|
1098 |
|
1099 | This is also available as `require('scourjs/utilities/indexed_map')`.
|
1100 |
|
1101 | ### scour.filter
|
1102 |
|
1103 | > `scour.filter(iterable, function(val, key), [isArray])`
|
1104 |
|
1105 | Creates a new Array or Object with all elements that pass the test
|
1106 | implemented by the provided function.
|
1107 |
|
1108 | Works like [Array#filter], but will return an object if an object is also passed.
|
1109 |
|
1110 | The optional `isArray` argument, when passed `true`, will always make this
|
1111 | return an `Array`. If `false`, it will always be an `Object`. Leave it
|
1112 | `undefined` for the default behavior.
|
1113 |
|
1114 | This is also available as `require('scourjs/utilities/filter')`.
|
1115 |
|
1116 | [Array#filter]: http://devdocs.io/javascript/global_objects/array/filter
|
1117 |
|
1118 | ### scour.sortBy
|
1119 |
|
1120 | > `scour.sortBy(iterable, criteria)`
|
1121 |
|
1122 | Sorts by a given criteria.
|
1123 |
|
1124 | ```js
|
1125 | list = [ { name: 'Fred' }, { name: 'Barney' }, { name: 'Wilma' } ]
|
1126 | scour.sortBy(list, 'name')
|
1127 | ```
|
1128 |
|
1129 | This is also available as `require('scourjs/utilities/sort_by')`.
|
1130 |
|
1131 |
|
1132 | [at()]: #at
|
1133 | [del()]: #del
|
1134 | [extend()]: #extend
|
1135 | [filter()]: #filter
|
1136 | [forEach()]: #foreach
|
1137 | [get()]: #get
|
1138 | [getAt()]: #getat
|
1139 | [go()]: #go
|
1140 | [keypath]: #keypath
|
1141 | [len()]: #len
|
1142 | [map()]: #map
|
1143 | [root]: #root
|
1144 | [scour]: #scour
|
1145 | [set()]: #set
|
1146 | [toArray()]: #toarray
|
1147 | [value]: #value
|
1148 | [use()]: #use
|
1149 | [scour.mapObject()]: #scour.mapobject
|
1150 | [scour.indexedMap()]: #scour.indexedmap
|
1151 | [scour.filter()]: #scour-filter
|
1152 | [Iteration methods]: #iteration-methods
|
1153 | [on null values]: #on-null-values
|
1154 |
|
1155 | [Extensions example]: docs/extensions_example.md
|
1156 | [Object.assign]: https://devdocs.io/javascript/global_objects/object/assign
|
1157 | [sift.js]: https://www.npmjs.com/package/sift
|
1158 | [Redux]: http://rackt.github.io/redux
|
1159 | [Immutable.js]: http://facebook.github.io/immutable-js/
|
1160 | [scour-search]: https://github.com/rstacruz/scour-search
|
1161 |
|
1162 | ## Thanks
|
1163 |
|
1164 | **scour** © 2015+, Rico Sta. Cruz. Released under the [MIT] License.<br>
|
1165 | Authored and maintained by Rico Sta. Cruz with help from contributors ([list][contributors]).
|
1166 |
|
1167 | > [ricostacruz.com](http://ricostacruz.com) ·
|
1168 | > GitHub [@rstacruz](https://github.com/rstacruz) ·
|
1169 | > Twitter [@rstacruz](https://twitter.com/rstacruz)
|
1170 |
|
1171 | [MIT]: http://mit-license.org/
|
1172 | [contributors]: http://github.com/rstacruz/scour/contributors
|