tabalinas / jsgrid

Lightweight Grid jQuery Plugin
http://js-grid.com
MIT License
1.53k stars 353 forks source link

Sorting with Promise - Part 2 #240

Closed ViniH closed 8 years ago

ViniH commented 8 years ago

Apologies that I didn't respond to my previous question - I was assigned other work and have only just had chance to come back to this.

I've got a JSGrid that is populated by data returned from a REST controller, I have pageloading and sortable true, and I have implemented sortField and sortOrder handling on the loadData method such that the sort params are send to the REST controller and correctly handled and the data comes back sorted appropriately.

The part I am having issues with is the front-end rendering.

Currently when I click on a header the sort headers are applied immediately, the call is fired, and when the data is returned the promise is fulfilled and the correctly sorted data is loaded. Unfortunately the classes are removed from the header rows and so when I click it again it simply loads the same new sort, I can never get to descending sort. To get around this I added some code to only go and get the data from the server if the sorting order or field has changes - this has the result of allowing me to sort descending, but it's still incorrect - it looks like the header classes are required for JSgrid to handle sorting properly, but whenever the promise is fulfilled they are reset back to default as if this was the first load and not the result of a sort.

The effect is that I have to click twice to sort in either direction.

Any help with this would be greatly appreciated, I am trying to apply the CSS classes to the appropriate header field manually in the field mapper, but it iterates columns not rows and I am really struggling to figure out how to apply a class to just the header rather than the entire column.

@christopher-worley - I think you've probably already solved this problem - can you elaborate on your answer in the other question?

tabalinas commented 8 years ago

Could you provide the code of loadData? It would be great if you provide an example forking this fiddle https://jsfiddle.net/tabalinas/kL291xp5/ To emulate synchronous behavior you could use setTimeout(function() { d.resolve(data); }, 1000) instead of server calls.

ViniH commented 8 years ago
loadData: function( filter )
{
    var dataPromise = $.Deferred();

    dataResult.execute( dataResult.cacheId, ( filter.pageIndex - 1 ) * dataResult.pageSize, true, filter.sortField, filter.sortOrder ).done( result =>
    {
        dataPromise.resolve( {
            data: result.dataSet.map( item =>
            {
                var row = {};
                result.schema.columnInfo.forEach(( info, index, array ) =>
                {
                    if ( info.dataType == dmgEnums.DataType.Datetime )
                    {
                        row[info.name] = moment( item[index] ).format( "lll" );
                    }
                    else
                    {
                        row[info.name] = item[index];
                    }
                });
                return row;
            }),
            itemsCount: result.totalRows,
        });
    });

    return dataPromise.promise();
}
ViniH commented 8 years ago

I will try testing with the suggested method.

tabalinas commented 8 years ago

loadData looks good. If you cannot reproduce the issue with the fiddle, past at least non-working code there so entire configuration will be available.

ViniH commented 8 years ago

Creating a fiddle is likely to be tricky as this solution is fairly complicated.

I've made some progress, I now believe the problem lies with filter not being properly set... just trying to figure out how to set it.

ViniH commented 8 years ago

Just to clarify my above post - there is no filtering applied here, but the paging params are passed to loadData via the filter object, as are sort params (sortField & sortOrder).

The problem seems to be that this is being reset every time pageLoading happens - so I somehow need to manually store it and make sure it is always in the correct state.

I am currently trying to determine how to set the filter on jsgrid programmatically so that I can ensure it is always in the correct state - I am also referencing this post to try and learn how to do this.

ViniH commented 8 years ago

So the bug/problem here is that the parameters sent to loadData are incorrect, because parameters (along with any filter params) are reset every time pageLoading happens. I have been unable to effect a workaround so far as all the examples are people using filter, you say to set the filter values using jquery, but the sorting params don't appear to be stored in the filter row!

Any idea where they are stored? Could you give me a simple example where a sort is applied by default?

I am currently having to manually manipulate classes jsgrid-header-sort and jsgrid-header-sort-asc/desc on the header row, and I do not think I should have to do this based on what I have seen in the other solutions.

All I need to be able to do is click the header to toggle sort, it shouldn't be this difficult surely?

ViniH commented 8 years ago

Right I am handling all of this manually now to avoid any further problems:

// Load data is executed via a promise to facilitate asynchronous paged data loading 
loadData: function( filter )
{
    var dataPromise = $.Deferred();

    // Get grid options
    let jsGridOptions = ko.utils.domData.get( element, "jsGridOptions" );

    // No sortField set, use the one from options
    if( typeof ( filter.sortField ) == "undefined" )
    {
        filter.sortField = jsGridOptions.sortField;
        filter.sortOrder = jsGridOptions.sortOrder;
    }
    else
    {
        jsGridOptions.sortField = filter.sortField;
        jsGridOptions.sortOrder = filter.sortOrder;

        // Update dom stored object.
        ko.utils.domData.set( element, "jsGridOptions", jsGridOptions );
    }

    // Apply CSS
    $( "th:contains(" + filter.sortField + ")" ).addClass( "jsgrid-header-sort" ).addClass( "jsgrid-header-sort-" + filter.sortOrder );

    // Execute dataresult.
    dataResult.execute( dataResult.cacheId, ( filter.pageIndex - 1 ) * dataResult.pageSize, true, filter.sortField, filter.sortOrder ).done( result =>
    {
        dataPromise.resolve( {

All I need to know now, is how to make jsGrid understand that a column is already sorted. Currently it is always sorting ascending because it doesn't ever store that it is already sorted.

In addition to setting the appropriate classes on the sorted TH element - what else do I need to set in order to make jsGrid understand that it is already sorted (without using the sort function!)??

ViniH commented 8 years ago

Right I've got a working solution. I don't like it, it's not very clean, but here is it:

// Load data is executed via a promise to facilitate asynchronous paged data loading 
loadData: function( filter )
{
    var dataPromise = $.Deferred();

    // Get grid options
    let jsGridOptions = ko.utils.domData.get( element, "jsGridOptions" );

    // No sortField set, use the one from options
    if( typeof ( filter.sortField ) == "undefined" )
    {
        filter.sortField = jsGridOptions.sortField;
        filter.sortOrder = jsGridOptions.sortOrder;
    }
    else
    {
        jsGridOptions.sortField = filter.sortField;
        jsGridOptions.sortOrder = filter.sortOrder;

        // Update dom stored object.
        ko.utils.domData.set( element, "jsGridOptions", jsGridOptions );
    }

    var grid = $( "#jsGrid" ).data( "JSGrid" );

    // Set jsGrid sorting properties.
    grid._setSortingParams( filter.sortField, filter.sortOrder );

    // Apply CSS
    $( "th:contains(" + filter.sortField + ")" ).addClass( "jsgrid-header-sort" ).addClass( "jsgrid-header-sort-" + filter.sortOrder );

    // Execute dataresult.
    dataResult.execute( dataResult.cacheId, ( filter.pageIndex - 1 ) * dataResult.pageSize, true, filter.sortField, filter.sortOrder ).done( result =>
}
ViniH commented 8 years ago

Last comment on this - I think you should make it clear in your docs that sorting dynamically loaded paged data is not currently supported - and/or add a demo example of how to actually do it. If I get chance I will contribute.

tabalinas commented 8 years ago

In the this simple fiddle we see that sorting is not reset http://jsfiddle.net/tabalinas/hzxth0xe/ In fact, sorting parameters are stored by the jsGrid. You should not take case about storing them and setting css classes. What is still unclear to me is why sorting is reset in your case?

ViniH commented 8 years ago

Probably because it is being used in conjunction with a knockout.js binding, it may be that the update method is causing the grid to reset.

ViniH commented 8 years ago

This is the code from the update method in the binding.

`

$( element )["jsGrid"]( {

        fields: dataResult.dataResult().schema.columnInfo.map(( info ) =>
        {
            // Translate the DataType into jsGrid dataType
            var dataType;
            var headingWidth = info.name.length;
            var fieldAlignmentClass;
            var minWidth = 2;

            switch( info.dataType )
            {
                case dmgEnums.DataType.BigInteger:
                case dmgEnums.DataType.Currency:
                case dmgEnums.DataType.Decimal:
                    dataType = "number";
                    minWidth = 150;
                    fieldAlignmentClass = "ind-grid-rightalign";
                    break;
                case dmgEnums.DataType.Integer:
                    dataType = "number";
                    minWidth = 150;
                    fieldAlignmentClass = "ind-grid-rightalign";
                    break;
                case dmgEnums.DataType.Boolean:
                    dataType = "checkbox";
                    minWidth = 100;
                    fieldAlignmentClass = "ind-grid-centeralign";
                    break;
                case dmgEnums.DataType.Datetime:
                    dataType = "text";
                    minWidth = 150;
                    //minWidth = moment( "9999-31-12 00:00:00" ).format( "lll" ).length;
                    fieldAlignmentClass = "ind-grid-leftalign";
                    break;
                default:
                    dataType = "text";
                    if( info.length == 1 )
                    {
                        minWidth = 100;
                        fieldAlignmentClass = "ind-grid-centeralign";
                    }
                    else
                    {
                        minWidth = 250;
                        fieldAlignmentClass = "ind-grid-leftalign";
                    }
            }

            // Column width - use heading if larger than minimum, or use minimum.
            headingWidth = Math.max( headingWidth * 5, minWidth );

            // Note that there is no differentation here between headings and data cells
            // therefore I've had to define the field alignment classes twice (one for each)
            // When applied to a <th> element, all of the fieldAlignmentClass classes align left (contrary to class names).
            return {
                name: info.name,
                type: dataType,
                sorter: dataType,
                width: headingWidth + "px",
                css: "has-context-menu ordinal-" + info.ordinal + " " + fieldAlignmentClass
            };
        }),

`

ViniH commented 8 years ago

Note that the search params sortField and sortOrder are stored in/sent via the filter - and it is this filter that is being reset - as per the various other cases - having looked at those cases it seems to be the exact same issue, and I've found at least four other examples online where other people have had to work around this same issue, so I do not believe it is specific to our code.

tabalinas commented 8 years ago

Could you please bring these examples (you mentioned), so that I could investigate the issue? Also I'm wondering how to change the fiddle I provided to see the issue...

tabalinas commented 8 years ago

Close due to inactivity.

ViniH commented 8 years ago

Just an update on this - the reset is happening with both sorting and column re-ordering, and is as a result of the integration with Knockout - the knockout binding has an update method which re-draws the grid when the promise is resolved, therefore any settings or changes done on the grid must be saved and restored each time the knockout observable changes.

tabalinas commented 8 years ago

Thank you for the update. One of upcoming feature is integration with knockoutjs as a custom binding.