Sorting Numbers and Text in Javascript - javascript

I am using a jquery drop down table filter plug in for table filters:
https://github.com/rbayliss/Dropdown-Table-Filter
When I have a column in my table that is numerical however, the numbers are sorted as text, e.g. 1, 10, 100, 2, 20, 200 ...
in the code the sorter looks like:
if(options.sortOpt) {
opts.sort(options.sortOptCallback);
}
I think this is a recursive call to:
sortOptCallback: function(a, b) {
return a.text.toLowerCase() > b.text.toLowerCase();
},
how should I amend this so that it will sort numerical fields correctly? I have tried the following:
sortOptCallback: function (a, b) {
if (isNaN(parseInt(a)) || isNaN(parseInt(b))) {
return a.text.toLowerCase() > b.text.toLowerCase();
} else {
return a > b;
}
},
Thanks,

Your attempt is almost correct. The only problem is that you are still comparing the elements as strings after having determined that they are numeric. Furthermore, sort callbacks expect:
A positive number if a > b
A negative number if a < b
Zero if they are equal.
With this in mind, try this callback:
if( a == b) return 0;
var anum = parseInt(a,10);
var bnum = parseInt(b,10);
if( isNaN(anum) || isNaN(bnum)) {
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
}
return anum > bnum ? 1 : -1;
EDIT: #PaoloMoretti brought my attention to the fact that all your items are numerical. In that case, you can just do this:
return a-b;

Related

Custom alphanumberic sort with jQuery Tablesorter

I'm currently using jQuery Tablesorter for my project's tables, and I want to have it handle a custom sort so that:
A, AA, B, BB, 1, 2
Would sort as:
1, 2, A, B, AA, BB
I'm able to achieve this through the textSorter option during the initialization like so:
$(".tablesorter").tablesorter({
textSorter: {
3: function (a, b) {
return b.length > a.length ? -1 : b.length < a.length ? 1 : b.toString() > a.toString() ? -1 : 1;
}
}
}
Or more clearly:
if (b.length > a.length) {
return -1;
} else if (b.length < a.length) {
return 1;
} else {
if (b.toString() > a.toString()) {
return -1;
} else {
return 1;
}
}
But what I'd like to do is be able to have this be default sort when a classname is specified, similar to how I'm able to add sorter-floats as a clas to any th like this <th class='sorter-floats'>COL NAME</th> and use the addParser method to handle parsing the value by which to sort like this:
$.tablesorter.addParser({
id: "floats",
is: function (s) {
return false;
},
format: function (s) {
return $.tablesorter.formatFloat(s.replace(/,/g, ''));
},
type: "numeric"
});
I haven't been able to find any methods in the tablesorter plugin that allow custom sorting in this way. Is this possible, or is the approach I've already used my best option?
Tablesorter wasn't set up to allow the textSorter option to contain anything other than an overall function, or an object containing zero-based column indexes.
I just updated the master branch with a patch that will also allow adding a column class name (demo):
$(function() {
$("table").tablesorter({
theme: 'blue',
textSorter: {
'.sorter-float': function(a, b) {
return b.length > a.length ? -1 :
b.length < a.length ? 1 :
b.toString() > a.toString() ? -1 : 1;
}
}
});
});
I haven't decided when the next release will be available, but it won't be too long.

Sort Elements jQuery Plugin, random sorting

I am using this jQuery plugin to sort elements:
http://james.padolsey.com/javascript/sorting-elements-with-jquery/
It works well with this code:
function sortGallery(element, sorting) {
$('input.sort').removeClass('active');
$(element).addClass('active');
if (sorting === 'bydate') {
$('#gallery-js > div').sortElements(function(a, b){
return $(a).find('img').attr('data-date') < $(b).find('img').attr('data-date') ? 1 : -1;
});
} else if (sorting === 'random') {
console.log('TODO');
}
}
The problem is I do not understand the return statement. Can someone tell me how the return value for the "random" part should look like and if possible a short description how it works?
Alright, first you might want to read up on how Array#sort works.
In the documentation, they provide this code as an example as to how the sort order works
function compare(a, b) {
if (a is less than b by some ordering criterion)
return -1;
if (a is greater than b by the ordering criterion)
return 1;
// a must be equal to b
return 0;
}
Since you want to just order randomly, all we need to do is generate a random integer from -1 to 1
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
Then you simply call the method like this
} else if (sorting === 'random') {
return randomInt(-1, 1);
}

How can a sorted javascript array return incorrect values from the comparator?

Say I have a list of items, which are sorted using a given comparator. I would expect that after sorting into ascending order comparator(element[1], element[1+n]) should return -1 for all values of n> 1, because according to that comparator, element[1]
I am performing a custom sort and finding that after sorting there are instances where comparator(element[1], element[1+n]) returns 1. When I look at the instance I see that the comparator giving the correct output, i.e. element[1]>element[1+n]. I don't understand how this could be the case after performing a sort with that comparator.
If anyone has any idea of sort subtleties that I might have missed, I'd really appreciate their thoughts. Also, if I can provide more information that might shed light please let me know.
edit
I thought this might be a more general question, but in response to mplungjan have added the custom sorter below.
The sort is for a hierarchical dataset in the form of a flat list of objects. Each object has an id which might be as follows:
0 for root 1.
0-0 for its first child.
0-1 for its second child.
etc.
Each object in the list had a field 'parent' which has the id of its parent. Essentially data.sort isn't doing what I think it should be.
function CurrencyTreeSorter(a, b) {
a_id = a.id.split("-");
b_id = b.id.split("-");
if(a_id.length != b_id.length || a_id.slice(0, a_id.length-1).toString() != b_id.slice(0, b_id.length-1).toString()){
var i = 0;
while (i < a_id.length && i < b_id.length && a_id[i] == b_id[i]) {
i++;
}
if (i == a_id.length || i == b_id.length){
return a_id.length > b_id.length ? 1 : -1;
}
else{
while (a.level > i) {
a = getParent(dataMap, a);
}
while (b.level > i) {
b = getParent(dataMap, b);
}
}
}
var x, y;
if (a[sortcol] == "-") {
x = -1;
}
else {
x = parseFloat(a[sortcol].replace(/[,\$£€]/g, ""));
}
if (b[sortcol] == "-") {
y = -1;
}
else {
y = parseFloat(b[sortcol].replace(/[,\$£€]/g, ""));
}
return sortdir * (x == y ? 0 : (x > y ? 1 : -1));
}
This turned out to be an issue with Chrome, described here and here. Essentially it's not safe to return zero from the comparator/comparer.

javascript custom sort when parts of an array has no data

I have an object model that looks like this:
MyObject = {
TheName: "",
OtherProps :....
}
I have an array of these objects and my custom sort function looks like this:
function SortArray() {
var CustomFunction;
var CustomSortAsc = function (a, b) {
return a['TheName'] - b['TheName'];
}
var CustomSortDsc = function (a, b) {
return b['TheName'] - a['TheName'];
}
if (SomeCondition) {
CustomFunction = CustomSortAsc;
} else {
CustomFunction = CustomSortDsc;
}
SomeArray.sort(CustomFunction);
}
SomeArray has somewhere around 200-300 objects in it and the problem is that sometimes the object has an empty TheName property. Because of that, the sort doesn't seem to work as expected. For instance, if there are values then sort by name and then put all the values for which there are no names according to the sort function.
What's the way to make this work? Thanks for your suggestions.
As your TheNames are strings, you probably better use localeCompare instead:
var CustomSortAsc = function(a, b) {
return (a['TheName'] || '').localeCompare(b['TheName'] || '');
};
And I'll probably write it all like this:
var baseSort = function(a, b) {
return (a['TheName'] || '').localeCompare(b['TheName'] || '');
};
var CustomFunction = SomeCondition
? baseSort
: function(a, b) { return -baseSort(a, b); };
UPDATE: and if you need to see the empty values the last all the time...
var CustomSortAsc = function(a, b) {
return a['TheName'] === ''
? b['TheName'] === ''
? 0
: 1
: b['TheName'] === ''
? -1
: a['TheName'].localeCompare(b['TheName']);
};
... or, if you prefer if:
if (a['TheName'] === '') {
if (b['TheName'] === '') {
return 0;
} else {
return 1;
}
} else {
if (b['TheName'] === '') {
return -1;
} else {
return a['TheName'].localeCompare(b['TheName']);
}
}
(is this actually more readable?)
A custom sort function must return a number (<,= or > 0). If it returns anything else (like NaN), the behaviour is "implementation specific" (according to the standard), which means it usually breaks and just sorts nothing.
I'm not sure what you are trying to sort for ('TheName' sounds like strings, your function is for numbers), but you should just return 1 if the property does not exist on b and -1 if on a; then your property-less items will be sorted to the end of the array.

Sort javascript array so that blank values are always at bottom

So I have an array of arrays which contain only strings.
The array of arrays is to be displayed as a table and may have over 1000 rows with 20 or more values in each.
eg:
var arr = [
["bob","12","yes"],
["joe","","no"],
["tim","19","no"],
["dan","","yes"],
["tim","",""],
["dan","0",""]
]
the strings may contain anything that can be represented as a string, including: " ", "", "0" or "00-00-00" etc... and any column my be used for ordering.
I am sorting the arrays ascending and descending but some of the values I am sorting by are blank strings: "". How could I get the blank strings (only) to always be at the end of the new arrays in all modern browsers?
currently they are at the end when ascending but at the start when descending.
I am sorting like below (Yes I'm sure I can do it shorter too):
if (direction == "asc") {
SortedArr = arr.sort(function (a, b) {
if (a[colToSortBy] == '') {
return -1;
}
if (a[colToSortBy].toUpperCase() < b[colToSortBy].toUpperCase()) {
return -1;
}
if (a[colToSortBy].toUpperCase() > b[colToSortBy].toUpperCase()) {
return 1;
}
return 0;
});
} else {
SortedArr = arr.sort(function (a, b) {
if (a[colToSortBy] == '') {
return -1;
}
if (b[colToSortBy].toUpperCase() < a[colToSortBy].toUpperCase()) {
return -1;
}
if (b[colToSortBy].toUpperCase() > a[colToSortBy].toUpperCase()) {
return 1;
}
return 0;
});
}
Empty strings at the end
Working example on JSFiddle that puts empty strings always at the end no matter whether order is ascending or descending. This may be a usability issue, but this is the solution:
if (direction == "asc") {
SortedArr = arr.sort(function (a, b) {
return (a[col] || "|||").toUpperCase().localeCompare((b[col] || "|||").toUpperCase())
});
} else {
SortedArr = arr.sort(function (a, b) {
return (b[col] || "!!!").toUpperCase().localeCompare((a[col] || "!!!").toUpperCase())
});
}
I think your problem comes from the fact that you're checking if a[colToSortBy] is an emtpy string but you don't do it for b[colToSortBy].
I am writing an application that must work on IE, FF, Safari, Chrome, Opera, Desktop, Tablet (including iPad) & phones (including iPhone). This means I keep testing across browsers. I thus discovered that my sort procedure below wasn't working correctly in FF until I added the 'new section' part of the code. Reason being I did not take care of sorting when a numeric value is not supplied (dash, -). FF was also not working correctly with negative (-) values. This code now works perfectly across:
if (SortByID == 0) { //string values (Bank Name)
myValues.sort( function (a,b) {
var nameA = a[SortByID].toUpperCase(), nameB = b[SortByID].toUpperCase();
if (SortOrderID == 1) { //sort string ascending
if (nameA < nameB) { return -1; } else { if (nameA > nameB) { return 1; } }
} else { //sort string descending
if (nameA < nameB) { return 1; } else { if (nameA > nameB) { return -1; } }
}
return 0 //default return value (no sorting)
})
} else { //numeric values (Items)
myValues.sort(function (a, b) {
if (isNumber(a[SortByID]) && isNumber(b[SortByID])) { //
if (SortOrderID == 1) { //sort number ascending
return parseFloat(a[SortByID]) - parseFloat(b[SortByID]);
} else { //sort string descending
return parseFloat(b[SortByID]) - parseFloat(a[SortByID]);
}
} else { //one of the values is not numeric
//new section
if (!isNumber(a[SortByID])) {
if (SortOrderID == 1) { //sort number ascending
return -1;
} else { //sort number descending
return 1;
}
} else {
if (!isNumber(b[SortByID])) {
if (SortOrderID == 1) { //sort number ascending
return 1;
} else { //sort number descending
return -1;
}
}
}//New section
return 0;
}
})
}
I know it is long, but it is simple enough for me to understand. I hope it also addresses the browser issue raised by Chris J.
*isNumber is a simple function testing if value !isNAN
I used this way in my app...
You can tweak it to get the favorable result.
we also have the Number.MAX_SAFE_INTEGER
var arr = [10, "", 8, "", 89, 72]
var min = Number.MIN_SAFE_INTEGER
var sorted = arr.sort(function (a,b) {
return (a || min) - (b || min)
})
var cons = document.getElementById("console")
cons.innerText = "Ascending " + JSON.stringify(sorted) + "\n" + "Descending " + JSON.stringify(sorted.reverse())
<html>
<head></head>
<body>
<p id="console"></p>
</body>
</html>

Categories