primefaces / primeng

The Most Complete Angular UI Component Library
https://primeng.org
Other
10.46k stars 4.6k forks source link

TableState for Table #304

Closed gatapia closed 5 years ago

gatapia commented 8 years ago

It would be good to be able to remember the state of the table to be able to give the user continuity as they use an application. This state would include:

A nice clean API to get and set this would be great:

# template
<p-datatable (stateChanged)="onStateChanged($event)" 
    [initialState]="loadInitialState()"...

# component
onStateChanged(event: any) {
  localStorage.set('table-state', event.data);
}

loadInitialState() {
  return localStorage.get('table-state');
}

Or perhaps even a totally automatic API, like:

<p-datatable persistState="true"...
GraemeSMiller commented 8 years ago

Would be a great feature. Automatically is a nice to have. Best for me if it exposed functions so can hookin and save to remote storage etc

gatapia commented 8 years ago

Just looking at source now and it looks like accessing the sort/filter state is simple enough, just attach to onFilter/onSort events. There appears to be now way to set these values back.

Somewhat related, onSort fires the onFilter event even if filter has not changed, i.e. filter data (get filter event), sort data (get filter event then sort event). I think that filter event is redundant. minor issue though.

vijosoco commented 8 years ago

Is there any update on this issue. I am using the LazyLoad event to store the sort and filter information. I am using local storage (which isn't ideal - but it works). Now when i navigate away from the page with my datatable i can persist those values. Once i return to that page (onInit) i can get those values back from localstorage and use them in very next query (lazy load). HOWEVER, there seems to be no way to visually update the data table UI to show that those filters are being used or that the sort order on a column has changed.

Is there any way to update the datatable header fields on the UI?

lbamburski commented 8 years ago

This pull request solves this. It uses localStorage for lazy load. Everything is updated visually. For integration with MultiSelect for toggle columns, event onPersistentStateApplied should be used similar to this:

onPersistentStateApplied(event): void { if (!event.state || !event.state.visibleColumnsState) { return; }

    let eventColumns = event.state.visibleColumnsState;

    this.columns.splice(0, this.columns.length);

    for (let i = 0;i < eventColumns.length;i++) {
        let eventColumn = eventColumns[i];
        this.columns.push({
            field: eventColumn.field,
            header: eventColumn.header,
            style: eventColumn.style
        });
    }

    let columnsOptionsByLabel = {};
    for(let i = 0; i < this.columnOptions.length; i++) {
        columnsOptionsByLabel[this.columnOptions[i].label] = this.columnOptions[i];
    }

    for(let j = 0; j < this.columns.length; j++) {
        let columnOption = columnsOptionsByLabel[this.columns[j].header];
        columnOption.value = this.columns[j];
    }
}
vijosoco commented 8 years ago

Any news on when we might see this released?

cagataycivici commented 7 years ago

This is on roadmap;

https://github.com/primefaces/primeng/wiki/Roadmap

Closing for now and will create a new ticket with details when we pick it up from roadmap after 1.0. At the moment, we're closing tickets that duplicate the upcoming features in roadmap as a part of issue tracker maintenance.

pchristou commented 7 years ago

@cagataycivici thanks for the roadmap link - are there any dates pencilled in for when this feature will be released?

asnp87 commented 7 years ago

any update on this?

cagataycivici commented 7 years ago

Planned for 2.1 due late march.

cagataycivici commented 7 years ago

Similar to PrimeFaces TableState;

https://www.primefaces.org/showcase/ui/data/datatable/tableState.xhtml

asnp87 commented 7 years ago

@cagataycivici do you have a new planning for this feature?

cagataycivici commented 7 years ago

Yes, but can't give a specific date right now. Rough estimation is in 2 months.

TimKohler commented 7 years ago

Love ngPrime but this is a major issue with an otherwise great data table. In the meantime, are there any events we could subscribe to and properties we could set that would allow us to implement this ourselves?

jamesgroat commented 7 years ago

@TimKohler -- I solved this manually using something like this:

public gridOptions = {
    first: 0,
    rows: 10,
    sortField: 'title',
    sortOrder: 1
  };

onSort(e: { field: string, order: number }) {
    this.gridOptions.sortField = e.field;
    this.gridOptions.sortOrder = e.order;
    this.gridOptions.first = 0;
    this.localStorageService.set('my-grid-options', this.gridOptions);
  }
  onPage(e: { first: number, rows: number }) {
    this.gridOptions.rows = e.rows;
    this.gridOptions.first = e.first;
    this.localStorageService.set('my-grid-options', this.gridOptions);
  }

 ngOnInit() {
  // after loading the table data then read the options and
  // if options exist set them wrapped in setTimeout():

      const opt = this.localStorageService.get('my-grid-options');
      if (opt) {
        setTimeout(() => {
          this.gridOptions = opt;
          this.dataTable.sortField = this.gridOptions.sortField;
          this.dataTable.sortOrder = this.gridOptions.sortOrder;
          this.dataTable.sortSingle();
          this.dataTable.paginate(this.gridOptions);
          this.blocked = false;
        }, 0);

        return;
      }
 }

The table looks like:

<p-dataTable #dt [value]="sets" (onRowClick)="select($event.data)" [paginator]="true" 
paginatorPosition="both" [pageLinks]="10" [rows]="10" [rowsPerPageOptions]="[5,10,20,30,40]"
(onPage)="onPage($event)" (onSort)="onSort($event)">
TimKohler commented 7 years ago

@jamesgroat Thank you! This is a great workaround. Got it working well.

cagataycivici commented 7 years ago

Yes, there are events to use until this is built-in.

latchkostov commented 7 years ago

I cannot get pagination to stick with the newest version of PrimeNG, since paginate() no longer accepts any arguments.

Does anyone have a solution? Here is my current code:

onPage(e: { first: number, rows: number }) {
    console.log(this.dataTable.first);
    this.gridOptions.rows = e.rows;
    this.gridOptions.first = e.first;
    localStorage.setItem(this.optionsKey, JSON.stringify(this.gridOptions));
  }

  const opt = localStorage.getItem(this.optionsKey);
    const filters = localStorage.getItem("filters");
    if (opt) {
        let go = JSON.parse(opt);
        setTimeout(() => {
            this.gridOptions = go;
            this.dataTable.sortField = this.gridOptions.sortField;
            this.dataTable.sortOrder = this.gridOptions.sortOrder;
            this.dataTable.sortSingle();
            this.dataTable.first = this.gridOptions.first;
            this.dataTable.rows = this.gridOptions.rows;
            this.dataTable.paginate();
            //this.dataTable.paginate(this.gridOptions);

            if (filters) {
                console.log(JSON.parse(filters));
                this.dataTable.filters = JSON.parse(filters);
            }
      }, 0);
jamesgroat commented 7 years ago

@blgrnboy Try this:

this.gridOptions = opt;
this.dataTable.sortField = this.gridOptions.sortField;
this.dataTable.sortOrder = this.gridOptions.sortOrder;
this.dataTable.sortSingle();
this.dataTable.onPageChange(this.gridOptions);

I don't think you need these:

this.dataTable.first = this.gridOptions.first;
this.dataTable.rows = this.gridOptions.rows;
latchkostov commented 7 years ago

@jamesgroat I tried that, with no success.

jamesgroat commented 7 years ago

@blgrnboy make sure you are calling that code after you have loaded your data?

latchkostov commented 7 years ago

@jamesgroat My sort and filter settings are working and persisting as expected, so I would expect pagination to work as well. I did try moving around the code anyway, but still doesn't seem to work.

latchkostov commented 7 years ago

@cagataycivici Could you please help out with this?

fberthel commented 7 years ago

Any Updates on this?

The workarounds are quite nice, but don't cover all use cases.
Besides rows setting I need to persist column visibility, order and width. For width there isn't an API as far as I could see in the typings, so I iterate over the columns DOM and get them by clientWidth, which is quite ugly and it's not that reliable I think. Also widths behave weirdly on resetting my config for table and some columns with fixed set width through style property grow ridiculously large on reset.

cagataycivici wrote:

Planned for 2.1 due late march.

primeng is on 4.2.2 now, is there a new concrete milestone for this on the horizon?
Don't like to annoy you, I know it's open source, but it would be quite unsatisfactory to implement it on my own if this comes anytime soon.

Really love your table otherwise and would appreciate an answer :)

Edit:
Okay, since my Product Owner wants this feature ASAP, I'll try to implement it, is there any place to start besides the table source file (and of course the official documentation)? Any Developer Documentation, some coding conventions to follow, so I could make a PR some day?

MiniWolskys commented 6 years ago

Hello @fberthel

Did you find a workaround for persistant width, visibility and order of columns ? I'm having the same issue, and I have implemented a similar solution for column width, but I can't seems to find a way to implement persistent order other than rendering datatable everytime. (I don't want to use Javascript method in my typescript file...).

Thanks for the help :) This is a great datatable overall, I just thinks it's missing some features.

Steven-Harris commented 6 years ago

@fberthel, I second that. Did you get it working?

Chi1985 commented 6 years ago

I couldn't get the filters working. Though i can retrieve the filter value and the corresponding column. The filter is not getting applied to the datatable. filters:FilterMetadata={};

while storing this.dataTableState={ groupField:this.dataTable.groupField, multiSortMeta:this.dataTable.multiSortMeta, filters:this.dataTable.filters }; while retrieving, I'm using this.dataTable.filters=this.dataTableState.filters;

fberthel commented 6 years ago

Hey guys,

Sorry for letting you down so long, had tons of work to do. I'll try to make it up with you.

We didn't extend the DataTable itself, but build some code around it so it works for us.

I've made a StackBlitz project where you can change width and order of columns, also visibility, but it messes around with the width of columns, don't know what's going on there. (Will have a look into the docs later) https://angular-primeng-tableconfig-demo.stackblitz.io

Feel free to play around, fork it, extend it, etc pp.

Side note: We got it working for our solution, but I can't post the exact code, on the one hand it's business code, and on the other hand it would be way to much to have a chance to understand it. It's still quite complex and not really minimal. The Visibility issues in the StackBlitz project are only there, in our solution it's working, but i can't tell why. Like I said, way more code than here, I'll try to figure it out and boil it down.. Also Pagination with page-size is not there and persisted now, I'll try to add that, again, it's different in our own project as we need full access to pagination (server-side-pagination) and therefore we needed to make it external, as the API through the DataTable is not nearly as large as for an isolated paginator component..

It doesn't feel good to add tons of "exoskeleton"-code to the DataTable, we didn't know how to include it. Like I said we also need server-side-features, so all filters, sort methods, data and pagination APIs would need rework, which would result in a huge PR. I hope the new TurboTable (nice name btw 😄 ) will get some of those extensions in the future. Also I dont like how we are iterating over DOM-Nodes in terms of widths calculation and setting it, but that was the way it worked, so don't be too harsh on us please 🤓

Hope this attempt will help some of you.

Edit: Found out that visibility issue is something we fixed directly in primeng but didn't pass in as Pull Request. PR probably wouldn't be accepted as DataTable is already deprecated.

diff --git a/src/app/components/datatable/datatable.ts b/src/app/components/datatable/datatable.ts
index 6073cc5..82fe13f 100644
--- a/src/app/components/datatable/datatable.ts
+++ b/src/app/components/datatable/datatable.ts
@@ -2196,9 +2196,12 @@ export class DataTable implements AfterViewChecked,AfterViewInit,AfterContentIni
        if(columnWidth + delta > parseInt(minWidth)) {
            if(this.columnResizeMode === 'fit') {
                let nextColumn = this.resizeColumn.nextElementSibling;
+                while(nextColumn.classList.contains("ui-helper-hidden")){
+                  nextColumn = nextColumn.nextElementSibling;
+                }
                let nextColumnWidth = nextColumn.offsetWidth - delta;

-                if(newColumnWidth > 15 && nextColumnWidth > 15) {
+                if(nextColumn && newColumnWidth > 15 && nextColumnWidth > 15) {
raj111 commented 6 years ago

Hi, any update on this feature.

fberthel commented 6 years ago

I've updated the StackBlitz to use the TurboTable, there is no workaroung needed anymore for columns widths if I've looked at it correctly. Otherwise the limitations still stand and it's still just external code.

Demo: https://angular-primeng-tableconfig-demo.stackblitz.io Code: https://stackblitz.com/edit/angular-primeng-tableconfig-demo

features so far:

Still quite far away from the inital post state requirements though..

Thanks for the great table guys 🎉

nandeshnair commented 6 years ago

Hello! Has any progress been made on this feature?

cagataycivici commented 6 years ago

Scheduled for 6.1.

nandeshnair commented 6 years ago

Thank you for your prompt reply. Eagerly awaiting this feature. Any tentative dates for 6.1.0?

ghost commented 6 years ago

Hi there !

Do we have any update within due date of Roadmap point: "State saving featuring across views"? I'm really looking forward to use the solution in PROD project.

BTW. do you know any working TurboTable workaround to save / apply filters state? If you can advise me somehow which method / strategy can be used to apply previously saved filters into the UI controls lik dropdowns, inputs etc., would be grateful.

AlejandroFlorin commented 6 years ago

When implementing the feature, please make sure to include the sorting and paging state as well.

I did implement a workaround for storing the filter state between page views (though not paging nor sorting). Basically:

Edit: You can also store the current page state by storing the table's first property in session and setting it during ngOnInit as well. I just implemented it and it works.

ghost commented 6 years ago

@AlejandroFlorin , could you please share your workaround solution of applying filters state to the TurboTable?

AlejandroFlorin commented 6 years ago

@emilwaw This is an untested trimmed down version of my code. but it should give you the idea.

Edit: Made a couple of corrections. I missed the pagingFirstIndex in a couple of places.

Edit2: Simplification - Turns out don't need this.pagingFirstIndex nor the onPage handler

Provide the guard service in AppModule

//app-module.ts
import { DeactivateGuardService } from 'app/core/services/deactivate-guard.service';
...

@NgModule({
    declarations: [...],
    imports: [...],
    providers: [
        ...,
        DeactivateGuardService]
})
export class AppModule {
}

Define the route guard:

//deativate-guard.service.ts

import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/observable';

export interface CanComponentDeactivate {
    canDeactivate: (
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState?: RouterStateSnapshot
    ) => Observable<boolean> | Promise<boolean> | boolean;
}

@Injectable()
export class DeactivateGuardService implements CanDeactivate<CanComponentDeactivate>{

    canDeactivate(
        component: CanComponentDeactivate,
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState?: RouterStateSnapshot
    ) {
        return component.canDeactivate ? component.canDeactivate(currentRoute, currentState, nextState) : true;
    }
}

Enable the deactivate guard service in your module's routes:

//table-state-store.module.ts
//Angular
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';

//Vendors
import { InputTextModule } from 'primeng/primeng';
import { TableModule } from 'primeng/table';

//App
import { DeactivateGuardService } from "[path]/deactivate-guard.service";

const routes: Routes = [
    {
        path: '',
        canDeactivate: [DeactivateGuardService],
        component: sample_table_state_store.component.ts,
    }
];

@NgModule({
    imports: [
        CommonModule,
        ReactiveFormsModule,
        RouterModule.forChild(routes)],
    declarations: [...],
    exports: [...]
})

export class SampleTableStateModule { }

Define the table's data set and an interface for the object that will be stored in session:

//TableData.ts

import { FilterMetadata } from 'primeng/primeng';

export interface TableData {
    id: number;
    name: string;
}

export interface TableState {
    idFilter: number;
    nameFilter: string;
    pagingFirstIndex: number;
    filters: {
        [s: string]: FilterMetadata;
    };
}

And the component:

// sample_table_state_store.component.ts

//Angular
import {
    Component,
    OnInit,
    ViewChild
} from "@angular/core";
import { Router } from '@angular/router';
import {
    FormBuilder,
    FormGroup,
    FormControl,
    FormControlName
} from '@angular/forms';
import { CanComponentDeactivate } from "app/core/services/deactivate-guard.service";

//App
import { Data, State } from "[path]/TableData";

export class SampleTableStateStore implements
    OnInit,
    CanComponentDeactivate {

    @ViewChild('myTable') myTable: Table;

    form: FormGroup;

    data: Data[]

    get idFilter(): FormControl {
        return this.form.get('idFilter') as FormControl;
    }
    get nameFilter(): FormControl {
        return this.form.get('nameFilter') as FormControl;
    }

    constructor() {
        this.form = this.fb.group({
            idFilter: [''],
            nameFilter: ['']
    }

    ngOnInit() {

        let tableState = JSON.parse(sessionStorage.getItem('TableState')) as TableState;

        if (tableState) {
            this.idFilter.setValue(tableState.id);
            this.nameFilter.setValue(tableState.name);
            this.myTable.filters = tableState.filters;
            this.myTable.first = tableState.pagingFirstIndex;
        }
    }

    canDeactivate() {

        sessionStorage.setItem(
            'TableState',
            JSON.stringify(
                {
                    id: this.idFilter.value,
                    name: this.nameFilter.value,
                    pagingFirstIndex: this.myTable.first,
                    filters: this.myTable.filters
                } as TableState)
        );

        return true;
    }

   //rest of the component code, including filling the table Data array
    ...

}
umdstu commented 6 years ago

@cagataycivici What does it mean that this was removed from the milestone/version again?

Thanks!

ghost commented 6 years ago

@cagataycivici, what is the next alternative date of potential TableState functionality implementation ? Do you plan to implement it at all ?

cagataycivici commented 6 years ago

Yes we'll work on it for 7.0. It will be one of the highlight of 7.0 in terms of new stuff.

cagataycivici commented 6 years ago

This is happening but should we use sessionstorage, localstorage or a service for this? We can't decide over here :). Any ideas?

umdstu commented 6 years ago

When I implemented this with angularjs's ui-grid, I used localStorage. My customers want their states to be there when they come back another day. sessionStorage is simply too short lived for this kind of thing. And technically if you used localStorage, you could also use an expiration date kinda deal, so the developer could choose how long it's stored. But that's not really common i'd guess. That said, the option/ability to store this server-side would also be nice, via a provided callback of some sort.

Just my .02 cents.

AlejandroFlorin commented 6 years ago

I implemented it using Session Storage as we didn't need it to persist between browser sessions but local storage gives devs the most options as they can clear it on logout if they want. A service would lose state between browser windows so that would be the most restrictive. I vote Local Storage since it is the most flexible IMO.

bossqone commented 6 years ago

What about providing some storage service interface (with default implementation e.g. localstorage) which could be implemented by user when needed, and overridden at DI level (whole app / per module / per component)?

I also suggest to make this feature optional (disabled by default), because from my perspective table should contain minimal (or none) internal state.

nandeshnair commented 6 years ago

local storage please. Kind Regards,

Nandesh

On Wed, 24 Oct 2018 at 18:36, Lukáš Kováč notifications@github.com wrote:

What about providing some storage service interface (with default implementation e.g. localstorage) which could be implemented by user when needed, and overridden at DI level (whole app / per module / per component)?

I also suggest to make this feature optional (disabled by default), because from my perspective table should contain minimal (or none) internal state.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/primefaces/primeng/issues/304#issuecomment-432732504, or mute the thread https://github.com/notifications/unsubscribe-auth/ALEgmgf-5YiYHbWouGmtg2qG0o-bvitJks5uoJb8gaJpZM4IWkG6 .

umdstu commented 6 years ago

As part of this, would it make sense to add a new supported state to the table, specifically column visibility? Right now it's only possible by ngIf'ing the column. If one were to add a way for users to hide columns (outside of p-table), there would likely not be a way to pass that into whatever state is saved as it stands.

AizeLeOuf commented 6 years ago

2 years for a mandatory feature.. who use a table with filters without saving it ? :/

cagataycivici commented 5 years ago

In progress...

malbarmavi commented 5 years ago

Is this issue include the state of columns resize ? I have manage the state of order and toggle but I can't find any solution for resize

https://stackblitz.com/edit/angular-primeng-table-order-resize-toggle

cagataycivici commented 5 years ago

Implemented now, usage is simple as defining stateStorage to selected where to keep it e..g sessionStorage or localStorage along with the state key;

<p-table[columns]="cols" [value]="cars" stateStorage="session | local" stateKey="key1">

Currently supported features that can be stateful are;

I'll mark it as resolved after doing the docs tomorrow. Thank you for all the feedback on the initial version.

cagataycivici commented 5 years ago

Ready for a test drive in upcoming 7.0.0-RC1.