├── .gitignore
├── index.js
├── .travis.yml
├── component.json
├── package.json
├── Makefile
├── test.html
├── History.md
├── lib
├── array.js
└── enumerable.js
├── test
├── array.js
└── enumerable.js
├── dist
├── array.min.js
└── array.js
└── Readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | components
2 | build
3 | node_modules
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/array');
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.9
4 | - 0.8
5 |
--------------------------------------------------------------------------------
/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "array",
3 | "repo": "matthewmueller/array",
4 | "description": "a better array",
5 | "version": "0.4.3",
6 | "keywords": [],
7 | "dependencies": {
8 | "component/emitter": "*",
9 | "component/to-function": "1.2.1",
10 | "yields/isArray": "1.0.0"
11 | },
12 | "development": {},
13 | "license": "MIT",
14 | "scripts": [
15 | "index.js",
16 | "lib/array.js",
17 | "lib/enumerable.js"
18 | ]
19 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "array",
3 | "version": "0.4.3",
4 | "description": "a vocal, functional array",
5 | "main": "array.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/MatthewMueller/array.git"
9 | },
10 | "keywords": [
11 | "array",
12 | "functional",
13 | "vocal",
14 | "enumerable"
15 | ],
16 | "author": "matthew mueller",
17 | "license": "MIT",
18 | "readmeFilename": "Readme.md",
19 | "dependencies": {
20 | "emitter-component": "~1.1.0",
21 | "to-function": "~1.2.1"
22 | },
23 | "scripts": {
24 | "test": "make test"
25 | },
26 | "devDependencies": {
27 | "mocha": "*",
28 | "better-assert": "*"
29 | }
30 | }
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | build: components index.js
3 | @component build --dev
4 |
5 | components: component.json
6 | @component install --dev
7 |
8 | dist: dist-build dist-minify
9 |
10 | test:
11 | @./node_modules/mocha/bin/mocha --reporter spec
12 |
13 | dist-build:
14 | @component build -s array -o dist -n array
15 |
16 | dist-minify: dist/array.js
17 | @curl -s \
18 | -d compilation_level=SIMPLE_OPTIMIZATIONS \
19 | -d output_format=text \
20 | -d output_info=compiled_code \
21 | --data-urlencode "js_code@$<" \
22 | http://closure-compiler.appspot.com/compile \
23 | > $<.tmp
24 | @mv $<.tmp dist/array.min.js
25 |
26 | clean:
27 | rm -fr build components template.js
28 |
29 | .PHONY: clean test build dist
30 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | array component
4 |
5 |
6 |
7 | array component
8 |
9 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 |
2 | 0.4.3 / 2014-02-12
3 | ==================
4 |
5 | * update to-function
6 |
7 | 0.4.2 / 2014-02-08
8 | ==================
9 |
10 | * update to-function
11 | * Emit `change` events when mutating the array
12 | * .map() should be non-destructive, fixes #18
13 | * emit 'sort' and 'reverse' events, fixes #19
14 |
15 | 0.4.1 / 2013-11-30
16 | ==================
17 |
18 | * maintain context with multiple inheritance
19 | * make reject non-destructive
20 |
21 | 0.4.0 / 2013-11-29
22 | ==================
23 |
24 | * added custom .get(obj) mapping.
25 | * filter, reject, etc. no longer destructive. fixes regression in 0.3.2.
26 | * added unique(fn|str)
27 | * npm install array.js => array. thanks to @enricomarino :-)
28 |
29 | 0.3.2 / 2013-11-26
30 | ==================
31 |
32 | * remake array to conserve context.
33 | * fixed unique()
34 |
35 | 0.3.1 / 2013-11-24
36 | ==================
37 |
38 | * update index in remove event when splicing multiple items
39 |
40 | 0.3.0 / 2013-11-24
41 | ==================
42 |
43 | * added array mixin support
44 | * added lastIndexOf(n) for IE
45 | * added index for add and remove events
46 | * updated emitter to 1.1.0
47 | * updated to-function to feature branch "getter/fns"
48 |
49 | 0.2.1 / 2013-03-11
50 | ==================
51 |
52 | * updated toJSON() (@rschmukler)
53 |
54 | 0.2.0 / 2013-02-28
55 | ==================
56 |
57 | * fixed toString
58 | * added toArray()
59 | * added sort(str)
60 | * readme cleanup
61 |
62 | 0.1.2 / 2013-02-27
63 | ==================
64 |
65 | * remove github-style dependencies
66 | * added .hash(key)
67 |
68 | 0.1.1 / 2013-02-27
69 | ==================
70 |
71 | * fix emitter
72 |
73 | 0.1.0 / 2013-02-14
74 | ==================
75 |
76 | * Updated API and readme
77 | * Added enumerable methods
78 | * Removed events for all mutator functions
79 | * Tests
80 |
81 | 0.0.2 / 2012-11-08
82 | ==================
83 |
84 | * api change: when you push, unshift, or splice more than 1 element, add and remove events will be called for each element added/removed
85 | * Only emit add if values actually added
86 |
87 | 0.0.1 / 2012-11-08
88 | ==================
89 |
90 | * Initial commit
91 |
--------------------------------------------------------------------------------
/lib/array.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var Enumerable = require('./enumerable');
6 | var proto = Array.prototype;
7 | var isArray = Array.isArray || require('isArray');
8 |
9 | try {
10 | var Emitter = require('emitter');
11 | } catch(e) {
12 | var Emitter = require('emitter-component');
13 | }
14 |
15 | /*
16 | * Expose `array`
17 | */
18 |
19 | module.exports = array;
20 |
21 | /**
22 | * Initialize `array`
23 | *
24 | * @param {Array|Object|Undefined} arr
25 | * @return {array}
26 | * @api public
27 | */
28 |
29 | function array(arr) {
30 | if(!(this instanceof array)) return new array(arr);
31 | arr = arr || [];
32 |
33 | if (isArray(arr)) {
34 | // create array-like object
35 | var len = this.length = arr.length;
36 | for(var i = 0; i < len; i++) this[i] = arr[i];
37 | } else if ('object' == typeof arr) {
38 | if (isObjectLiteral(arr)) {
39 | arr._ctx = this._ctx = JSON.parse(JSON.stringify(arr));
40 | }
41 |
42 | // mixin to another object
43 | for(var key in array.prototype) arr[key] = array.prototype[key];
44 | return arr;
45 | }
46 | }
47 |
48 | /**
49 | * Mixin `Emitter`
50 | */
51 |
52 | Emitter(array.prototype);
53 |
54 | /**
55 | * Mixin `Enumerable`
56 | */
57 |
58 | Enumerable(array.prototype);
59 |
60 | /**
61 | * Removes the last element from an array and returns that element
62 | *
63 | * @return {Mixed} removed element
64 | * @api public
65 | */
66 |
67 | array.prototype.pop = function() {
68 | var ret = proto.pop.apply(this, arguments);
69 | this.emit('remove', ret, this.length);
70 | this.emit('change');
71 | return ret;
72 | };
73 |
74 | /**
75 | * Push a value onto the end of the array,
76 | * returning the length of the array
77 | *
78 | * @param {Mixed, ...} elements
79 | * @return {Number}
80 | * @api public
81 | */
82 |
83 | array.prototype.push = function() {
84 | var ret = proto.push.apply(this, arguments),
85 | args = [].slice.call(arguments);
86 | for(var i = 0, len = args.length; i < len; i++) this.emit('add', args[i], ret - len + i);
87 | this.emit('change');
88 | return ret;
89 | };
90 |
91 | /**
92 | * Removes the first element from an array and returns that element.
93 | *
94 | * @return {Mixed}
95 | * @api public
96 | */
97 |
98 | array.prototype.shift = function() {
99 | var ret = proto.shift.apply(this, arguments);
100 | this.emit('remove', ret, 0);
101 | this.emit('change');
102 | return ret;
103 | };
104 |
105 | /**
106 | * Adds and/or removes elements from an array.
107 | *
108 | * @param {Number} index
109 | * @param {Number} howMany
110 | * @param {Mixed, ...} elements
111 | * @return {Array} removed elements
112 | * @api public
113 | */
114 |
115 | array.prototype.splice = function(index) {
116 | var ret = proto.splice.apply(this, arguments),
117 | added = [].slice.call(arguments, 2);
118 | for(var i = 0, len = ret.length; i < len; i++) this.emit('remove', ret[i], index);
119 | for( i = 0, len = added.length; i < len; i++) this.emit('add', added[i], index + i);
120 | this.emit('change');
121 | return ret;
122 | };
123 |
124 | /**
125 | * Adds one or more elements to the front of an array
126 | * and returns the new length of the array.
127 | *
128 | * @param {Mixed, ...} elements
129 | * @return {Number} length
130 | * @api public
131 | */
132 |
133 | array.prototype.unshift = function() {
134 | var ret = proto.unshift.apply(this, arguments),
135 | args = [].slice.call(arguments);
136 | for(var i = 0, len = args.length; i < len; i++) this.emit('add', args[i], i);
137 | this.emit('change');
138 | return ret;
139 | };
140 |
141 | /**
142 | * Reverses the array, emitting the `reverse` event
143 | *
144 | * @api public
145 | */
146 |
147 | array.prototype.reverse = function () {
148 | var ret = proto.reverse.apply(this, arguments);
149 | this.emit('reverse');
150 | this.emit('change');
151 | return ret;
152 | };
153 |
154 | /**
155 | * Sort the array, emitting the `sort` event
156 | *
157 | * With strings:
158 | *
159 | * fruits.sort('calories')
160 | *
161 | * Descending sort:
162 | *
163 | * fruits.sort('calories', 'desc')
164 | *
165 | * @param {undefined|Function|String} fn
166 | * @param {Nunber|String|Boolean} dir
167 | * @return {Array}
168 | * @api public
169 | */
170 | var sort = array.prototype.sort;
171 | array.prototype.sort = function () {
172 | var ret = sort.apply(this, arguments);
173 | this.emit('sort');
174 | this.emit('change');
175 | return ret;
176 | }
177 |
178 |
179 | /**
180 | * toJSON
181 | *
182 | * @return {Object}
183 | * @api public
184 | */
185 |
186 | array.prototype.toJSON = function() {
187 | return this.map(function(obj) {
188 | return (obj.toJSON) ? obj.toJSON() : obj;
189 | }).toArray();
190 | }
191 |
192 | /**
193 | * Convert the array-like object to an actual array
194 | *
195 | * @return {Array}
196 | * @api public
197 | */
198 |
199 | array.prototype.toArray = function() {
200 | return proto.slice.call(this);
201 | };
202 |
203 | /**
204 | * Static: get the array item
205 | *
206 | * @param {Mixed} obj
207 | * @return {Mixed}
208 | * @api public
209 | */
210 |
211 | array.get = function(obj) {
212 | return obj;
213 | };
214 |
215 | /**
216 | * Get the array item
217 | *
218 | * @param {Number} i
219 | * @return {Mixed}
220 | * @api public
221 | */
222 |
223 | array.prototype.get = array.get;
224 |
225 | /**
226 | * Attach the rest of the array methods
227 | */
228 |
229 | var methods = ['toString', 'concat', 'join', 'slice'];
230 |
231 | methods.forEach(function(method) {
232 | array.prototype[method] = function() {
233 | return proto[method].apply(this, arguments);
234 | };
235 | });
236 |
237 | /**
238 | * Remake the array, emptying it, then adding values back in
239 | *
240 | * @api private
241 | */
242 |
243 | array.prototype._remake = function(arr) {
244 | var construct = this.constructor;
245 | var clone = (this._ctx) ? new construct(this._ctx) : new construct();
246 | proto.push.apply(clone, arr);
247 | clone.get = this.get || array.get;
248 | return clone;
249 | };
250 |
251 | /**
252 | * Is object utility
253 | *
254 | * @param {Mixed} obj
255 | * @return {Boolean}
256 | * @api private
257 | */
258 |
259 | function isObjectLiteral(obj) {
260 | return obj.constructor == Object;
261 | }
262 |
--------------------------------------------------------------------------------
/test/array.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module Dependencies
3 | */
4 |
5 | var array = require('../'),
6 | assert = require('better-assert');
7 |
8 | /**
9 | * Array tests
10 | */
11 |
12 | describe('array', function () {
13 | var arr;
14 | beforeEach(function() {
15 | arr = array();
16 | });
17 |
18 | it('() should initialize an empty array', function(){
19 | var arr = array();
20 | assert(0 === arr.length);
21 | });
22 |
23 | it('should be array-like', function () {
24 | var arr = array(['1', '2', '3']);
25 | assert(3 === arr.length);
26 | assert('1' === arr[0]);
27 | assert('2' === arr[1]);
28 | assert('3' === arr[2]);
29 | });
30 |
31 | it('should support mixins', function() {
32 | function Notes() {}
33 | array(Notes.prototype);
34 | var notes = new Notes();
35 | notes.push('1', '2', '3');
36 | assert(3 === notes.length);
37 | assert('1' === notes[0]);
38 | assert('2' === notes[1]);
39 | assert('3' === notes[2]);
40 | });
41 |
42 | describe('pop', function () {
43 | it('should pop() like [].pop()', function () {
44 | arr.push('1', '2');
45 | var val = arr.pop();
46 | assert('2' === val);
47 | assert('1' === arr[0]);
48 | assert(1 === arr.length);
49 | });
50 |
51 | it('should emit "remove" and "change" events', function() {
52 | arr.push('1', '2');
53 | arr.on('remove', function(v, i) {
54 | assert('2' === v);
55 | assert(1 === i);
56 | });
57 | var changeCalled = 0;
58 | arr.on('change', function () {
59 | changeCalled++;
60 | });
61 | arr.pop();
62 | assert(changeCalled === 1);
63 | });
64 | });
65 |
66 | describe('push', function () {
67 | it('should push() like [].push()', function(){
68 | arr.push('1', '2');
69 | assert(2 === arr.length);
70 | });
71 |
72 | it('should emit "add" and "change" events', function(){
73 | var calls = 0;
74 | arr.on('add', function(v, i) {
75 | switch (calls++) {
76 | case 0:
77 | assert('hi' === v);
78 | assert(0 === i);
79 | break;
80 | case 1:
81 | assert('world' === v);
82 | assert(1 === i);
83 | break;
84 | }
85 | });
86 | var changeCalled = 0;
87 | arr.on('change', function () {
88 | changeCalled++;
89 | });
90 | arr.push('hi', 'world');
91 | assert(changeCalled === 1);
92 | });
93 | });
94 |
95 | describe('shift', function () {
96 | it('should emit "remove" and "change" events', function () {
97 | arr.push('1', '2');
98 | arr.on('remove', function (v, i) {
99 | assert('1' === v);
100 | assert(0 === i);
101 | });
102 | var changeCalled = 0;
103 | arr.on('change', function () {
104 | changeCalled++;
105 | });
106 | arr.shift();
107 | assert(changeCalled === 1);
108 | });
109 | });
110 |
111 | describe('unshift', function () {
112 | it('should emit "add" and "change" events', function () {
113 | var calls = 0;
114 | arr.on('add', function(v, i) {
115 | switch (calls++) {
116 | case 0:
117 | assert('hi' === v);
118 | assert(0 === i);
119 | break;
120 | case 1:
121 | assert('world' === v);
122 | assert(1 === i);
123 | break;
124 | }
125 | });
126 | var changeCalled = 0;
127 | arr.on('change', function () {
128 | changeCalled++;
129 | });
130 | arr.unshift('hi', 'world');
131 | assert(changeCalled === 1);
132 | });
133 | });
134 |
135 | describe('splice', function () {
136 | it('should emit "add", "remove" and "change" events', function () {
137 | arr.push('1', '2', '3', '4');
138 | var remcalls = 0;
139 | arr.on('remove', function(v, i) {
140 | switch (remcalls++) {
141 | case 0:
142 | assert('2' === v);
143 | assert(1 === i);
144 | break;
145 | case 1:
146 | assert('3' === v);
147 | assert(1 === i);
148 | break;
149 | }
150 | });
151 | var addcalls = 0;
152 | arr.on('add', function(v, i) {
153 | switch (addcalls++) {
154 | case 0:
155 | assert('2.' === v);
156 | assert(1 === i);
157 | break;
158 | case 1:
159 | assert('3.' === v);
160 | assert(2 === i);
161 | break;
162 | }
163 | });
164 | var changeCalled = 0;
165 | arr.on('change', function () {
166 | changeCalled++;
167 | });
168 | arr.splice(1, 2, '2.', '3.');
169 | assert(changeCalled === 1);
170 | });
171 | });
172 |
173 | describe('reverse', function () {
174 | it('should emit a `reverse` and `change` event', function () {
175 | arr.push('1', '2', '3', '4');
176 | var called = false;
177 | arr.on('reverse', function () {
178 | called = true;
179 | assert(arr[0] === '4');
180 | assert(arr[3] === '1');
181 | });
182 | var changeCalled = false;
183 | arr.on('change', function () {
184 | changeCalled = true;
185 | });
186 | arr.reverse();
187 | assert(called === true);
188 | assert(changeCalled === true);
189 | });
190 | });
191 |
192 | describe('sort', function () {
193 | it('should emit a `sort` and `change` event', function () {
194 | arr.push(4, 2, 1, 2, 3);
195 | var called = false;
196 | arr.on('sort', function () {
197 | called = true;
198 | assert(arr[0] === 1);
199 | assert(arr[4] === 4);
200 | });
201 | var changeCalled = false;
202 | arr.on('change', function () {
203 | changeCalled = true;
204 | });
205 | arr.sort();
206 | assert(called === true);
207 | assert(changeCalled === true);
208 | });
209 | });
210 |
211 | describe('toString', function() {
212 | it('should look just like a real array', function() {
213 | var orig = [1, 2, 3, 4],
214 | arr = array(orig);
215 |
216 | arr = arr.toString();
217 | assert('string' == typeof arr);
218 | assert(arr === orig.toString());
219 | });
220 | });
221 |
222 | describe('toArray', function() {
223 | it('should create a real array out of array object', function(){
224 | var orig = [ 3, 4, 6, 2 ],
225 | arr = array(orig);
226 |
227 | arr = arr.toArray();
228 | assert(Array.isArray(arr));
229 | assert(JSON.stringify(orig) === JSON.stringify(arr));
230 | });
231 | });
232 |
233 | describe('toJSON', function() {
234 | it('should return a JSON representation of the array', function(){
235 | var orig = [{a: 'abc', b: 123}, {a: 'def', b: 456}],
236 | arr = array(orig);
237 | arr = arr.toArray();
238 | assert(Array.isArray(arr));
239 | assert(JSON.stringify(orig) === JSON.stringify(arr));
240 | });
241 |
242 | it('should call toJSON on objects if possible', function() {
243 | var orig = [{a: 'abc', toJSON: function() { return "Hello" }},
244 | {a: 'abc', toJSON: function() { return "World" }}],
245 | arr = array(orig);
246 |
247 | arr = arr.toJSON();
248 | assert(Array.isArray(arr));
249 | assert(JSON.stringify(["Hello", "World"]) === JSON.stringify(arr));
250 | });
251 | });
252 |
253 | describe('array.get', function() {
254 | it ('custom getter', function() {
255 | var nums = array([ { n: 1 }, { n: 3 }, { n: 5}, { n: 8}, { n: 20 } ]);
256 | nums.get = function(obj) {
257 | return obj.n;
258 | };
259 |
260 | var out = nums.filter('> 4');
261 | assert(3 == out.length);
262 | assert(5 == out[0].n);
263 | assert(8 == out[1].n);
264 | assert(20 == out[2].n);
265 | });
266 | });
267 | });
268 |
--------------------------------------------------------------------------------
/dist/array.min.js:
--------------------------------------------------------------------------------
1 | (function(){function g(l,c,k){var d=g.resolve(l);if(null==d)throw k=k||l,c=c||"root",d=Error('Failed to require "'+k+'" from "'+c+'"'),d.path=k,d.parent=c,d.require=!0,d;c=g.modules[d];c._resolving||c.exports||(k={exports:{}},k.client=k.component=!0,c._resolving=!0,c.call(this,k.exports,g.relative(d),k),delete c._resolving,c.exports=k.exports);return c.exports}g.modules={};g.aliases={};g.resolve=function(l){"/"===l.charAt(0)&&(l=l.slice(1));for(var c=[l,l+".js",l+".json",l+"/index.js",l+"/index.json"],
2 | k=0;ka?e:a;else for(c=0;ca?e:a;return a};h.min=function(b){var f=this.length,a=Infinity,c=0,g;if(b)for(b=d(b),g=0;gd?c:0})}});g.alias("component-emitter/index.js",
20 | "array/deps/emitter/index.js");g.alias("component-emitter/index.js","emitter/index.js");g.alias("component-indexof/index.js","component-emitter/deps/indexof/index.js");g.alias("component-to-function/index.js","array/deps/to-function/index.js");g.alias("component-to-function/index.js","to-function/index.js");g.alias("component-props/index.js","component-to-function/deps/props/index.js");g.alias("component-props/index.js","component-to-function/deps/props/index.js");g.alias("component-props/index.js",
21 | "component-props/index.js");g.alias("yields-isArray/index.js","array/deps/isArray/index.js");g.alias("yields-isArray/index.js","isArray/index.js");"object"==typeof exports?module.exports=g("array"):"function"==typeof define&&define.amd?define(function(){return g("array")}):this.array=g("array")})();
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # array [](http://travis-ci.org/MatthewMueller/array) [](https://david-dm.org/MatthewMueller/array) [](http://badge.fury.io/js/array.js)
2 |
3 | A better array for the browser and node.js. Supports events & many functional goodies.
4 |
5 | The functional bits are based off the [Enumerable](https://github.com/component/enumerable) component.
6 |
7 | **BREAKING:** the module's package name on npm has changed from `array.js` to `array`. Please update your `package.json`.
8 |
9 | ## Installation
10 |
11 | ### Node.js
12 |
13 | npm install array
14 |
15 | ### Browser with component
16 |
17 | component install matthewmueller/array
18 |
19 | ### Browser (standalone, amd, etc.)
20 |
21 | * Development (24k): [dist/array.js](https://raw.github.com/MatthewMueller/array/master/dist/array.js)
22 | * Production (4k w/ gzip): [dist/array.js](https://raw.github.com/MatthewMueller/array/master/dist/array.min.js)
23 |
24 | > Note: if you use this library standalone, `array` will be attached to the window. You can access it with `window.array()` or just `array()`. Keep in mind javascript is case-sensitive and `Array()` will create a native array.
25 |
26 | ## Examples
27 |
28 | ### Iteration:
29 |
30 | ```js
31 | users
32 | .map('friends')
33 | .select('age > 20 && age < 30')
34 | .map('name.first')
35 | .select(/^T/)
36 | ```
37 |
38 | ```js
39 | fruits.find({ name : 'apple' }).color
40 | ```
41 |
42 | ```js
43 | users.sort('name.last', 'descending')
44 | ```
45 |
46 | ### Events:
47 |
48 | ```js
49 | var array = require('array'),
50 | users = array();
51 |
52 | users.on('add', function(user) {
53 | console.log('added', user);
54 | });
55 |
56 | users.on('remove', function(user) {
57 | console.log('removed', user);
58 | });
59 |
60 | users.push(user);
61 | users.splice(0, 3, user);
62 | ```
63 |
64 | ## Design
65 |
66 | This library uses an array-like object to implement all its methods. This is very similar to how jQuery lets you do: `$('.modal')[0]` and `$('p').length`.
67 |
68 | This library differs from `component/enumerable` in that it has events and does not wrap the array. To access the actual array in `component/enumerable` you have to call `.value()`. For the most part, you can treat `array` just like a real array, because it implements all the same methods.
69 |
70 | ## Caveats
71 |
72 | When working with `array` it's important to keep in mind that `array` is not an actual Array, but an array-like object. There are a few caveats that come with this data type:
73 |
74 | * you cannot manually set array indexes because the length value will not be updated. You will have to use the mutator methods provided like push, pop, etc.
75 | * `arr instanceof Array` will return `false`. `arr instanceof Object` will return `true`. So there may be some interoperability issues if you try to blindly pass these arrays through other libraries.
76 |
77 | Keep in mind both these issues are also present when working with jQuery objects as well as Backbone Collections.
78 |
79 | ## Events
80 |
81 | * `add` (item, index) - emitted when items are added to the array (`push`, `unshift`, `splice`)
82 | * `remove` (item, index) - emitted when items are removed from the array (`pop`, `shift`, `splice`)
83 | * `sort` - emitted when array is sorted
84 | * `reverse` - emitted when array is reversed
85 | * `change` - emitted at most once for every mutating operation
86 |
87 | An event will be emitted for each item you add or remove. So if you do something like:
88 |
89 | ```js
90 | fruits.on('add', function(item) {});
91 | fruits.push('apple', 'orange', 'pear')
92 | ```
93 |
94 | The `add` event will be fired 3 times with the `item` being `"apple"`, `"orange"`, and `"pear"` respectively.
95 |
96 | ## API
97 |
98 | #### `array(mixed)`
99 |
100 | Initialize an `array`.
101 |
102 | As an empty array:
103 |
104 | ```js
105 | var arr = array();
106 | ```
107 |
108 | As an array with values:
109 |
110 | ```js
111 | var arr = array([1, 2, 3, 4]);
112 | ```
113 |
114 | Or as a mixin:
115 |
116 | ```js
117 | function Notes() {}
118 | array(Notes.prototype);
119 | ```
120 |
121 | ### Array methods
122 |
123 | `array` implements all the same methods as a native array. For more information, visit [MDN](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array).
124 |
125 | #### Mutators:
126 |
127 | Mutator methods that modify the array will emit "add" and "remove" events.
128 |
129 | * `pop()`: Removes the last element from an array and returns that element.
130 | * `push(item, ...)`: Adds one or more elements to the end of an array and returns the new length of the array.
131 | * `reverse()`: Reverses the order of the elements of an array -- the first becomes the last, and the last becomes the first.
132 | * `shift()`: Removes the first element from an array and returns that element.
133 | * `splice(i, k, [item, ...])`: Adds and/or removes elements from an array.
134 | * `unshift(item, ...)`: Adds one or more elements to the front of an array and returns the new length of the array.
135 |
136 | #### Accessors:
137 |
138 | * `concat(arr)`: Returns a new array comprised of this array joined with other array(s) and/or value(s).
139 | * `join(str)`: Joins all elements of an array into a string.
140 | * `slice(i, k)`: Extracts a section of an array and returns a new array.
141 | * `toString()`: Returns a string representing the array and its elements. Overrides the Object.prototype.toString method.
142 | * `lastIndexOf(item)`: Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
143 |
144 | ### Iteration Methods:
145 |
146 | `array` implements most of the methods of [component/enumerable](https://github.com/component/enumerable). The documentation below was originally written by [visionmedia](https://github.com/visionmedia).
147 |
148 | #### `.each(fn)`
149 |
150 | Iterate each value and invoke `fn(val, i)`.
151 |
152 | ```js
153 | users.each(function(val, i){})
154 | ```
155 |
156 | #### `.map(fn|str)`
157 |
158 | Map each return value from `fn(val, i)`.
159 |
160 | Passing a callback function:
161 |
162 | ```js
163 | users.map(function(user){
164 | return user.name.first
165 | })
166 | ```
167 |
168 |
169 | Passing a property string:
170 |
171 | ```js
172 | users.map('name.first')
173 | ```
174 |
175 | #### `.select(fn|str)`
176 |
177 | Select all values that return a truthy value of `fn(val, i)`. The argument passed in can either be a function or a string.
178 |
179 | ```js
180 | users.select(function(user){
181 | return user.age > 20
182 | })
183 | ```
184 |
185 | With a property:
186 |
187 | ```js
188 | items.select('complete')
189 | ```
190 |
191 | With a condition:
192 |
193 | ```js
194 | users.select('age > 20')
195 | ```
196 |
197 | #### `.unique(fn|str)`
198 |
199 | Select all unique values.
200 |
201 | ```js
202 | nums.unique()
203 | ```
204 |
205 | ```js
206 | users.unique('age')
207 | ```
208 |
209 | #### `.reject(fn|str|mixed)`
210 |
211 | Reject all values that return a truthy value of `fn(val, i)`.
212 |
213 | Rejecting using a callback:
214 |
215 | ```js
216 | users.reject(function(user){
217 | return user.age < 20
218 | })
219 | ```
220 |
221 |
222 | Rejecting with a property:
223 |
224 | ```js
225 | items.reject('complete')
226 | ```
227 |
228 |
229 | Rejecting values via `==`:
230 |
231 | ```js
232 | data.reject(null)
233 | users.reject(toni)
234 | ```
235 |
236 | #### `.sort([str|fn], [direction])`
237 |
238 | Sorts the array
239 |
240 | Basic sort:
241 |
242 | ```js
243 | prices.sort()
244 | ```
245 |
246 | Sort by the `created` key in ascending order. the following are equivalent:
247 |
248 | ```js
249 | users.sort('created')
250 | users.sort('created', 'ascending')
251 | users.sort('created', 'asc')
252 | users.sort('created', 1)
253 | users.sort('created', true)
254 | ```
255 |
256 | Sort in descending order. The following are equivalent:
257 |
258 | ```js
259 | food.sort('calories', 'descending')
260 | food.sort('calories', 'desc')
261 | food.sort('calories', -1)
262 | food.sort('calories', false)
263 | ```
264 |
265 | Using a function:
266 |
267 | ```js
268 | users.sort(function(user1, user2) {})
269 | ```
270 |
271 | #### `.compact()`
272 |
273 | Reject `null` and `undefined`.
274 |
275 | ```js
276 | [1, null, 5, undefined].compact()
277 | // => [1,5]
278 | ```
279 |
280 | #### `.find(fn|str)`
281 |
282 | Return the first value when `fn(val, i)` is truthy,
283 | otherwise return `undefined`.
284 |
285 | ```js
286 | users.find(function(user){
287 | return user.role == 'admin'
288 | })
289 | ```
290 |
291 | #### `.findLast(fn|str)`
292 |
293 | Return the last value when `fn(val, i)` is truthy,
294 | otherwise return `undefined`.
295 |
296 | ```js
297 | users.findLast(function(user){
298 | return user.role == 'admin'
299 | })
300 | ```
301 |
302 | #### `.none(fn|str)`
303 |
304 | Assert that none of the invocations of `fn(val, i)` are truthy.
305 |
306 | For example ensuring that no pets are admins:
307 |
308 | ```js
309 | pets.none(function(p){ return p.admin })
310 | pets.none('admin')
311 | ```
312 |
313 | #### `.any(fn|str)`
314 |
315 | Assert that at least one invocation of `fn(val, i)` is truthy.
316 |
317 | For example checking to see if any pets are ferrets:
318 |
319 | ```js
320 | pets.any(function(pet){
321 | return pet.species == 'ferret'
322 | })
323 | ```
324 |
325 | #### `.count(fn|str)`
326 |
327 | Count the number of times `fn(val, i)` returns true.
328 |
329 | ```js
330 | var n = pets.count(function(pet){
331 | return pet.species == 'ferret'
332 | })
333 | ```
334 |
335 | #### `.indexOf(mixed)`
336 |
337 | Determine the indexof `mixed` or return `-1`.
338 |
339 | #### `.has(mixed)`
340 |
341 | Check if `mixed` is present in this enumerable.
342 |
343 | #### `.reduce(fn, mixed)`
344 |
345 | Reduce with `fn(accumulator, val, i)` using
346 | optional `init` value defaulting to the first
347 | enumerable value.
348 |
349 | #### `.reduceRight(fn, mixed)`
350 |
351 | Reduce with `fn(accumulator, val, i)` using
352 | optional `init` value defaulting to the first
353 | enumerable value - like `reduce`, except goes
354 | from right to left.
355 |
356 | #### `.max(fn|str)`
357 |
358 | Determine the max value.
359 |
360 | With a callback function:
361 |
362 | ```js
363 | pets.max(function(pet){
364 | return pet.age
365 | })
366 | ```
367 |
368 |
369 | With property strings:
370 |
371 | ```js
372 | pets.max('age')
373 | ```
374 |
375 |
376 | With immediate values:
377 |
378 | ```js
379 | nums.max()
380 | ```
381 |
382 | #### `.sum(fn|str)`
383 |
384 | Determine the sum.
385 |
386 | With a callback function:
387 |
388 | ```js
389 | pets.sum(function(pet){
390 | return pet.age
391 | })
392 | ```
393 |
394 |
395 | With property strings:
396 |
397 | ```js
398 | pets.sum('age')
399 | ```
400 |
401 | With immediate values:
402 |
403 | ```js
404 | nums.sum()
405 | ```
406 |
407 | #### `.first([fn|str])`
408 |
409 | Return the first value, or first `n` values. If you pass in an object or a function, first will call `find`.
410 |
411 | #### `.last([fn|str])`
412 |
413 | Return the last value, or last `n` values. If you pass in an object or function, last will call `findLast`.
414 |
415 | #### `.hash(key)`
416 |
417 | Create a hash from the given `key`.
418 |
419 | ```js
420 | var fruits = array();
421 | fruits.push({ name : "apple", color : "red" });
422 | fruits.push({ name : "pear", color : "green" });
423 | fruits.push({ name : "orange", color : "orange" });
424 |
425 | var obj = fruits.hash('name');
426 | obj.apple //=> { name : "apple", color : "red" }
427 | obj.pear //=> { name : "pear", color : "green" }
428 | obj.orange //=> { name : "orange", color : "orange" }
429 | ```
430 |
431 | #### toJSON()
432 |
433 | Return an array. If array contains objects that implement `,toJSON()`, array.js will call `obj.toJSON()` on each item. Otherwise return the contents.
434 |
435 | #### toArray()
436 |
437 | Returns an native array.
438 |
439 | ## Benchmarks
440 |
441 | Benchmarks are preliminary but also promising:
442 |
443 | * native vs. array.js vs underscore.js: http://jsperf.com/native-vs-array-js-vs-underscore
444 |
445 | ## Run tests
446 |
447 | npm install .
448 | npm test
449 |
450 | ## License
451 |
452 | (The MIT License)
453 |
454 | Copyright (c) 2013 Matt Mueller
455 |
456 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
457 |
458 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
459 |
460 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
461 |
--------------------------------------------------------------------------------
/lib/enumerable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module Dependencies
3 | */
4 |
5 | var toFunction = require('to-function'),
6 | proto = Array.prototype,
7 | enumerable = {};
8 |
9 | /**
10 | * Mixin to `obj`.
11 | *
12 | * var Enumerable = require('enumerable');
13 | * Enumerable(Something.prototype);
14 | *
15 | * @param {Object} obj
16 | * @return {Object} obj
17 | * @api private
18 | */
19 |
20 | module.exports = function(obj) {
21 | for(var key in enumerable) obj[key] = enumerable[key];
22 | return obj;
23 | };
24 |
25 | /**
26 | * Iterate each value and invoke `fn(val, i)`.
27 | *
28 | * users.each(function(val, i){
29 | *
30 | * })
31 | *
32 | * @param {Function} fn
33 | * @return {Object} self
34 | * @api public
35 | */
36 |
37 | enumerable.forEach =
38 | enumerable.each = function(fn, context){
39 | var arr = this,
40 | len = arr.length;
41 |
42 | for (var i = 0; i < len; i++) {
43 | fn.call(context, arr[i], i, arr);
44 | }
45 |
46 | return this;
47 | };
48 |
49 | /**
50 | * Map each return value from `fn(val, i)`.
51 | *
52 | * Passing a callback function:
53 | *
54 | * users.map(function(user){
55 | * return user.name.first
56 | * })
57 | *
58 | * Passing a property string:
59 | *
60 | * users.map('name.first')
61 | *
62 | * @param {Function} fn
63 | * @return {Enumerable}
64 | * @api public
65 | */
66 |
67 | enumerable.map = function(fn, context){
68 | fn = toFunction(fn);
69 | var out = [],
70 | arr = this,
71 | len = arr.length;
72 |
73 | for (var i = 0; i < len; ++i) {
74 | out.push(fn.call(context, arr.get(arr[i]), i, arr));
75 | }
76 |
77 | return this._remake(out);
78 | };
79 |
80 | /**
81 | * Select all values that return a truthy value of `fn(val, i)`.
82 | *
83 | * users.select(function(user){
84 | * return user.age > 20
85 | * })
86 | *
87 | * With a property:
88 | *
89 | * items.select('complete')
90 | *
91 | * @param {Function|String} fn
92 | * @return {Enumerable}
93 | * @api public
94 | */
95 |
96 | enumerable.filter =
97 | enumerable.select = function(fn, context){
98 | fn = toFunction(fn);
99 | var out = [],
100 | arr = this,
101 | len = arr.length,
102 | val;
103 |
104 | for (var i = 0; i < len; ++i) {
105 | val = arr.get(arr[i]);
106 | if (fn.call(context, val, i, arr)) out.push(arr[i]);
107 | }
108 |
109 | return this._remake(out);
110 | };
111 |
112 | /**
113 | * Select all unique values.
114 | *
115 | * nums.unique()
116 | *
117 | * @param {Function|String} fn
118 | * @return {Enumerable}
119 | * @api public
120 | */
121 |
122 | enumerable.unique = function(fn, context){
123 | var out = [],
124 | vals = [],
125 | arr = this,
126 | len = arr.length,
127 | val;
128 |
129 | fn = (fn) ? toFunction(fn) : function(o) { return o; };
130 |
131 | for (var i = 0; i < len; ++i) {
132 | val = fn.call(context, arr.get(arr[i]), i, arr);
133 | if (~vals.indexOf(val)) continue;
134 | vals.push(val);
135 | out.push(arr[i]);
136 | }
137 |
138 | return this._remake(out);
139 | };
140 |
141 | /**
142 | * Reject all values that return a truthy value of `fn(val, i)`.
143 | *
144 | * Rejecting using a callback:
145 | *
146 | * users.reject(function(user){
147 | * return user.age < 20
148 | * })
149 | *
150 | * Rejecting with a property:
151 | *
152 | * items.reject('complete')
153 | *
154 | * Rejecting values via `==`:
155 | *
156 | * data.reject(null)
157 | * users.reject(tobi)
158 | *
159 | * @param {Function|String|Mixed} fn
160 | * @return {Enumerable}
161 | * @api public
162 | */
163 |
164 | enumerable.reject = function(fn, context){
165 | var out = [],
166 | arr = this,
167 | len = arr.length,
168 | val, i;
169 |
170 | if ('string' == typeof fn) fn = toFunction(fn);
171 | if (fn) {
172 | for (i = 0; i < len; ++i) {
173 | val = arr.get(arr[i]);
174 | if (!fn.call(context, val, i, arr)) out.push(arr[i]);
175 | }
176 | } else {
177 | for (i = 0; i < len; ++i) {
178 | val = arr.get(arr[i]);
179 | if (val != fn) out.push(arr[i]);
180 | }
181 | }
182 |
183 | return this._remake(out);
184 | };
185 |
186 | /**
187 | * Reject `null` and `undefined`.
188 | *
189 | * [1, null, 5, undefined].compact()
190 | * // => [1,5]
191 | *
192 | * @return {Enumerable}
193 | * @api public
194 | */
195 |
196 |
197 | enumerable.compact = function(){
198 | return this.reject(null);
199 | };
200 |
201 | /**
202 | * Return the first value when `fn(val, i)` is truthy,
203 | * otherwise return `undefined`.
204 | *
205 | * users.find(function(user){
206 | * return user.role == 'admin'
207 | * })
208 | *
209 | * With a property string:
210 | *
211 | * users.find('age > 20')
212 | *
213 | * @param {Function|String} fn
214 | * @return {Mixed}
215 | * @api public
216 | */
217 |
218 | enumerable.find = function(fn, context){
219 | fn = toFunction(fn);
220 | var arr = this,
221 | len = arr.length,
222 | val;
223 |
224 | for (var i = 0; i < len; ++i) {
225 | val = arr.get(arr[i]);
226 | if (fn.call(context, val, i, arr)) return arr[i];
227 | }
228 | };
229 |
230 | /**
231 | * Return the last value when `fn(val, i)` is truthy,
232 | * otherwise return `undefined`.
233 | *
234 | * users.findLast(function(user){
235 | * return user.role == 'admin'
236 | * })
237 | *
238 | * @param {Function} fn
239 | * @return {Mixed}
240 | * @api public
241 | */
242 |
243 | enumerable.findLast = function (fn, context) {
244 | fn = toFunction(fn);
245 | var arr = this,
246 | i = arr.length;
247 |
248 | while(i--) if (fn.call(context, arr.get(arr[i]), i, arr)) return arr[i];
249 | };
250 |
251 | /**
252 | * Assert that all invocations of `fn(val, i)` are truthy.
253 | *
254 | * For example ensuring that all pets are ferrets:
255 | *
256 | * pets.all(function(pet){
257 | * return pet.species == 'ferret'
258 | * })
259 | *
260 | * users.all('admin')
261 | *
262 | * @param {Function|String} fn
263 | * @return {Boolean}
264 | * @api public
265 | */
266 |
267 | enumerable.every = function(fn, context){
268 | fn = toFunction(fn);
269 | var arr = this,
270 | len = arr.length,
271 | val;
272 |
273 | for (var i = 0; i < len; ++i) {
274 | val = arr.get(arr[i]);
275 | if (!fn.call(context, val, i, arr)) return false;
276 | }
277 |
278 | return true;
279 | };
280 |
281 | /**
282 | * Assert that none of the invocations of `fn(val, i)` are truthy.
283 | *
284 | * For example ensuring that no pets are admins:
285 | *
286 | * pets.none(function(p){ return p.admin })
287 | * pets.none('admin')
288 | *
289 | * @param {Function|String} fn
290 | * @return {Boolean}
291 | * @api public
292 | */
293 |
294 | enumerable.none = function(fn, context){
295 | fn = toFunction(fn);
296 | var arr = this,
297 | len = arr.length,
298 | val;
299 |
300 | for (var i = 0; i < len; ++i) {
301 | val = arr.get(arr[i]);
302 | if (fn.call(context, val, i, arr)) return false;
303 | }
304 | return true;
305 | };
306 |
307 | /**
308 | * Assert that at least one invocation of `fn(val, i)` is truthy.
309 | *
310 | * For example checking to see if any pets are ferrets:
311 | *
312 | * pets.any(function(pet){
313 | * return pet.species == 'ferret'
314 | * })
315 | *
316 | * @param {Function} fn
317 | * @return {Boolean}
318 | * @api public
319 | */
320 |
321 | enumerable.any =
322 | enumerable.some = function(fn, context){
323 | fn = toFunction(fn);
324 | var arr = this,
325 | len = arr.length,
326 | val;
327 |
328 | for (var i = 0; i < len; ++i) {
329 | val = arr.get(arr[i]);
330 | if (fn.call(context, val, i, arr)) return true;
331 | }
332 | return false;
333 | };
334 |
335 | /**
336 | * Count the number of times `fn(val, i)` returns true.
337 | *
338 | * var n = pets.count(function(pet){
339 | * return pet.species == 'ferret'
340 | * })
341 | *
342 | * @param {Function} fn
343 | * @return {Number}
344 | * @api public
345 | */
346 |
347 | enumerable.count = function(fn, context){
348 | fn = toFunction(fn);
349 | var n = 0,
350 | arr = this,
351 | len = arr.length,
352 | val;
353 |
354 | if(!fn) return len;
355 |
356 | for (var i = 0; i < len; ++i) {
357 | val = arr.get(arr[i]);
358 | if (fn.call(context, val, i, arr)) ++n;
359 | }
360 | return n;
361 | };
362 |
363 | /**
364 | * Determine the indexof `obj` or return `-1`.
365 | *
366 | * @param {Mixed} obj
367 | * @return {Number}
368 | * @api public
369 | */
370 |
371 | enumerable.indexOf = function(obj, fromIndex) {
372 | var arr = this,
373 | len = arr.length,
374 | val,
375 | start = 0;
376 |
377 | if (fromIndex !== undefined){
378 | start = fromIndex < 0 ? fromIndex + len : fromIndex;
379 | }
380 |
381 | for (var i = start; i < len; ++i) {
382 | val = arr.get(arr[i]);
383 | if (val === obj) return i;
384 | }
385 |
386 | return -1;
387 | };
388 |
389 | /**
390 | * Determine the last indexof `obj` or return `-1`.
391 | *
392 | * @param {Mixed} obj
393 | * @return {Number}
394 | * @api public
395 | */
396 |
397 | enumerable.lastIndexOf = function(obj, fromIndex) {
398 | var arr = this,
399 | len = arr.length,
400 | val,
401 | start = len - 1;
402 |
403 | if (fromIndex !== undefined){
404 | start = fromIndex < 0 ? fromIndex + len : fromIndex;
405 | }
406 |
407 | for (var i = start; i >= 0; --i) {
408 | val = arr.get(arr[i]);
409 | if (val === obj) return i;
410 | }
411 |
412 | return -1;
413 | };
414 |
415 | /**
416 | * Check if `obj` is present in this enumerable.
417 | *
418 | * @param {Mixed} obj
419 | * @return {Boolean}
420 | * @api public
421 | */
422 |
423 | enumerable.has = function(obj) {
424 | return !! ~this.indexOf(obj);
425 | };
426 |
427 | /**
428 | * Reduce with `fn(accumulator, val, i)` using
429 | * optional `init` value defaulting to the first
430 | * enumerable value.
431 | *
432 | * @param {Function} fn
433 | * @param {Mixed} [val]
434 | * @return {Mixed}
435 | * @api public
436 | */
437 |
438 | enumerable.reduce = function(fn, init){
439 | var arr = this,
440 | len = arr.length,
441 | i = 0,
442 | val;
443 |
444 | val = null == init
445 | ? arr.get(i++)
446 | : init;
447 |
448 | for (; i < len; ++i) {
449 | val = fn(val, arr.get(arr[i]), i, arr);
450 | }
451 |
452 | return val;
453 | };
454 |
455 | /**
456 | * Reduce with `fn(accumulator, val, i)` using
457 | * optional `init` value defaulting to the first
458 | * enumerable value - like reduce, except from right
459 | * to left.
460 | *
461 | * @param {Function} fn
462 | * @param {Mixed} [val]
463 | * @return {Mixed}
464 | * @api public
465 | */
466 |
467 | enumerable.reduceRight = function(fn, init){
468 | var arr = this,
469 | len = arr.length,
470 | i = len,
471 | val;
472 |
473 | val = null == init
474 | ? arr.get(i++)
475 | : init;
476 |
477 | for (; i--;) {
478 | val = fn(val, arr.get(arr[i]), i, arr);
479 | }
480 |
481 | return val;
482 | };
483 |
484 | /**
485 | * Determine the max value.
486 | *
487 | * With a callback function:
488 | *
489 | * pets.max(function(pet){
490 | * return pet.age
491 | * })
492 | *
493 | * With property strings:
494 | *
495 | * pets.max('age')
496 | *
497 | * With immediate values:
498 | *
499 | * nums.max()
500 | *
501 | * @param {Function|String} fn
502 | * @return {Number}
503 | * @api public
504 | */
505 |
506 | enumerable.max = function(fn, context){
507 | var arr = this,
508 | len = arr.length,
509 | max = -Infinity,
510 | n = 0,
511 | val, i;
512 |
513 | if (fn) {
514 | fn = toFunction(fn);
515 | for (i = 0; i < len; ++i) {
516 | n = fn.call(context, arr.get(arr[i]), i, arr);
517 | max = n > max ? n : max;
518 | }
519 | } else {
520 | for (i = 0; i < len; ++i) {
521 | n = arr.get(arr[i]);
522 | max = n > max ? n : max;
523 | }
524 | }
525 |
526 | return max;
527 | };
528 |
529 | /**
530 | * Determine the min value.
531 | *
532 | * With a callback function:
533 | *
534 | * pets.min(function(pet){
535 | * return pet.age
536 | * })
537 | *
538 | * With property strings:
539 | *
540 | * pets.min('age')
541 | *
542 | * With immediate values:
543 | *
544 | * nums.min()
545 | *
546 | * @param {Function|String} fn
547 | * @return {Number}
548 | * @api public
549 | */
550 |
551 | enumerable.min = function(fn, context){
552 | var arr = this,
553 | len = arr.length,
554 | min = Infinity,
555 | n = 0,
556 | val, i;
557 |
558 | if (fn) {
559 | fn = toFunction(fn);
560 | for (i = 0; i < len; ++i) {
561 | n = fn.call(context, arr.get(arr[i]), i, arr);
562 | min = n < min ? n : min;
563 | }
564 | } else {
565 | for (i = 0; i < len; ++i) {
566 | n = arr.get(arr[i]);
567 | min = n < min ? n : min;
568 | }
569 | }
570 |
571 | return min;
572 | };
573 |
574 | /**
575 | * Determine the sum.
576 | *
577 | * With a callback function:
578 | *
579 | * pets.sum(function(pet){
580 | * return pet.age
581 | * })
582 | *
583 | * With property strings:
584 | *
585 | * pets.sum('age')
586 | *
587 | * With immediate values:
588 | *
589 | * nums.sum()
590 | *
591 | * @param {Function|String} fn
592 | * @return {Number}
593 | * @api public
594 | */
595 |
596 | enumerable.sum = function(fn, context){
597 | var arr = this,
598 | len = arr.length,
599 | n = 0,
600 | val, i;
601 |
602 | if (fn) {
603 | fn = toFunction(fn);
604 | for (i = 0; i < len; ++i) {
605 | n += fn.call(context, arr.get(arr[i]), i, arr);
606 | }
607 | } else {
608 | for (i = 0; i < len; ++i) {
609 | n += arr.get(arr[i]);
610 | }
611 | }
612 |
613 | return n;
614 | };
615 |
616 | /**
617 | * Determine the average value.
618 | *
619 | * With a callback function:
620 | *
621 | * pets.avg(function(pet){
622 | * return pet.age
623 | * })
624 | *
625 | * With property strings:
626 | *
627 | * pets.avg('age')
628 | *
629 | * With immediate values:
630 | *
631 | * nums.avg()
632 | *
633 | * @param {Function|String} fn
634 | * @return {Number}
635 | * @api public
636 | */
637 |
638 | enumerable.avg =
639 | enumerable.mean = function(fn, context){
640 | var arr = this,
641 | len = arr.length,
642 | n = 0,
643 | val, i;
644 |
645 | if (fn) {
646 | fn = toFunction(fn);
647 | for (i = 0; i < len; ++i) {
648 | n += fn.call(context, arr.get(arr[i]), i, arr);
649 | }
650 | } else {
651 | for (i = 0; i < len; ++i) {
652 | n += arr.get(arr[i]);
653 | }
654 | }
655 |
656 | return n / len;
657 | };
658 |
659 | /**
660 | * Return the first value, or first `n` values.
661 | *
662 | * @param {Number|Function} [n]
663 | * @return {Array|Mixed}
664 | * @api public
665 | */
666 |
667 | enumerable.first = function(n, context) {
668 | var arr = this;
669 |
670 | if(!n) return arr[0];
671 | else if ('number' !== typeof n) return this.find(n, context);
672 |
673 | var len = Math.min(n, arr.length),
674 | out = new Array(len);
675 |
676 | for (var i = 0; i < len; ++i) {
677 | out[i] = arr[i];
678 | }
679 |
680 | return out;
681 |
682 | };
683 |
684 | /**
685 | * Return the last value, or last `n` values.
686 | *
687 | * @param {Number|Function} [n]
688 | * @return {Array|Mixed}
689 | * @api public
690 | */
691 |
692 | enumerable.last = function(n, context){
693 | var arr = this,
694 | len = arr.length;
695 |
696 | if(!n) return arr[len - 1];
697 | else if ('number' !== typeof n) return this.findLast(n, context);
698 |
699 | var i = Math.max(0, len - n),
700 | out = [];
701 |
702 | for (; i < len; ++i) {
703 | out.push(arr[i]);
704 | }
705 |
706 | return out;
707 | };
708 |
709 | /**
710 | * Create a hash from a given `key`
711 | *
712 | * @param {String} key
713 | * @return {Object}
714 | * @api public
715 | */
716 |
717 | enumerable.hash = function(str) {
718 | var arr = this,
719 | len = arr.length,
720 | out = {},
721 | key;
722 |
723 | for (var i = 0, len = arr.length; i < len; i++) {
724 | key = arr.get(arr[i])[str];
725 | // TODO: assess, maybe we want out[i] = arr.get(i)
726 | if(!key) continue;
727 | out[key] = arr[i];
728 | };
729 |
730 | return out;
731 | };
732 |
733 | /**
734 | * Sort the array.
735 | *
736 | * With strings:
737 | *
738 | * fruits.sort('calories')
739 | *
740 | * Descending sort:
741 | *
742 | * fruits.sort('calories', 'desc')
743 | *
744 | * @param {undefined|Function|String} fn
745 | * @param {Nunber|String|Boolean} dir
746 | * @return {Array}
747 | * @api public
748 | */
749 |
750 | enumerable.sort = function(fn, dir) {
751 | dir = (dir !== undefined) ? dir : 1;
752 | var sort = proto.sort;
753 | if(!fn) return sort.apply(this);
754 | else if('function' == typeof fn) return sort.apply(this, arguments);
755 |
756 | var self = this;
757 | fn = toFunction(fn);
758 |
759 | // support ascending and descending directions
760 | if('string' == typeof dir) {
761 | if(/asc/.test(dir)) dir = 1;
762 | else if(/des/.test(dir)) dir = -1;
763 | } else if('boolean' == typeof dir) {
764 | dir = (dir) ? 1 : -1;
765 | }
766 |
767 | function compare(a, b) {
768 | a = fn(self.get(a)), b = fn(self.get(b));
769 | if(a < b) return -(dir);
770 | else if(a > b) return dir;
771 | return 0
772 | };
773 |
774 | return sort.call(this, compare);
775 | };
776 |
--------------------------------------------------------------------------------
/test/enumerable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module Dependencies
3 | */
4 |
5 | var array = require('../'),
6 | assert = require('better-assert');
7 |
8 | var fixture = [
9 | { name : 'apple', 'color' : 'red', 'calories' : 100 },
10 | { name : 'pear', 'color' : 'green', 'calories' : 200 },
11 | { name : 'grape', 'color' : 'purple', 'calories' : 20 }
12 | ];
13 |
14 | describe('enumerable', function () {
15 | var fruits;
16 | var views;
17 |
18 | beforeEach(function() {
19 | fruits = array(fixture);
20 | views = array(fixture).map(function(fruit) {
21 | return { model: fruit };
22 | });
23 |
24 | views.get = function(obj) {
25 | return obj.model;
26 | };
27 | });
28 |
29 | describe('each', function () {
30 | it('should call a function', function () {
31 | var out = [];
32 |
33 | fruits.each(function(fruit) {
34 | out.push(fruit);
35 | });
36 |
37 | assert('apple' === out[0].name);
38 | assert('pear' === out[1].name);
39 | assert('grape' === out[2].name);
40 | });
41 | });
42 |
43 | describe('map', function () {
44 | it('should create a new array with ret vals', function () {
45 | var names = fruits.map(function(fruit) {
46 | return fruit.name;
47 | });
48 |
49 | assert('apple' === names[0]);
50 | assert('pear' === names[1]);
51 | assert('grape' === names[2]);
52 | assert(names instanceof array);
53 | });
54 |
55 | it('should not change the original array', function () {
56 | var names = fruits.map(function(fruit) {
57 | return fruit.name;
58 | });
59 |
60 | assert('apple' === fruits[0].name);
61 | assert('pear' === fruits[1].name);
62 | assert('grape' === fruits[2].name);
63 | });
64 |
65 | it('should work with strings too', function(){
66 | var names = fruits.map('name');
67 | assert('apple' === names[0]);
68 | assert('pear' === names[1]);
69 | assert('grape' === names[2]);
70 | assert(names instanceof array);
71 | });
72 |
73 | it('should work with a custom array.get', function() {
74 | var names = views.map('name');
75 | assert(3 == names.length);
76 | assert('apple' == names[0]);
77 | assert('pear' == names[1]);
78 | assert('grape' == names[2]);
79 | });
80 | });
81 |
82 | describe('filter', function () {
83 | it('should create a new array with ret vals', function () {
84 | var names = fruits.filter(function(fruit) {
85 | return fruit.calories > 50;
86 | }).map('name');
87 |
88 | assert(2 === names.length);
89 | assert('apple' === names[0]);
90 | assert('pear' === names[1]);
91 | assert(names instanceof array);
92 | });
93 |
94 | it('should work with strings too', function(){
95 | var names = fruits.filter('calories > 50').map('name');
96 | assert(2 === names.length);
97 | assert('apple' === names[0]);
98 | assert('pear' === names[1]);
99 | assert(names instanceof array);
100 | });
101 |
102 | it('should chain without losing context', function() {
103 |
104 | function List(obj) {
105 | for(key in List.prototype) {
106 | obj[key] = List.prototype[key];
107 | }
108 | }
109 |
110 | array(List.prototype);
111 | List.prototype.get = function(o) { return o; };
112 |
113 | function ListView() {}
114 | List(ListView.prototype);
115 | ListView.prototype.hide = function() { return 'hidden'; };
116 |
117 | var list = new ListView();
118 | list.push(1, 2, 3, 4);
119 |
120 | assert('hidden' == list.filter('> 3').hide());
121 | });
122 |
123 | it('should work with custom array.get', function() {
124 | var out = views.filter('calories > 50');
125 | assert(2 == out.length);
126 | assert('apple' == out[0].model.name);
127 | assert('pear' == out[1].model.name);
128 | });
129 |
130 | it('shouldn\'t mutate the original array', function() {
131 | var len = fruits.length;
132 | var out = fruits.filter('calories < 50');
133 | assert(len == fruits.length);
134 | assert(len > out.length);
135 | assert('apple' == fruits[0].name);
136 | assert('grape' == out[0].name);
137 | })
138 | });
139 |
140 | describe('unique', function() {
141 | it('should undupe objects', function() {
142 | var arr = array([1,2,4,4,4,2,1,5,0]);
143 | var out = arr.unique();
144 | assert(5 == out.length);
145 | assert(1 == out[0]);
146 | assert(2 == out[1]);
147 | assert(4 == out[2]);
148 | assert(5 == out[3]);
149 | assert(0 == out[4]);
150 | });
151 |
152 | it('should chain without losing context', function() {
153 | var obj = { name: 'matt' };
154 | var arr = array(obj);
155 | arr.push(1,2,4,4,4,2,1,5,0);
156 | var out = arr.unique();
157 | assert(5 == out.length);
158 | assert('matt' == out.name);
159 | });
160 |
161 | it('should uniquify based on fields', function() {
162 | fruits.push({ name: 'orange', calories: 100 });
163 | var names = fruits.unique('calories').map('name');
164 | assert(3 == names.length);
165 | assert('apple' === names[0]);
166 | assert('pear' === names[1]);
167 | assert('grape' === names[2]);
168 | });
169 |
170 | it('should work with a custom get', function() {
171 | views.push({ model: { name: 'orange', calories: 100 }});
172 |
173 | var names = views.unique('calories').map('name');
174 | assert(3 == names.length);
175 | assert('apple' === names[0]);
176 | assert('pear' === names[1]);
177 | assert('grape' === names[2]);
178 | });
179 | });
180 |
181 | describe('reject', function() {
182 | it('should reject when function is true', function() {
183 | var len = fruits.reject(function(fruit) {
184 | return fruit.name === 'apple';
185 | }).length;
186 | assert(2 == len);
187 | });
188 |
189 | it('should reject when strings are true', function() {
190 | fruits[0].eaten = true;
191 | var len = fruits.reject('eaten').length;
192 | assert(2 == len);
193 | });
194 |
195 | it('should chain without losing context', function() {
196 | var obj = { name: 'matt' };
197 | var arr = array(obj);
198 | arr.push(1, 2, 3, 4);
199 | var out = arr.reject('>= 3');
200 | assert(2 == out.length);
201 | assert(1 == out[0]);
202 | assert(2 == out[1]);
203 | assert('matt' == out.name);
204 | });
205 |
206 | it('should\'t mutate the original array', function() {
207 | var obj = { name: 'matt' };
208 | var arr = array(obj);
209 | arr.push(1, 2, 3, 4);
210 | var out = arr.reject('>= 3');
211 | assert(4 == arr.length);
212 | assert(1 == arr[0]);
213 | assert(2 == arr[1]);
214 | assert(3 == arr[2]);
215 | assert(4 == arr[3]);
216 | assert('matt' == arr.name);
217 | });
218 |
219 | it('should work with a custom array.get', function() {
220 | var out = views.reject('calories > 50');
221 | assert(1 == out.length);
222 | assert('grape' == out[0].model.name);
223 | });
224 |
225 | it('shouldn\'t mutate the original array', function() {
226 | var len = fruits.length;
227 | var out = fruits.reject('calories > 50');
228 | assert(len == fruits.length);
229 | assert(len > out.length);
230 | })
231 | });
232 |
233 | describe('find', function() {
234 | it('should work with objects', function(){
235 | fruits.push({ name : 'grape', color : 'red'});
236 | var fruit = fruits.find({ name : 'grape'});
237 | assert('grape' == fruit.name);
238 | assert('purple' == fruit.color);
239 | });
240 |
241 | it('should work with functions', function(){
242 | fruits.push({ name : 'grape', color : 'red'});
243 | var fruit = fruits.find(function(fruit) {
244 | return fruit.name == 'grape';
245 | });
246 | assert('grape' == fruit.name);
247 | assert('purple' == fruit.color);
248 | });
249 | });
250 |
251 | describe('findLast', function() {
252 | it('should find strings', function(){
253 | fruits.push({ name : 'grape', color : 'red'});
254 | var fruit = fruits.findLast({ name : 'grape'});
255 | assert('grape' == fruit.name);
256 | assert('red' == fruit.color);
257 | });
258 |
259 | it('should work with functions', function(){
260 | fruits.push({ name : 'grape', color : 'red' });
261 | var fruit = fruits.findLast(function(fruit) {
262 | return fruit.name == 'grape';
263 | });
264 | assert('grape' == fruit.name);
265 | assert('red' == fruit.color);
266 | });
267 | });
268 |
269 | describe('first', function() {
270 | it('get first n models', function(){
271 | var out = fruits.first(2);
272 | assert(2 == out.length);
273 | });
274 |
275 | it('should get first model if nothing is specified', function() {
276 | var out = fruits.first();
277 | assert('apple' == out.name);
278 | });
279 |
280 | it('should use find to handle fns', function(){
281 | var fruit = fruits.first({ color : 'purple' });
282 | assert('grape' == fruit.name);
283 | });
284 | });
285 |
286 | describe('last', function () {
287 | it('get last n models', function(){
288 | var out = fruits.last(2);
289 | assert(2 == out.length);
290 | assert('pear' == out[0].name);
291 | assert('grape' == out[1].name);
292 | });
293 |
294 | it('should get last model if nothing is specified', function() {
295 | var out = fruits.last();
296 | assert('grape' == out.name);
297 | });
298 |
299 | it('should use find to handle fns', function(){
300 | var fruit = fruits.last({ color : 'purple' });
301 | assert('grape' == fruit.name);
302 | });
303 | });
304 |
305 | describe('count', function () {
306 | it('counts with function', function () {
307 | assert(1 === fruits.count(function(f){
308 | return f.name === 'grape'
309 | }));
310 | });
311 |
312 | it('counts with string', function () {
313 | assert(1 === fruits.count('name === "grape"'));
314 | });
315 | });
316 |
317 | describe('indexOf', function () {
318 | it('work with indexOf', function () {
319 | var arr = array(['1', '2']);
320 | assert(0 === arr.indexOf('1'));
321 | assert(1 === arr.indexOf('2'));
322 | });
323 |
324 | it('should also work with objects', function(){
325 | var i = fruits.indexOf(fruits[2]);
326 | assert(2 == i);
327 | });
328 |
329 | it('should start from fromIndex', function(){
330 | assert(array([1, 1]).indexOf(1, 1) === 1);
331 | });
332 |
333 | it('should start from a negative fromIndex (from the back)', function(){
334 | assert(array([1, 1]).indexOf(1, -1) === 1);
335 | });
336 | });
337 |
338 | describe('lastIndexOf', function () {
339 | it('work with lastIndexOf', function () {
340 | var arr = array(['1', '2']);
341 | assert(0 === arr.lastIndexOf('1'));
342 | assert(1 === arr.lastIndexOf('2'));
343 | });
344 |
345 | it('should also work with objects', function(){
346 | var i = fruits.lastIndexOf(fruits[2]);
347 | assert(2 == i);
348 | });
349 |
350 | it('should start from fromIndex', function(){
351 | assert(array([1, 1]).lastIndexOf(1, 0) === 0);
352 | });
353 |
354 | it('should start from a negative fromIndex (from the back)', function(){
355 | assert(array([1, 1]).lastIndexOf(1, -1) === 1);
356 | });
357 | });
358 |
359 | describe('hash', function () {
360 | it('should create hashes from an array', function () {
361 | var out = fruits.hash('name');
362 | assert(fruits[0] === out.apple);
363 | assert(fruits[1] === out.pear);
364 | assert(fruits[2] === out.grape);
365 | });
366 | });
367 |
368 | describe('sort', function () {
369 | it('should work without args and numbers', function(){
370 | var arr = array([4, 2, 1, 2, 3])
371 | arr = arr.sort();
372 | assert(1 === arr[0])
373 | assert(2 === arr[1])
374 | assert(2 === arr[2])
375 | assert(3 === arr[3])
376 | assert(4 === arr[4])
377 | });
378 |
379 | it('should work with functions', function () {
380 | fruits.sort(function(a, b) {
381 | if(a.calories < b.calories) return -1;
382 | else if(a.calories > b.calories) return 1;
383 | return 0
384 | });
385 |
386 | assert('grape' === fruits[0].name);
387 | assert('apple' === fruits[1].name);
388 | assert('pear' === fruits[2].name);
389 | });
390 |
391 | it('should support strings', function(){
392 | fruits.sort('calories')
393 | assert('grape' === fruits[0].name);
394 | assert('apple' === fruits[1].name);
395 | assert('pear' === fruits[2].name);
396 | });
397 |
398 | it('should work with custom get', function() {
399 | views.sort('calories');
400 | assert('grape' === views[0].model.name);
401 | assert('apple' === views[1].model.name);
402 | assert('pear' === views[2].model.name);
403 | })
404 |
405 | describe('descending direction', function () {
406 | it('should support numbers', function(){
407 | fruits.sort('calories', -1)
408 | assert('pear' === fruits[0].name);
409 | assert('apple' === fruits[1].name);
410 | assert('grape' === fruits[2].name);
411 | });
412 |
413 | it('should support shorthand string', function(){
414 | fruits.sort('calories', 'desc')
415 | assert('pear' === fruits[0].name);
416 | assert('apple' === fruits[1].name);
417 | assert('grape' === fruits[2].name);
418 | });
419 |
420 | it('should support full string', function(){
421 | fruits.sort('calories', 'descending')
422 | assert('pear' === fruits[0].name);
423 | assert('apple' === fruits[1].name);
424 | assert('grape' === fruits[2].name);
425 | });
426 |
427 | it('should support booleans', function(){
428 | fruits.sort('calories', false)
429 | assert('pear' === fruits[0].name);
430 | assert('apple' === fruits[1].name);
431 | assert('grape' === fruits[2].name);
432 | });
433 | });
434 | });
435 |
436 | describe('reduce', function () {
437 | it('should reduce to a value', function(){
438 | var totalCalories = fruits.reduce(function(sum, fruit){
439 | return sum + fruit.calories;
440 | }, 0);
441 | assert(320 === totalCalories);
442 | });
443 | it('should go from left to right', function(){
444 | var order = [];
445 | fruits.reduce(function(curr, fruit){
446 | order.push(fruit.name);
447 | }, 0);
448 | assert(order.join(',') === 'apple,pear,grape');
449 | });
450 | });
451 |
452 | describe('reduceRight', function () {
453 | it('should reduce to a value', function(){
454 | var totalCalories = fruits.reduceRight(function(sum, fruit){
455 | return sum + fruit.calories;
456 | }, 0);
457 | assert(320 === totalCalories);
458 | });
459 | it('should go from right to left', function(){
460 | var order = [];
461 | fruits.reduceRight(function(curr, fruit){
462 | order.push(fruit.name);
463 | }, 0);
464 | assert(order.join(',') === 'grape,pear,apple');
465 | });
466 | });
467 |
468 | describe('any', function () {
469 | it('returns true if any is true', function(){
470 | assert(fruits.any(function(fruit){
471 | return fruit.name === 'apple';
472 | }));
473 | });
474 | it('returns false if none is true', function(){
475 | assert(!fruits.any(function(fruit){
476 | return fruit.name === 'kiwi';
477 | }));
478 | });
479 | it('accepts strings', function(){
480 | assert(fruits.any('name === "apple"'));
481 | });
482 | });
483 |
484 | describe('none', function () {
485 | it('returns true if none is true', function(){
486 | assert(fruits.none(function(fruit){
487 | return fruit.name === 'kiwi';
488 | }));
489 | });
490 | it('returns false if any is true', function(){
491 | assert(!fruits.none(function(fruit){
492 | return fruit.name === 'apple';
493 | }));
494 | });
495 | it('accepts strings', function(){
496 | assert(fruits.none('name === "kiwi"'));
497 | });
498 | });
499 |
500 | var iterationMethods = [
501 | 'map',
502 | 'each',
503 | 'forEach',
504 | 'count',
505 | 'any',
506 | 'some',
507 | 'none',
508 | 'every',
509 | 'find',
510 | 'findLast',
511 | 'reject',
512 | 'unique',
513 | 'filter',
514 | 'select',
515 | 'max',
516 | 'min',
517 | 'sum',
518 | 'avg',
519 | 'mean',
520 | 'first',
521 | 'last'
522 | ];
523 |
524 | var reduceMethods = ['reduce', 'reduceRight'];
525 |
526 | describe('3rd parameter to callback', function(){
527 | iterationMethods.forEach(function(method){
528 | it('passes array for ' + method, function(){
529 | var arr = array([1]);
530 | arr[method](function(item, idx, arr_){
531 | assert(arr === arr_);
532 | });
533 | });
534 | });
535 |
536 | reduceMethods.forEach(function(method){
537 | it('passes array for ' + method, function(){
538 | var arr = array([1]);
539 | arr[method](function(curr, item, idx, arr_){
540 | assert(arr === arr_);
541 | }, 0);
542 | });
543 | });
544 | });
545 |
546 | describe('context to callback in iteration methods', function(){
547 | iterationMethods.forEach(function(method){
548 | it('passes context for ' + method, function(){
549 | var arr = array([1]);
550 | var context = {}
551 | arr[method](function(item, idx, arr_){
552 | assert(this === context);
553 | }, context);
554 | });
555 | });
556 | });
557 |
558 |
559 |
560 | });
561 |
--------------------------------------------------------------------------------
/dist/array.js:
--------------------------------------------------------------------------------
1 | ;(function(){
2 |
3 | /**
4 | * Require the given path.
5 | *
6 | * @param {String} path
7 | * @return {Object} exports
8 | * @api public
9 | */
10 |
11 | function require(path, parent, orig) {
12 | var resolved = require.resolve(path);
13 |
14 | // lookup failed
15 | if (null == resolved) {
16 | orig = orig || path;
17 | parent = parent || 'root';
18 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
19 | err.path = orig;
20 | err.parent = parent;
21 | err.require = true;
22 | throw err;
23 | }
24 |
25 | var module = require.modules[resolved];
26 |
27 | // perform real require()
28 | // by invoking the module's
29 | // registered function
30 | if (!module._resolving && !module.exports) {
31 | var mod = {};
32 | mod.exports = {};
33 | mod.client = mod.component = true;
34 | module._resolving = true;
35 | module.call(this, mod.exports, require.relative(resolved), mod);
36 | delete module._resolving;
37 | module.exports = mod.exports;
38 | }
39 |
40 | return module.exports;
41 | }
42 |
43 | /**
44 | * Registered modules.
45 | */
46 |
47 | require.modules = {};
48 |
49 | /**
50 | * Registered aliases.
51 | */
52 |
53 | require.aliases = {};
54 |
55 | /**
56 | * Resolve `path`.
57 | *
58 | * Lookup:
59 | *
60 | * - PATH/index.js
61 | * - PATH.js
62 | * - PATH
63 | *
64 | * @param {String} path
65 | * @return {String} path or null
66 | * @api private
67 | */
68 |
69 | require.resolve = function(path) {
70 | if (path.charAt(0) === '/') path = path.slice(1);
71 |
72 | var paths = [
73 | path,
74 | path + '.js',
75 | path + '.json',
76 | path + '/index.js',
77 | path + '/index.json'
78 | ];
79 |
80 | for (var i = 0; i < paths.length; i++) {
81 | var path = paths[i];
82 | if (require.modules.hasOwnProperty(path)) return path;
83 | if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
84 | }
85 | };
86 |
87 | /**
88 | * Normalize `path` relative to the current path.
89 | *
90 | * @param {String} curr
91 | * @param {String} path
92 | * @return {String}
93 | * @api private
94 | */
95 |
96 | require.normalize = function(curr, path) {
97 | var segs = [];
98 |
99 | if ('.' != path.charAt(0)) return path;
100 |
101 | curr = curr.split('/');
102 | path = path.split('/');
103 |
104 | for (var i = 0; i < path.length; ++i) {
105 | if ('..' == path[i]) {
106 | curr.pop();
107 | } else if ('.' != path[i] && '' != path[i]) {
108 | segs.push(path[i]);
109 | }
110 | }
111 |
112 | return curr.concat(segs).join('/');
113 | };
114 |
115 | /**
116 | * Register module at `path` with callback `definition`.
117 | *
118 | * @param {String} path
119 | * @param {Function} definition
120 | * @api private
121 | */
122 |
123 | require.register = function(path, definition) {
124 | require.modules[path] = definition;
125 | };
126 |
127 | /**
128 | * Alias a module definition.
129 | *
130 | * @param {String} from
131 | * @param {String} to
132 | * @api private
133 | */
134 |
135 | require.alias = function(from, to) {
136 | if (!require.modules.hasOwnProperty(from)) {
137 | throw new Error('Failed to alias "' + from + '", it does not exist');
138 | }
139 | require.aliases[to] = from;
140 | };
141 |
142 | /**
143 | * Return a require function relative to the `parent` path.
144 | *
145 | * @param {String} parent
146 | * @return {Function}
147 | * @api private
148 | */
149 |
150 | require.relative = function(parent) {
151 | var p = require.normalize(parent, '..');
152 |
153 | /**
154 | * lastIndexOf helper.
155 | */
156 |
157 | function lastIndexOf(arr, obj) {
158 | var i = arr.length;
159 | while (i--) {
160 | if (arr[i] === obj) return i;
161 | }
162 | return -1;
163 | }
164 |
165 | /**
166 | * The relative require() itself.
167 | */
168 |
169 | function localRequire(path) {
170 | var resolved = localRequire.resolve(path);
171 | return require(resolved, parent, path);
172 | }
173 |
174 | /**
175 | * Resolve relative to the parent.
176 | */
177 |
178 | localRequire.resolve = function(path) {
179 | var c = path.charAt(0);
180 | if ('/' == c) return path.slice(1);
181 | if ('.' == c) return require.normalize(p, path);
182 |
183 | // resolve deps by returning
184 | // the dep in the nearest "deps"
185 | // directory
186 | var segs = parent.split('/');
187 | var i = lastIndexOf(segs, 'deps') + 1;
188 | if (!i) i = 0;
189 | path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
190 | return path;
191 | };
192 |
193 | /**
194 | * Check if module is defined at `path`.
195 | */
196 |
197 | localRequire.exists = function(path) {
198 | return require.modules.hasOwnProperty(localRequire.resolve(path));
199 | };
200 |
201 | return localRequire;
202 | };
203 | require.register("component-indexof/index.js", function(exports, require, module){
204 | module.exports = function(arr, obj){
205 | if (arr.indexOf) return arr.indexOf(obj);
206 | for (var i = 0; i < arr.length; ++i) {
207 | if (arr[i] === obj) return i;
208 | }
209 | return -1;
210 | };
211 | });
212 | require.register("component-emitter/index.js", function(exports, require, module){
213 |
214 | /**
215 | * Module dependencies.
216 | */
217 |
218 | var index = require('indexof');
219 |
220 | /**
221 | * Expose `Emitter`.
222 | */
223 |
224 | module.exports = Emitter;
225 |
226 | /**
227 | * Initialize a new `Emitter`.
228 | *
229 | * @api public
230 | */
231 |
232 | function Emitter(obj) {
233 | if (obj) return mixin(obj);
234 | };
235 |
236 | /**
237 | * Mixin the emitter properties.
238 | *
239 | * @param {Object} obj
240 | * @return {Object}
241 | * @api private
242 | */
243 |
244 | function mixin(obj) {
245 | for (var key in Emitter.prototype) {
246 | obj[key] = Emitter.prototype[key];
247 | }
248 | return obj;
249 | }
250 |
251 | /**
252 | * Listen on the given `event` with `fn`.
253 | *
254 | * @param {String} event
255 | * @param {Function} fn
256 | * @return {Emitter}
257 | * @api public
258 | */
259 |
260 | Emitter.prototype.on =
261 | Emitter.prototype.addEventListener = function(event, fn){
262 | this._callbacks = this._callbacks || {};
263 | (this._callbacks[event] = this._callbacks[event] || [])
264 | .push(fn);
265 | return this;
266 | };
267 |
268 | /**
269 | * Adds an `event` listener that will be invoked a single
270 | * time then automatically removed.
271 | *
272 | * @param {String} event
273 | * @param {Function} fn
274 | * @return {Emitter}
275 | * @api public
276 | */
277 |
278 | Emitter.prototype.once = function(event, fn){
279 | var self = this;
280 | this._callbacks = this._callbacks || {};
281 |
282 | function on() {
283 | self.off(event, on);
284 | fn.apply(this, arguments);
285 | }
286 |
287 | fn._off = on;
288 | this.on(event, on);
289 | return this;
290 | };
291 |
292 | /**
293 | * Remove the given callback for `event` or all
294 | * registered callbacks.
295 | *
296 | * @param {String} event
297 | * @param {Function} fn
298 | * @return {Emitter}
299 | * @api public
300 | */
301 |
302 | Emitter.prototype.off =
303 | Emitter.prototype.removeListener =
304 | Emitter.prototype.removeAllListeners =
305 | Emitter.prototype.removeEventListener = function(event, fn){
306 | this._callbacks = this._callbacks || {};
307 |
308 | // all
309 | if (0 == arguments.length) {
310 | this._callbacks = {};
311 | return this;
312 | }
313 |
314 | // specific event
315 | var callbacks = this._callbacks[event];
316 | if (!callbacks) return this;
317 |
318 | // remove all handlers
319 | if (1 == arguments.length) {
320 | delete this._callbacks[event];
321 | return this;
322 | }
323 |
324 | // remove specific handler
325 | var i = index(callbacks, fn._off || fn);
326 | if (~i) callbacks.splice(i, 1);
327 | return this;
328 | };
329 |
330 | /**
331 | * Emit `event` with the given args.
332 | *
333 | * @param {String} event
334 | * @param {Mixed} ...
335 | * @return {Emitter}
336 | */
337 |
338 | Emitter.prototype.emit = function(event){
339 | this._callbacks = this._callbacks || {};
340 | var args = [].slice.call(arguments, 1)
341 | , callbacks = this._callbacks[event];
342 |
343 | if (callbacks) {
344 | callbacks = callbacks.slice(0);
345 | for (var i = 0, len = callbacks.length; i < len; ++i) {
346 | callbacks[i].apply(this, args);
347 | }
348 | }
349 |
350 | return this;
351 | };
352 |
353 | /**
354 | * Return array of callbacks for `event`.
355 | *
356 | * @param {String} event
357 | * @return {Array}
358 | * @api public
359 | */
360 |
361 | Emitter.prototype.listeners = function(event){
362 | this._callbacks = this._callbacks || {};
363 | return this._callbacks[event] || [];
364 | };
365 |
366 | /**
367 | * Check if this emitter has `event` handlers.
368 | *
369 | * @param {String} event
370 | * @return {Boolean}
371 | * @api public
372 | */
373 |
374 | Emitter.prototype.hasListeners = function(event){
375 | return !! this.listeners(event).length;
376 | };
377 |
378 | });
379 | require.register("component-props/index.js", function(exports, require, module){
380 | /**
381 | * Global Names
382 | */
383 |
384 | var globals = /\b(Array|Date|Object|Math|JSON)\b/g;
385 |
386 | /**
387 | * Return immediate identifiers parsed from `str`.
388 | *
389 | * @param {String} str
390 | * @param {String|Function} map function or prefix
391 | * @return {Array}
392 | * @api public
393 | */
394 |
395 | module.exports = function(str, fn){
396 | var p = unique(props(str));
397 | if (fn && 'string' == typeof fn) fn = prefixed(fn);
398 | if (fn) return map(str, p, fn);
399 | return p;
400 | };
401 |
402 | /**
403 | * Return immediate identifiers in `str`.
404 | *
405 | * @param {String} str
406 | * @return {Array}
407 | * @api private
408 | */
409 |
410 | function props(str) {
411 | return str
412 | .replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '')
413 | .replace(globals, '')
414 | .match(/[a-zA-Z_]\w*/g)
415 | || [];
416 | }
417 |
418 | /**
419 | * Return `str` with `props` mapped with `fn`.
420 | *
421 | * @param {String} str
422 | * @param {Array} props
423 | * @param {Function} fn
424 | * @return {String}
425 | * @api private
426 | */
427 |
428 | function map(str, props, fn) {
429 | var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g;
430 | return str.replace(re, function(_){
431 | if ('(' == _[_.length - 1]) return fn(_);
432 | if (!~props.indexOf(_)) return _;
433 | return fn(_);
434 | });
435 | }
436 |
437 | /**
438 | * Return unique array.
439 | *
440 | * @param {Array} arr
441 | * @return {Array}
442 | * @api private
443 | */
444 |
445 | function unique(arr) {
446 | var ret = [];
447 |
448 | for (var i = 0; i < arr.length; i++) {
449 | if (~ret.indexOf(arr[i])) continue;
450 | ret.push(arr[i]);
451 | }
452 |
453 | return ret;
454 | }
455 |
456 | /**
457 | * Map with prefix `str`.
458 | */
459 |
460 | function prefixed(str) {
461 | return function(_){
462 | return str + _;
463 | };
464 | }
465 |
466 | });
467 | require.register("component-to-function/index.js", function(exports, require, module){
468 | /**
469 | * Module Dependencies
470 | */
471 |
472 | try {
473 | var expr = require('props');
474 | } catch(e) {
475 | var expr = require('props-component');
476 | }
477 |
478 | /**
479 | * Expose `toFunction()`.
480 | */
481 |
482 | module.exports = toFunction;
483 |
484 | /**
485 | * Convert `obj` to a `Function`.
486 | *
487 | * @param {Mixed} obj
488 | * @return {Function}
489 | * @api private
490 | */
491 |
492 | function toFunction(obj) {
493 | switch ({}.toString.call(obj)) {
494 | case '[object Object]':
495 | return objectToFunction(obj);
496 | case '[object Function]':
497 | return obj;
498 | case '[object String]':
499 | return stringToFunction(obj);
500 | case '[object RegExp]':
501 | return regexpToFunction(obj);
502 | default:
503 | return defaultToFunction(obj);
504 | }
505 | }
506 |
507 | /**
508 | * Default to strict equality.
509 | *
510 | * @param {Mixed} val
511 | * @return {Function}
512 | * @api private
513 | */
514 |
515 | function defaultToFunction(val) {
516 | return function(obj){
517 | return val === obj;
518 | }
519 | }
520 |
521 | /**
522 | * Convert `re` to a function.
523 | *
524 | * @param {RegExp} re
525 | * @return {Function}
526 | * @api private
527 | */
528 |
529 | function regexpToFunction(re) {
530 | return function(obj){
531 | return re.test(obj);
532 | }
533 | }
534 |
535 | /**
536 | * Convert property `str` to a function.
537 | *
538 | * @param {String} str
539 | * @return {Function}
540 | * @api private
541 | */
542 |
543 | function stringToFunction(str) {
544 | // immediate such as "> 20"
545 | if (/^ *\W+/.test(str)) return new Function('_', 'return _ ' + str);
546 |
547 | // properties such as "name.first" or "age > 18" or "age > 18 && age < 36"
548 | return new Function('_', 'return ' + get(str));
549 | }
550 |
551 | /**
552 | * Convert `object` to a function.
553 | *
554 | * @param {Object} object
555 | * @return {Function}
556 | * @api private
557 | */
558 |
559 | function objectToFunction(obj) {
560 | var match = {}
561 | for (var key in obj) {
562 | match[key] = typeof obj[key] === 'string'
563 | ? defaultToFunction(obj[key])
564 | : toFunction(obj[key])
565 | }
566 | return function(val){
567 | if (typeof val !== 'object') return false;
568 | for (var key in match) {
569 | if (!(key in val)) return false;
570 | if (!match[key](val[key])) return false;
571 | }
572 | return true;
573 | }
574 | }
575 |
576 | /**
577 | * Built the getter function. Supports getter style functions
578 | *
579 | * @param {String} str
580 | * @return {String}
581 | * @api private
582 | */
583 |
584 | function get(str) {
585 | var props = expr(str);
586 | if (!props.length) return '_.' + str;
587 |
588 | var val;
589 | for(var i = 0, prop; prop = props[i]; i++) {
590 | val = '_.' + prop;
591 | val = "('function' == typeof " + val + " ? " + val + "() : " + val + ")";
592 | str = str.replace(new RegExp(prop, 'g'), val);
593 | }
594 |
595 | return str;
596 | }
597 |
598 | });
599 | require.register("yields-isArray/index.js", function(exports, require, module){
600 |
601 | /**
602 | * isArray
603 | */
604 |
605 | var isArray = Array.isArray;
606 |
607 | /**
608 | * toString
609 | */
610 |
611 | var str = Object.prototype.toString;
612 |
613 | /**
614 | * Wether or not the given `val`
615 | * is an array.
616 | *
617 | * example:
618 | *
619 | * isArray([]);
620 | * // > true
621 | * isArray(arguments);
622 | * // > false
623 | * isArray('');
624 | * // > false
625 | *
626 | * @param {mixed} val
627 | * @return {bool}
628 | */
629 |
630 | module.exports = isArray || function (val) {
631 | return !! val && '[object Array]' == str.call(val);
632 | };
633 |
634 | });
635 | require.register("array/index.js", function(exports, require, module){
636 | module.exports = require('./lib/array');
637 |
638 | });
639 | require.register("array/lib/array.js", function(exports, require, module){
640 | /**
641 | * Module dependencies
642 | */
643 |
644 | var Enumerable = require('./enumerable');
645 | var proto = Array.prototype;
646 | var isArray = Array.isArray || require('isArray');
647 |
648 | try {
649 | var Emitter = require('emitter');
650 | } catch(e) {
651 | var Emitter = require('emitter-component');
652 | }
653 |
654 | /*
655 | * Expose `array`
656 | */
657 |
658 | module.exports = array;
659 |
660 | /**
661 | * Initialize `array`
662 | *
663 | * @param {Array|Object|Undefined} arr
664 | * @return {array}
665 | * @api public
666 | */
667 |
668 | function array(arr) {
669 | if(!(this instanceof array)) return new array(arr);
670 | arr = arr || [];
671 |
672 | if (isArray(arr)) {
673 | // create array-like object
674 | var len = this.length = arr.length;
675 | for(var i = 0; i < len; i++) this[i] = arr[i];
676 | } else if ('object' == typeof arr) {
677 | if (isObjectLiteral(arr)) {
678 | arr._ctx = this._ctx = JSON.parse(JSON.stringify(arr));
679 | }
680 |
681 | // mixin to another object
682 | for(var key in array.prototype) arr[key] = array.prototype[key];
683 | return arr;
684 | }
685 | }
686 |
687 | /**
688 | * Mixin `Emitter`
689 | */
690 |
691 | Emitter(array.prototype);
692 |
693 | /**
694 | * Mixin `Enumerable`
695 | */
696 |
697 | Enumerable(array.prototype);
698 |
699 | /**
700 | * Removes the last element from an array and returns that element
701 | *
702 | * @return {Mixed} removed element
703 | * @api public
704 | */
705 |
706 | array.prototype.pop = function() {
707 | var ret = proto.pop.apply(this, arguments);
708 | this.emit('remove', ret, this.length);
709 | return ret;
710 | };
711 |
712 | /**
713 | * Push a value onto the end of the array,
714 | * returning the length of the array
715 | *
716 | * @param {Mixed, ...} elements
717 | * @return {Number}
718 | * @api public
719 | */
720 |
721 | array.prototype.push = function() {
722 | var ret = proto.push.apply(this, arguments),
723 | args = [].slice.call(arguments);
724 | for(var i = 0, len = args.length; i < len; i++) this.emit('add', args[i], ret - len + i);
725 | return ret;
726 | };
727 |
728 | /**
729 | * Removes the first element from an array and returns that element.
730 | *
731 | * @return {Mixed}
732 | * @api public
733 | */
734 |
735 | array.prototype.shift = function() {
736 | var ret = proto.shift.apply(this, arguments);
737 | this.emit('remove', ret, 0);
738 | return ret;
739 | };
740 |
741 | /**
742 | * Adds and/or removes elements from an array.
743 | *
744 | * @param {Number} index
745 | * @param {Number} howMany
746 | * @param {Mixed, ...} elements
747 | * @return {Array} removed elements
748 | * @api public
749 | */
750 |
751 | array.prototype.splice = function(index) {
752 | var ret = proto.splice.apply(this, arguments),
753 | added = [].slice.call(arguments, 2);
754 | for(var i = 0, len = ret.length; i < len; i++) this.emit('remove', ret[i], index);
755 | for( i = 0, len = added.length; i < len; i++) this.emit('add', added[i], index + i);
756 | return ret;
757 | };
758 |
759 | /**
760 | * Adds one or more elements to the front of an array
761 | * and returns the new length of the array.
762 | *
763 | * @param {Mixed, ...} elements
764 | * @return {Number} length
765 | * @api public
766 | */
767 |
768 | array.prototype.unshift = function() {
769 | var ret = proto.unshift.apply(this, arguments),
770 | args = [].slice.call(arguments);
771 | for(var i = 0, len = args.length; i < len; i++) this.emit('add', args[i], i);
772 | return ret;
773 | };
774 |
775 | /**
776 | * toJSON
777 | *
778 | * @return {Object}
779 | * @api public
780 | */
781 |
782 | array.prototype.toJSON = function() {
783 | return this.map(function(obj) {
784 | return (obj.toJSON) ? obj.toJSON() : obj;
785 | }).toArray();
786 | }
787 |
788 | /**
789 | * Convert the array-like object to an actual array
790 | *
791 | * @return {Array}
792 | * @api public
793 | */
794 |
795 | array.prototype.toArray = function() {
796 | return proto.slice.call(this);
797 | };
798 |
799 | /**
800 | * Static: get the array item
801 | *
802 | * @param {Mixed} obj
803 | * @return {Mixed}
804 | * @api public
805 | */
806 |
807 | array.get = function(obj) {
808 | return obj;
809 | };
810 |
811 | /**
812 | * Get the array item
813 | *
814 | * @param {Number} i
815 | * @return {Mixed}
816 | * @api public
817 | */
818 |
819 | array.prototype.get = array.get;
820 |
821 | /**
822 | * Attach the rest of the array methods
823 | */
824 |
825 | var methods = ['toString', 'reverse', 'concat', 'join', 'slice'];
826 |
827 | methods.forEach(function(method) {
828 | array.prototype[method] = function() {
829 | return proto[method].apply(this, arguments);
830 | };
831 | });
832 |
833 | /**
834 | * Remake the array, emptying it, then adding values back in
835 | *
836 | * @api private
837 | */
838 |
839 | array.prototype._remake = function(arr) {
840 | var construct = this.constructor;
841 | var clone = (this._ctx) ? new construct(this._ctx) : new construct();
842 | proto.push.apply(clone, arr);
843 | clone.get = this.get || array.get;
844 | return clone;
845 | };
846 |
847 | /**
848 | * Is object utility
849 | *
850 | * @param {Mixed} obj
851 | * @return {Boolean}
852 | * @api private
853 | */
854 |
855 | function isObjectLiteral(obj) {
856 | return obj.constructor == Object;
857 | }
858 |
859 | });
860 | require.register("array/lib/enumerable.js", function(exports, require, module){
861 | /**
862 | * Module Dependencies
863 | */
864 |
865 | var toFunction = require('to-function'),
866 | proto = Array.prototype,
867 | enumerable = {};
868 |
869 | /**
870 | * Mixin to `obj`.
871 | *
872 | * var Enumerable = require('enumerable');
873 | * Enumerable(Something.prototype);
874 | *
875 | * @param {Object} obj
876 | * @return {Object} obj
877 | * @api private
878 | */
879 |
880 | module.exports = function(obj) {
881 | for(var key in enumerable) obj[key] = enumerable[key];
882 | return obj;
883 | };
884 |
885 | /**
886 | * Iterate each value and invoke `fn(val, i)`.
887 | *
888 | * users.each(function(val, i){
889 | *
890 | * })
891 | *
892 | * @param {Function} fn
893 | * @return {Object} self
894 | * @api public
895 | */
896 |
897 | enumerable.forEach =
898 | enumerable.each = function(fn){
899 | var arr = this,
900 | len = arr.length;
901 |
902 | for (var i = 0; i < len; i++) {
903 | fn(arr[i], i);
904 | }
905 |
906 | return this;
907 | };
908 |
909 | /**
910 | * Map each return value from `fn(val, i)`.
911 | *
912 | * Passing a callback function:
913 | *
914 | * users.map(function(user){
915 | * return user.name.first
916 | * })
917 | *
918 | * Passing a property string:
919 | *
920 | * users.map('name.first')
921 | *
922 | * @param {Function} fn
923 | * @return {Enumerable}
924 | * @api public
925 | */
926 |
927 | enumerable.map = function(fn){
928 | fn = toFunction(fn);
929 | var arr = this,
930 | len = arr.length;
931 |
932 | for (var i = 0; i < len; ++i) {
933 | arr[i] = fn(arr.get(arr[i]), i);
934 | }
935 |
936 | return this;
937 | };
938 |
939 | /**
940 | * Select all values that return a truthy value of `fn(val, i)`.
941 | *
942 | * users.select(function(user){
943 | * return user.age > 20
944 | * })
945 | *
946 | * With a property:
947 | *
948 | * items.select('complete')
949 | *
950 | * @param {Function|String} fn
951 | * @return {Enumerable}
952 | * @api public
953 | */
954 |
955 | enumerable.filter =
956 | enumerable.select = function(fn){
957 | fn = toFunction(fn);
958 | var out = [],
959 | arr = this,
960 | len = arr.length,
961 | val;
962 |
963 | for (var i = 0; i < len; ++i) {
964 | val = arr.get(arr[i]);
965 | if (fn(val, i)) out.push(arr[i]);
966 | }
967 |
968 | return this._remake(out);
969 | };
970 |
971 | /**
972 | * Select all unique values.
973 | *
974 | * nums.unique()
975 | *
976 | * @param {Function|String} fn
977 | * @return {Enumerable}
978 | * @api public
979 | */
980 |
981 | enumerable.unique = function(fn){
982 | var out = [],
983 | vals = [],
984 | arr = this,
985 | len = arr.length,
986 | val;
987 |
988 | fn = (fn) ? toFunction(fn) : function(o) { return o; };
989 |
990 | for (var i = 0; i < len; ++i) {
991 | val = fn(arr.get(arr[i]));
992 | if (~vals.indexOf(val)) continue;
993 | vals.push(val);
994 | out.push(arr[i]);
995 | }
996 |
997 | return this._remake(out);
998 | };
999 |
1000 | /**
1001 | * Reject all values that return a truthy value of `fn(val, i)`.
1002 | *
1003 | * Rejecting using a callback:
1004 | *
1005 | * users.reject(function(user){
1006 | * return user.age < 20
1007 | * })
1008 | *
1009 | * Rejecting with a property:
1010 | *
1011 | * items.reject('complete')
1012 | *
1013 | * Rejecting values via `==`:
1014 | *
1015 | * data.reject(null)
1016 | * users.reject(tobi)
1017 | *
1018 | * @param {Function|String|Mixed} fn
1019 | * @return {Enumerable}
1020 | * @api public
1021 | */
1022 |
1023 | enumerable.reject = function(fn){
1024 | var out = [],
1025 | arr = this,
1026 | len = arr.length,
1027 | val, i;
1028 |
1029 | if ('string' == typeof fn) fn = toFunction(fn);
1030 | if (fn) {
1031 | for (i = 0; i < len; ++i) {
1032 | val = arr.get(arr[i]);
1033 | if (!fn(val, i)) out.push(arr[i]);
1034 | }
1035 | } else {
1036 | for (i = 0; i < len; ++i) {
1037 | val = arr.get(arr[i]);
1038 | if (val != fn) out.push(arr[i]);
1039 | }
1040 | }
1041 |
1042 | return this._remake(out);
1043 | };
1044 |
1045 | /**
1046 | * Reject `null` and `undefined`.
1047 | *
1048 | * [1, null, 5, undefined].compact()
1049 | * // => [1,5]
1050 | *
1051 | * @return {Enumerable}
1052 | * @api public
1053 | */
1054 |
1055 |
1056 | enumerable.compact = function(){
1057 | return this.reject(null);
1058 | };
1059 |
1060 | /**
1061 | * Return the first value when `fn(val, i)` is truthy,
1062 | * otherwise return `undefined`.
1063 | *
1064 | * users.find(function(user){
1065 | * return user.role == 'admin'
1066 | * })
1067 | *
1068 | * With a property string:
1069 | *
1070 | * users.find('age > 20')
1071 | *
1072 | * @param {Function|String} fn
1073 | * @return {Mixed}
1074 | * @api public
1075 | */
1076 |
1077 | enumerable.find = function(fn){
1078 | fn = toFunction(fn);
1079 | var arr = this,
1080 | len = arr.length,
1081 | val;
1082 |
1083 | for (var i = 0; i < len; ++i) {
1084 | val = arr.get(arr[i]);
1085 | if (fn(val, i)) return arr[i];
1086 | }
1087 | };
1088 |
1089 | /**
1090 | * Return the last value when `fn(val, i)` is truthy,
1091 | * otherwise return `undefined`.
1092 | *
1093 | * users.findLast(function(user){
1094 | * return user.role == 'admin'
1095 | * })
1096 | *
1097 | * @param {Function} fn
1098 | * @return {Mixed}
1099 | * @api public
1100 | */
1101 |
1102 | enumerable.findLast = function (fn) {
1103 | fn = toFunction(fn);
1104 | var arr = this,
1105 | i = arr.length;
1106 |
1107 | while(i--) if (fn(arr.get(arr[i]), i)) return arr[i];
1108 | };
1109 |
1110 | /**
1111 | * Assert that all invocations of `fn(val, i)` are truthy.
1112 | *
1113 | * For example ensuring that all pets are ferrets:
1114 | *
1115 | * pets.all(function(pet){
1116 | * return pet.species == 'ferret'
1117 | * })
1118 | *
1119 | * users.all('admin')
1120 | *
1121 | * @param {Function|String} fn
1122 | * @return {Boolean}
1123 | * @api public
1124 | */
1125 |
1126 | enumerable.every = function(fn){
1127 | fn = toFunction(fn);
1128 | var arr = this,
1129 | len = arr.length,
1130 | val;
1131 |
1132 | for (var i = 0; i < len; ++i) {
1133 | val = arr.get(arr[i]);
1134 | if (!fn(val, i)) return false;
1135 | }
1136 |
1137 | return true;
1138 | };
1139 |
1140 | /**
1141 | * Assert that none of the invocations of `fn(val, i)` are truthy.
1142 | *
1143 | * For example ensuring that no pets are admins:
1144 | *
1145 | * pets.none(function(p){ return p.admin })
1146 | * pets.none('admin')
1147 | *
1148 | * @param {Function|String} fn
1149 | * @return {Boolean}
1150 | * @api public
1151 | */
1152 |
1153 | enumerable.none = function(fn){
1154 | fn = toFunction(fn);
1155 | var arr = this,
1156 | len = arr.length,
1157 | val;
1158 |
1159 | for (var i = 0; i < len; ++i) {
1160 | val = arr.get(arr[i]);
1161 | if (fn(val, i)) return false;
1162 | }
1163 | return true;
1164 | };
1165 |
1166 | /**
1167 | * Assert that at least one invocation of `fn(val, i)` is truthy.
1168 | *
1169 | * For example checking to see if any pets are ferrets:
1170 | *
1171 | * pets.any(function(pet){
1172 | * return pet.species == 'ferret'
1173 | * })
1174 | *
1175 | * @param {Function} fn
1176 | * @return {Boolean}
1177 | * @api public
1178 | */
1179 |
1180 | enumerable.any = function(fn){
1181 | fn = toFunction(fn);
1182 | var arr = this,
1183 | len = arr.length,
1184 | val;
1185 |
1186 | for (var i = 0; i < len; ++i) {
1187 | val = arr.get(arr[i]);
1188 | if (fn(val, i)) return true;
1189 | }
1190 | return false;
1191 | };
1192 |
1193 | /**
1194 | * Count the number of times `fn(val, i)` returns true.
1195 | *
1196 | * var n = pets.count(function(pet){
1197 | * return pet.species == 'ferret'
1198 | * })
1199 | *
1200 | * @param {Function} fn
1201 | * @return {Number}
1202 | * @api public
1203 | */
1204 |
1205 | enumerable.count = function(fn){
1206 | fn = toFunction(fn);
1207 | var n = 0,
1208 | arr = this,
1209 | len = arr.length,
1210 | val;
1211 |
1212 | if(!fn) return len;
1213 |
1214 | for (var i = 0; i < len; ++i) {
1215 | val = arr.get(arr[i]);
1216 | if (fn(val, i)) ++n;
1217 | }
1218 | return n;
1219 | };
1220 |
1221 | /**
1222 | * Determine the indexof `obj` or return `-1`.
1223 | *
1224 | * @param {Mixed} obj
1225 | * @return {Number}
1226 | * @api public
1227 | */
1228 |
1229 | enumerable.indexOf = function(obj) {
1230 | var arr = this,
1231 | len = arr.length,
1232 | val;
1233 |
1234 | for (var i = 0; i < len; ++i) {
1235 | val = arr.get(arr[i]);
1236 | if (val === obj) return i;
1237 | }
1238 |
1239 | return -1;
1240 | };
1241 |
1242 | /**
1243 | * Determine the last indexof `obj` or return `-1`.
1244 | *
1245 | * @param {Mixed} obj
1246 | * @return {Number}
1247 | * @api public
1248 | */
1249 |
1250 | enumerable.lastIndexOf = function(obj) {
1251 | var arr = this,
1252 | len = arr.length,
1253 | val;
1254 |
1255 | for (var i = --len; i >= 0; --i) {
1256 | val = arr.get(arr[i]);
1257 | if (val === obj) return i;
1258 | }
1259 |
1260 | return -1;
1261 | };
1262 |
1263 | /**
1264 | * Check if `obj` is present in this enumerable.
1265 | *
1266 | * @param {Mixed} obj
1267 | * @return {Boolean}
1268 | * @api public
1269 | */
1270 |
1271 | enumerable.has = function(obj) {
1272 | return !! ~this.indexOf(obj);
1273 | };
1274 |
1275 | /**
1276 | * Reduce with `fn(accumulator, val, i)` using
1277 | * optional `init` value defaulting to the first
1278 | * enumerable value.
1279 | *
1280 | * @param {Function} fn
1281 | * @param {Mixed} [val]
1282 | * @return {Mixed}
1283 | * @api public
1284 | */
1285 |
1286 | enumerable.reduce = function(fn, init){
1287 | var arr = this,
1288 | len = arr.length,
1289 | i = 0,
1290 | val;
1291 |
1292 | val = null == init
1293 | ? arr.get(i++)
1294 | : init;
1295 |
1296 | for (; i < len; ++i) {
1297 | val = fn(val, arr.get(arr[i]), i);
1298 | }
1299 |
1300 | return val;
1301 | };
1302 |
1303 |
1304 | /**
1305 | * Determine the max value.
1306 | *
1307 | * With a callback function:
1308 | *
1309 | * pets.max(function(pet){
1310 | * return pet.age
1311 | * })
1312 | *
1313 | * With property strings:
1314 | *
1315 | * pets.max('age')
1316 | *
1317 | * With immediate values:
1318 | *
1319 | * nums.max()
1320 | *
1321 | * @param {Function|String} fn
1322 | * @return {Number}
1323 | * @api public
1324 | */
1325 |
1326 | enumerable.max = function(fn){
1327 | var arr = this,
1328 | len = arr.length,
1329 | max = -Infinity,
1330 | n = 0,
1331 | val, i;
1332 |
1333 | if (fn) {
1334 | fn = toFunction(fn);
1335 | for (i = 0; i < len; ++i) {
1336 | n = fn(arr.get(arr[i]), i);
1337 | max = n > max ? n : max;
1338 | }
1339 | } else {
1340 | for (i = 0; i < len; ++i) {
1341 | n = arr.get(arr[i]);
1342 | max = n > max ? n : max;
1343 | }
1344 | }
1345 |
1346 | return max;
1347 | };
1348 |
1349 | /**
1350 | * Determine the min value.
1351 | *
1352 | * With a callback function:
1353 | *
1354 | * pets.min(function(pet){
1355 | * return pet.age
1356 | * })
1357 | *
1358 | * With property strings:
1359 | *
1360 | * pets.min('age')
1361 | *
1362 | * With immediate values:
1363 | *
1364 | * nums.min()
1365 | *
1366 | * @param {Function|String} fn
1367 | * @return {Number}
1368 | * @api public
1369 | */
1370 |
1371 | enumerable.min = function(fn){
1372 | var arr = this,
1373 | len = arr.length,
1374 | min = Infinity,
1375 | n = 0,
1376 | val, i;
1377 |
1378 | if (fn) {
1379 | fn = toFunction(fn);
1380 | for (i = 0; i < len; ++i) {
1381 | n = fn(arr.get(arr[i]), i);
1382 | min = n < min ? n : min;
1383 | }
1384 | } else {
1385 | for (i = 0; i < len; ++i) {
1386 | n = arr.get(arr[i]);
1387 | min = n < min ? n : min;
1388 | }
1389 | }
1390 |
1391 | return min;
1392 | };
1393 |
1394 | /**
1395 | * Determine the sum.
1396 | *
1397 | * With a callback function:
1398 | *
1399 | * pets.sum(function(pet){
1400 | * return pet.age
1401 | * })
1402 | *
1403 | * With property strings:
1404 | *
1405 | * pets.sum('age')
1406 | *
1407 | * With immediate values:
1408 | *
1409 | * nums.sum()
1410 | *
1411 | * @param {Function|String} fn
1412 | * @return {Number}
1413 | * @api public
1414 | */
1415 |
1416 | enumerable.sum = function(fn){
1417 | var arr = this,
1418 | len = arr.length,
1419 | n = 0,
1420 | val, i;
1421 |
1422 | if (fn) {
1423 | fn = toFunction(fn);
1424 | for (i = 0; i < len; ++i) {
1425 | n += fn(arr.get(arr[i]), i);
1426 | }
1427 | } else {
1428 | for (i = 0; i < len; ++i) {
1429 | n += arr.get(arr[i]);
1430 | }
1431 | }
1432 |
1433 | return n;
1434 | };
1435 |
1436 | /**
1437 | * Determine the average value.
1438 | *
1439 | * With a callback function:
1440 | *
1441 | * pets.avg(function(pet){
1442 | * return pet.age
1443 | * })
1444 | *
1445 | * With property strings:
1446 | *
1447 | * pets.avg('age')
1448 | *
1449 | * With immediate values:
1450 | *
1451 | * nums.avg()
1452 | *
1453 | * @param {Function|String} fn
1454 | * @return {Number}
1455 | * @api public
1456 | */
1457 |
1458 | enumerable.avg =
1459 | enumerable.mean = function(fn){
1460 | var arr = this,
1461 | len = arr.length,
1462 | n = 0,
1463 | val, i;
1464 |
1465 | if (fn) {
1466 | fn = toFunction(fn);
1467 | for (i = 0; i < len; ++i) {
1468 | n += fn(arr.get(arr[i]), i);
1469 | }
1470 | } else {
1471 | for (i = 0; i < len; ++i) {
1472 | n += arr.get(arr[i]);
1473 | }
1474 | }
1475 |
1476 | return n / len;
1477 | };
1478 |
1479 | /**
1480 | * Return the first value, or first `n` values.
1481 | *
1482 | * @param {Number|Function} [n]
1483 | * @return {Array|Mixed}
1484 | * @api public
1485 | */
1486 |
1487 | enumerable.first = function(n) {
1488 | var arr = this;
1489 |
1490 | if(!n) return arr[0];
1491 | else if ('number' !== typeof n) return this.find(n);
1492 |
1493 | var len = Math.min(n, arr.length),
1494 | out = new Array(len);
1495 |
1496 | for (var i = 0; i < len; ++i) {
1497 | out[i] = arr[i];
1498 | }
1499 |
1500 | return out;
1501 |
1502 | };
1503 |
1504 | /**
1505 | * Return the last value, or last `n` values.
1506 | *
1507 | * @param {Number|Function} [n]
1508 | * @return {Array|Mixed}
1509 | * @api public
1510 | */
1511 |
1512 | enumerable.last = function(n){
1513 | var arr = this,
1514 | len = arr.length;
1515 |
1516 | if(!n) return arr[len - 1];
1517 | else if ('number' !== typeof n) return this.findLast(n);
1518 |
1519 | var i = Math.max(0, len - n),
1520 | out = [];
1521 |
1522 | for (; i < len; ++i) {
1523 | out.push(arr[i]);
1524 | }
1525 |
1526 | return out;
1527 | };
1528 |
1529 | /**
1530 | * Create a hash from a given `key`
1531 | *
1532 | * @param {String} key
1533 | * @return {Object}
1534 | * @api public
1535 | */
1536 |
1537 | enumerable.hash = function(str) {
1538 | var arr = this,
1539 | len = arr.length,
1540 | out = {},
1541 | key;
1542 |
1543 | for (var i = 0, len = arr.length; i < len; i++) {
1544 | key = arr.get(arr[i])[str];
1545 | // TODO: assess, maybe we want out[i] = arr.get(i)
1546 | if(!key) continue;
1547 | out[key] = arr[i];
1548 | };
1549 |
1550 | return out;
1551 | };
1552 |
1553 | /**
1554 | * Sort the array.
1555 | *
1556 | * With strings:
1557 | *
1558 | * fruits.sort('calories')
1559 | *
1560 | * Descending sort:
1561 | *
1562 | * fruits.sort('calories', 'desc')
1563 | *
1564 | * @param {undefined|Function|String} fn
1565 | * @param {Nunber|String|Boolean} dir
1566 | * @return {Array}
1567 | * @api public
1568 | */
1569 |
1570 | enumerable.sort = function(fn, dir) {
1571 | dir = (dir !== undefined) ? dir : 1;
1572 | var sort = proto.sort;
1573 | if(!fn) return sort.apply(this);
1574 | else if('function' == typeof fn) return sort.apply(this, arguments);
1575 |
1576 | var self = this;
1577 | fn = toFunction(fn);
1578 |
1579 | // support ascending and descending directions
1580 | if('string' == typeof dir) {
1581 | if(/asc/.test(dir)) dir = 1;
1582 | else if(/des/.test(dir)) dir = -1;
1583 | } else if('boolean' == typeof dir) {
1584 | dir = (dir) ? 1 : -1;
1585 | }
1586 |
1587 | function compare(a, b) {
1588 | a = fn(self.get(a)), b = fn(self.get(b));
1589 | if(a < b) return -(dir);
1590 | else if(a > b) return dir;
1591 | return 0
1592 | };
1593 |
1594 | return sort.call(this, compare);
1595 | };
1596 |
1597 | });
1598 |
1599 |
1600 |
1601 |
1602 |
1603 |
1604 | require.alias("component-emitter/index.js", "array/deps/emitter/index.js");
1605 | require.alias("component-emitter/index.js", "emitter/index.js");
1606 | require.alias("component-indexof/index.js", "component-emitter/deps/indexof/index.js");
1607 |
1608 | require.alias("component-to-function/index.js", "array/deps/to-function/index.js");
1609 | require.alias("component-to-function/index.js", "to-function/index.js");
1610 | require.alias("component-props/index.js", "component-to-function/deps/props/index.js");
1611 | require.alias("component-props/index.js", "component-to-function/deps/props/index.js");
1612 | require.alias("component-props/index.js", "component-props/index.js");
1613 | require.alias("yields-isArray/index.js", "array/deps/isArray/index.js");
1614 | require.alias("yields-isArray/index.js", "isArray/index.js");
1615 | if (typeof exports == "object") {
1616 | module.exports = require("array");
1617 | } else if (typeof define == "function" && define.amd) {
1618 | define(function(){ return require("array"); });
1619 | } else {
1620 | this["array"] = require("array");
1621 | }})();
--------------------------------------------------------------------------------