I'm populating a few drop downs from some JSON, it works as expected at the moment but i'm trying to sort the values in the drop downs. For all my drop downs it works fine, except for one of them which has numbers in, in which case it lists it 1, 10, 12, 2.
Any ideas how i can keep it sorting alphabetically for everything else but get the sort to work with the numeric values too?
Here's my JS (this replicates for each field - probably should try to find a way to make this reuseable):
var populateGenres = _.map(data.genres, function (val) {
return '<option>' + val + '</option>';
}).join();
var target = '#genreField';
$('#genreField').html(populateGenres);
reArrangeSelect(target);
Here's the sort JS:
function reArrangeSelect(target) {
$(target).html($(target + " option").sort(function(a, b) {
return a.text == b.text ? 0 : a.text < b.text ? -1 : 1
}))
}
My HTML is in this format:
<td>
<select id="genreField" class="form-control"></select>
</td>
<td>
<select id="authorField" class="form-control"></select>
</td>
function reArrangeSelect(target) {
$(target).html($(target + " option").sort(function(a, b) {
// u can use that 'getValue' function
// for "a.text == 'some string'" it will return 'some string' (string type),
// for "a.text == '10'" it will return 10 (number type)
var aVal = getValue(a.text);
var bVal = getValue(b.text);
return aVal == bVal ? 0 : aVal < bVal ? -1 : 1;
}));
}
function getValue(val) {
var asNumber = parseFloat(val);
return isNaN(asNumber) ? val : asNumber;
}
You can sort data.genres before running it through the _.map functions:
data.genres.sort(function (a, b) {
a = parseInt(a, 10);
b = parseInt(b, 10);
if(a > b) {
return 1;
} else if(a < b) {
return -1;
} else {
return 0;
}
});
Once you sort the data then run your _.map snippet:
var populateGenres = _.map(data.genres, function (val) {
return '<option>' + val + '</option>';
}).join();
All of your options should now be sorted before you append them to the <select>.
DEMO
Related
I have an Array that I need to sort exactly like using order by in Oracle SQl.
If I have following Array:
var array = ['Ba12nes','Apfel','Banane','banane','abc','ABC','123','2', null,'ba998ne']
var.sort(compare);
I would like to have the following result
var array = ['abc','ABC','Apfel','banane','Banane','Ba12nes','ba998ne','123','2', null]
If the null values are somewhere else, I don't have a Problem with it.
My current solution, which does not help me ^^
function compare(a,b) {
if(a == null)
return -1;
if (b == null)
return 1;
if (a.toLowerCase() < b.toLowerCase())
return -1;
if (a.toLowerCase() > b.toLowerCase())
return 1;
return 0;
}
I do understand that i need a custom sorting function. And at the moment I am thinking that only a regular expression can solve the problem of sorting the string values in front of the numbers. But I am still not sure how to solve the Problem with lowercase letters in bevor Uppercase letters.
Iirc, Oracle implements a 3-tiered lexicographic sorting (but heed the advice of Alex Poole and check the NLS settings first):
First sort by base characters ignoring case and diacritics, digits come after letters in the collation sequence.
Second, on ties sort respecting diacritics, ignoring case.
Third, on ties sort by case.
You can emulate the behavior using javascript locale apis by mimicking each step in turn in a custom compare function, with the exception of the letter-digit inversion in the collation sequence.
Tackle the latter by identifying 10 contiguous code points that do not represent digits and that lie beyond the set of code points that may occur in the strings you are sorting. Map digits onto the the chosen code point range preserving order. When you sort, specify the Unicode collating extension 'direct' which means 'sorting by code point'. Remap after sorting.
In the PoC code below I have chosen some cyrillic characters.
function cmptiered(a,b) {
//
// aka oracle sort
//
return lc_base.compare(a, b) || lc_accent.compare(a, b) || lc_case.compare(a, b);
} // cmptiered
var lc_accent = new Intl.Collator('de', { sensitivity: 'accent' });
var lc_base = new Intl.Collator('de-DE-u-co-direct', { sensitivity: 'base' });
var lc_case = new Intl.Collator('de', { caseFirst: 'lower', sensitivity: 'variant' });
var array = ['Ba12nes','Apfel','Banane','banane','abc','ABC','123','2', null, 'ba998ne' ];
// Map onto substitute code blocks
array = array.map ( function ( item ) { return (item === null) ? null : item.replace ( /[0-9]/g, function (c) { return String.fromCharCode(c.charCodeAt(0) - "0".charCodeAt(0) + "\u0430".charCodeAt(0)); } ); } );
array.sort(cmptiered);
// Remap substitute code point
array = array.map ( function ( item ) { return (item === null) ? null : item.replace ( /[\u0430-\u0439]/g, function (c) { return String.fromCharCode(c.charCodeAt(0) - "\u0430".charCodeAt(0) + "0".charCodeAt(0)); } ); } );
Edit
Function cmptiered streamlined following Nina Scholz' comment.
This proposals feature sorting without use of Intl.Collator. The first solution works with direct sort and comparing the given values.
var array = ['Ba12nes', 'Apfel', 'Banane', 'banane', 'abc', 'ABC', '123', '2', null, 'ba998ne'];
array.sort(function (a, b) {
var i = 0;
if (a === null && b === null) { return 0; }
if (a === null) { return 1; }
if (b === null) { return -1; }
while (i < a.length && i < b.length && a[i].toLocaleLowerCase() === b[i].toLocaleLowerCase()) {
i++;
}
if (isFinite(a[i]) && isFinite(b[i])) { return a[i] - b[i]; }
if (isFinite(a[i])) { return 1; }
if (isFinite(b[i])) { return -1; }
return a.localeCompare(b);
});
document.write(JSON.stringify(array));
The second solution features a different approach, based on Sorting with map and a custom sort scheme which takes a new string. The string is build by this rules:
If the value is null take the string 'null'.
If a character is a decimal, takes the character with space paddded around, eg. if it is 9 take the string ' 9 '.
Otherwise for every other character take two spaces and the character itself, like ' o'.
The new build string is used with a a.value.localeCompare(b.value).
Here are the strings with the mapped values:
' B a 1 2 n e s'
' A p f e l'
' B a n a n e'
' b a n a n e'
' a b c'
' A B C'
' 1 2 3 '
' 2 '
'null'
' b a 9 9 8 n e'
sorted, it became
' a b c'
' A B C'
' A p f e l'
' b a n a n e'
' B a n a n e'
' B a 1 2 n e s'
' b a 9 9 8 n e'
' 1 2 3 '
' 2 '
'null'
var array = ['Ba12nes', 'Apfel', 'Banane', 'banane', 'abc', 'ABC', '123', '2', null, 'ba998ne'],
mapped = array.map(function (el, i) {
var j, o = { index: i, value: '' };
if (el === null) {
o.value = 'null';
return o;
}
for (j = 0; j < el.length; j++) {
o.value += /\d/.test(el[j]) ? ' ' + el[j] + ' ' : ' ' + el[j];
}
return o;
});
mapped.sort(function (a, b) {
return a.value.localeCompare(b.value);
});
var result = mapped.map(function (el) {
return array[el.index];
});
document.write(JSON.stringify(result));
A simple head on solution that works at least for english & russian (mimicking NLS_SORT=RUSSIAN) and doesn't rely on fancy things like Intl.Collator, locales and options that don't exist for IE<11.
function compareStringOracle(str1, str2) {
if (str1 == null && str2 != null)
return 1;
else if (str1 != null && str2 == null)
return -1;
else if (str1 == null && str2 == null)
return 0;
else {
return compareStringCaseInsensitiveDigitsLast(str1, str2) ||
/* upper case wins between otherwise equal values, which can be checked with
a simple binary comparison (at least for eng & rus) */
((str1 < str2) ? -1 : (str1 > str2) ? 1 : 0);
}
}
function compareStringCaseInsensitiveDigitsLast(str1, str2) {
for (var i = 0; i < str1.length; ++i) {
if (i === str2.length)
return 1;
// toLocaleLowerCase is unnecessary for eng & rus
var c1 = str1.charAt(i).toLowerCase();
var c2 = str2.charAt(i).toLowerCase();
var d1 = "0" <= c1 && c1 <= "9";
var d2 = "0" <= c2 && c2 <= "9";
if (!d1 && d2)
return -1;
else if (d1 && !d2)
return 1;
else if (c1 !== c2)
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
if (str1.length < str2.length)
return -1;
else
return 0;
}
I am using Angular DataTables within my app. So far everything works well, except when I try to add custom sorting.
I have a set of data that returns a hyphen, "-" if there is no data. Here is my sorting function::
$.fn.dataTableExt.oSort['nullable-asc'] = function(a,b) {
if (a == '-')
return 1;
else if (b == '-')
return -1;
else
{
var ia = parseInt(a);
var ib = parseInt(b);
return (ia<ib) ? -1 : ((ia > ib) ? 1 : 0);
}
}
$.fn.dataTableExt.oSort['nullable-desc'] = function(a,b) {
console.log(a,b)
if (a == '-')
return 1;
else if (b == '-')
return -1;
else
{
var ia = parseInt(a);
var ib = parseInt(b);
return (ia>ib) ? -1 : ((ia < ib) ? 1 : 0);
}
}
..start controller...
vm.dtColumnDefs = [
DTColumnDefBuilder.newColumnDef(0).notSortable().withClass('hidden-print')
DTColumnDefBuilder.newColumnDef(1).withClass('td-fieldname'), //name
DTColumnDefBuilder.newColumnDef(2), //fieldType
DTColumnDefBuilder.newColumnDef(3).withOption('type', 'html-num-fmt'),
DTColumnDefBuilder.newColumnDef(4).notSortable(), //distribution
DTColumnDefBuilder.newColumnDef(5).withOption('type', 'html-num-fmt'), //cardinality
DTColumnDefBuilder.newColumnDef(6).withOption('type', 'nullable'), //Min
DTColumnDefBuilder.newColumnDef(7).withOption('type', 'nullable') //Max
];
...other angular code.....
This works just fine when tried on a standard table, but it doesn't play nice with the Angular app.
When I click on the heading of the table to sort - nothing happens. console.log above yields blank values. Is there a way to use custom sorting with the Angular directive?
I am using jquery datatable to display data. I display '--' when there is no data. Currently when the table sorts the data all the '--' comes in the beginning and the order looks like below:
--
--
10
20
400
800
But I need to make '--' to be displayed last when sorted in ascending order and should look something like below:
10
20
400
800
--
--
Please let me know how can we get this behavior in jquery datatable?
you can use an exstension
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
"myorder-pre": function (a) {
},
"myorder-asc": function (a, b) {
if(a == '--' && b != '--')
return 1;
else if(b == '--' && a != '--')
return -1;
else if(b == '--'&& a == '--')
return 0;
else
{
a = parseFloat(a);
b = parseFloat(b);
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
}
},
"myorder-desc": function (a, b) {
if(a == '--' && b != '--')
return -1;
else if(b == '--' && a != '--')
return 1;
else if(b == '--'&& a == '--')
return 0;
else
{
a = parseFloat(a);
b = parseFloat(b);
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
}
}
});
myorder-pre is used before all the order call.
myorder-asc when you order asc. Return number negative if a minor b, positive if a major b, 0 if equal.
Desc work adverse
then in the definition of columns of datatable, use
"aoColumnDefs": [{ "sType": 'myorder'}]
You can make use of the following code :
$('#example').dataTable( {
"aaSorting": [[ 4, "desc" ]]
} );
For the reference
I would like to know if it's possible to change the data type for a column. For instance, the json data passed to the grid are strings, but I would like slickgrid to consider it as integers or floats to be able to sort it correctly.
var data = [{"NOM": "Saguenay - Lac-Saint-Jean", "CODE": "02", "id": "0", "integer": "1"},]
I would like the 'integer' column to be an int not a string, without changing the data itself.
Thank you for your help.
As I mentioned in my comment, you are looking at the wrong place (no offense); there is no need to change datatype as actually this will not fix your problem with sort, since the SlickGrid default sort is string sort. But you could use custom sort to fix your problem.
So here is the solution: Define sort function and use them as needed. Here is a list of custom sort functions you could create:
function sorterStringCompare(a, b) {
var x = a[sortcol], y = b[sortcol];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterNumeric(a, b) {
var x = (isNaN(a[sortcol]) || a[sortcol] === "" || a[sortcol] === null) ? -99e+10 : parseFloat(a[sortcol]);
var y = (isNaN(b[sortcol]) || b[sortcol] === "" || b[sortcol] === null) ? -99e+10 : parseFloat(b[sortcol]);
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterRating(a, b) {
var xrow = a[sortcol], yrow = b[sortcol];
var x = xrow[3], y = yrow[3];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterDateIso(a, b) {
var regex_a = new RegExp("^((19[1-9][1-9])|([2][01][0-9]))\\d-([0]\\d|[1][0-2])-([0-2]\\d|[3][0-1])(\\s([0]\\d|[1][0-2])(\\:[0-5]\\d){1,2}(\\:[0-5]\\d){1,2})?$", "gi");
var regex_b = new RegExp("^((19[1-9][1-9])|([2][01][0-9]))\\d-([0]\\d|[1][0-2])-([0-2]\\d|[3][0-1])(\\s([0]\\d|[1][0-2])(\\:[0-5]\\d){1,2}(\\:[0-5]\\d){1,2})?$", "gi");
if (regex_a.test(a[sortcol]) && regex_b.test(b[sortcol])) {
var date_a = new Date(a[sortcol]);
var date_b = new Date(b[sortcol]);
var diff = date_a.getTime() - date_b.getTime();
return sortdir * (diff === 0 ? 0 : (date_a > date_b ? 1 : -1));
}
else {
var x = a[sortcol], y = b[sortcol];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
}
and then in your columns definition you would use whichever custom filter you need, in your case the sorterNumeric() is what you're looking for...so your columns definition would look like the following (custom filter are at the end):
var columns = [
{id:"column1", name:"column1", field: "Column String", width:40, sortable:true, sorter:sorterStringCompare},
{id:"column2", name:"column2", field: "Column integer", width:40, sortable:true, sorter:sorterNumeric},
{id:"column3", name:"column3", field: "Column rating", width:40, sortable:true, sorter:sorterRating}
];
Saguenay...? Quebecois? :)
EDIT
I forgot to add the piece of code that attach the new sorter property to the onSort event (of course without it then it won't work), make sure you have same object name for grid and dataView, correct to whatever your variables naming are (if need be), here is the code:
grid.onSort.subscribe(function (e, args) {
var cols = args.sortCols;
dataView.sort(function (dataRow1, dataRow2) {
for (var i = 0, l = cols.length; i < l; i++) {
sortdir = cols[i].sortAsc ? 1 : -1;
sortcol = cols[i].sortCol.field;
var result = cols[i].sortCol.sorter(dataRow1, dataRow2); // sorter property from column definition comes in play here
if (result != 0) {
return result;
}
}
return 0;
});
args.grid.invalidateAllRows();
args.grid.render();
});
You could also put your code directly into the last onSort.subscribe but I suggest having the sorter into a separate function since it is cleaner (which is the code I sent).
I used this to sort the numbers correctly.
grid.onSort.subscribe(function(e, args) {
var cols = args.sortCols;
data.sort(function(dataRow1, dataRow2) {
for (var i = 0, l = cols.length; i < l; i++) {
var result = sortOnString(cols, i, dataRow1, dataRow2);
if (result != 0) {
return result;
}
}
return 0;
});
grid.invalidate();
grid.render();
});
function sortOnString(cols, i, dataRow1, dataRow2) {
var field = cols[i].sortCol.field;
var sign = cols[i].sortAsc ? 1 : -1;
console.log("name filed " + field);
if (field === 'Folio' || field === 'Orden') {
var value1 = parseInt(dataRow1[field]),
value2 = parseInt(dataRow2[field]);
console.log("name value 1 " + value1 + "name value 2 " + value2);
} else {
var value1 = dataRow1[field],
value2 = dataRow2[field];
}
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
return result;
}
I'm sorting an object array that has a primary contact name, among other things. Sometimes this has a blank value and when I use the function below it sorts it all correctly, but all the blanks go at the top of the list instead of the bottom. I thought that adding the condition shown below would work, but it does not.
this.comparePrimaryContactName = function (a, b)
{
if(a.PrimaryContactName == "") return -1;
return a.PrimaryContactName > b.PrimaryContactName ? 1 : -1;
}
What am I missing?
I usually use something like this:
this.comparePrimaryContactName = function(a, b) {
a = a.PrimaryContactName || '';
b = b.PrimaryContactName || '';
if(a.length == 0 && b.length == 0)
return 0;
else if(a.length == 0)
return 1;
else if(b.length == 0)
return -1;
else if(a > b)
return 1;
else if(a < b)
return -1;
return 0;
}
Comparison functions must be reflective, transitive, and anti-symmetric. Your function does not satisfy these criteria. For example, if two blank entries are compared with each other, you must return 0, not -1.
this.comparePrimaryContactName = function (a, b)
{
var aName = a.PrimaryContactName;
var bName = b.PrimaryContactName;
return aName === bName ? 0 :
aName.length===0 ? -1 :
bName.length===0 ? 1 :
aName > bName ? 1 : -1;
}
Return 1 instead of -1 for blanks.
this.comparePrimaryContactName = function (a, b) {
if (a.PrimaryContactName == b.PrimaryContactName)
return 0;
if(a.PrimaryContactName == "") return 1;
return a.PrimaryContactName > b.PrimaryContactName ? 1 : -1;
}
Your sort function should return 0 if the two are equal, -1 if a comes before b, and 1 if a comes after b.
See the MDN sort doco for more information.