This is a migrated thread and some comments may be shown as answers.

Persisting grid state

8 Answers 143 Views
General Discussions
This is a migrated thread and some comments may be shown as answers.
Bob
Top achievements
Rank 1
Veteran
Bob asked on 07 Aug 2018, 10:07 PM

I have worked with the MVC grid for many years and I am finding it hard to replicate basic functionality with the angular grid.  For example the instructions for persisting grid state at this page runs through how to persist the angular grid settings in local storage.  Good grief, I was prepared for a steep learning curve but I was not prepared for hundreds of lines of code where previously a few would do.

I do not want to create an interface for columnConfig and then use *ngFor to loop through  a column collection.  This loses my ability to use ng-template in each grid column.  My use case is to save the current settings of the grid when a user selects a button which routes to the details page of a particular row in the grid.  When the user then selects an option to return to the grid the grid should display the previous state with the correct row selected:

$("#peopleGrid").on("click", ".k-grid-ReDirect", function (e) {
    e.preventDefault();
 
    var row = $(e.target).closest("tr");
    var grid = $("#peopleGrid").data("kendoGrid");
    var dataItem = grid.dataItem(row);
    var personId = dataItem.PersonId;
 
    var dataSource = grid.dataSource;
    var state = {
        columns: grid.columns,
        page: dataSource.page(),
        pageSize: dataSource.pageSize(),
        sort: dataSource.sort(),
        filter: dataSource.filter(),
        selectedRow: personId
    }
    localStorage["people-grid-options"] = kendo.stringify(state);
    window.location.href = "@Url.Action("Details", "People")" + "/" + personId;
});

 

and then when the user returned to the page we pick up the previous state from local storage:

$(document).ready(function () {
    var grid = $("#peopleGrid").data("kendoGrid");
    var toolbar = $("#peopleGrid .k-grid-toolbar").html();
    var options = localStorage["people-grid-options"];
    if (options) {
        var state = JSON.parse(options);
        var options = grid.options;
        options.columns = state.columns;
        options.dataSource.page = state.page;
        options.dataSource.pageSize = state.pageSize;
        options.dataSource.sort = state.sort;
        options.dataSource.filter = state.filter;
        options.dataSource.group = state.group;
 
        grid.destroy();
        $("#peopleGrid").empty().kendoGrid(options);
        $("#peopleGrid .k-grid-toolbar").html(toolbar);
        $("#peopleGrid .k-grid-toolbar").addClass("k-grid-top");
    }
    $("#peopleGrid").data("kendoGrid").dataSource.read();
});

 

And then, when the grid is data bound I find the relevant row and add the selected class.

function onPeopleGridDataBound(e) {
    var grid = $("#peopleGrid").data("kendoGrid");
 
    var options = localStorage["people-grid-options"];
    if (options) {
        var state = JSON.parse(options);
        var selectedRow = state.selectedRow;
        //throws an error on first row - probably down to Filterable settings but completes ok
        $.each(grid.tbody.find('tr'), function () {
            var model = grid.dataItem(this);
            if (model.PersonId == selectedRow) {
                $('[data-uid=' + model.uid + ']').addClass('k-state-selected');
            }
        });
    }
}

 

Now, how in the hell do I do that with the angular grid or am I simply asking for functionality which is not available?

 

 

 

8 Answers, 1 is accepted

Sort by
0
Svet
Telerik team
answered on 09 Aug 2018, 08:36 AM
Hi Bob,

Thank you for the details.

Indeed, the referenced article demonstrates an approach of persisting the State. We can further use the *ngFor to create dynamically columns and we can conditionally load ng-templates for the different columns using Angular's *ngIf directive.

For the specific scenario, we can load a grid with a predefined state. The state should be of type State. This will make sure to load the grid with the proper settings for filtering, grouping, sorting, and pagination. Then we can bind the grid's [filter] [sort] [group] [skip] [pageSize] input properties to the corresponding properties of the state. Check the following sample plunker demonstrating this approach:
https://next.plnkr.co/edit/ZMnKOTVAuCwx5kSf

In this case, when the grid is initially loaded it will have predefined filter, skip and pageSize properies:
public state: State = {
    skip: 0,
    take: 5,
 
    // Initial filter descriptor
    filter: {
      logic: 'and',
      filters: [{ field: 'ProductName', operator: 'contains', value: 'Chef' }]
    }
};

If we want to select a specific row, we will need to use the rowSelected function as demonstrated in the following article:
https://www.telerik.com/kendo-angular-ui/components/grid/selection/#toc-setting-the-selected-rows

I hope this helps.

Regards,
Svetlin
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Bob
Top achievements
Rank 1
Veteran
answered on 14 Aug 2018, 08:33 AM

Thanks, Svetlin,

Should anyone else come across this problem here's some simple code without interfaces and persisting services.
Firstly declare your state object:

public state: DataSourceRequestState = {
    skip: 0,
    take: 7
};

Then bind your grid properties to the state (I am not using group but add it if you are).

[pageSize]="state.take"
[skip]="state.skip"
[sort]="state.sort"
[filter]="state.filter"

Add a public property for "public hasSettings = false" to the head of your component and add your methods for saving and removing:

removeGridSettings(): void {
  localStorage.removeItem('ncEntityGridSettings');
  this.hasSettings = false;
  this.state = {
    skip: 0,
    take: 7
  };
  this.getEntitiesForKendo();
}
 
saveGridSettings(): void {
    const gridConfig = {
        state: this.state,
    };
    localStorage.setItem('ncEntityGridSettings', JSON.stringify(gridConfig));

    this.hasSettings = true;

}

 

And add buttons to the grid header template:

<button type="button" *ngIf="!hasSettings" kendoButton (click)="saveGridSettings(ncEntityGrid)" [iconClass]="'fa fa-floppy-o fa-fw'"> Save settings</button>
<button type="button" *ngIf="hasSettings" kendoButton (click)="removeGridSettings()" [iconClass]="'fa fa-trash-o fa-fw'"> Remove settings</button>

 

Finally, if you want to load and find an id at page load add "public selectedId: any;" to the head of your component and bind it to your grid:

<kendo-grid #ncEntityGrid
    [kendoGridSelectBy]="'id'"
...removed for brevity

And then hook it up in you NgOnInit lifecycle operator:

ngOnInit() {
    //get grid config setting from localstorage
    const retrievedState = localStorage.getItem('ncEntityGridSettings');
        if (retrievedState) {
          let settings = JSON.parse(retrievedState);
          this.state = settings.state;
          this.hasSettings = true;
          this.selectedId = settings.id;
        }
    this.getEntitiesForKendo();
}
 

It would be nice here if Svetlin could add to this and show us how to persist the column collection as well..

0
Bob
Top achievements
Rank 1
Veteran
answered on 15 Aug 2018, 04:57 PM

Svetlin,

Any update on a simple method to persist the column collection?

Cheers,
Bob

0
Svet
Telerik team
answered on 16 Aug 2018, 07:41 AM
Hi Bob,

We can keep an array of objects that store information for the current columns' settings. Then using Angular's *ngIf directive we can load the columns dynamically in the grid. Check the following example demonstrating this approach:
https://stackblitz.com/edit/angular-uqx66n?file=app/app.component.ts

I hope this helps. Let me know in case I am missing something or further assistance is required for this case.

Regards,
Svetlin
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Bob
Top achievements
Rank 1
Veteran
answered on 16 Aug 2018, 11:39 AM

Hi Svetlin,
Sorry to be a pain.  My grid has some fairly complex filters using ng-template and also I am using the kendo-grid-span-column  in conjunction with the media property to give me a crude solution for responsive design.  Therefore I cannot generate the columns dynamically using an *ngFor="let col of columns" statement.  What I have is a method to save my state:

saveGridSettings(grid: GridComponent): void {
    const columns = grid.columns;
 
    let columnsConfig = columns.toArray()
        .map(item => {
            return Object.keys(item)
            .filter(propName => !propName.toLowerCase()
              .includes('template'))
              .reduce((acc, curr) => ({...acc, ...{[curr]: item[curr]}}), {});
        });
 
    const gridConfig = {
        state: this.gridSettings.state,
        columnsConfig: columnsConfig
    };
    localStorage.setItem('ncEntityGridSettings', JSON.stringify(gridConfig));
    this.hasSettings = true;
}

Which works excellent.  Then I initialise the grid using the ngOnInit function:

ngOnInit() {
    //get grid config setting from localstorage
    const storedGridSettings = localStorage.getItem('ncEntityGridSettings');
        if (storedGridSettings) {
          let settings = JSON.parse(storedGridSettings);
          this.gridSettings.state = settings.state;
          if (settings.state.filter) {
            this.mapDateFilter(settings.state.filter);
          }
          if (settings.columnsConfig) {
            //how to get an instance of the grid and then loop the columns and apply settings from the gridSettings.columnsConfig object
            //this.gridSettings.columnsConfig = settings.columnsConfig.sort((a, b) => a.orderIndex - b.orderIndex);
          }
          this.hasSettings = true;
          if (settings.id) {
            this.selectedIds.push(settings.id);
          }
        }
        this.getEntitiesForKendo();
}

Having returned the columns settings from local storage I need to get an instance of the grid and then loop the columns and apply settings from the gridSettings.columnsConfig object.  In MVC I would have done this using an event such as DataBound or DataBinding but I can not see how to apply state using angular.  Does this make sense?

My use case is that the grid is rendered only with base columns and then the user unhides columns according to what s/he wants to analyse such as a "date created" column.  They may sort or filter on the "date created" column but when the grid is rendered the column hidden/unhidden status reverts back to original state.  The user then has to unhide the "date created" column to continue with analysis.  At the moment I would be satisfied to simply persist the hide/unhide state of each column and apply the same value to the grid after returning the gridSettings.columnsConfig object.

The dynamic column solution suggests I would have to persist ng-templates for each column along with the column state and I have seen no examples of how to do this.  The approach would not be a bad idea, we could then define each grid as a json object (as per the examples) and load as and when required.  This would have the further advantage of allowing us to persist grid configurations server-side against particular users.

I hope all this makes sense and you can now see the conundrum.

Cheers,
Bob

 

 

0
Svet
Telerik team
answered on 20 Aug 2018, 06:46 AM
Hi Bob,

Thank you for the additional details.

The demonstrated code shows an approach, that is not supported by the grid. The columns property of the grid is intended only for internal use. This is why, its use is not documented by us.

We could use as many ng-templates as needed for each column. We can match the proper ng-template to the grid using Angular's *ngIf directive which will evaluate based on a condition.
About the hidden functionality of the columns, we can use their [hidden] property. We can also enable the [columnMenu] functionality, which will further add an additional UI on the columns' headers, from which we can select, which columns to hide/unhide.

Check the following example demonstrating the described approaches:
https://stackblitz.com/edit/angular-uqx66n-ycvyje?file=app/app.component.ts

I hope this helps.

Regards,
Svetlin
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
0
Bob
Top achievements
Rank 1
Veteran
answered on 20 Aug 2018, 07:09 AM

Hi Svetlin,

I kind of thought this would be the answer.  It's a different way of doing things to MVC/JQuery and will take a lot of fiddly code but your answer gives me some decent pointers to work with.  If you want to close this issue that's ok but if you leave it open I will post back my findings and any subsequent code others may find useful.

Thanks again for your time spent with this problem.

Cheers,
Bob

0
Svet
Telerik team
answered on 21 Aug 2018, 12:26 PM
Hi Bob,

I will proceed with closing this case. However, you will still be able to submit any further responses to it.  

Regards,
Svetlin
Progress Telerik
Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
Tags
General Discussions
Asked by
Bob
Top achievements
Rank 1
Veteran
Answers by
Svet
Telerik team
Bob
Top achievements
Rank 1
Veteran
Share this question
or