Non case-sensitive sorting in dojo dgrid
answer posted by #https://stackoverflow.com/users/237950/ken-franqueiro
grid.on('dgrid-sort', function (event) {
// Cancel the event to prevent dgrid's default behavior which simply
// passes the sort criterion through to the store and updates the UI
event.preventDefault();
// sort is an array as expected by the store API, but dgrid's UI only sorts one field at a time
var sort = event.sort[0];
grid.set('sort', function (a, b) {
var aValue = a[sort.attribute].toLowerCase();
var bValue = b[sort.attribute].toLowerCase();
if (aValue === bValue) {
return 0;
}
var result = aValue > bValue ? 1 : -1;
return result * (sort.descending ? -1 : 1);
});
// Since we're canceling the event, we need to update the UI ourselves;
// the `true` tells it to also update dgrid's internal representation
// of the sort setting, so that toggling between asc/desc will still work
grid.updateSortArrow(event.sort, true);
});
What are the values of a and b variables?
In my case I don't want to touch the library file, my event argument has the complete details of the grid, so I wanted to sort the data present in event argument and update the grid, how should I modify the above code?
Related
I'm using Datatables 1.10.19 though I would happily upgrade if that will help.
I have an application with several tables on the same page. For one of those tables I want to offer the user the option to do a negative search. They enter a value and see all the rows that don't have that value.
So far as I can see the search() API allows simple text or regex criteria. I've seen examples such as this.
var allTables = $('table.dataTable').DataTable();
allTables.column( 0 ).search( 'mySearchTerm', true, false ).draw();
Some regex dialects support negative look ahead so regex such as those described in this answer would allow me to specify negation, but it appears that that the regex engine in use in Datatables does not work with such expressions.
My alternative is to use a filter function which I can establish with this:
$.fn.dataTable.ext.search.push()
However that seems to be a global construct affecting all tables, which I really don't want.
Any suggestions please?
You can use $.fn.dataTable.ext.search.push() - but it requires some additional work, to handle the fact (as you point out) that this is a global function, affecting all tables.
The following is not a full implementation, but shows the main points:
The Approach
My example uses 2 tables, with hard-coded data in each HTML table.
Each table is initialized as follows:
$(document).ready(function() {
// ---- first table with custom search ---------//
var table_a = $('#example_a').DataTable( {
pageLength: 10
} );
// remove the default DT search events - otherwise
// they will always fire before our custom event:
$( "#example_a_filter input" ).unbind();
// add our custom filter event for table_a:
$('#example_a_filter input').keyup( function( e ) {
table_a.draw();
} );
// ---- second table with DT default search ----//
var table_b = $('#example_b').DataTable( {
pageLength: 10
} );
} );
For my custom search function, I make use of the fact that the function includes a settings parameter which we can use to see which table is being searched:
$.fn.dataTable.ext.search.push (
function( settings, searchData, index, rowData, counter ) {
var tableID = settings.nTable.id;
var searchTerm = $('#' + tableID + '_filter input').val()
//console.log( tableID );
//console.log( searchTerm );
//console.log( searchData );
switch(tableID) {
case 'example_a':
if (searchTerm === '') {
return true;
} else {
show_me = true;
searchData.forEach(function (item, index) {
if (show_me && item.includes(searchTerm)) {
show_me = false;
}
});
return show_me;
}
break;
default:
// for all other tables, pass through the rows
// already filtered by the default DT filter:
return true;
}
}
);
The following line is where we ID which table is being filtered:
var tableID = settings.nTable.id;
After that, I can use a switch statement to handle each table's search separately.
In the default case (for example_b), I'm just passing through what was already filtered by the DT default search:
default: return true;
The above filter looks like this when I search each table for the letter x:
Incomplete Implementation
This logic is incomplete for the custom search logic. It assumes the search term is a single string. If I enter "x y" in the input field, that will exclude all records where a field contains "x y" - which is probably not what you want.
You probably want the exact negation of the standard search - so the input term would need to be split on spaces and each sub-term (x and y) would need to be checked separately across each row of data.
I'm trying to override the sort logic in dgrid as suggested by kfranqueiro in this link - https://github.com/SitePen/dgrid/issues/276.
I get the data from the server in sorted order and just want to update the UI of column header. I'm doing this -
On(mygrid, 'dgrid-sort', lang.hitch( this,function(event){
var sort = event.sort[0];
var order = this.sort.descending ? "descending" : "ascending";
console.log("Sort "+ this.sort.property + " in " +order+" order.");
event.preventDefault();
mygrid.updateSortArrow(event.sort, true);
myFunctionToRefreshGrid();
}));
...
myFunctionToRefreshGrid: function() {
...//get data from server in sorted order
var mystore = new Memory({data: sortedDataFromServer, idProperty: 'id'});
mygrid.set("collection", mystore);
...
}
Memory here is "dstore/Memory". I'm using dgrid 0.4, dstore 1.1 and dojo 1.10.4
Before calling set('collection',...) I see that sortedDataFromServer is in the desired sorted order. But for some reason, the order in the grid is different. For example, when sorted in descending order, I see that the values starting with lower case appear first in descending order and then the values starting with upper case appear in sorted order. It looks like dstore is doing something more.
What could be going on? Am I doing something wrong? Is there a different/better way to do custom sorting?
Thanks,
This is how I ended up addressing the situation -
As suspected, the collection/store was further sorting my data and hence the inconsistency. I customized the store (Memory) as shown below and use the custom store when setting data to my grid.
var CustomGridStore = declare([Memory],{
sort: function (sorted) {
sorted = [];//Prevent the collection from sorting the data
return this.inherited(arguments);
}
});
I think you are doing the right thing, only problem here is that you are not resetting sort property of grid, one you re-initiate memory with sorted order, it get's sorted automatically
after you are calling
event.preventDefault();
call this
mygrid.set("sort", null);
I am doing custom sorting in my one of grid as following
self.xrefGrid.on("dgrid-sort", function (event) {
var sort = event.sort[0];
event.preventDefault();
self.xrefGrid.set('sort', function (a, b) {
var aValue,bValue;
if (a[sort.attribute] && typeof a[sort.attribute] == "string")
aValue = a[sort.attribute].toLowerCase();
if (b[sort.attribute] && typeof b[sort.attribute] == "string")
bValue = b[sort.attribute].toLowerCase();
var result = aValue > bValue ? 1 : -1;
return result * (sort.descending ? -1 : 1);
});
self.xrefGrid.updateSortArrow(event.sort, true);
});
I am implementing Devexpress Grid Control in MVC. I was stuck at a point where i need the current Sorted By column and Sort Order (asc/desc) in javascript. I also tried the clientSide Event OnColumnSortingChanged(s , e) , it is only providing the name of the column at click event not from the gridview javascript object.
Got the answer after research...
Have to add CustomJsProperty to the control in the partial view as below
settings.CustomJSProperties = (s, e) =>
{
var Grid = s as MVCxGridView;
var result = new System.Collections.Hashtable();
foreach (var col in Grid.AllColumns)
{
var dataCol = col as GridViewDataColumn;
if (dataCol != null)
{
if (dataCol.SortIndex == 0)
{
e.Properties["cpColumnsProp"] = new Dictionary<string, object>()
{
{ "sortcolumn", dataCol.FieldName },
{ "sortorder", ((Convert.ToString(dataCol.SortOrder).Equals("Ascending")) ? "asc" : "desc")}
};
}
}
}
};
Handle the ColumnSortingChange event as follows
function gvChartList_OnColumnSortingChanged(s, e) {
$("#hdsortname").val(e.column.fieldName); // contains the sort column name
$("#hdsortorder").val(((s.cpColumnsProp.sortcolumn == e.column.fieldName) ? "desc" : "asc")); // contains the sort column order
}
Last but not the least, One must have to define the default sort index and sort order for the column
settings.Columns.Add(col =>
{
// column details
col.SortIndex = 0;
col.SortAscending();
});
I recently needed similar, where I wanted to save the column order, sort order, and filter information; such that a user could create saved views of a grid, and not have to keep re-entering all these preferences.
I found that in an event CustomJSPropeties I could interact with a sender being an ASPxGridView, such that I could grab a needed value from ASPxGridView.SaveClientLayout(). Also useful here can be the FilterExpression, and the VisibileRowCount - if you want to use the filter expression for exporting, but only when the VisibleRowCount is less than a certain threshold (this is the number of rows shown in the bottom pager area)
settings.CustomJSProperties = (s, e) =>
{
ASPxGridView gridView = (ASPxGridView)s;
e.Properties["cpGridFilterExpression"] = gridView.FilterExpression; //Make Filter Expressions Availabe to JavaScript
e.Properties["cpVisibleRowCount"] = gridView.VisibleRowCount; //Make Visible Row Count Availabe to JavaScript
e.Properties["cpLayout"] = gridView.SaveClientLayout(); //Get Layout String and make it available to JavaScript
};
So what does this do? Properties defined in this event are available as properties of the javascript grid view object. So here we grab these values when we can, and place them in a place where we normally cannot access them.
And then, right from JavaScript we can now access GridView.cpLayout (where GridView is the name/id supplied to your grid.)
<script type="text/javascript">
$(document).ready(function () {
$('#saveButton').click(
function () {
var layout = GridView.cpLayout;
//Perform Save...
}
);
});
</script>
To be clear, this "layout" contains sort order, visible column info, and filter information.
Layout:
https://documentation.devexpress.com/#AspNet/CustomDocument4342
https://documentation.devexpress.com/#AspNet/DevExpressWebASPxGridViewASPxGridView_SaveClientLayouttopic
CustomJSProperties:
https://documentation.devexpress.com/#AspNet/DevExpressWebMvcGridViewSettings_CustomJSPropertiestopic
Note: I add this solution here because this is what I found when I was searching for my issue. As one can see, these are similar topics of accessing the AspxGridView (base of) or MVCGridView within a CustomJSProperties event handler.
I have a stream holding an array, each element of which has an id. I need to split this into a stream per id, which will complete when the source stream no longer carries the id.
E.g. input stream sequence with these three values
[{a:1}, {b:1}] [{a:2}, {b:2}, {c:1}] [{b:3}, {c:2}]
should return three streams
a -> 1 2 |
b -> 1 2 3
c -> 1 2
Where a has completed on the 3rd value, since its id is gone, and c has been created on the 2nd value, since its id has appeared.
I'm trying groupByUntil, a bit like
var input = foo.share();
var output = input.selectMany(function (s) {
return rx.Observable.fromArray(s);
}).groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return input.filter(
function (s) { return !findkey(s, g.key); }
); }
)
So, group by the id, and dispose of the group when the input stream no longer has the id. This seems to work, but the two uses of input look odd to me, like there could a weird order dependency when using a single stream to control the input of the groupByUntil, and the disposal of the groups.
Is there a better way?
update
There is, indeed, a weird timing problem here. fromArray by default uses the currentThread scheduler, which will result in events from that array being interleaved with events from input. The dispose conditions on the group are then evaluated at the wrong time (before the groups from the previous input have been processed).
A possible workaround is to do fromArray(.., rx.Scheduler.immediate), which will keep the grouped events in sync with input.
yeah the only alternative I can think of is to manage the state yourself. I don't know that it is better though.
var d = Object.create(null);
var output = input
.flatMap(function (s) {
// end completed groups
Object
.keys(d)
.filter(function (k) { return !findKey(s, k); })
.forEach(function (k) {
d[k].onNext(1);
d[k].onCompleted();
delete d[k];
});
return Rx.Observable.fromArray(s);
})
.groupByUntil(
function (s) { return s.keys()[0]; },
null,
function (g) { return d[g.key] = new Rx.AsyncSubject(); });
I have a fullcalendar where I display non-editable events which are collected from a google calendar, or from a database. Then I want to register customer requests for events from the calendar. This works, but I am not able to list only the events that are added by the user.
Any hint on how to do this?
I tried this:
function retrieve_events() {
var rdv=$('#calendar').fullCalendar( 'clientEvents', undefined);
for (i=0; i<=rdv.length-1; i++) {
/*alert(rdv.toSource());*/
alert(rdv[i].title+" id: "+rdv[i].id+" start: "+rdv[i].start+" end:"+rdv[i].end+" heldag:"+rdv[i].allDay);
}
}
The the "undefined" as id, means that I have given all the non-editable events an id, while the new ones haven't got one. But this way I get all events listed, even those without an id. The same happens with null and ''. But using hardcoded id-numbers returns that specific event.
I see from the documentation that there seems to be other ways to get hold of the events I need, by using other criteria like classes. However I cannot figure out how to specify this filter.
I haven't worked with FullCalendar yet nor do I intend to extensively test this, so I cannot guarantee that this will work.
However, why don't you simple test whether rdv[i].id evaluates to false?
Try:
function retrieve_events( ) {
var rdv = $('#calendar').fullCalendar('clientEvents'),
results = [];
for( var i = 0; i < rdv.length; ++i ) {
if( !rdv[i].id ) {
results.push(rdv[i]);
}
}
return results;
}
P.S.: Passing undefined to .fullCalendar() probably is redundant. It would be equivalent to passing only a single variable. I'd guess the second parameter is a type of events that you can filter for, but passing only a single parameter would cause the plugin to return all events. Also, note that !!'' === false.
The internal check whether the second parameter is set is probably similar to this:
$.fn.fullCalendar = function( command ) {
switch( command ) {
// ... some case's
case 'clientEvents':
var filter = arguments[1];
if( !filter ) {
// Retrieve ALL client events
}
else {
// Filter client events
}
break;
// ... some more case's
}
};
This does not compare types. Testing filter === false would only return true, if filter would evaluate to false and is a boolean.
Following are examples of values that evaluate to false. There may be more, but I believe those are all.
undefined
null
0
false
''