andrew4582

Javascript Generic List Implementation

May 14th, 2012
294
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*******************************************************************************************
  2.    Title:  Javascript Generic List Implementation
  3.    Description:  An implementation of a Generic List with LINQ support (based off .NET).
  4.    Author:  Julius Friedman / Shawn Lawsure
  5.    Usage Example:
  6.  
  7.         function Car(make, model)
  8.         {
  9.             this.make = make;
  10.             this.model = model;
  11.         }
  12.  
  13.         var myList = new List();
  14.         myList.Add(new Car("Honda", "Civic"));
  15.         myList.Add(new Car("Nissan", "Sentra"));
  16.         myList.Add(new Car("Honda", "Cr-V"));
  17.         myList.Add(new Car("Honda", "Cr-V"));
  18.  
  19.         var selList = myList.Where("make == 'Honda'").OrderByDescending("model").Distinct();
  20.          
  21. *********************************************************************************************/
  22.  
  23. /*namespace GenericList*/(function () {
  24.  
  25.     // ===============  Static Members  =================================================
  26.  
  27.     var $List$Created = -1, // Id counter used to identify each List instance.
  28.         $List$Instances = {}, // Storage for each instance which has yet to be disposed.
  29.     //Destructor
  30.         $List$Dispose = (function (who, disposing) {
  31.             try {
  32.  
  33.                 //Ensure who is a List instance
  34.                 if (typeof who === 'number') who = $List$Instances[who];
  35.  
  36.                 //Determine if event was called
  37.                 disposing = disposing || this === window;
  38.  
  39.                 //Remove instance from Type storage
  40.                 $List$Instances[who.$key] = null;
  41.                 delete $List$Instances[who.$key];
  42.  
  43.                 //Dispose Type in a closure
  44.                 if (disposing && Object.keys($List$Instances).length === 0) {
  45.                     //Anonymously
  46.                     (function () {
  47.                         //set a timeout for 0 seconds to dispose the List Type
  48.                         setTimeout((function () {
  49.                             List = null;
  50.                             return delete List;
  51.                         }), 0);
  52.                     })();
  53.                 }
  54.  
  55.             } catch (E) { }
  56.         });
  57.  
  58.     // Method: Constructor
  59.     // Description: Returns a new List instance based on the given parameters.
  60.     function List(/*type, array*/) {
  61.  
  62.         // ===============  Private Attributes  =================================================
  63.  
  64.         var key = ++$List$Created,  // Identify each List instance with an incremented Id.
  65.         oType = undefined,      // Used to ensure that all objects added to the list are of the same type.
  66.         listArray = [];         // Stores all the list data.
  67.         $List$Instances[key] = this; // Store instance with key
  68.  
  69.         //Copy constructor (utilized if first parameter is a List instance
  70.         if (arguments[0] && arguments[0] instanceof List) {
  71.             oType = arguments[0].$type; // Set the type of the List from the given
  72.             listArray = arguments[0].array; // Set the inner array of the List from the given
  73.         } else if (arguments[0] && arguments[0].length) { //If there is a type given it may be contained in an array which is to be used as the interal array..      
  74.             try {
  75.                 oType = arguments[0][0].constructor; // Set type of the List from the first element in the given array
  76.                 arguments[1] = arguments[1] || arguments[0]; // Make a new argument incase one is not given which should be the array given. This will be used after AddRange is constructed to verify each given item complies with the List logic.
  77.             } catch (e) { }
  78.         };
  79.  
  80.         // ===============  Public Properties  =================================================
  81.  
  82.         //If supported define public properties on the List instance being created
  83.         if (Object.defineProperty) {
  84.  
  85.             // Property: array
  86.             // Description: Gets or Sets in Native inner array utilized by the List for storage. The elements contained must be of the same type in which this List instance was created with.
  87.             Object.defineProperty(List.prototype, 'array',
  88.            {
  89.                // writable: false, // True if and only if the value associated with the property may be changed. (data descriptors only). Defaults to false.
  90.                enumerable: true, // true if and only if this property shows up during enumeration of the properties on the corresponding object. Defaults to false.
  91.                configurable: true, // true if and only if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. Defaults to false.
  92.                get: function () {
  93.                    return listArray;
  94.                },
  95.                set: function (value) {
  96.                    //Ensure Array
  97.                    if (value instanceof Array) {
  98.                        //Ensure length
  99.                        if (value.length) {
  100.                            //Ensure types
  101.                            value.forEach(function (v) { if (v.constructor !== oType) throw "Only one object type is allowed in a list"; });
  102.                        }
  103.                        //Set member
  104.                        listArray = value;
  105.                    }
  106.                }
  107.            });
  108.  
  109.             // Property: $key
  110.             // Description: Gets the machine key which identifies this List instance in the memory of all created List instances.
  111.             Object.defineProperty(List.prototype, '$key',
  112.            {
  113.                configurable: true,
  114.                get: function () {
  115.                    return key;
  116.                }
  117.            });
  118.  
  119.             // Property: $key
  120.             // Description: Gets the constructor or type in which this List was created with
  121.             Object.defineProperty(List.prototype, '$type',
  122.            {
  123.                enumerable: true,
  124.                configurable: true,
  125.                get: function () {
  126.                    return oType;
  127.                }
  128.            });
  129.  
  130.  
  131.         } else {
  132.             this.$type = oType;         // Compatibility
  133.             this.array = listArray;
  134.             this.$key = key;
  135.         }
  136.  
  137.         // ===============  Private Methods  ====================================================
  138.  
  139.         // Method:  validate
  140.         // Description:  Make sure that all objects added to the List are of the same type.
  141.         function validate(object) {
  142.             //If we have not yet determined a type it is determined by the first object added
  143.             if (!oType) {
  144.                 oType = object.constructor;
  145.             }
  146.             else if (object.constructor !== oType) {
  147.                 throw "Only one object type is allowed in a list";
  148.             }
  149.         }
  150.  
  151.         // Method:  select
  152.         // Description:  Return a copy of this List object with only the elements that meet the criteria
  153.         //               as defined by the 'query' parameter.
  154.         // Usage Example:  
  155.         //              var selList = select("make == 'Honda'").
  156.         //              var anotherList = select(function(){ return this.make === 'Honda' });
  157.         function select(query) {
  158.             var selectList = new List();
  159.             listArray.forEach(function (tEl) {
  160.                 with (tEl)
  161.                     if (eval(query))
  162.                         selectList.Add(tEl);
  163.             });
  164.             return selectList;
  165.         }
  166.  
  167.         // Method:  genericSort
  168.         // Description:  Sort comparison function using an object property name.  Pass this function to
  169.         //               the Javascript sort function to sort the list by a given property name.
  170.         // Usage Example:
  171.         //              var sortedList = listArray.sort(genericSort('model'));
  172.         function genericSort(property) {
  173.             return function (a, b) {
  174.                 return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
  175.             }
  176.         }
  177.  
  178.         // ===============  Public Methods  ======================================================
  179.  
  180.         // Method:  Add
  181.         // Description:  Add an element to the end of the list.
  182.         this.Add = function (object) {
  183.  
  184.             validate(object);
  185.  
  186.             listArray.push(object);
  187.         }
  188.  
  189.         // Method:  AddRange
  190.         // Description:  Adds each of the elements in the given Array or List to this List instance
  191.         this.AddRange = function (arrayOrList) {
  192.             if (!arrayOrList) return;
  193.             if (arrayOrList instanceof Array) {
  194.                 try {
  195.                     arrayOrList.forEach(function (tEl) {
  196.                         this.Add(tEl);
  197.                     }, this);
  198.                 } catch (e) { throw e; }
  199.             } else if (arrayOrList instanceof List) {
  200.                 try {
  201.                     arrayOrList.array.forEach(function (tEl) {
  202.                         this.Add(tEl);
  203.                     }, this);
  204.                 } catch (e) { throw e; }
  205.             }
  206.         }
  207.  
  208.         //If there is an array given then each member of the array must be added and verified
  209.         if (arguments[1] && arguments[1].length) {
  210.             try {
  211.                 this.AddRange(arguments[1]);
  212.             } catch (e) { throw e; }
  213.         }
  214.  
  215.         // Method:  Clear
  216.         // Description:  Removes all elements from this List instance
  217.         this.Clear = function () { listArray = []; }
  218.  
  219.         // Method:  CopyTo
  220.         // Description:  Adds all elemets in this List to the given array, List or Object
  221.         this.CopyTo = function (source) {
  222.             if (!source) return;
  223.             if (source instanceof List) {
  224.                 listArray.forEach(function (tEl) {
  225.                     source.Add(tEl);
  226.                 });
  227.             } else if (source instanceof Array) {
  228.                 listArray.forEach(function (tEl) {
  229.                     source.push(tEl);
  230.                 });
  231.             } else {
  232.                 listArray.forEach(function (tEl) {
  233.                     source[tEl.toString()] = tEl;
  234.                 });
  235.             }
  236.         }
  237.  
  238.         // Method:  Insert
  239.         // Description:  Inserts an element into the List at the specified index (where).
  240.         this.Insert = function (where, what) {
  241.             if (where < 0 || where > listArray.length) throw "Invalid index parameter in call to List.Insert (arguments[0] = '" + where + "')";
  242.             if (what.length) what.forEach(validate)
  243.             else validate(what);
  244.             listArray.splice(where, 0, what);
  245.         }
  246.  
  247.         //Insert and InsertRange are the same physcially
  248.         this.InsertRange = this.Insert;
  249.  
  250.         // Method:  Sort
  251.         // Description:  Sorts the elements in the entire List using the specified comparer or genericSort
  252.         this.Sort = function (comparer) {
  253.             comparer = comparer || genericSort;
  254.             listArray.sort(comparer);
  255.         }
  256.  
  257.         // Method:  All
  258.         // Description:  returns true if all elements in the List match the given query
  259.         this.All = function (query) { return this.Count() === this.Where(query).Count(); }
  260.  
  261.         // Method:  Remove
  262.         // Description:  Removes occurrences of a specific object from the List or a given amount.
  263.         this.Remove = function (what/*, all, where, howMany */) {
  264.             //Determine arguments for logic
  265.             var all = arguments[1] || false,
  266.             where = arguments[2] || undefined,
  267.             howMany = arguments[3] || 1,
  268.             results = new List();
  269.             //Determine branch
  270.             if (where < 0 || where > listArray.length) throw "Invalid index parameter in call to List.Remove (arguments[3] = '" + where + "')";
  271.             if (!where && this.Contains(what)) {
  272.                 try { results.AddRange(listArray.splice($containsLastResult, howMany)); } catch (e) { throw e; }
  273.             }
  274.             else if (where) try { results.AddRange(listArray.splice(where, howMany)); } catch (e) { throw e; }
  275.             if (all && this.Contains(what)) try { results.AddRange(this.Remove(undefined, undefined, $containsLastResult)); } catch (E) { throw e; }
  276.             return results;
  277.         }
  278.  
  279.         // Method:  RemoveAt
  280.         // Description:  Removes the element at the specified index.
  281.         this.RemoveAt = function (index) {
  282.             return this.Remove(undefined, undefined, index, 1);
  283.         }
  284.  
  285.         // Method:  RemoveRange
  286.         // Description:  Removes a range of elements from the List starting at the given index count times.
  287.         this.RemoveRange = function (index, count) {
  288.             return this.Remove(undefined, undefined, index, count);
  289.         }
  290.  
  291.         // Method:  RemoveAll
  292.         // Description:  Removes all the elements that match the conditions defined by the specified predicate.
  293.         this.RemoveAll = function (query) {
  294.             //Run Where with the query to get results then pass the resulting List to ForEach on this instances Remove function
  295.             return this.Where(query).ForEach(this.Remove);
  296.         }
  297.  
  298.         // Method:  ElementAt
  299.         // Description:  Get the element at a specific index in the list.
  300.         this.ElementAt = function (index) {
  301.             if (index >= this.Count() || index < 0)
  302.                 throw "Invalid index parameter in call to List.ElementAt";
  303.             return listArray[index];
  304.         }
  305.  
  306.         // Method:  Where
  307.         // Description:  Return a copy of this List object with only the elements that meet the criteria
  308.         //               as defined by the 'query' parameter.
  309.         this.Where = function (query) { return select(query); }
  310.  
  311.         // Method:  FirstOrDefault
  312.         // Description:  Return the first object in the list that meets the 'query' criteria or null if no objects are found.        
  313.         this.FirstOrDefault = function (query/*, last*/) {
  314.             var list = select(query),
  315.             last = arguments[1] || false;
  316.             return list ? list.ElementAt(last ? list.Count() - 1 : 0) : null;
  317.         }
  318.  
  319.         // Method:  Count
  320.         // Description:  Return the number of elements in the list or optionally the number of elements which are equal to the object given
  321.         this.Count = function (what) {
  322.             if (!what) return listArray.length;
  323.             else {
  324.                 var count = 0;
  325.                 listArray.forEach(function (el) { if (el === what) ++count; });
  326.                 return count;
  327.             }
  328.         }
  329.  
  330.         //Method: Reverse
  331.         //Description: Reverses the list starting at the given index || 0 count times.
  332.         this.Reverse = function (index, count) {
  333.             index = index || 0;
  334.             count = count || listArray.length - 1;
  335.  
  336.             if (index < 0 || count > listArray.length) throw "Invalid index or count parameter in call to List.Reverse";
  337.  
  338.             for (; count > index; ++index, --count) {
  339.                 var temp = listArray[index];
  340.                 listArray[index] = listArray[count];
  341.                 listArray[count] = temp;
  342.             }
  343.  
  344.             return;
  345.         }
  346.  
  347.         //Method: ToArray
  348.         //Description: Copies the elements of the List to a new array.
  349.         this.ToArray = function () { return Array(listArray); }
  350.  
  351.         // Method:  OrderBy
  352.         // Description:  Order (ascending) the objects in the list by the given object property name.
  353.         this.OrderBy = function (property/*, desc*/) {
  354.             //Make the list and the interval array
  355.             var l = new List(listArray.slice(0).sort(genericSort(property))),
  356.             //Determine if we need to reverse
  357.             desc = arguments[1] || false;
  358.             if (desc) l.Reverse();
  359.             //return
  360.             return l;
  361.         }
  362.  
  363.         // Method:  OrderByDescending
  364.         // Description:  Order (descending) the objects in the list by the given object property name.
  365.         this.OrderByDescending = function (property) {
  366.             return this.OrderBy(property, true);
  367.         }
  368.  
  369.         //Storage pointer for the last result of Contains call
  370.         var $containsLastResult = undefined;
  371.         // Method: Contains
  372.         // Description: Determines if the given object is contained in the List
  373.         this.Contains = function (object, start) {
  374.             var contained = false,
  375.             keys = Object.keys(object);
  376.             start = start || 0;
  377.             //Iterate list
  378.             listArray.forEach(function (tEl) {
  379.                 //Iterate keys
  380.                 keys.forEach(function (key, index) {
  381.                     if (index < start) return;
  382.                     //Try to ascertain equality
  383.                     try {
  384.                         //contained is equal to the expression of tEl[key] being exactly equal to object[key]'s value
  385.                         if (contained = (tEl[key] === object[key])) $containsLastResult = index; //Store the last index if contained
  386.                         else $containsLastResult = -1;
  387.                     } catch (e) { contained = false; $containsLastResult = -1; }
  388.                 });
  389.             });
  390.             return contained;
  391.         }
  392.  
  393.         // Method:  IndexOf
  394.         // Description:  Returns the index of the specified element in this List or -1 if not found
  395.         this.IndexOf = function (what, start) {
  396.             try { return this.Contains(what, start || 0) ? $containsLastResult : -1; }
  397.             catch (e) { return false; }
  398.         }
  399.  
  400.         // Method:  LastIndexOf
  401.         // Description:  Returns the last index of the specified element in this List or -1 if not found
  402.         this.LastIndexOf = function (what, start, end) {
  403.             var lastIndex = -1;
  404.             start = start || 0;
  405.             end = end || listArray.length;
  406.             while (this.Contains(what, start++) && start < end) lastIndex = $containsLastResult;
  407.             return lastIndex;
  408.         }
  409.  
  410.         // Method: Distinct
  411.         // Description: Gets a copy of the list with only unique elements.
  412.         this.Distinct = function () {
  413.             var results = new List();
  414.             try {
  415.                 //this.ForEach(function (tEl) { if (!results.Contains(tEl)) results.Add(tEl); });
  416.                 //Equivelant to the following except the below is native
  417.                 listArray.forEach(function (tEl) { if (!results.Contains(tEl)) results.Add(tEl); });
  418.             } catch (E) { }
  419.             return results;
  420.         }
  421.  
  422.         // Method: ForEach
  423.         // Description: Calls the given query on each element of the List
  424.         this.ForEach = function (query, start, end) {
  425.             start = start || 0;
  426.             end = end || listArray.length - 1;
  427.             if (start < 0 || end > listArray.length) throw "Invalid start or end parameter in call to List.ForEach";
  428.             listArray.forEach(function (tEl, index) {
  429.                 if (start > index || end < index) return;
  430.                 with (tEl) query();
  431.             });
  432.         }
  433.  
  434.         // Method: TrueForAll
  435.         // Description: etermines whether every element in the List matches the conditions defined by the specified predicate.
  436.         this.TrueForAll = function (query) {
  437.             return this.Where(query).Count() === this.Count();
  438.             //this.ForEach(function () { if (!query()) return false; });
  439.             //return true;
  440.         }
  441.  
  442.         // Extension Methods
  443.         this.First = function () { return listArray[0]; }
  444.         this.Last = function () { return listArray[listArray.length - 1]; }
  445.  
  446.         // Method:  Any
  447.         // Description:  returns true on the first element in the List which meets the given query.
  448.         this.Any = function (query) {
  449.             var result = false;
  450.             try {
  451.                 listArray.forEach(function (tEl) {
  452.                     with (tEl)
  453.                         if (query()) {
  454.                             result = true;
  455.                             throw new Error();
  456.                         }
  457.                 });
  458.             } catch (E) { }
  459.             return result;
  460.         }
  461.  
  462.         // Method:  LastOrDefault
  463.         // Description:  Return the last object in the list that meets the 'query' criteria or null if no objects are found.
  464.         this.LastOrDefault = function (query) { return query ? this.FirstOrDefault(query, true) : null; }
  465.  
  466.         // Method:  Single
  467.         // Description:  Returns the first object in the list that meets the 'query' criteria or null if no objects are found.
  468.         this.Single = function (query) { return query ? this.FirstOrDefault(query) : null; }
  469.  
  470.         // Method:  Single
  471.         // Description:  Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements. The element's index is used in the logic of the predicate function.
  472.         this.SkipWhile = function (query, start) {
  473.             if (!query) return null;
  474.             start = start || 0;
  475.             if (start < 0 || start > listArray.length) throw "Invalid start parameter in call to List.SkipWhile";
  476.             var results = new List();
  477.             listArray.forEach(function (tEl, index) {
  478.                 if (index < start) return;
  479.                 with (tEl) if (!query()) results.Add(tEl);
  480.             });
  481.             return results;
  482.         }
  483.  
  484.         //Cleanup instance prototype
  485.         for (var p in this) if (!this.hasOwnProperty(p)) delete this.p;
  486.  
  487.         //Add event for destructor in executed closure
  488.         window.addEventListener('unload', function (self) { $List$Dispose(self, true); } (this));
  489.  
  490.         //freeze new instance
  491.         return Object.freeze(this);
  492.  
  493.     }
  494.  
  495.     //Method: indexOf
  496.     //Description adds logic to retrieve the index of an element from an array if present, otherwise -1
  497.     if (!Array.prototype.indexOf) {
  498.         Array.prototype.indexOf = function (elt /*, from*/) {
  499.             var len = this.length,
  500.             from = Number(arguments[1]) || 0;
  501.             from = (from < 0) ? Math.ceil(from) : Math.floor(from);
  502.             if (from < 0) from += len;
  503.             for (; from < len; from++)
  504.                 if (from in this && this[from] === elt)
  505.                     return from;
  506.             return -1;
  507.         };
  508.     }
  509.  
  510.     if (!Array.prototype.forEach) {
  511.         Array.prototype.forEach = function () { };
  512.     }
  513.  
  514.     if (!Object.keys) {
  515.         Object.keys = function (that) {
  516.             var results = [];
  517.             for (var p in that)
  518.                 if (that.hasOwnProperty(p))
  519.                     results.push(that.p);
  520.             return results;
  521.         };
  522.     }
  523.  
  524.     if (!Object.freeze) {
  525.         Object.freeze = function (object) { };
  526.     }
  527.  
  528.     //Export and Freeze List to the window namespace
  529.     Object.freeze(window.List = List);
  530.  
  531. })();
Advertisement
Add Comment
Please, Sign In to add comment