Sorting Slickgrid by Multiple Columns? - javascript

I just started testing out Slickgrid for a project I'm working on and I'm very impressed with its performance. One requirement I have is sorting on multiple columns. I don't fully have my head wrapped around the Dataview in Slickgrid, so maybe I'm missing something obvious, but is there a way to sort a grid on multiple columns? Even if the UI can't handle sorting by more than one, I would like to be able to call a function with the columns in order, plus ascending or descending. I was able to do this with Datatables, but it doesn't have grouping (another requirement for the project).
In the worst case, I will resort to doing the sorting on the server and serving the content back to the client statically sorted.

I got it working for dataView with multi-column sort in the way. Was the easiest one to understand too. This is from the example in github, except that I've to pass one more parameter for dataView.sort(). It can always be true, and you can take care of the sort direction in your function.
grid.onSort.subscribe(function (e, args) {
gridSorter(args.sortCols, dataView);
});
function gridSorter(sortCols, dataview) {
dataview.sort(function (row1, row2) {
for (var i = 0, l = sortCols.length; i < l; i++) {
var field = sortCols[i].sortCol.field;
var sign = sortCols[i].sortAsc ? 1 : -1;
var x = row1[field], y = row2[field];
var result = (x < y ? -1 : (x > y ? 1 : 0)) * sign;
if (result != 0) {
return result;
}
}
return 0;
}, true);
}
Just in case it helps someone.

You can chain sort comparers to do multiple column sorting. Instead of doing
function comparerOnCol1(a, b) {
return a["col1"] - b["col1"];
}
function comparerOnCol2(a, b) {
return a["col2"] - b["col2"];
}
you can do
// sort by col1, then col2
function combinedComparer(a, b) {
return comparerOnCol1(a, b) || comparerOnCol2(a, b); // etc.
}
or just implement it inline.
As far as reflecting the sort order in the UI, while you can't do directly, you can apply the sort indicators by setting "headerCssClass" on the column definitions you're sorting by and having them display the arrows (or however else you're indicating sort columns).

There's an example here that uses the 'multiColumnSort' option.
http://mleibman.github.com/SlickGrid/examples/example-multi-column-sort.html
I don't think it works though, because args.sortCols is always an array of 1.
[Edit]
In order for it work, I need to hold shift before clicking on a column header (not very intuitive IMHO)
See also: https://github.com/mleibman/SlickGrid/pull/276

I spent a while trying to solve this with dataview (without shift key shenanigans) and I think I found the way to do it.
Use single column sort {multiColumnSort: false} and store the sort arguments in a closure. Defer to the previous comparitor if fields are equal.
var currentSortCmp = null;
grid.onSort.subscribe(function (e, args) {
// declarations for closure
var field = args.sortCol.field;
var sign = args.sortAsc ? 1 : -1;
var prevSortCmp = currentSortCmp;
// store closure in global
currentSortCmp = function (dataRow1, dataRow2) {
var value1 = dataRow1[field], value2 = dataRow2[field];
//if equal then sort in previous closure (recurs)
if (value1 == value2 && prevSortCmp)
return prevSortCmp(dataRow1, dataRow2);
return (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
};
dataView.sort(currentSortCmp);
grid.invalidate();
grid.render();
});
remembers all previous orders. just works. works as expected.

Related

How reverse sort on react

I'm trying to reverse this sort on click using ReactJS:
if (this.state.Prz==3) {
this.props.files.sort(function(a,b){
return a.lastModified < b.lastModified ? 1 : -1;
});
}
I tried many tricks searching on Google but I'm still stuck.
You can change the direction of sort (since you seem to be sorting by Date) by swapping 1 and -1:
this.props.files.sort(function(a,b){
return a.lastModified < b.lastModified ? -1 : 1;
});
Or you can reverse the array, assuming it was sorted before doing that (concat() is for making a copy of the array, keeping things "immutable"):
this.props.files.concat().reverse();
You can reverse an array simply by calling reverse
this.props.files.reverse()
Beware though that this will mutate the original array.
Here finally how I did. I add a condition.
if (this.state.activePrz==3) {
this.props.files.sort(function(a,b){
var asc = a.lastModified < b.lastModified;
var desc = a.lastModified > b.lastModified;
if(asc == true){
return asc;
}else{
return desc;
}
})
}
And it works.
I couldn't :
this.props.files.reverse();
or
this.props.files.concat().reverse();
The error message was :
Cannot read property 'files' of undefined
Many thanks for your help.

How to sort elements of array in natural order with mixed (letters and numbers) elements

i am trying to create google-form which is used to register students agreements on practice. Every agreement is registered and got agreement number which format is Last to digits of current year-T-number of agreement at this year/M. For example for now it is 17-T-11/M. The number of agreement currently is written by person which is responsible for practice.
Here is code of script below:
function onChange(e)
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var range = sheet.getDataRange();
var values = range.getValues();
var comboValues = ['16-T-105/M'];
// removing titles from 0 column and 1 line (titles)
for (var i = 1; i <= values.length; i++) {
var v = values[i] && values[i][0];
v && comboValues.push(v)
}
// Sort the values
comboValues.sort(
function(a, b) {
if (b.toLowerCase() < a.toLowerCase()) return -1;
if (b.toLowerCase() > a.toLowerCase()) return 1;
return 0;
}
);
Logger.log(comboValues);
// google-form modification
var form = FormApp.openById('1SHgVIosoE34m9cny9EQySljvgnRpzffdFEZe-kzNOzA');
var items = form.getItems();
for (i = 4; i < items.length; i++) {
Logger.log("ID: " + items[i].getId(), ': ' + items[i].getType());
}
form.getItemById('2087613006').asListItem().setChoiceValues(comboValues);
I got issue which is related with lexicographical order. Person which register agreement choose from list last registered agreement number: i tryed to do that last registered agreement number will always be at list top. As time when i started this everything was fine (it started with number 16-T-105/M), but new year come and soon after 17-T-10/M agreement was registered i got issue, that 17-T-10/M was not on list top. Soon i realised that this happens because script use lexicographical order and "thinks" that 2 is more than 10. So i understood that i somehow will have to change that order and do that 2 is less than 10, 11 is less than 101 and so on.
My question is how to do that? I guess that i need to sort array elements in natural order - but i do not have idea how to do this.
I tryed to google how to do it , but result was not satisfactory - maybe my knowledge of coding is pretty limited (i am PhD student of Psychology, not Informatics) :)
Maybe someone will help how to solve that problem.
Updates:
Link to spreadsheet: https://docs.google.com/spreadsheets/d/1FH5qYTrLUNI2SCrcaqlwgu8lzAylaTkZsiALg0zIpCM/edit#gid=1620956794
Link to google-form (Copy of actual form): https://docs.google.com/forms/d/e/1FAIpQLSerJfkv1dgHexUwxppXNyhb46twOZgvEMOIVXSOJoED3SLmyQ/viewform
You should adjust the sorting method to account of the peculiarities of the data. Here is one way to do this: the function splitConvert processes each string, splitting it by non-word characters and then converting what can be converted to integers (and lowercasing the rest). Then the comparison goes through this array one by one.
comboValues.sort(
function(a, b) {
var as = splitConvert(a);
var bs = splitConvert(b);
for (var i = 0; i < as.length; i++) {
if (bs[i] < as[i]) return -1;
if (bs[i] > as[i]) return 1;
}
return 0;
}
);
function splitConvert(str) {
return str.split(/\W/).map(function(part) {
var x = parseInt(part, 10);
return isNaN(x) ? part.toLowerCase() : x;
});
}
This is not the most performance-oriented solution: the split-parse function will be repeatedly called on the same strings as they are being sorted. If this becomes an issue (I don't really think so), one can optimize by having one run of conversion, creating an array of arrays, and then sorting that.

Sorting JSON data with Knockout .sort()

I can sort my data using Knockout's .sort(). But, when I try to sort dynamically on a user's click, the sort goes haywire. Here's my code:
var patientReport = [{"first_name":"Lyle","last_name":"Erickson","patient_id":1000},{"first_name":"Janna","last_name":"Barr","patient_id":1001},{"first_name":"Shelly","last_name":"Delacruz","patient_id":1002},{"first_name":"Nissim","last_name":"Wong","patient_id":1003},{"first_name":"Yvonne","last_name":"Rocha","patient_id":1004},{"first_name":"Leo","last_name":"Holland","patient_id":1005},{"first_name":"Melinda","last_name":"Curtis","patient_id":1006},{"first_name":"Orlando","last_name":"Peters","patient_id":1007},{"first_name":"Miriam","last_name":"Bates","patient_id":1008},{"first_name":"Otto","last_name":"Hurley","patient_id":1009},{"first_name":"Doris","last_name":"Byrd","patient_id":1010}];
var myObservableArray = ko.observableArray(patientReport);
$('.sort_header').on('click', function() {
var data = $(this).data('header');
sortRows(data);
});
function sortRows(row) {
myObservableArray.sort(
function(left, right) {
return left.row == right.row ? 0 : (left.row < right.row ? -1 : 1);
}
);
}
Any ideas what is going wrong?
You need to use left[row] rather than left.row (and the same for right). The way you have it you are trying to sort by a property actually called "row". Using the square bracket syntax lets you use the property with the name specified by the variable row.
function sortRows(row) {
myObservableArray.sort(
function(left, right) {
return left[row] == right[row] ? 0 : (left[row] < right[row] ? -1 : 1);
}
);
}

Better way to see if an array contains an object?

I have an array of items (terms), which will be put as <option> tags in a <select>. If any of these items are in another array (termsAlreadyTaking), they should be removed first. Here is how I have done it:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
for (var i = 0; i < terms.length; i++)
{
for (var iAlreadyTaking = 0; iAlreadyTaking < termsAlreadyTaking.length; iAlreadyTaking++)
{
if (terms[i]['pk'] == termsAlreadyTaking[iAlreadyTaking]['pk'])
{
terms.splice(i, 1); // remove terms[i] without leaving a hole in the array
continue;
}
}
}
Is there a better way to do this? It feels a bit clumsy.
I'm using jQuery, if it makes a difference.
UPDATE Based on #Matthew Flaschen's answer:
// If the user has a term like "Fall 2010" already selected, we don't need that in the list of terms to add.
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
var already_chosen = false;
$.each(response_chosen_items, function(index, chosen_elem)
{
if (chosen_elem['pk'] == elem['pk'])
{
already_chosen = true;
return;
}
});
return ! already_chosen;
});
The reason it gets a bit more verbose in the middle is that $.inArray() is returning false, because the duplicates I'm looking for don't strictly equal one another in the == sense. However, all their values are the same. Can I make this more concise?
var terms = $.grep(terms, function(el)
{
return $.inArray(el, termsAlreadyTaking) == -1;
});
This still has m * n performance (m and n are the lengths of the arrays), but it shouldn't be a big deal as long as they're relatively small. To get m + n, you could use a hashtable
Note that ECMAScript provides the similar Array.filter and Array.indexOf. However, they're not implemented in all browsers yet, so you would have to use the MDC implementations as a fallback. Since you're using jQuery, grep and inArray (which uses native indexOf when available) are easier.
EDIT:
You could do:
var response_chosen_pk = $.map(response_chosen_items, function(elem)
{
return elem.pk;
});
var options_for_selector = $.grep(all_possible_choices, function(elem)
{
return $.inArray(elem.pk, response_chosen_pk) == -1;
});
http://github.com/danstocker/jorder
Create a jOrder table on termsAlreadyTaking, and index it with pk.
var table = jOrder(termsAlreadyTaking)
.index('pk', ['pk']);
Then you can search a lot faster:
...
if ([] == table.where([{ pk: terms[i].pk }]))
{
...
}
...

JS sort works in Firefox but not IE - can't work out why

I have a line in a javascript function that sort an array of objects based on the order of another array of strings. This is working in firefox but not in IE and i don't know why. Here's what my data looks like going into the sort call, in IE7. (I'm using an array of three items just to illustrate the point here).
//cherry first then the rest in alphabetical order
originalData = ['cherry','apple','banana','clementine','nectarine','plum']
//data before sorting - note how clementine is second item - we wan to to to be after apple and banana
csub = [
{"value":"cherry","data":["cherry"],"result":"cherry"},
{"value":"clementine","data":["clementine"],"result":"clementine"},
{"value":"apple","data":["apple"],"result":"apple"},
{"value":"banana","data":["banana"],"result":"banana"},
{"value":"nectarine","data":["nectarine"],"result":"nectarine"},
{"value":"plum","data":["plum"],"result":"plum"}
]
//after sorting, csub has been rearranged but still isn't right: clementine is before banana. in FF it's in the right place.
csubSorted = [
{"value":"cherry","data":["cherry"],"result":"cherry"},
{"value":"apple","data":["apple"],"result":"apple"},
{"value":"clementine","data":["clementine"],"result":"clementine"},
{"value":"banana","data":["banana"],"result":"banana"},
{"value":"nectarine","data":["nectarine"],"result":"nectarine"},
{"value":"plum","data":["plum"],"result":"plum"}
]
Here's the actual sort code:
csubSorted = csub.sort(function(a,b){ return (originalData.indexOf(a.value) > originalData.indexOf(b.value)); });
Can anyone see why this wouldn't work? Is the basic javascript sort function not cross-browser compatible? Can i do this a different way (eg with jquery) that would be cross-browser?
grateful for any advice - max
EDIT - this also fails to work in safari and chrome - in other words, it only seems to work in firefox.
SOLVED - thanks to Tim Down.
I'd actually made my code simpler because i realised that the order that i needed was always "the first item in the returned array followed by the rest of the array sorted using .value". So, i changed my code thus:
first = csub.shift();
csubSorted = csub.sort(function(a,b){
return (a.value > b.value);
});
csubSorted.unshift(first);
But, it still wasn't working. Then Tim (below) pointed out that sort expects to get a -1, 0 or 1 back from the function, NOT true or false which is what my code was returning. Obviously firefox lets you get away with this, but the other browsers don't. All that was required was to 'translate' true or false into 1 and -1 (i don't worry about the case where both strings are the samae, effectively that will get returned as -1 which wouldn't make any difference to the sort order anyway):
first = csub.shift();
csubSorted = csub.sort(function(a,b){
return (a.value > b.value ? 1 : -1);
});
csubSorted.unshift(first);
Tim also told me that array.indexOf() isn't supported in IE which is annoying as even though i'm not using it here any more i am using it in other bits of code. Goddamit. Is there an API page somewhere which definitively lists only the cross-browser compatible javscript API?
First, there's no indexOfmethod of Array in IE <= 8. You'll need to write your own. Second, the comparison function passed to the sort() method of an Array should return a number rather than a Boolean.
var indexOf = (typeof Array.prototype.indexOf == "function") ?
function(arr, val) {
return arr.indexOf(val);
} :
function(arr, val) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (typeof arr[i] != "undefined" && arr[i] === val) {
return i;
}
}
return -1;
};
csubSorted = csub.sort(function(a,b){
return indexOf(originalData, a.value) - indexOf(originalData, b.value);
});

Categories

Resources