I always felt the need to have in JavaScript a more robust set of collections, like those from .NET: ArrayList, Dictionary, strong typed collections. So, some time ago I have decided to implement them everything having as base the JavaScript array object. The code I assume is self explanatory except the class definition which makes use of the custom pattern presented earlier on this blog. It shouldn’t matter too much because it’s very easy for you to implement same functionality using any desired inheritance pattern, like Base2 and Prototype. I think it’s a matter of copy/paste in most cases. You will notice that I didn’t use any special tricks in the code, just the well known JavaScript array object methods and properties. I did not use closures in this case so the ‘_array ‘ property it is consider private base on ‘_’ notation. Using closures eats up more resources so I preferred to avoid them in his case. If you have any questions feel free to post them and I’ll try to give you an answer as fast as possible. You can download the source from HERE.

if (typeof (MG) == 'undefined')
    throw ("MG.Core.js is required!");
if (typeof (MG.Collections) == 'undefined')
    MG.Collections = {};

MG.Collections.IComparer = function() {
    throw ("IComparer is an interface. Interfaces can't be instantiated directly!");
};
MG.Collections.IComparer.prototype = {
    Compare: function(object1, object2) { }
}

/******************************************************************************/
// IList interface definition.
/******************************************************************************/
MG.Collections.IList = function() {
    throw ("IList is an interface. Interfaces can't be instantiated directly!");
};
MG.Collections.IList.prototype = {
    ___name: "IList",
    Add: function(object) { },
    Clear: function() { },
    Contains: function(object) { },
    Remove: function(object) { },
    IsReadOnly: Boolean,
    Item: function(index) { }
};

/******************************************************************************/
// ICollection interface definition.
/******************************************************************************/
MG.Collections.ICollection = function() {
    throw ("ICollection is an interface. Interfaces can't be instantiated directly!");
};
MG.Collections.ICollection.prototype = {
    ___name: "ICollection",
    CopyTo: function(array, arrayIndex) { },
    Count: function() { }
};

/******************************************************************************/
// IDictionary interface definition.
/******************************************************************************/
MG.Collections.IDictionary = function() {
    throw ("IDictionary is an interface. Interfaces can't be instantiated directly!");
};
MG.Collections.IDictionary.prototype = {
    ___name: "IDictionary",
    Add: function(key, value) { },
    Clear: function() { },
    Contains: function(key) { },
    Remove: function(key) { },
    IsReadOnly: Boolean,
    Item: function(key) { }
}

/******************************************************************************/
// ArrayList class definition.
// Implements interfaces: ICollection
/******************************************************************************/

MG.Collections.ArrayList = MG.Class.Create(
                {
                    _array: null,
                    IsReadOnly: Boolean,

                    constructor: function() {
                        this._array = [];
                        this.IsReadOnly = false;
                    },

                    Add: function(object) {
                        if (!this.IsReadOnly)
                            this._array.push(object);
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    Clear: function() {
                        if (!this.IsReadOnly)
                            this._array = [];
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    Contains: function(object) {
                        for (var index = 0; index < this._array.length; index++) {
                            if (this._array[index] == object)
                                return true;
                        }
                        return false;
                    },

                    CopyTo: function(array, arrayIndex) {
                        if (!array)
                            throw ("Destination array is null!");
                        if (arrayIndex < 0 || arrayIndex > array.length)
                            throw ("Index of destination array out of bound! Index value is: " + arrayIndex);

                        for (var i = this._array.length - 1; i >= 0; i--)
                            array.splice(arrayIndex, 0, this._array[i]);
                    },

                    Remove: function(object) {
                        if (!this.IsReadOnly) {
                            for (var index = 0; index < this._array.length; index++) {
                                if (this._array[index] == object)
                                    this._array.splice(index, 1);
                            }
                        }
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    Count: function() {
                        return this._array.length;
                    },

                    Item: function(index) {
                        if (index < 0 || index >= this._array.length)
                            throw ("Index out of bound! Index value is: " + index);

                        return this._array[index];
                    },

                    IndexOf: function(object) {
                        for (var index = 0; index < this._array.length; index++) {
                            if (this._array[index] == object)
                                return index;
                        }
                        return -1;
                    },

                    LastIndexOf: function(object) {
                        var lastIndex = -1;
                        for (var index = 0; index < this._array.length; index++) {
                            if (this._array[index] == object)
                                lastIndex = index;
                        }
                        return lastIndex;
                    },

                    RemoveAt: function(index) {
                        if (!this.IsReadOnly) {
                            if (index < 0 || index >= this._array.length)
                                throw ("Index out of bound! Index value is: " + index);

                            this._array.splice(index, 1);
                        }
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    RemoveRange: function(index, Count) {
                        if (!this.IsReadOnly) {
                            if (index < 0 || index >= this._array.length)
                                throw ("Index out of bound! Index value is: " + index);
                            if (Count < 0 || (Count + index) > this._array.length)
                                throw ("Range out of bound! Range value is: " + Count);

                            this._array.splice(index, Count);
                        }
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    InsertAt: function(index, object) {
                        if (!this.IsReadOnly) {
                            if (index < 0 || index >= this._array.length)
                                throw ("Index out of bound! Index value is: " + index);

                            this._array.splice(index, 0, object);
                        }
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    InsertRange: function(index, _ICollection) {
                        if (!this.IsReadOnly) {
                            if (index < 0 || index >= this._array.length)
                                throw ("Index out of bound! Index value is: " + index);

                            var _newarray = _ICollection.GetRange(0, _ICollection.Count());

                            for (var i = 0; i < _newarray.length; i++)
                                this.InsertAt(index, _newarray[i]);
                        }
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    GetRange: function(index, Count) {
                        if (index < 0 || index >= this._array.length)
                            throw ("Index out of bound! Index value is: " + index);
                        if (Count < 0 || (Count + index) > this._array.length)
                            throw ("Range out of bound! Range value is: " + Count);

                        var arrayList = new ArrayList();
                        arrayList._array = this._array.slice(index, (Count + index));
                        return arrayList;
                    },

                    Reverse: function() {
                        if (!this.IsReadOnly)
                            this._array.reverse();
                        else
                            throw ("ArrayList is read only! Check IsReadOnly value.");
                    },

                    Sort: function() {
                        this._array.sort();
                    }
                }, null, [MG.Collections.ICollection]);

/******************************************************************************/
// DictionaryEntry class definition.
/******************************************************************************/
MG.Collections.DictionaryEntry = MG.Class.Create(
                    {
                        key: null,
                        value: null,
                        constructor: function(key, value) {
                            this.key = key;
                            this.value = value;
                        }
                    });

/******************************************************************************/
// Dictionary class definition.
// Implements interfaces: IDictionary
//                        ICollection
/******************************************************************************/
                    MG.Collections.Dictionary = MG.Class.Create(
                {
                    _dictionary: null,
                    IsReadOnly: Boolean,

                    constructor: function() {
                        this._dictionary = [];
                        this.IsReadOnly = false;
                    },

                    Add: function(key, value) {
                        if (!this.IsReadOnly) {
                            if (this.Contains(key))
                                throw ("Dictionary does not allow duplicate keys!");

                            var dicEntry = new MG.Collections.DictionaryEntry(key, value);
                            this._dictionary.push(dicEntry);
                        }
                        else
                            throw ("Dictionary is read only! Check IsReadOnly value.");
                    },

                    Clear: function() {
                        if (!this.IsReadOnly)
                            this._dictionary = [];
                        else
                            throw ("Dictionary is read only! Check IsReadOnly value.");
                    },

                    Contains: function(key) {
                        for (var index = 0; index < this._dictionary.length; index++) {
                            if (this._dictionary[index].key == key)
                                return true;
                        }
                        return false;
                    },

                    Remove: function(key) {
                        if (!this.IsReadOnly) {
                            for (var index = 0; index < this._dictionary.length; index++) {
                                if (this._dictionary[index].key == key)
                                    this._dictionary.splice(index, 1);
                            }
                        }
                        else
                            throw ("Dictionary is read only! Check IsReadOnly value.");
                    },

                    Item: function(key) {
                        if (!key)
                            throw ("Invalid key! Key value is: " + key);

                        for (var index = 0; index < this._dictionary.length; index++) {
                            if (this._dictionary[index].key == key)
                                return this._dictionary[index].value;
                        }
                        return null;
                    },

                    CopyTo: function(array, arrayIndex) {
                        if (!array)
                            throw ("Destination array is null!");
                        if (arrayIndex < 0 || arrayIndex > array.length)
                            throw ("Index of destination array out of bound! Index value is: " + arrayIndex);

                        for (var i = this._dictionary.length - 1; i >= 0; i--)
                            array.splice(arrayIndex, 0, this._dictionary[i]);
                    },

                    Count: function() {
                        return this._dictionary.length;
                    },

                    Keys: function() {
                        var keys = [];
                        for (var index = 0; index < this._dictionary.length; index++)
                            keys.push(this._dictionary[index].key);

                        return keys.reverse();
                    },

                    Values: function() {
                        var values = [];
                        for (var index = 0; index < this._dictionary.length; index++)
                            values.push(this._dictionary[index].value);

                        return values.reverse();
                    },

                    IndexOf: function(key) {
                        var keys = [];
                        for (var index = 0; index < this._dictionary.length; index++) {
                            if (this._dictionary[index].key == key)
                                return index;
                        }
                    },

                    InsertAt: function(index, key, value) {
                        if (!this.IsReadOnly) {
                            if (index < 0 || index >= this._dictionary.length)
                                throw ("Index out of bound! Index value is: " + index);

                            var dicEntry = new MG.Collections.DictionaryEntry(key, value);
                            this._dictionary.splice(index, 0, dicEntry);
                        }
                        else
                            throw ("Dictionary is read only! Check IsReadOnly value.");
                    },

                    Reverse: function() {
                        if (!this.IsReadOnly)
                            this._dictionary.reverse();
                        else
                            throw ("Dictionary is read only! Check IsReadOnly value.");
                    }
                }, null, [MG.Collections.IDictionary, MG.Collections.ICollection]);

/******************************************************************************/
// Comparer class definition.
// Implements interfaces: IComparer
/******************************************************************************/
MG.Collections.Comparer = MG.Class.Create({
    constructor: function() { },
    Compare: function(object1, object2) {
        var result = false;
        var type = typeof object1;
        if (type !== typeof object2)
            throw ("Object 'object1' doesn't have same type as object 'object2'!");
        else if (type === "object") {
            switch (object1.constructor) {
                case Array:
                    {
                        if (object1.length === object2.length) {
                            for (var index = 0; index < object1.length; index++) {
                                result = this.Compare(object1[index], object2[index]);
                                if (!result)
                                    break;
                            }
                        }
                    }
                    break;
                case Dictionary:
                    {
                        if (object1.Count() === object2.Count()) {
                            var keys = object1.Keys();
                            for (var index = 0; index < keys.length; index++) {
                                if (object2.Contains(keys[index])) {
                                    result = this.Compare(object1.Item(keys[index]),
                                                                                        object2.Item(keys[index]));

                                    if (!result)
                                        break;
                                }
                                else {
                                    result = false;
                                    break;
                                }
                            }
                        }
                    }
                    break;
                case ArrayList:
                    {
                        if (object1.Count() === object2.Count()) {
                            for (var index = 0; index < object1.Count(); index++) {
                                result = this.Compare(object1.Item(index), object2.Item(index));

                                if (!result)
                                    break;
                            }
                        }
                    }
                    break;
                default:
                    {
                        if (object1 === object2)
                            result = true;
                    }
                    break;
            }
        }

        switch (type) {
            case "string":
            case "number":
            case "boolean":
                if (object1 === object2)
                    result = true;
                break;
        }

        return result;
    }
}, null, [MG.Collections.IComparer]);


MG.Collections.TypedCollection = MG.Class.Create({
    Type: null,
    Browser: null,

    constructor: function(type) {
        var _type = typeof type;
        if (_type && _type === "function")
            this.Type = type;
        else
            throw ("The 'type' parameter must be a function object.");

        this.Browser = MG.Browser.getInstance();
    },

    Add: function() {
        var key = null;
        var value = null;

        if (arguments.length == 2) {
            key = arguments[0];
            value = arguments[1];
        }
        else if (arguments.length == 1)
            value = arguments[0];
        else
            throw ("Invalid number of parameters. Number of parameters expected is two or one.");

        if (this.Browser.NS) {
            if (value.constructor !== this.Type.constructor)
                throw ("The 'value' parameter doesn't have same type as this collection.");
        }
        else if (this.Browser.IE) {
            if (value.constructor !== this.Type)
                throw ("The 'value' parameter doesn't have same type as this collection.");
        }

        if (!this.IsReadOnly) {
            if (key) {
                var dicEntry = new MG.Collections.DictionaryEntry(key, value);
                this._dictionary.push(dicEntry);
            }
            else {
                var dicEntry = new MG.Collections.DictionaryEntry("key", value);
                this._dictionary.push(dicEntry);
            }
        }
        else
            throw ("Dictionary is read only! Check IsReadOnly value.");
    },

    Item: function() {
        if (!arguments.length == 1)
            throw ("Invalid number of parameters. Number of parameters expected is two or one.");

        var arg = arguments[0];
        var type = typeof arg;
        var value = null;

        if (type == "string")
            value = this.___Item(arg);
        else if (type == "number") {
            if (this._dictionary.length > arg)
                value = this._dictionary[arg].value;
            else
                throw ("Index is out of range.");
        }

        return value;
    }

}, MG.Collections.Dictionary, null);