Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Stateful TreeTable #6384

Merged
merged 1 commit into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 61 additions & 0 deletions components/doc/common/apidoc/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -54672,6 +54672,22 @@
"default": "",
"description": "Order to sort the data by default."
},
{
"name": "stateKey",
"optional": true,
"readonly": false,
"type": "string",
"default": "",
"description": "Unique identifier of a stateful table to use in state storage."
},
{
"name": "stateStorage",
"optional": true,
"readonly": false,
"type": "\"custom\" | \"local\" | \"session\"",
"default": "session",
"description": "Defines where a stateful table keeps its state, valid values are \"session\" for sessionStorage, \"local\" for localStorage and \"custom\"."
},
{
"name": "stripedRows",
"optional": true,
Expand Down Expand Up @@ -54749,6 +54765,25 @@
"callbacks": {
"description": "Defines callbacks that determine the behavior of the component based on a given condition or report the actions that the component takes.",
"values": [
{
"name": "customRestoreState",
"parameters": [],
"returnType": "undefined | object",
"description": "A function to implement custom restoreState with stateStorage=\"custom\". Need to return state object."
},
{
"name": "customSaveState",
"parameters": [
{
"name": "state",
"optional": false,
"type": "object",
"description": "The object to be stored."
}
],
"returnType": "void",
"description": "A function to implement custom saveState with stateStorage=\"custom\"."
},
{
"name": "onCollapse",
"parameters": [
Expand Down Expand Up @@ -54931,6 +54966,32 @@
"returnType": "void",
"description": "Callback to invoke on sort."
},
{
"name": "onStateRestore",
"parameters": [
{
"name": "state",
"optional": false,
"type": "object",
"description": "Table state."
}
],
"returnType": "void",
"description": "Callback to invoke table state is restored."
},
{
"name": "onStateSave",
"parameters": [
{
"name": "state",
"optional": false,
"type": "object",
"description": "Table state."
}
],
"returnType": "void",
"description": "Callback to invoke table state is saved."
},
{
"name": "onToggle",
"parameters": [
Expand Down
125 changes: 125 additions & 0 deletions components/doc/treetable/statefuldoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { DocSectionCode } from '@/components/doc/common/docsectioncode';
import { DocSectionText } from '@/components/doc/common/docsectiontext';
import { Column } from '@/components/lib/column/Column';
import { TreeTable } from '@/components/lib/treetable/TreeTable';
import { useEffect, useState } from 'react';
import { NodeService } from '../../../service/NodeService';

export function StatefulDoc(props) {
const [nodes, setNodes] = useState([]);

useEffect(() => {
NodeService.getTreeTableNodes().then((data) => setNodes(data));
}, []); // eslint-disable-line react-hooks/exhaustive-deps

const code = {
basic: `
<TreeTable value={nodes} stateKey={'tree-table-state-demo-session'} stateStorage={'session'}>
<Column field="name" header="Name" expander filter filterPlaceholder="Filter by name"></Column>
<Column field="size" header="Size" filter filterPlaceholder="Filter by size"></Column>
<Column field="type" header="Type" filter filterPlaceholder="Filter by type"></Column>
</TreeTable>
`,
javascript: `
import React, { useState, useEffect } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { NodeService } from './service/NodeService';

export default function StatefulDemo() {
const [nodes, setNodes] = useState([]);

useEffect(() => {
NodeService.getTreeTableNodes().then((data) => setNodes(data));
}, []);

return (
<div className="card">
<TreeTable value={nodes} tableStyle={{ minWidth: '50rem' }} stateKey={'tree-table-state-demo-session'} stateStorage={'session'}
paginator rows={5} rowsPerPageOptions={[5, 10, 25]}>
<Column field="name" header="Name" expander filter filterPlaceholder="Filter by name"></Column>
<Column field="size" header="Size" filter filterPlaceholder="Filter by size"></Column>
<Column field="type" header="Type" filter filterPlaceholder="Filter by type"></Column>
</TreeTable>
</div>
)
}
`,
typescript: `
import React, { useState, useEffect } from 'react';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
import { TreeNode } from 'primereact/treenode';
import { NodeService } from './service/NodeService';

export default function StatefulDemo() {
const [nodes, setNodes] = useState<TreeNode[]>([]);

useEffect(() => {
NodeService.getTreeTableNodes().then((data) => setNodes(data));
}, []);



return (
<div className="card">
<TreeTable value={nodes} tableStyle={{ minWidth: '50rem' }} stateKey={'tree-table-state-demo-session'} stateStorage={'session'}
paginator rows={5} rowsPerPageOptions={[5, 10, 25]}>
<Column field="name" header="Name" expander filter filterPlaceholder="Filter by name"></Column>
<Column field="size" header="Size" filter filterPlaceholder="Filter by size"></Column>
<Column field="type" header="Type" filter filterPlaceholder="Filter by type"></Column>
</TreeTable>
</div>
)
}
`,
data: `
{
key: '0',
label: 'Documents',
data: 'Documents Folder',
icon: 'pi pi-fw pi-inbox',
children: [
{
key: '0-0',
label: 'Work',
data: 'Work Folder',
icon: 'pi pi-fw pi-cog',
children: [
{ key: '0-0-0', label: 'Expenses.doc', icon: 'pi pi-fw pi-file', data: 'Expenses Document' },
{ key: '0-0-1', label: 'Resume.doc', icon: 'pi pi-fw pi-file', data: 'Resume Document' }
]
},
{
key: '0-1',
label: 'Home',
data: 'Home Folder',
icon: 'pi pi-fw pi-home',
children: [{ key: '0-1-0', label: 'Invoices.txt', icon: 'pi pi-fw pi-file', data: 'Invoices for this month' }]
}
]
},
...
`
};

return (
<>
<DocSectionText {...props}>
<p>Stateful table allows keeping the state such as page, sort and filtering either at local storage or session storage so that when the page is visited again, table would render the data using the last settings.</p>
<p>
Change the state of the table e.g paginate or expand rows, navigate away and then return to this table again to test this feature. The setting is set as <i>session</i> with the <i>stateStorage</i> property so that Table retains
the state until the browser is closed. Other alternative is <i>local</i> referring to <i>localStorage</i> for an extended lifetime.
</p>
</DocSectionText>
<div className="card">
<TreeTable value={nodes} tableStyle={{ minWidth: '50rem' }} stateKey={'tree-table-state-demo-session'} stateStorage={'session'} paginator rows={5} rowsPerPageOptions={[5, 10, 25]}>
<Column field="name" header="Name" expander filter filterPlaceholder="Filter by name" sortable />
<Column field="size" header="Size" filter filterPlaceholder="Filter by size" sortable />
<Column field="type" header="Type" filter filterPlaceholder="Filter by type" sortable />
</TreeTable>
</div>
<DocSectionCode code={code} service={['NodeService']} />
</>
);
}
25 changes: 5 additions & 20 deletions components/lib/datatable/DataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DataTableBase } from './DataTableBase';
import { TableBody } from './TableBody';
import { TableFooter } from './TableFooter';
import { TableHeader } from './TableHeader';
import { getStorage } from '../../utils/utils';

export const DataTable = React.forwardRef((inProps, ref) => {
const context = React.useContext(PrimeReactContext);
Expand Down Expand Up @@ -175,24 +176,8 @@ export const DataTable = React.forwardRef((inProps, ref) => {
return columns;
};

const getStorage = () => {
switch (props.stateStorage) {
case 'local':
return window.localStorage;

case 'session':
return window.sessionStorage;

case 'custom':
return null;

default:
throw new Error(props.stateStorage + ' is not a valid value for the state storage, supported values are "local", "session" and "custom".');
}
};

const saveState = () => {
let state = {};
const state = {};

if (props.paginator) {
state.first = getFirst();
Expand Down Expand Up @@ -237,7 +222,7 @@ export const DataTable = React.forwardRef((inProps, ref) => {
props.customSaveState(state);
}
} else {
const storage = getStorage();
const storage = getStorage(props.stateStorage);

if (ObjectUtils.isNotEmpty(state)) {
storage.setItem(props.stateKey, JSON.stringify(state));
Expand All @@ -250,7 +235,7 @@ export const DataTable = React.forwardRef((inProps, ref) => {
};

const clearState = () => {
const storage = getStorage();
const storage = getStorage(props.stateStorage);

if (storage && props.stateKey) {
storage.removeItem(props.stateKey);
Expand All @@ -265,7 +250,7 @@ export const DataTable = React.forwardRef((inProps, ref) => {
restoredState = props.customRestoreState();
}
} else {
const storage = getStorage();
const storage = getStorage(props.stateStorage);
const stateString = storage.getItem(props.stateKey);
const dateFormat = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;

Expand Down