I was given a requirement to sort a javascript object by multipl properties. I needed to sort a list of vehicles by year, then make, then model, then mileage. With help from my super javascript guru friends... Here is something.
Here is the js source. and modifications below..
//----------------------------------------------------------------------- // Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com // Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved. // This project is licensed under the Microsoft Reciprocal License (Ms-RL) // This license can be found here: http://jslinq.codeplex.com/license //----------------------------------------------------------------------- (function () { JSLINQ = window.JSLINQ = function (dataItems) { return new JSLINQ.fn.init(dataItems); }; var getType = function (obj) { return typeof (obj); // o.prototype.toString.call(obj).replace('object ', ''); }; var _undefined; JSLINQ.fn = JSLINQ.prototype = { init: function (dataItems) { this.items = dataItems; }, // The current version of JSLINQ being used jslinq: "2.10", ToArray: function () { return this.items; }, Where: function (clause) { var item; var newArray = new Array(); // The clause was passed in as a Method that return a Boolean for (var index = 0; index < this.items.length; index++) { if (clause(this.items[index], index)) { newArray[newArray.length] = this.items[index]; } } return new JSLINQ(newArray); }, Select: function (clause) { var item; var newArray = new Array(); // The clause was passed in as a Method that returns a Value for (var i = 0; i < this.items.length; i++) { if (clause(this.items[i])) { newArray[newArray.length] = clause(this.items[i]); } } return new JSLINQ(newArray); }, OrderBy: function (clause) { var tempArray = new Array(); for (var i = 0; i < this.items.length; i++) { tempArray[tempArray.length] = this.items[i]; } return new JSLINQ( tempArray.sort(function (a, b) { var x = clause(a); var y = clause(b); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }) ); }, OrderByDescending: function (clause) { var tempArray = new Array(); for (var i = 0; i < this.items.length; i++) { tempArray[tempArray.length] = this.items[i]; } return new JSLINQ( tempArray.sort(function (a, b) { var x = clause(b); var y = clause(a); return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }) ); }, SelectMany: function (clause) { var r = new Array(); for (var i = 0; i < this.items.length; i++) { r = r.concat(clause(this.items[i])); } return new JSLINQ(r); }, Count: function (clause) { if (clause == null) return this.items.length; else return this.Where(clause).items.length; }, Distinct: function (clause) { var item; var dict = new Object(); var retVal = new Array(); for (var i = 0; i < this.items.length; i++) { item = clause(this.items[i]); // TODO - This doens't correctly compare Objects. Need to fix this if (dict[item] == null) { dict[item] = true; retVal[retVal.length] = item; } } dict = null; return new JSLINQ(retVal); }, Any: function (clause) { for (var index = 0; index < this.items.length; index++) { if (clause(this.items[index], index)) { return true; } } return false; }, All: function (clause) { for (var index = 0; index < this.items.length; index++) { if (!clause(this.items[index], index)) { return false; } } return true; }, Reverse: function () { var retVal = new Array(); for (var index = this.items.length - 1; index > -1; index--) retVal[retVal.length] = this.items[index]; return new JSLINQ(retVal); }, First: function (clause) { if (clause != null) { return this.Where(clause).First(); } else { // If no clause was specified, then return the First element in the Array if (this.items.length > 0) return this.items[0]; else return null; } }, Last: function (clause) { if (clause != null) { return this.Where(clause).Last(); } else { // If no clause was specified, then return the First element in the Array if (this.items.length > 0) return this.items[this.items.length - 1]; else return null; } }, ElementAt: function (index) { return this.items[index]; }, Concat: function (array) { var arr = array.items || array; return new JSLINQ(this.items.concat(arr)); }, Intersect: function (secondArray, clause) { var clauseMethod; if (clause != undefined) { clauseMethod = clause; } else { clauseMethod = function (item, index, item2, index2) { return item == item2; }; } var sa = secondArray.items || secondArray; var result = new Array(); for (var a = 0; a < this.items.length; a++) { for (var b = 0; b < sa.length; b++) { if (clauseMethod(this.items[a], a, sa[b], b)) { result[result.length] = this.items[a]; } } } return new JSLINQ(result); }, DefaultIfEmpty: function (defaultValue) { if (this.items.length == 0) { return defaultValue; } return this; }, ElementAtOrDefault: function (index, defaultValue) { if (index >= 0 && index < this.items.length) { return this.items[index]; } return defaultValue; }, FirstOrDefault: function (defaultValue) { return this.First() || defaultValue; }, LastOrDefault: function (defaultValue) { return this.Last() || defaultValue; }, Range: function (from, to) { return JSLINQ(this.items.slice(from, to)); }, //sort options = [{Prop:'SortProperty',IsAsc:true},ect....] OrderBySortOptions: function (sortOptions) { var sortFunc = function (a, b) { var i, result; for (i = 0; i < sortOptions.length; i++) { var x, y, currentOption; currentOption = sortOptions[i]; if (currentOption.IsAsc) { x = a[currentOption.Prop]; y = b[currentOption.Prop]; } else { x = b[currentOption.Prop]; y = a[currentOption.Prop]; } if (x !== y) { if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) {//sort numeric result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else {//sort alphanumeric result = ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return result; } } return 0; }; return JSLINQ(this.items.sort(sortFunc)); }, Min: function (prop) { var arr = (prop === _undefined) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items; return JSLINQ(arr).OrderBy(function (x) { return x; }).First(); }, Max: function (prop) { var arr = (prop === _undefined) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items; return JSLINQ(arr).OrderBy(function (x) { return x; }).Last(); }, Average: function (prop, numberOfDecimalPlaces) { var arr = (prop === _undefined || prop === null) ? this.items : JSLINQ(this.items).Select(function (x) { return x[prop]; }).items; var result = 0; var sum = 0; var count = 0; for (var i = 0; i < arr.length; i += 1) { if (typeof (arr[i]) === typeof (1)) { sum += arr[i]; count += 1; } } result = (sum / count); if (numberOfDecimalPlaces !== _undefined) { if (typeof (numberOfDecimalPlaces) === typeof (1)) { result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces); } } return result; } }; JSLINQ.fn.init.prototype = JSLINQ.fn; })();Here is the Typescript version!! With a couple new methods as well.
//----------------------------------------------------------------------- // Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com // Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved. // This project is licensed under the Microsoft Reciprocal License (Ms-RL) // This license can be found here: http://jslinq.codeplex.com/license //----------------------------------------------------------------------- //SortOption is used to sort an array by multiple item properties //Example: Sort by Age then by Name where two records with the same Age value would sub sort by Name asc or class SortOption { propSelector: (item: any) => any; isAsc: bool; constructor(propSelector: (item: any) => any, isAsc: bool) { this.propSelector = propSelector; this.isAsc = isAsc; } } class JLinq { items: any[]; constructor(list: any[]) { this.items = []; for (var i = 0; i < list.length; i++) { this.items.push(list[i]); } } where = function (clause: (item: any) => bool): JLinq { var result = []; for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; if (clause(item)) { result.push(item); } } return new JLinq(result); }; //This does not compare complex objects properly, its fine for 1 dimensional arrays distinct = function (clause: (item: any) => any): JLinq { var obj = {}; var result = []; for (var i = 0; i < this.items.length; i++) { var item = clause(this.items[i]); if (obj[item] == null) { obj[item] = true; result.push(item); } } return new JLinq(result); }; orderBy = function (clause: (item: any) => any): JLinq { var sortFunc = function (a, b) { var x = clause(a); var y = clause(b); if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) { return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else {//sort alphanumeric return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return 0; }; return new JLinq(this.items.sort(sortFunc)); }; orderByDescending = function (clause: (item: any) => any): JLinq { var sortFunc = function (a, b) { var x = clause(b); var y = clause(a); if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) { return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else {//sort alphanumeric return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return 0; }; return new JLinq(this.items.sort(sortFunc)); }; orderBySortOptions = function (sortOptions: SortOption[]): JLinq { var sortFunc = function (a, b) { var result = 0; for (var i = 0; i < sortOptions.length; i++) { var x; var y; var currentOption = sortOptions[i]; if (currentOption.isAsc) { x = currentOption.propSelector(a); y = currentOption.propSelector(b); } else { x = currentOption.propSelector(b); y = currentOption.propSelector(a); } if (x !== y) { if (typeof (x) === typeof (1) && typeof (y) === typeof (1)) { //sort numeric result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else { //sort alphanumeric result = ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return result; } } return result; }; return new JLinq(this.items.sort(sortFunc)); }; any = function (clause?: (item: any) => bool): bool { if (clause != null) { return this.where(clause).any(); } else { return this.items.length > 0; } }; average = function (clause: (item: any) => number, numberOfDecimalPlaces: number): number { var result = 0; if (clause != null) { var jl = new JLinq(this.select(clause).items); var numAry = jl.items; var sum = jl.sum(function (x) { return x; }); var count = numAry.length; result = (sum / count); result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces); } return result; }; select = function (clause: (item: any) => any): JLinq { var result = []; for (var i = 0; i < this.items.length; i++) { result.push(clause(this.items[i])); } return new JLinq(result); }; first = function (clause?: (item: any) => bool): any { if (clause != null) { return this.where(clause).first(null); } else { return (this.items.length > 0 ? this.items[0] : null); } }; last = function (clause?: (item: any) => bool): any { if (clause != null) { return this.where(clause).last(); } else { return (this.items.length > 0 ? this.items[this.items.length - 1] : null); } }; min = function (clause: (item: any) => number): number { var result = this.select(clause).orderBy(function (x) { return x; }).first(); return result; }; max = function (clause: (item: any) => number): number { var result = this.select(clause).orderBy(function (x) { return x; }).last(); return result; }; sum = function (clause: (item: any) => number): number { var ary = this.select(clause).items; var sum = 0; for (var i in ary) { sum += ary[i]; } return sum; }; }
Here is the rendered javascript ready to paste
//----------------------------------------------------------------------- // Part of the LINQ to JavaScript (JSLINQ) v2.10 Project - http://jslinq.codeplex.com // Copyright (C) 2009 Chris Pietschmann (http://pietschsoft.com). All rights reserved. // This project is licensed under the Microsoft Reciprocal License (Ms-RL) // This license can be found here: http://jslinq.codeplex.com/license //----------------------------------------------------------------------- //SortOption is used to sort an array by multiple item properties //Example: Sort by Age then by Name where two records with the same Age value would sub sort by Name asc or var SortOption = (function () { function SortOption(propSelector, isAsc) { this.propSelector = propSelector; this.isAsc = isAsc; } return SortOption; })(); var JLinq = (function () { function JLinq(list) { this.where = function (clause) { var result = []; for(var i = 0; i < this.items.length; i++) { var item = this.items[i]; if(clause(item)) { result.push(item); } } return new JLinq(result); }; //This does not compare complex objects properly, its fine for 1 dimensional arrays this.distinct = function (clause) { var obj = { }; var result = []; for(var i = 0; i < this.items.length; i++) { var item = clause(this.items[i]); if(obj[item] == null) { obj[item] = true; result.push(item); } } return new JLinq(result); }; this.orderBy = function (clause) { var sortFunc = function (a, b) { var x = clause(a); var y = clause(b); if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) { return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else { //sort alphanumeric return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return 0; }; return new JLinq(this.items.sort(sortFunc)); }; this.orderByDescending = function (clause) { var sortFunc = function (a, b) { var x = clause(b); var y = clause(a); if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) { return ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else { //sort alphanumeric return ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return 0; }; return new JLinq(this.items.sort(sortFunc)); }; this.orderBySortOptions = function (sortOptions) { var sortFunc = function (a, b) { var result = 0; for(var i = 0; i < sortOptions.length; i++) { var x; var y; var currentOption = sortOptions[i]; if(currentOption.isAsc) { x = currentOption.propSelector(a); y = currentOption.propSelector(b); } else { x = currentOption.propSelector(b); y = currentOption.propSelector(a); } if(x !== y) { if(typeof (x) === typeof (1) && typeof (y) === typeof (1)) { //sort numeric result = ((x - y) < 0) ? -1 : ((x - y) > 0) ? 1 : 0; } else { //sort alphanumeric result = ((x < y) ? -1 : ((x > y) ? 1 : 0)); } return result; } } return result; }; return new JLinq(this.items.sort(sortFunc)); }; this.any = function (clause) { if(clause != null) { return this.where(clause).any(); } else { return this.items.length > 0; } }; this.average = function (clause, numberOfDecimalPlaces) { var result = 0; if(clause != null) { var jl = new JLinq(this.select(clause).items); var numAry = jl.items; var sum = jl.sum(function (x) { return x; }); var count = numAry.length; result = (sum / count); result = Math.round(result * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces); } return result; }; this.select = function (clause) { var result = []; for(var i = 0; i < this.items.length; i++) { result.push(clause(this.items[i])); } return new JLinq(result); }; this.first = function (clause) { if(clause != null) { return this.where(clause).first(null); } else { return (this.items.length > 0 ? this.items[0] : null); } }; this.last = function (clause) { if(clause != null) { return this.where(clause).last(); } else { return (this.items.length > 0 ? this.items[this.items.length - 1] : null); } }; this.min = function (clause) { var result = this.select(clause).orderBy(function (x) { return x; }).first(); return result; }; this.max = function (clause) { var result = this.select(clause).orderBy(function (x) { return x; }).last(); return result; }; this.sum = function (clause) { var ary = this.select(clause).items; var sum = 0; for(var i in ary) { sum += ary[i]; } return sum; }; this.items = []; for(var i = 0; i < list.length; i++) { this.items.push(list[i]); } } return JLinq; })();