浏览代码

Page key should integrate functions (#1855)

* Page key should integrate functions

* test

* test

* test

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 7 月之前
父节点
当前提交
9a5a6c5018

+ 44 - 44
frontend/taipy-gui/package-lock.json

@@ -2448,44 +2448,44 @@
       }
     },
     "node_modules/@shikijs/core": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.20.0.tgz",
-      "integrity": "sha512-KlO3iE0THzSdYkzDFugt8SHe6FR3qNYTkmpbdW1d6xo8juQkMjybxAw/cBi2npL2eb2F4PbbnSs5Z9tDusfvyg==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.21.0.tgz",
+      "integrity": "sha512-zAPMJdiGuqXpZQ+pWNezQAk5xhzRXBNiECFPcJLtUdsFM3f//G95Z15EHTnHchYycU8kIIysqGgxp8OVSj1SPQ==",
       "dev": true,
       "dependencies": {
-        "@shikijs/engine-javascript": "1.20.0",
-        "@shikijs/engine-oniguruma": "1.20.0",
-        "@shikijs/types": "1.20.0",
+        "@shikijs/engine-javascript": "1.21.0",
+        "@shikijs/engine-oniguruma": "1.21.0",
+        "@shikijs/types": "1.21.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "@types/hast": "^3.0.4",
         "hast-util-to-html": "^9.0.3"
       }
     },
     "node_modules/@shikijs/engine-javascript": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.20.0.tgz",
-      "integrity": "sha512-ZUMo758uduM0Tfgzi/kd+0IKMbNdumCxxWjY36uf1DIs2Qyg9HIq3vA1Wfa/vc6HE7tHWFpANRi3mv7UzJ68MQ==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.21.0.tgz",
+      "integrity": "sha512-jxQHNtVP17edFW4/0vICqAVLDAxmyV31MQJL4U/Kg+heQALeKYVOWo0sMmEZ18FqBt+9UCdyqGKYE7bLRtk9mg==",
       "dev": true,
       "dependencies": {
-        "@shikijs/types": "1.20.0",
+        "@shikijs/types": "1.21.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "oniguruma-to-js": "0.4.3"
       }
     },
     "node_modules/@shikijs/engine-oniguruma": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.20.0.tgz",
-      "integrity": "sha512-MQ40WkVTZk7by33ces4PGK6XNFSo6PYvKTSAr2kTWdRNhFmOcnaX+1XzvFwB26eySXR7U74t91czZ1qJkEgxTA==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.21.0.tgz",
+      "integrity": "sha512-AIZ76XocENCrtYzVU7S4GY/HL+tgHGbVU+qhiDyNw1qgCA5OSi4B4+HY4BtAoJSMGuD/L5hfTzoRVbzEm2WTvg==",
       "dev": true,
       "dependencies": {
-        "@shikijs/types": "1.20.0",
+        "@shikijs/types": "1.21.0",
         "@shikijs/vscode-textmate": "^9.2.2"
       }
     },
     "node_modules/@shikijs/types": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.20.0.tgz",
-      "integrity": "sha512-y+EaDvU2K6/GaXOKXxJaGnr1XtmZMF7MfS0pSEDdxEq66gCtKsLwQvVwoQFdp7R7dLlNAro3ijEE19sMZ0pzqg==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.21.0.tgz",
+      "integrity": "sha512-tzndANDhi5DUndBtpojEq/42+dpUF2wS7wdCDQaFtIXm3Rd1QkrcVgSSRLOvEwexekihOXfbYJINW37g96tJRw==",
       "dev": true,
       "dependencies": {
         "@shikijs/vscode-textmate": "^9.2.2",
@@ -3099,9 +3099,9 @@
       "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
     },
     "node_modules/@types/node": {
-      "version": "20.16.9",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.9.tgz",
-      "integrity": "sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w==",
+      "version": "20.16.10",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
+      "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
       "dependencies": {
         "undici-types": "~6.19.2"
       }
@@ -3128,9 +3128,9 @@
       "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
     },
     "node_modules/@types/react": {
-      "version": "18.3.9",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz",
-      "integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==",
+      "version": "18.3.10",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
+      "integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
       "dependencies": {
         "@types/prop-types": "*",
         "csstype": "^3.0.2"
@@ -8137,9 +8137,9 @@
       }
     },
     "node_modules/html-url-attributes": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz",
-      "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+      "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/unified"
@@ -12726,9 +12726,9 @@
       }
     },
     "node_modules/nwsapi": {
-      "version": "2.2.12",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz",
-      "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==",
+      "version": "2.2.13",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz",
+      "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==",
       "dev": true
     },
     "node_modules/object-assign": {
@@ -14453,15 +14453,15 @@
       }
     },
     "node_modules/shiki": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.20.0.tgz",
-      "integrity": "sha512-MZJJ1PCFsQB1Piq+25wiz0a75yUv8Q3/fzy7SzRx5ONdjdtGdyiKwYn8vb/FnK5kjS0voWGnPpjG16POauUR+g==",
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.21.0.tgz",
+      "integrity": "sha512-apCH5BoWTrmHDPGgg3RF8+HAAbEL/CdbYr8rMw7eIrdhCkZHdVGat5mMNlRtd1erNG01VPMIKHNQ0Pj2HMAiog==",
       "dev": true,
       "dependencies": {
-        "@shikijs/core": "1.20.0",
-        "@shikijs/engine-javascript": "1.20.0",
-        "@shikijs/engine-oniguruma": "1.20.0",
-        "@shikijs/types": "1.20.0",
+        "@shikijs/core": "1.21.0",
+        "@shikijs/engine-javascript": "1.21.0",
+        "@shikijs/engine-oniguruma": "1.21.0",
+        "@shikijs/types": "1.21.0",
         "@shikijs/vscode-textmate": "^9.2.2",
         "@types/hast": "^3.0.4"
       }
@@ -15125,9 +15125,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.34.0",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.0.tgz",
-      "integrity": "sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==",
+      "version": "5.34.1",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz",
+      "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==",
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
         "acorn": "^8.8.2",
@@ -15948,9 +15948,9 @@
       "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg=="
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
-      "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
+      "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
       "funding": [
         {
           "type": "opencollective",
@@ -15966,8 +15966,8 @@
         }
       ],
       "dependencies": {
-        "escalade": "^3.1.2",
-        "picocolors": "^1.0.1"
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.0"
       },
       "bin": {
         "update-browserslist-db": "cli.js"

+ 1 - 1
frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.spec.tsx

@@ -28,7 +28,7 @@ jest.mock(
             children({ height: 600, width: 600 })
 );
 
-const valueKey = "Infinite-Entity--asc";
+const valueKey = "Infinite-Entity-asc";
 const tableValue = {
     [valueKey]: {
         data: [

+ 4 - 12
frontend/taipy-gui/src/components/Taipy/AutoLoadingTable.tsx

@@ -39,6 +39,7 @@ import {
 } from "../../context/taipyReducers";
 import {
     ColumnDesc,
+    FilterDesc,
     getSortByIndex,
     Order,
     TaipyTableProps,
@@ -64,6 +65,7 @@ import {
     OnRowClick,
     DownloadAction,
     getFormatFn,
+    getPageKey,
 } from "./tableUtils";
 import {
     useClassNames,
@@ -74,7 +76,7 @@ import {
     useFormatConfig,
     useModule,
 } from "../../utils/hooks";
-import TableFilter, { FilterDesc } from "./TableFilter";
+import TableFilter from "./TableFilter";
 import { getSuffixedClassNames, getUpdateVar } from "./utils";
 import { emptyArray } from "../../utils";
 
@@ -383,19 +385,9 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                 page.current.promises[startIndex].reject();
             }
             return new Promise<void>((resolve, reject) => {
-                const agg = aggregates.length
-                    ? colsOrder.reduce((pv, col, idx) => {
-                          if (aggregates.includes(columns[col].dfid)) {
-                              return pv + "-" + idx;
-                          }
-                          return pv;
-                      }, "-agg")
-                    : "";
                 const cols = colsOrder.map((col) => columns[col].dfid).filter((c) => c != EDIT_COL);
                 const afs = appliedFilters.filter((fd) => Object.values(columns).some((cd) => cd.dfid === fd.col));
-                const key = `Infinite-${cols.join()}-${orderBy}-${order}${agg}${afs.map(
-                    (af) => `${af.col}${af.action}${af.value}`
-                )}`;
+                const key = getPageKey(columns, "Infinite", cols, orderBy, order, afs, aggregates, styles, tooltips, formats);
                 page.current = {
                     key: key,
                     promises: { ...page.current.promises, [startIndex]: { resolve: resolve, reject: reject } },

+ 4 - 4
frontend/taipy-gui/src/components/Taipy/PaginatedTable.spec.tsx

@@ -21,7 +21,7 @@ import { TaipyContext } from "../../context/taipyContext";
 import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
 import { TableValueType } from "./tableUtils";
 
-const valueKey = "0-99-Entity,Daily hospital occupancy--asc";
+const valueKey = "0-99-Entity,Daily hospital occupancy-asc";
 const tableValue = {
     [valueKey]: {
         data: [
@@ -110,7 +110,7 @@ const changedValue = {
 };
 
 const editableValue = {
-    "0--1-bool,int,float,Code--asc": {
+    "0--1-bool,int,float,Code-asc": {
         data: [
             {
                 bool: true,
@@ -137,7 +137,7 @@ const editableColumns = JSON.stringify({
 });
 
 const buttonImgValue = {
-    "0--1-bool,int,float,Code--asc": {
+    "0--1-bool,int,float,Code-asc": {
         data: [
             {
                 bool: true,
@@ -311,7 +311,7 @@ describe("PaginatedTable Component", () => {
                 end: 199,
                 id: "table",
                 orderby: "",
-                pagekey: "100-199-Entity,Daily hospital occupancy--asc",
+                pagekey: "100-199-Entity,Daily hospital occupancy-asc",
                 handlenan: false,
                 sort: "asc",
                 start: 100,

+ 94 - 103
frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx

@@ -70,6 +70,8 @@ import {
     OnRowClick,
     DownloadAction,
     getFormatFn,
+    getPageKey,
+    FilterDesc,
 } from "./tableUtils";
 import {
     useClassNames,
@@ -80,7 +82,7 @@ import {
     useFormatConfig,
     useModule,
 } from "../../utils/hooks";
-import TableFilter, { FilterDesc } from "./TableFilter";
+import TableFilter from "./TableFilter";
 import { getSuffixedClassNames, getUpdateVar } from "./utils";
 import { emptyArray } from "../../utils";
 
@@ -133,99 +135,98 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const baseColumns = useDynamicJsonProperty(props.columns, props.defaultColumns, defaultColumns);
 
-    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable, nbWidth] =
-        useMemo(() => {
-            let hNan = !!props.nanValue;
-            if (baseColumns) {
-                try {
-                    let filter = false;
-                    let partialEditable = editable;
-                    const newCols: Record<string, ColumnDesc> = {};
-                    Object.entries(baseColumns).forEach(([cId, cDesc]) => {
-                        const nDesc = (newCols[cId] = { ...cDesc });
-                        if (typeof nDesc.filter != "boolean") {
-                            nDesc.filter = !!props.filter;
-                        }
-                        filter = filter || nDesc.filter;
-                        if (typeof nDesc.notEditable == "boolean") {
-                            partialEditable = partialEditable || !nDesc.notEditable;
-                        } else {
-                            nDesc.notEditable = !editable;
-                        }
-                        if (nDesc.tooltip === undefined) {
-                            nDesc.tooltip = props.tooltip;
-                        }
-                    });
-                    addActionColumn(
-                        (active && partialEditable && (onAdd || onDelete) ? 1 : 0) +
-                            (active && filter ? 1 : 0) +
-                            (active && downloadable ? 1 : 0),
-                        newCols
-                    );
-                    const colsOrder = Object.keys(newCols).sort(getSortByIndex(newCols));
-                    let nbWidth = 0;
-                    const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
-                        if (newCols[col].style) {
-                            pv.styles = pv.styles || {};
-                            pv.styles[newCols[col].dfid] = newCols[col].style;
-                        }
-                        hNan = hNan || !!newCols[col].nanValue;
-                        if (newCols[col].tooltip) {
-                            pv.tooltips = pv.tooltips || {};
-                            pv.tooltips[newCols[col].dfid] = newCols[col].tooltip;
-                        }
-                        if (newCols[col].formatFn) {
-                            pv.formats = pv.formats || {};
-                            pv.formats[newCols[col].dfid] = newCols[col].formatFn;
-                        }
-                        if (newCols[col].width !== undefined) {
-                            nbWidth ++;
-                        }
-                        return pv;
-                    }, {});
-                    nbWidth = colsOrder.length - nbWidth;
-                    if (props.lineStyle) {
-                        styTt.styles = styTt.styles || {};
-                        styTt.styles[LINE_STYLE] = props.lineStyle;
+    const [colsOrder, columns, styles, tooltips, formats, handleNan, filter, partialEditable, nbWidth] = useMemo(() => {
+        let hNan = !!props.nanValue;
+        if (baseColumns) {
+            try {
+                let filter = false;
+                let partialEditable = editable;
+                const newCols: Record<string, ColumnDesc> = {};
+                Object.entries(baseColumns).forEach(([cId, cDesc]) => {
+                    const nDesc = (newCols[cId] = { ...cDesc });
+                    if (typeof nDesc.filter != "boolean") {
+                        nDesc.filter = !!props.filter;
+                    }
+                    filter = filter || nDesc.filter;
+                    if (typeof nDesc.notEditable == "boolean") {
+                        partialEditable = partialEditable || !nDesc.notEditable;
+                    } else {
+                        nDesc.notEditable = !editable;
+                    }
+                    if (nDesc.tooltip === undefined) {
+                        nDesc.tooltip = props.tooltip;
+                    }
+                });
+                addActionColumn(
+                    (active && partialEditable && (onAdd || onDelete) ? 1 : 0) +
+                        (active && filter ? 1 : 0) +
+                        (active && downloadable ? 1 : 0),
+                    newCols
+                );
+                const colsOrder = Object.keys(newCols).sort(getSortByIndex(newCols));
+                let nbWidth = 0;
+                const styTt = colsOrder.reduce<Record<string, Record<string, string>>>((pv, col) => {
+                    if (newCols[col].style) {
+                        pv.styles = pv.styles || {};
+                        pv.styles[newCols[col].dfid] = newCols[col].style;
+                    }
+                    hNan = hNan || !!newCols[col].nanValue;
+                    if (newCols[col].tooltip) {
+                        pv.tooltips = pv.tooltips || {};
+                        pv.tooltips[newCols[col].dfid] = newCols[col].tooltip;
+                    }
+                    if (newCols[col].formatFn) {
+                        pv.formats = pv.formats || {};
+                        pv.formats[newCols[col].dfid] = newCols[col].formatFn;
                     }
-                    return [
-                        colsOrder,
-                        newCols,
-                        styTt.styles,
-                        styTt.tooltips,
-                        styTt.formats,
-                        hNan,
-                        filter,
-                        partialEditable,
-                        nbWidth,
-                    ];
-                } catch (e) {
-                    console.info("PaginatedTable.columns: ", (e as Error).message || e);
+                    if (newCols[col].width !== undefined) {
+                        nbWidth++;
+                    }
+                    return pv;
+                }, {});
+                nbWidth = colsOrder.length - nbWidth;
+                if (props.lineStyle) {
+                    styTt.styles = styTt.styles || {};
+                    styTt.styles[LINE_STYLE] = props.lineStyle;
                 }
+                return [
+                    colsOrder,
+                    newCols,
+                    styTt.styles,
+                    styTt.tooltips,
+                    styTt.formats,
+                    hNan,
+                    filter,
+                    partialEditable,
+                    nbWidth,
+                ];
+            } catch (e) {
+                console.info("PaginatedTable.columns: ", (e as Error).message || e);
             }
-            return [
-                [] as string[],
-                {} as Record<string, ColumnDesc>,
-                {} as Record<string, string>,
-                {} as Record<string, string>,
-                {} as Record<string, string>,
-                hNan,
-                false,
-                false,
-                0,
-            ];
-        }, [
-            active,
-            editable,
-            onAdd,
-            onDelete,
-            baseColumns,
-            props.lineStyle,
-            props.tooltip,
-            props.nanValue,
-            props.filter,
-            downloadable,
-        ]);
+        }
+        return [
+            [] as string[],
+            {} as Record<string, ColumnDesc>,
+            {} as Record<string, string>,
+            {} as Record<string, string>,
+            {} as Record<string, string>,
+            hNan,
+            false,
+            false,
+            0,
+        ];
+    }, [
+        active,
+        editable,
+        onAdd,
+        onDelete,
+        baseColumns,
+        props.lineStyle,
+        props.tooltip,
+        props.nanValue,
+        props.filter,
+        downloadable,
+    ]);
 
     useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
 
@@ -252,19 +253,9 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
 
     useEffect(() => {
         const endIndex = showAll ? -1 : startIndex + rowsPerPage - 1;
-        const agg = aggregates.length
-            ? colsOrder.reduce((pv, col, idx) => {
-                  if (aggregates.includes(columns[col].dfid)) {
-                      return pv + "-" + idx;
-                  }
-                  return pv;
-              }, "-agg")
-            : "";
         const cols = colsOrder.map((col) => columns[col].dfid).filter((c) => c != EDIT_COL);
         const afs = appliedFilters.filter((fd) => Object.values(columns).some((cd) => cd.dfid === fd.col));
-        pageKey.current = `${startIndex}-${endIndex}-${cols.join()}-${orderBy}-${order}${agg}${afs.map(
-            (af) => `${af.col}${af.action}${af.value}`
-        )}`;
+        pageKey.current = getPageKey(columns, `${startIndex}-${endIndex}`, cols, orderBy, order, afs, aggregates, styles, tooltips, formats);
         if (refresh || !props.data || props.data[pageKey.current] === undefined) {
             setLoading(true);
             const applies = aggregates.length
@@ -514,7 +505,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                                                 columns[col].width
                                                     ? { width: columns[col].width }
                                                     : nbWidth
-                                                    ? { width: `${100/nbWidth}%`, maxWidth: 0 }
+                                                    ? { width: `${100 / nbWidth}%`, maxWidth: 0 }
                                                     : undefined
                                             }
                                         >

+ 2 - 9
frontend/taipy-gui/src/components/Taipy/TableFilter.tsx

@@ -30,17 +30,10 @@ import Tooltip from "@mui/material/Tooltip";
 import { DateField, LocalizationProvider } from "@mui/x-date-pickers";
 import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
 
-import { ColumnDesc, defaultDateFormat, getSortByIndex, iconInRowSx } from "./tableUtils";
+import { ColumnDesc, defaultDateFormat, getSortByIndex, iconInRowSx, FilterDesc } from "./tableUtils";
 import { getDateTime, getTypeFromDf } from "../../utils";
 import { getSuffixedClassNames } from "./utils";
 
-export interface FilterDesc {
-    col: string;
-    action: string;
-    value: string | number | boolean | Date;
-    type: string;
-}
-
 interface TableFilterProps {
     columns: Record<string, ColumnDesc>;
     colsOrder?: Array<string>;
@@ -173,7 +166,7 @@ const FilterRow = (props: FilterRowProps) => {
     );
     const onDateChange = useCallback(
         (v: Date | null) => {
-            const dv = (!(v instanceof Date) || isNaN(v.valueOf())) ?  "": v.toISOString();
+            const dv = !(v instanceof Date) || isNaN(v.valueOf()) ? "" : v.toISOString();
             setVal(dv);
             setEnableCheck(!!getFilterDesc(columns, colId, action, dv));
         },

+ 53 - 0
frontend/taipy-gui/src/components/Taipy/tableUtils.tsx

@@ -201,6 +201,13 @@ interface EditableCellProps {
     formattedVal?: string;
 }
 
+export interface FilterDesc {
+    col: string;
+    action: string;
+    value: string | number | boolean | Date;
+    type: string;
+}
+
 export const defaultColumns = {} as Record<string, ColumnDesc>;
 
 export const getSortByIndex = (cols: Record<string, ColumnDesc>) => (key1: string, key2: string) =>
@@ -278,6 +285,52 @@ export const getFormatFn = (row: Record<string, unknown>, formatFn?: string, col
 const getToolFn = (prefix: string, row: Record<string, unknown>, toolFn?: string, col?: string) =>
     toolFn ? (((col && row[`${prefix}__${col}__${toolFn}`]) || row[toolFn]) as string) : undefined;
 
+export const getPageKey = (
+    columns: Record<string, ColumnDesc>,
+    prefix: string,
+    cols: string[],
+    orderBy: string,
+    order: string,
+    filters: FilterDesc[],
+    aggregates?: string[],
+    styles?: Record<string, string>,
+    tooltips?: Record<string, string>,
+    formats?: Record<string, string>
+) =>
+    [
+        prefix,
+        cols.join(),
+        orderBy,
+        order,
+        aggregates?.length
+            ? cols.reduce((pv, col, idx) => {
+                  if (aggregates.includes(columns[col].dfid)) {
+                      return `${pv}${idx}`;
+                  }
+                  return pv;
+              }, "-")
+            : undefined,
+        filters.map((filter) => `${filter.col}${filter.action}${filter.value}`).join(),
+        [
+            styles &&
+                Object.entries(styles)
+                    .map((col, style) => `${col}:${style}`)
+                    .join(),
+            tooltips &&
+                Object.entries(tooltips)
+                    .map((col, tooltip) => `${col}:${tooltip}`)
+                    .join(),
+            formats &&
+                Object.entries(formats)
+                    .map((col, format) => `${col}:${format}`)
+                    .join(),
+        ]
+            .filter((v) => v)
+            .join(";"),
+    ]
+        .filter((v) => v)
+        .join("-");
+
 const setInputFocus = (input: HTMLInputElement) => input && input.focus();
 
 const textFieldProps = { textField: { margin: "dense" } } as BaseDateTimePickerSlotProps<Date>;

+ 1 - 1
frontend/taipy-gui/src/context/taipyReducers.ts

@@ -17,7 +17,7 @@ import merge from "lodash/merge";
 import { Dispatch } from "react";
 import { io, Socket } from "socket.io-client";
 
-import { FilterDesc } from "../components/Taipy/TableFilter";
+import { FilterDesc } from "../components/Taipy/tableUtils";
 import { stylekitModeThemes, stylekitTheme } from "../themes/stylekit";
 import { getBaseURL, TIMEZONE_CLIENT } from "../utils";
 import { parseData } from "../utils/dataFormat";

+ 2 - 1
frontend/taipy-gui/src/extensions/exports.ts

@@ -17,7 +17,8 @@ import FileSelector from "../components/Taipy/FileSelector";
 import Login from "../components/Taipy/Login";
 import Router from "../components/Router";
 import Table from "../components/Taipy/Table";
-import TableFilter, { FilterDesc } from "../components/Taipy/TableFilter";
+import TableFilter from "../components/Taipy/TableFilter";
+import { FilterDesc } from "../components/Taipy/tableUtils";
 import TableSort, { SortDesc } from "../components/Taipy/TableSort";
 import Metric from "../components/Taipy/Metric";
 import { useLovListMemo, LoV, LoVElt } from "../components/Taipy/lovUtils";

+ 1 - 1
tests/gui/server/ws/test_du.py

@@ -44,7 +44,7 @@ def test_du_table_data_fetched(gui: Gui, helpers, csvdata):
             "name": "_TpD_tpec_TpExPr_csvdata_TPMDL_0",
             "payload": {
                 "columns": ["Day", "Entity", "Code", "Daily hospital occupancy"],
-                "pagekey": "0-100--asc",
+                "pagekey": "0-100-asc",
                 "start": 0,
                 "end": 9,
                 "orderby": "",