├── .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 [![Build Status](https://secure.travis-ci.org/MatthewMueller/array.png?branch=master)](http://travis-ci.org/MatthewMueller/array) [![Build Status](https://david-dm.org/MatthewMueller/array.png)](https://david-dm.org/MatthewMueller/array) [![NPM version](https://badge.fury.io/js/array.js.png)](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 | }})(); --------------------------------------------------------------------------------