I'm trying to make a HTML table sorting system in JavaScript. All I want is to sort each column by clicking on it, but I think I have to use regex to do so. You see, I want to sort numbers (0-9) first, then sort it alphabetically (a-z) then everything else (special characters) and last empty cells.
This is the order it has to go.
1
03
5
data
data1
-data
-data1
*empty cells*
*empty cells*
The code underneath can only sort the empty cells to the bottom (Code currently taken from Sorting HTML table with JavaScript):
var getCellValue = function(tr, idx) {
return tr.children[idx].innerText || tr.children[idx].textContent;
}
var comparer = function(idx, asc) {
return function(a, b) {
return function(v1, v2) {
if (v1 == "")
return 1;
if (v2 == "")
return -1;
return v1 != "" && v2 != "" && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2);
}(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
}
};
Array.prototype.slice.call(document.querySelectorAll("th.sortable")).forEach(function(th) {
th.addEventListener("click", function() {
var table = th.parentNode
var tbody = document.querySelector("tbody")
while (table.tagName.toUpperCase() != "TABLE") table = table.parentNode;
Array.prototype.slice.call(tbody.querySelectorAll("tr"))
.sort(comparer(Array.prototype.slice.call(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(function(tr) {
tbody.appendChild(tr)
});
})
});
Does anyone know a simple solution to my problem?
Related
I have list that contain various object. A few objects on this list have a date field (which basically is returned to me as a string from server, not a date object), while for others this field is null.
The requirement I have is to display objects without date at top, and those with date needs to be displayed after them sorted by date field.
Also for objects without date sorting needs to be done alphabetically.
Earlier I was using
$scope.lists.sort(function (a, b) {
return new Date(a.date.split("-")[2], a.date.split("-")[1], a.date.split("-")[0]) - new Date(b.date.split("-")[2], b.date.split("-")[1], b.date.split("-")[0]);
});
But now with null date fields this would not work. So unable to find anything, I wrote this logic:
{
var datelists=[];
var savelists =[];
$scope.lists.forEach(function (t) {
if (t.date !== null) {
datelists.push(t);
} else {
savelists.push(t);
}
});
datelists.sort(function (a, b) {
return new Date(a.date.split("-")[2], a.date.split("-")[1], a.date.split("-")[0]) - new Date(b.date.split("-")[2], b.date.split("-")[1], b.date.split("-")[0]);
});
savelists.sort(function (a, b) {
return a.name - b.name;
});
$scope.lists = [];
$scope.lists = savelists.concat(datelists);
}
I don't like this long method. I am sure there is an elegant way to do this.
I would like to know what other alternatives do I have?
To avoid splitting the arrays, sort the entire array on primary and secondary keys of date and text. Usingobj.date and obj.text as example property names holding date and sort text respectively:
function dateCompare( d, e)
{ // code to compare date strings
// return -1 for date d before e
// 0 for date d same as e
// +1 for date e before d
// algorithm depends on server date string format but could start
if( !d)
return e ? -1 : 0; // d is empty
if( !e)
return 1; // e is empty, d is not
// ... compare date strings
}
function textCompare(s, t)
{ // code to compare string values
// return -1 for s < t
// 0 for s == t
// +1 for t > s
// algorithms vary according to requirements.
}
function objCompare( a, b)
{ // return a non zero result of dateCompare, or the result of textCompare:
return dateCompare(a.date, b.date) || textCompare( a.text, b.text);
}
$scope.lists.sort( objCompare);
outlines how to go about it without getting into application specifics. Convert objCompare into an inline anonymous function with nested date and text comparison support functions (or inline the code) as required to match with existing programming style.
While sorting you must check if date is null, is undefined or is normal date (not tested)
.sort(function (a, b) {
// sort alphabetically
if (typeof a.date == 'undefined' && typeof b.date != 'undefined') {
return -1;
} else if (typeof a.date != 'undefined' && typeof b.date == 'undefined') {
return 1;
} else if (typeof a.date == 'undefined' && typeof b.date == 'undefined') {
return a.name.localeCompare(b.name);
// move null to top
} else if (a.date == null && b.date != null) {
return -1;
} else if (a.date != null && b.date == null) {
return 1;
} else if (a.date == null && b.date == null) {
return 0;
// both objects has date, sort by date.
} else {
var d1 = Date.parse(a.date);
var d2 = Date.parse(b.date);
return d1 - d2;
}
})
First, you can convert date to an ISO6801 date string.
If a date is supplied, or a falsy value, which is replaced by an empty string, you could use a String#localeCompare. This sorts empty strings to top.
If the date is the same or both have no date, then its sorted by name.
$scope.lists.sort(function (a, b) {
function date(s) {
return s.replace(/(\d{2})-(\d{2})-(\d{4})/g, '$3-$2-$1');
}
return (date(a.date) || '').localeCompare(date(b.date) || '') ||
a.name.localeCompare(b.name);
});
I have this function:
tests.sort(function (a, b) {
var diff = a.title.localeCompare(b.title);
return diff == 0 ? b.modifiedDate.localeCompare(a.modifiedDate) : diff;
});
I am using it to sort the tests array first by title and then by modifiedDate. The code was working but now I found it gives an error.
When the modifiedDate is null and when this happens the compare fails.
How could I make it so that if the modifiedDate is null then the sort still works and places those rows after the rows with a modifiedDate that's not null?
Thinking off the top of my head, to sort by title and then modifiedDate with nulls last:
tests.sort(function (a, b) {
var diff = a.title.localeCompare(b.title),
has_modifiedDate = a.modifiedDate && b.modifiedDate && true || false;
if (diff === 0) {
if (has_modifiedDate) {
return a.modifiedDate.localeCompare(b.modifiedDate)
}
else {
if (! b.modifiedDate){
if (! a.modifiedDate)
return 0;
else
return -1;
}
else {
if (! a.modifiedDate)
return 1;
else
return -1;
}
}
}
else
return diff;
});
Note: this is untested and it's very late/early here. If this is incorrect, post and I'll delete or update; but way to tired to think.
Quick dataset you can try testing with; fill in with whatever data you want:
var tests = [
{modifiedDate:'z', title:'foo'},
{modifiedDate:'a', title:'foo'},
{modifiedDate:null, title:'foo'},
{modifiedDate:'a', title:'foo'},
{modifiedDate:'null', title:'foo'},
{modifiedDate:'z', title:'foo'},
{modifiedDate:'z', title:'bar'},
{modifiedDate:'a', title:'bar'},
{modifiedDate:null, title:'bar'}
];
Try this:
tests.sort(function(a, b) {
var diff = a.title.localeCompare(b.title);
return diff == 0 ? (a.modifiedDate ? a.modifiedDate.getTime() : Infinity) - (b.modifiedDate ? b.modifiedDate.getTime() : Infinity) : diff;
})
I am trying to organize a observablearray that has inside 2 boolean values and a price. I need via knockout and 2 checkboxes, filter the elements by these two values. Also sort by price ( ascending and descending) the displayed values . I don't put any code because I'm new in knockout and I can't see the way to make these actions.
Appreciate someone who instructed me.
Simple answer, I tried with this, but making some changes on my personal viewModel to supply my needs. So, I make something like this:
self.elementsToShow = ko.pureComputed(function () {
// Represents a filtered and ordered list of elements
var recomend = self.showRecommended(); //chekbox 1
var special = self.showSpecial(); // checkbox2
var sorting = self.currentSortDirection(); //sort direction: price or rating //ascending or descending, represented by an observableArray with that conditions and the //selectedSortDirection
if (!recomend && !special) return self.myOservableArray().sort(function (a, b) {
//in case that no one of the checkboxes where selected but the sort direction was't by default
if (sorting.price != null) {
var fp = sorting.price ? -1 : 1;
ap = parseInt(a.price);
bp = parseInt(b.price);
return ap == bp ? 0 : (fp * (ap < bp ? -1 : 1));
}
else if (sorting.rate != null) {
var f = sorting.rate ? -1 : 1;
ar = parseFloat(a.rating);
br = parseFloat(b.rating);
return ar == br ? 0 : (f * (ar < br ? -1 : 1));
}
});
return ko.utils.arrayFilter(self.myOservableArray(), function (element) {
return (element.recommended != "0" && recomend) || (element.offer != "" && special); //some other conditions for the relection of the checkboxes in the observableArray
}).sort(function (a, b) {
if (sorting.price != null) {
var fs = sorting.price ? -1 : 1;
ap = a.price;
bp = b.price;
return ap == bp ? 0 : (fs * (ap < bp ? -1 : 1));
}
if (sorting.rate != null) {
var fu = sorting.rate ? -1 : 1;
ar = a.rating;
br = b.rating;
return ar == br ? 0 : (fu * (ar < br ? -1 : 1));
}
});
}, self);
I am trying to implement a custom sorting in slick grid. I am using this as a reference https://github.com/mleibman/SlickGrid/wiki/DataView#sorting. I want to force rows with specific properties to always sort to the bottom of the grid. (I tried to get a working example of slickgrid to work in jsfiddle.net but I couldn't.) I have tried this but it's not working.
var gridData =[
{ Id:1, Code: '232046', Depth2: 4000,},
{ Id:2, Code: '23247', Depth2: 2000 },
{ Id:3, Code: '12543', Depth2: 1500, rowoption_stickyorder: 1 }
];
grid.onSort.subscribe(function (e, args) {
var footerOrderProperty = 'rowoption_stickyorder';
var comparer = function (a, b) {
var result = (a[args.sortCol.field] > b[args.sortCol.field]) ? 1 : -1;
if (b[footerOrderProperty] != undefined || a[footerOrderProperty] != undefined)
result = -1;
return result;
}
dataview.sort(comparer, args.sortAsc);
});
What am I missing?
if (b[footerOrderProperty] != undefined || a[footerOrderProperty] != undefined)
result = -1;
Won't work. false || true places an object/row with the property above one that does not have it.
var bb = b[footerOrderProperty],
aa = a[footerOrderProperty];
if(aa && !bb){
result = 1;
}
else if(!aa && bb)
{
result = -1
}
See my fiddle
Note: Fiddle won't work in Chrome due to the mime type issue and the common workaround of making the resource rawgithub doesn't seem to be working.
Origineil, great work! Your suggestion got me 99% of the way there. I really want to have the columns sortable in both directions and keep the rows at the bottom. I modified your code to take in account the sortOrder and it works like a champ.
grid.onSort.subscribe(function (e, args) {
console.log(args.sortAsc)
var footerOrderProperty = 'rowoption_stickyorder';
var comparer = function (a, b) {
var result = (a[args.sortCol.field] > b[args.sortCol.field]) ? 1 : -1;
var bb = b[footerOrderProperty],
aa = a[footerOrderProperty];
if (aa && !bb) {
result = args.sortAsc ? 1 : -1;
} else if (!aa && bb) {
result = args.sortAsc ? -1 : 1;
} else if (aa != undefined && bb != undefined) {
result = (aa > bb) ? 1 : -1;
result *= args.sortAsc ? 1 : -1
}
console.log(a.id + " | " + b.id + " " + result)
return result
}
dataView.sort(comparer, args.sortAsc);
});
Here is the updated fiddle.
I'm trying to sort an array of values that can be a mixture of numeric or string values (e.g. [10,"20",null,"1","bar","-2",-3,null,5,"foo"]). How can I sort this array such that
null values are always placed last (regardless of sorting order, see jsFiddle)
negative numbers are sorted correctly (i.e. they are less than positive numbers and sort correctly amongst themselves)
? I made a jsFiddle with detailed numeric and string examples (using localeCompare and the numeric option), but will paste the numeric version of my sorting algorithm below as a starting point.
// Sorting order
var order = "asc"; // Try switching between "asc" and "dsc"
// Dummy arrays
var numericArr = [10,20,null,1,-2,-3,null,5];
// Sort arrays
$(".output1").append(numericArr.toString());
numericArr.sort(sortByDataNumeric);
$(".output2").append(numericArr.toString());
// Numeric sorting function
function sortByDataNumeric(a, b, _order) {
// Replace internal parameters if not used
if (_order == null) _order = order;
// If values are null, place them at the end
var dflt = (_order == "asc" ? Number.MAX_VALUE : -Number.MAX_VALUE);
// Numeric values
var aVal = (a == null ? dflt : a);
var bVal = (b == null ? dflt : b);
return _order == "asc" ? (aVal - bVal) : (bVal - aVal);
}
The problem with my string sorting algorithm (see jsFiddle) is that I can't find a way to always place null values last and negative values aren't correctly sorted within themselves (e.g. -3 should be less than -2)
Edit
To answer the comments, I expect [10,"20",null,"1","bar","-2",-3,null,5,"foo"] to sort to [-3,"-2","1",5,10,"20","bar","foo",null,null]
You should first check to see if either value is null and return the opposite value.
On a side note:
For your default _order value, you should check if the parameter is undefined instead of comparing its value to null. If you try to compare something that is undefined directly you will get a reference error:
(undefinedVar == null) // ReferenceError: undefinedVar is not defined
Instead, you should check if the variable is undefined:
(typeof undefinedVar == "undefined") // true
Also, it's probably a better idea to wrap your compare function in a closure instead of relying on a global order variable.
Sometime like:
[].sort(function(a, b){ return sort(a, b, order)})
This way you can sort at a per-instance level.
http://jsfiddle.net/gxFGN/10/
JavaScript
function sort(a, b, asc) {
var result;
/* Default ascending order */
if (typeof asc == "undefined") asc = true;
if (a === null) return 1;
if (b === null) return -1;
if (a === null && b === null) return 0;
result = a - b;
if (isNaN(result)) {
return (asc) ? a.toString().localeCompare(b) : b.toString().localeCompare(a);
}
else {
return (asc) ? result : -result;
}
}
function sortByDataString(a, b) {
if (a === null) {
return 1;
}
if (b === null) {
return -1;
}
if (isNumber(a) && isNumber(b)) {
if (parseInt(a,10) === parseInt(b,10)) {
return 0;
}
return parseInt(a,10) > parseInt(b,10) ? 1 : -1;
}
if (isNumber(a)) {
return -1;
}
if (isNumber(b)) {
return 1;
}
if (a === b) {
return 0;
}
return a > b ? 1 : -1;
}
fiddle here: http://jsfiddle.net/gxFGN/6/
I left out the order parameter, but you could always reverse the array at the end if needed.
Use this:
function typeOrder(x) {
if (x == null)
return 2;
if (isNaN(+x))
return 1;
return 0;
}
function sortNumber(a, b) {
a = parseInt(a, 10); b = parseInt(b, 10);
if (isNaN(a) || isNaN(b))
return 0;
return a - b;
}
function sortString(a, b) {
if (typeof a != "string" || typeof b != "string")
return 0;
return +(a > b) || -(b > a);
}
order = order == "dsc" ? -1 : 1;
numericArr.sort(function(a, b) {
return order * ( typeOrder(a)-typeOrder(b)
|| sortNumber(a, b)
|| sortString(a, b)
);
});
(updated fiddle)
I'm pretty sure that your problem is a red herring... the abstract function that you past into sort doesn't get a third parameter (in your case _order). So in your situation that's always going to be undefined.
Please reconsider your code with that in mind and see what you get.
The array you specify is entirely Numeric so your sort should work correctly, though as other commenters have suggested, if your array ever winds up with string values (i.e. "10", "-7" etc) you'll want to parseInt and test for isNaN before doing your comparison.