Last active
March 26, 2018 16:08
-
-
Save fatso83/3773d4cb5f39128b3732 to your computer and use it in GitHub Desktop.
Typescript implementation of Ben Nadel's Collection class (http://www.bennadel.com/blog/2292-extending-javascript-arrays-while-keeping-native-bracket-notation-functionality.htm)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var expect = require('chai').expect; | |
var sinon = require('sinon'); | |
var base = require('./Collection'); | |
var Collection = base.Collection; | |
var extended = require('./ExtendedCollection'); | |
var ExtendedCollection = extended.ExtendedCollection; | |
describe('constructor', function () { | |
it('should be instantiated with its arguments', function () { | |
var c = new Collection(1, 2, 3, 4); | |
expect(c.length).to.equal(4); | |
}); | |
it('should add the methods to the collection as unlistable properties', function () { | |
var c = new Collection(); | |
expect(c).to.be.empty; | |
}) | |
}); | |
describe('length', function () { | |
it('should not be enumerable', function() { | |
expect(Object.keys(new Collection)).to.be.empty; | |
}); | |
it('should be updated as we add items', function () { | |
var c = new Collection(1, 2); | |
c.add(3); | |
expect(c.length).to.equal(3); | |
}); | |
}); | |
describe('add', function () { | |
var c1; | |
beforeEach(function () { | |
c1 = new Collection(); | |
}); | |
it('should add only the first argument', function () { | |
c1.add(1, 2, 3); | |
expect(c1.length).to.equal(1); | |
}); | |
it('should add all the elements of an array', function () { | |
c1.add([1, 2, 3]); | |
expect(c1.length).to.equal(3); | |
}); | |
}); | |
describe('remove', function () { | |
it('should remove an identifable element', function () { | |
function identifiable (id) { | |
return { | |
getId : function () { return id; } | |
} | |
} | |
var elemToRemove = identifiable('foobar'); | |
var c = new Collection( | |
identifiable('a'), identifiable(1010), elemToRemove | |
); | |
c.remove(elemToRemove); | |
expect(c.find('foobar')).to.eql(null); | |
}); | |
it('should remove an element identfied by a function', function () { | |
var c = new Collection(1, 2, 3, 4, 5); | |
c.remove(function (e) { return e === 4}); | |
expect(c).not.to.contain(4); | |
}) | |
}); | |
describe('subclassing', function () { | |
it('should be subclassable', function () { | |
var c = new ExtendedCollection(6,4,2,0,-2); | |
expect(c.length).to.equal(5); | |
expect(c.mySquare()).to.equal(5*5); | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Utility "class" extending Array with lookup functions | |
* | |
* Can do everything an Array can, including indexing by brackets, | |
* use in loop constructions (for, while), etc | |
* | |
* Example: | |
* var foo = new Foo({id : 1}) | |
* foo.getId() === 1; // => true | |
* var c = new Collection(); | |
* c.add(foo) | |
* foo === c.find(1); // => true | |
* | |
* Typescript conversion of Ben Nadel's Collection class. | |
* https://gist.github.com/fatso83/3773d4cb5f39128b3732 | |
* | |
* @author Carl-Erik Kopseng | |
* @author Ben Nadel (javascript original) | |
*/ | |
export interface Identifiable { | |
getId : () => any; | |
} | |
export class Collection<T extends Identifiable> implements Array<T> { | |
constructor(...initialItems:any[]) { | |
var collection = Object.create(Array.prototype); | |
Collection.init(collection, initialItems, Collection.prototype); | |
return collection; | |
} | |
static init(collection, initialItems:any[], prototype) { | |
Object.getOwnPropertyNames(prototype) | |
.forEach((prop) => { | |
if (prop === 'constructor') return; | |
Object.defineProperty(collection, prop, { value: prototype[prop] }) | |
}); | |
// If we don't redefine the property, the length property is suddenly enumerable! | |
// Failing to do this, this would fail: Object.keys([]) === Object.keys(new Collection() ) | |
Object.defineProperty(collection, 'length', { | |
value: collection.length, | |
writable: true, | |
enumerable: false | |
}); | |
var itemsToPush = initialItems; | |
if (Array.isArray(initialItems[0]) && initialItems.length === 1) { | |
itemsToPush = initialItems[0]; | |
} | |
Array.prototype.push.apply(collection, itemsToPush); | |
return collection; | |
} | |
// Find an element by checking each element's getId() method | |
public find(id:any):T; | |
// Find an element using a lookup function that | |
// returns true when given the right element | |
public find(lookupFn:(e:T) => boolean):T ; | |
find(x:any) { | |
var res, comparitor; | |
if (typeof x === 'function') { | |
comparitor = x; | |
} else { | |
comparitor = (e) => { | |
return e.getId() === x; | |
} | |
} | |
res = [].filter.call(this, comparitor); | |
if (res.length) return res[0]; | |
else return null; | |
} | |
// Add an element | |
add(value:T); | |
// Adds all ements in the array (flattens it) | |
add(arr:T[]); | |
add(arr:Collection<T>); | |
add(value) { | |
// Check to see if the item is an array or a subtype thereof | |
if (value instanceof Array) { | |
// Add each sub-item using default push() method. | |
Array.prototype.push.apply(this, value); | |
} else { | |
// Use the default push() method. | |
Array.prototype.push.call(this, value); | |
} | |
// Return this object reference for method chaining. | |
return this; | |
} | |
remove(elem:T):boolean; | |
remove(lookupFn:(e:T) => boolean):boolean ; | |
remove(x:any):boolean { | |
return !!this._remove(x); | |
} | |
/** | |
* @return the removed element if found, else null | |
*/ | |
_remove(x:any):T { | |
var arr = this; | |
var index = -1; | |
if (typeof x === 'function') { | |
for (var i = 0, len = arr.length; i < len; i++) { | |
if (x(this[i])) { | |
index = i; | |
break; | |
} | |
} | |
} else { | |
index = arr.indexOf(x); | |
} | |
if (index === -1) { | |
return null; | |
} | |
else { | |
var res = arr.splice(index, 1); | |
return res.length ? res[0] : null; | |
} | |
} | |
// dummy declarations | |
// copied from lib.d.ts in the typescript npm module | |
toString:()=> string; | |
toLocaleString:()=> string; | |
concat:<U extends T[]>(...items:U[])=> T[]; | |
join:(separator?:string)=> string; | |
pop:()=> T; | |
push:(...items:T[])=> number; | |
reverse:()=> T[]; | |
shift:()=> T; | |
slice:(start?:number, end?:number)=> T[]; | |
sort:(compareFn?:(a:T, b:T) => number)=> T[]; | |
splice:(start?:number, deleteCount?:number, ...items:T[])=> T[]; | |
unshift:(...items:T[])=> number; | |
indexOf:(searchElement:T, fromIndex?:number)=> number; | |
lastIndexOf:(searchElement:T, fromIndex?:number)=> number; | |
every:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean; | |
some:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean; | |
forEach:(callbackfn:(value:T, index:number, array:T[]) => void, thisArg?:any)=> void; | |
map:<U>(callbackfn:(value:T, index:number, array:T[]) => U, thisArg?:any)=> U[]; | |
filter:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> T[]; | |
reduce:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U; | |
reduceRight:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U; | |
length:number; | |
[n: number]: T; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import coll = require('./Collection'); | |
export class ExtendedCollection<T extends coll.Identifiable> extends coll.Collection<T> { | |
constructor(...items:T[]) { | |
var s = <any>super(); | |
coll.Collection.init(s,items,ExtendedCollection.prototype); | |
return s; | |
} | |
mySquare() : number { | |
return this.length*this.length; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "Collection.ts", | |
"description": "Typescript conversion of Ben Nadel's original Collection class (http://www.bennadel.com/blog/2292-extending-javascript-arrays-while-keeping-native-bracket-notation-functionality.htm)", | |
"version": "0.0.2", | |
"devDependencies": { | |
"mocha": "^1.21.4", | |
"chai": "^1.9.1", | |
"sinon": "^1.10.3" | |
}, | |
"scripts" : { | |
"test" : "mocha collection-test.js" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment