Selaa lähdekoodia

refresh data by requesting specific variables with context (#1237)

* refresh data by requesting specific variables with context
resolves #1231

* dependencies

* typo

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
Fred Lefévère-Laoide 1 vuosi sitten
vanhempi
säilyke
edeea36500

+ 2 - 1
frontend/taipy-gui/packaging/taipy-gui.d.ts

@@ -272,7 +272,8 @@ export declare const createRequestUpdateAction: (
     id: string | undefined,
     context: string | undefined,
     names: string[],
-    forceRefresh?: boolean
+    forceRefresh?: boolean,
+    stateContext?: Record<string, unknown>
 ) => Action;
 /**
  * A column description as received by the backend.

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

@@ -406,7 +406,8 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
                         handleNan,
                         afs,
                         compare ? onCompare : undefined,
-                        updateVars && getUpdateVar(updateVars, "comparedatas")
+                        updateVars && getUpdateVar(updateVars, "comparedatas"),
+                        typeof userData == "object" ? (userData as Record<string, Record<string, unknown>>).context : undefined
                     )
                 );
             });
@@ -428,6 +429,7 @@ const AutoLoadingTable = (props: TaipyTableProps) => {
             onCompare,
             dispatch,
             module,
+            userData
         ]
     );
 

+ 3 - 1
frontend/taipy-gui/src/components/Taipy/PaginatedTable.tsx

@@ -258,7 +258,8 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
                     handleNan,
                     afs,
                     compare ? onCompare : undefined,
-                    updateVars && getUpdateVar(updateVars, "comparedatas")
+                    updateVars && getUpdateVar(updateVars, "comparedatas"),
+                    typeof userData == "object" ? (userData as Record<string, Record<string, unknown>>).context : undefined
                 )
             );
         } else {
@@ -285,6 +286,7 @@ const PaginatedTable = (props: TaipyPaginatedTableProps) => {
         module,
         compare,
         onCompare,
+        userData
     ]);
 
     const onSort = useCallback(

+ 8 - 9
frontend/taipy-gui/src/components/pages/TaipyRendered.tsx

@@ -99,16 +99,15 @@ const TaipyRendered = (props: TaipyRenderedProps) => {
                         Array.isArray(result.data.head) && setHead(result.data.head);
                     }
                 })
-                .catch((error) =>
+                .catch((error) => {
+                    const res =
+                        error.response?.data && /<p\sclass=\"errormsg\">([\s\S]*?)<\/p>/gm.exec(error.response?.data);
                     setPageState({
-                        jsx: `<h1>${
-                            error.response?.data ||
-                            `No data fetched from backend from ${
-                                path === "/TaiPy_root_page" ? baseURL : baseURL + path
-                            }`
-                        }</h1><br></br>${error}`,
-                    })
-                );
+                        jsx: `<h1>${res ? res[0] : "Unknown Error"}</h1><h2>No data fetched from backend from ${
+                            path === "/TaiPy_root_page" ? baseURL : baseURL + path
+                        }</h2><br></br>${res[0] ? "" : error}`,
+                    });
+                });
         }
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [path, state.id, dispatch, partial, fromBlock, baseURL]);

+ 24 - 9
frontend/taipy-gui/src/context/taipyReducers.ts

@@ -576,6 +576,15 @@ export const createRequestChartUpdateAction = (
         true
     );
 
+const ligtenPayload = (payload: Record<string, unknown>) => {
+    return Object.keys(payload || {}).reduce((pv, key) => {
+        if (payload[key] !== undefined) {
+            pv[key] = payload[key];
+        }
+        return pv;
+    }, {} as typeof payload)
+}
+
 export const createRequestTableUpdateAction = (
     name: string | undefined,
     id: string | undefined,
@@ -593,9 +602,10 @@ export const createRequestTableUpdateAction = (
     handleNan?: boolean,
     filters?: Array<FilterDesc>,
     compare?: string,
-    compareDatas?: string
+    compareDatas?: string,
+    stateContext?: Record<string, unknown>
 ): TaipyAction =>
-    createRequestDataUpdateAction(name, id, context, columns, pageKey, {
+    createRequestDataUpdateAction(name, id, context, columns, pageKey, ligtenPayload({
         start: start,
         end: end,
         orderby: orderBy,
@@ -608,7 +618,8 @@ export const createRequestTableUpdateAction = (
         filters: filters,
         compare: compare,
         compare_datas: compareDatas,
-    });
+        state_context: stateContext,
+    }));
 
 export const createRequestInfiniteTableUpdateAction = (
     name: string | undefined,
@@ -627,9 +638,10 @@ export const createRequestInfiniteTableUpdateAction = (
     handleNan?: boolean,
     filters?: Array<FilterDesc>,
     compare?: string,
-    compareDatas?: string
+    compareDatas?: string,
+    stateContext?: Record<string, unknown>
 ): TaipyAction =>
-    createRequestDataUpdateAction(name, id, context, columns, pageKey, {
+    createRequestDataUpdateAction(name, id, context, columns, pageKey, ligtenPayload({
         infinite: true,
         start: start,
         end: end,
@@ -643,7 +655,8 @@ export const createRequestInfiniteTableUpdateAction = (
         filters: filters,
         compare: compare,
         compare_datas: compareDatas,
-    });
+        state_context: stateContext,
+    }));
 
 /**
  * Create a *request data update* `Action` that will be used to update the `Context`.
@@ -710,16 +723,18 @@ export const createRequestUpdateAction = (
     id: string | undefined,
     context: string | undefined,
     names: string[],
-    forceRefresh = false
+    forceRefresh = false,
+    stateContext?: Record<string, unknown>
 ): TaipyAction => ({
     type: Types.RequestUpdate,
     name: "",
     context: context,
-    payload: {
+    payload: ligtenPayload({
         id: id,
         names: names,
         refresh: forceRefresh,
-    },
+        state_context: stateContext,
+    }),
 });
 
 export const createSetLocationsAction = (locations: Record<string, string>): TaipyAction => ({

+ 55 - 55
frontend/taipy/package-lock.json

@@ -1206,16 +1206,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz",
-      "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.1.tgz",
+      "integrity": "sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/type-utils": "7.7.0",
-        "@typescript-eslint/utils": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/type-utils": "7.7.1",
+        "@typescript-eslint/utils": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
@@ -1241,15 +1241,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz",
-      "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.1.tgz",
+      "integrity": "sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/typescript-estree": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/typescript-estree": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1269,13 +1269,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz",
-      "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.1.tgz",
+      "integrity": "sha512-PytBif2SF+9SpEUKynYn5g1RHFddJUcyynGpztX3l/ik7KmZEv19WCMhUBkHXPU9es/VWGD3/zg3wg90+Dh2rA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0"
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1286,13 +1286,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz",
-      "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.1.tgz",
+      "integrity": "sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.7.0",
-        "@typescript-eslint/utils": "7.7.0",
+        "@typescript-eslint/typescript-estree": "7.7.1",
+        "@typescript-eslint/utils": "7.7.1",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -1313,9 +1313,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz",
-      "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.1.tgz",
+      "integrity": "sha512-AmPmnGW1ZLTpWa+/2omPrPfR7BcbUU4oha5VIbSbS1a1Tv966bklvLNXxp3mrbc+P2j4MNOTfDffNsk4o0c6/w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1326,13 +1326,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz",
-      "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.1.tgz",
+      "integrity": "sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/visitor-keys": "7.7.0",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/visitor-keys": "7.7.1",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -1354,17 +1354,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz",
-      "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.1.tgz",
+      "integrity": "sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
         "@types/json-schema": "^7.0.15",
         "@types/semver": "^7.5.8",
-        "@typescript-eslint/scope-manager": "7.7.0",
-        "@typescript-eslint/types": "7.7.0",
-        "@typescript-eslint/typescript-estree": "7.7.0",
+        "@typescript-eslint/scope-manager": "7.7.1",
+        "@typescript-eslint/types": "7.7.1",
+        "@typescript-eslint/typescript-estree": "7.7.1",
         "semver": "^7.6.0"
       },
       "engines": {
@@ -1379,12 +1379,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz",
-      "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==",
+      "version": "7.7.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.1.tgz",
+      "integrity": "sha512-gBL3Eq25uADw1LQ9kVpf3hRM+DWzs0uZknHYK3hq4jcTPqVCClHGDnB6UUUV2SFeBeA4KWHWbbLqmbGcZ4FYbw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/types": "7.7.1",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -2077,9 +2077,9 @@
       }
     },
     "node_modules/clsx": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
-      "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
       "engines": {
         "node": ">=6"
       }
@@ -2345,9 +2345,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.745",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.745.tgz",
-      "integrity": "sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==",
+      "version": "1.4.748",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.748.tgz",
+      "integrity": "sha512-VWqjOlPZn70UZ8FTKUOkUvBLeTQ0xpty66qV0yJcAGY2/CthI4xyW9aEozRVtuwv3Kpf5xTesmJUcPwuJmgP4A==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
@@ -2465,14 +2465,14 @@
       }
     },
     "node_modules/es-iterator-helpers": {
-      "version": "1.0.18",
-      "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz",
-      "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==",
+      "version": "1.0.19",
+      "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz",
+      "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==",
       "dev": true,
       "dependencies": {
         "call-bind": "^1.0.7",
         "define-properties": "^1.2.1",
-        "es-abstract": "^1.23.0",
+        "es-abstract": "^1.23.3",
         "es-errors": "^1.3.0",
         "es-set-tostringtag": "^2.0.3",
         "function-bind": "^1.1.2",
@@ -3025,9 +3025,9 @@
       }
     },
     "node_modules/formik": {
-      "version": "2.4.5",
-      "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.5.tgz",
-      "integrity": "sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==",
+      "version": "2.4.6",
+      "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
+      "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
       "funding": [
         {
           "type": "individual",

+ 2 - 2
frontend/taipy/src/CoreSelector.tsx

@@ -233,7 +233,7 @@ const getExpandedIds = (nodeId: string, exp?: string[], entities?: Entities) =>
         const res = ret[1].map((r) => r[0]);
         return exp ? [...exp, ...res] : res;
     }
-    return exp;
+    return exp || [];
 };
 
 const CoreSelector = (props: CoreSelectorProps) => {
@@ -259,7 +259,7 @@ const CoreSelector = (props: CoreSelectorProps) => {
     const [selected, setSelected] = useState("");
     const [pins, setPins] = useState<[Pinned, Pinned]>([{}, {}]);
     const [hideNonPinned, setShowPinned] = useState(false);
-    const [expandedItems, setExpandedItems] = useState<string[]>();
+    const [expandedItems, setExpandedItems] = useState<string[]>([]);
 
     const dispatch = useDispatch();
     const module = useModule();

+ 7 - 1
frontend/taipy/src/DataNodeTable.tsx

@@ -51,6 +51,7 @@ interface DataNodeTableProps {
     editInProgress?: boolean;
     editLock: MutableRefObject<boolean>;
     editable: boolean;
+    idVar?: string;
 }
 
 const pushRightSx = { ml: "auto" };
@@ -117,7 +118,11 @@ const DataNodeTable = (props: DataNodeTableProps) => {
         [nodeId, dispatch, module, props.onLock, props.editLock]
     );
 
-    const userData = useMemo(() => ({ dn_id: nodeId, comment: "" }), [nodeId]);
+    const userData = useMemo(() => {
+        const ret: Record<string, unknown> = {dn_id: nodeId, comment: ""};
+        props.idVar && (ret.context = { [props.idVar]: nodeId });
+        return ret
+    }, [nodeId, props.idVar]);
     const [comment, setComment] = useState("");
     const changeComment = useCallback(
         (e: ChangeEvent<HTMLInputElement>) => {
@@ -198,6 +203,7 @@ const DataNodeTable = (props: DataNodeTableProps) => {
                 onEdit={tableEdit ? props.onEdit : undefined}
                 filter={true}
                 libClassName="taipy-table"
+                pageSize={25}
             />
         </>
     );

+ 341 - 209
frontend/taipy/src/DataNodeViewer.tsx

@@ -23,10 +23,10 @@ import React, {
     MouseEvent,
     useRef,
 } from "react";
-import { CheckCircle, Cancel, ArrowForwardIosSharp, Launch, LockOutlined } from "@mui/icons-material";
 import Accordion from "@mui/material/Accordion";
 import AccordionDetails from "@mui/material/AccordionDetails";
 import AccordionSummary from "@mui/material/AccordionSummary";
+import Alert from "@mui/material/Alert";
 import Box from "@mui/material/Box";
 import Divider from "@mui/material/Divider";
 import Grid from "@mui/material/Grid";
@@ -39,6 +39,13 @@ import Tabs from "@mui/material/Tabs";
 import TextField from "@mui/material/TextField";
 import Tooltip from "@mui/material/Tooltip";
 import Typography from "@mui/material/Typography";
+
+import CheckCircle from "@mui/icons-material/CheckCircle";
+import Cancel from "@mui/icons-material/Cancel";
+import ArrowForwardIosSharp from "@mui/icons-material/ArrowForwardIosSharp";
+import Launch from "@mui/icons-material/Launch";
+import LockOutlined from "@mui/icons-material/LockOutlined";
+
 import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
 import { BaseDateTimePickerSlotProps } from "@mui/x-date-pickers/DateTimePicker/shared";
 import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
@@ -67,13 +74,14 @@ import {
     IconPaddingSx,
     MainBoxSx,
     TableViewType,
+    getUpdateVarNames,
     hoverSx,
     iconLabelSx,
     popoverOrigin,
     tinySelPinIconButtonSx,
     useClassNames,
 } from "./utils";
-import PropertiesEditor from "./PropertiesEditor";
+import PropertiesEditor, { DatanodeProperties } from "./PropertiesEditor";
 import { NodeType, Scenarios } from "./utils/types";
 import CoreSelector from "./CoreSelector";
 import { useUniqueId } from "./utils/hooks";
@@ -103,7 +111,7 @@ type DataNodeFull = [
     string, // ownerId
     string, // ownerLabel
     number, // ownerType
-    Array<[string, string]>, // properties
+    DatanodeData, // data
     boolean, // editInProgress
     string, // editorId
     boolean, // readable
@@ -120,7 +128,7 @@ enum DataNodeFullProps {
     ownerId,
     ownerLabel,
     ownerType,
-    properties,
+    data,
     editInProgress,
     editorId,
     readable,
@@ -145,7 +153,6 @@ interface DataNodeViewerProps {
     defaultDataNode?: string;
     dataNode?: DataNodeFull | Array<DataNodeFull>;
     onEdit?: string;
-    onIdSelect?: string;
     error?: string;
     coreChanged?: Record<string, unknown>;
     defaultActive: boolean;
@@ -166,11 +173,13 @@ interface DataNodeViewerProps {
     data?: DatanodeData;
     tabularData?: TableValueType;
     tabularColumns?: string;
+    dnProperties?: DatanodeProperties;
     onDataValue?: string;
     onTabularDataEdit?: string;
     chartConfig?: string;
     width?: string;
     onLock?: string;
+    updateDnVars?: string;
 }
 
 const dataValueFocus = "data-value";
@@ -184,7 +193,22 @@ const getValidDataNode = (datanode: DataNodeFull | DataNodeFull[]) =>
         ? (datanode[0] as DataNodeFull)
         : undefined;
 
-const invalidDatanode: DataNodeFull = ["", "", "", "", "", "", "", "", -1, [], false, "", false, false];
+const invalidDatanode: DataNodeFull = [
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    -1,
+    [null, null, null, null],
+    false,
+    "",
+    false,
+    false,
+];
 
 enum TabValues {
     Data,
@@ -204,6 +228,8 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
         showProperties = true,
         showHistory = true,
         showData = true,
+        updateVars = "",
+        updateDnVars = "",
     } = props;
 
     const { state, dispatch } = useContext<Store>(Context);
@@ -224,12 +250,16 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
         dnOwnerId,
         dnOwnerLabel,
         dnOwnerType,
-        dnProperties,
+        dnData,
         dnEditInProgress,
         dnEditorId,
         dnReadable,
         dnEditable,
     ] = datanode;
+    const dtType = dnData[DatanodeDataProps.type];
+    const dtValue = dnData[DatanodeDataProps.value] ?? (dtType == "float" ? null : undefined);
+    const dtTabular = dnData[DatanodeDataProps.tabular] ?? false;
+    const dtError = dnData[DatanodeDataProps.error];
 
     // Tabs
     const [tabValue, setTabValue] = useState<TabValues>(TabValues.Data);
@@ -237,25 +267,64 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
         (_: SyntheticEvent, newValue: number) => {
             if (valid) {
                 if (newValue == TabValues.History) {
-                    setHistoryRequested(
-                        (req) =>
-                            req ||
-                            dispatch(createSendActionNameAction(id, module, props.onIdSelect, { history_id: dnId })) ||
-                            true
-                    );
+                    setHistoryRequested((req) => {
+                        if (!req) {
+                            const idVar = getUpdateVar(updateDnVars, "history_id");
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    getUpdateVarNames(updateVars, "history"),
+                                    true,
+                                    idVar ? { [idVar]: dnId } : undefined
+                                )
+                            );
+                        }
+                        return true;
+                    });
+                    setDataRequested(false);
+                    setPropertiesRequested(false);
                 } else if (newValue == TabValues.Data) {
-                    setDataRequested(
-                        (req) =>
-                            req ||
-                            dispatch(createSendActionNameAction(id, module, props.onIdSelect, { data_id: dnId })) ||
-                            true
-                    );
+                    setDataRequested((req) => {
+                        if (!req && dtTabular) {
+                            const idVar = getUpdateVar(updateDnVars, "data_id");
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    getUpdateVarNames(updateVars, "tabularData", "tabularColumns"),
+                                    true,
+                                    idVar ? { [idVar]: dnId } : undefined
+                                )
+                            );
+                        }
+                        return true;
+                    });
+                    setHistoryRequested(false);
+                    setPropertiesRequested(false);
+                } else if (newValue == TabValues.Properties) {
+                    setPropertiesRequested((req) => {
+                        if (!req) {
+                            const idVar = getUpdateVar(updateDnVars, "properties_id");
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    getUpdateVarNames(updateVars, "properties"),
+                                    true,
+                                    idVar ? { [idVar]: dnId } : undefined
+                                )
+                            );
+                        }
+                        return true;
+                    });
+                    setDataRequested(false);
                     setHistoryRequested(false);
                 }
                 setTabValue(newValue);
             }
         },
-        [dnId, dispatch, id, valid, module, props.onIdSelect]
+        [dnId, dispatch, id, valid, module, updateVars, updateDnVars, dtTabular]
     );
 
     useEffect(() => {
@@ -278,11 +347,16 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             const isNewDn = oldDn[DataNodeFullProps.id] !== newDnId;
             // clean lock on change
             if (oldDn[DataNodeFullProps.id] && isNewDn && editLock.current) {
-                dispatch(
-                    createSendActionNameAction(id, module, props.onLock, {
-                        id: oldDn[DataNodeFullProps.id],
-                        lock: false,
-                    })
+                const oldId = oldDn[DataNodeFullProps.id];
+                setTimeout(
+                    () =>
+                        dispatch(
+                            createSendActionNameAction(id, module, props.onLock, {
+                                id: oldId,
+                                lock: false,
+                            })
+                        ),
+                    1
                 );
             }
             if (!dn || isNewDn) {
@@ -294,21 +368,62 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             editLock.current = dn[DataNodeFullProps.editInProgress];
             setHistoryRequested((req) => {
                 if (req && !isNewDn && tabValue == TabValues.History) {
-                    dispatch(
-                        createSendActionNameAction(id, module, props.onIdSelect, {
-                            history_id: newDnId,
-                        })
+                    const idVar = getUpdateVar(updateDnVars, "history_id");
+                    const vars = getUpdateVarNames(updateVars, "history");
+                    setTimeout(
+                        () =>
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    vars,
+                                    true,
+                                    idVar ? { [idVar]: newDnId } : undefined
+                                )
+                            ),
+                        1
                     );
                     return true;
                 }
                 return false;
             });
             setDataRequested(() => {
-                if (tabValue == TabValues.Data) {
-                    dispatch(
-                        createSendActionNameAction(id, module, props.onIdSelect, {
-                            data_id: newDnId,
-                        })
+                if (showData && tabValue == TabValues.Data && dn[DataNodeFullProps.data][DatanodeDataProps.tabular]) {
+                    const idVar = getUpdateVar(updateDnVars, "data_id");
+                    const vars = getUpdateVarNames(updateVars, "tabularData", "tabularColumns");
+                    setTimeout(
+                        () =>
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    vars,
+                                    true,
+                                    idVar ? { [idVar]: newDnId } : undefined
+                                )
+                            ),
+                        1
+                    );
+                    return true;
+                }
+                return false;
+            });
+            setPropertiesRequested((req) => {
+                if ((req || !showData) && tabValue == TabValues.Properties) {
+                    const idVar = getUpdateVar(updateDnVars, "properties_id");
+                    const vars = getUpdateVarNames(updateVars, "properties");
+                    setTimeout(
+                        () =>
+                            dispatch(
+                                createRequestUpdateAction(
+                                    id,
+                                    module,
+                                    vars,
+                                    true,
+                                    idVar ? { [idVar]: newDnId } : undefined
+                                )
+                            ),
+                        1
                     );
                     return true;
                 }
@@ -320,7 +435,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             return dn;
         });
         // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [props.dataNode, props.defaultDataNode, showData, id, dispatch, module, props.onLock, props.onIdSelect]);
+    }, [props.dataNode, props.defaultDataNode, showData, id, dispatch, module, props.onLock]);
 
     // clean lock on unmount
     useEffect(
@@ -338,6 +453,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
     // history & data
     const [historyRequested, setHistoryRequested] = useState(false);
     const [dataRequested, setDataRequested] = useState(false);
+    const [propertiesRequested, setPropertiesRequested] = useState(false);
 
     // userExpanded
     const [userExpanded, setUserExpanded] = useState(valid && expanded);
@@ -378,48 +494,49 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             setLabel(dnLabel);
             setFocusName("");
         },
-        [dnLabel, setLabel, setFocusName]
+        [dnLabel]
     );
     const onLabelChange = useCallback((e: ChangeEvent<HTMLInputElement>) => setLabel(e.target.value), []);
 
     // scenarios
     const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
+    const scenarioUpdateVars = useMemo(() => getUpdateVarNames(updateVars, "scenario", "scenarios"), [updateVars]);
     const showScenarios = useCallback(
         (e: MouseEvent<HTMLElement>) => {
             e.stopPropagation();
             if (valid) {
-                dispatch(createSendActionNameAction(id, module, props.onIdSelect, { owner_id: dnOwnerId }));
+                const ownerIdVar = getUpdateVar(updateDnVars, "owner_id");
+                dispatch(
+                    createRequestUpdateAction(
+                        id,
+                        module,
+                        scenarioUpdateVars,
+                        true,
+                        ownerIdVar ? { [ownerIdVar]: dnOwnerId } : undefined
+                    )
+                );
                 setAnchorEl(e.currentTarget);
             }
         },
-        [dnOwnerId, dispatch, id, valid, module, props.onIdSelect]
+        [dnOwnerId, valid, updateDnVars, scenarioUpdateVars, dispatch, id, module]
     );
     const handleClose = useCallback(() => setAnchorEl(null), []);
-    const scenarioUpdateVars = useMemo(
-        () => [getUpdateVar(props.updateVars, "scenario"), getUpdateVar(props.updateVars, "scenarios")],
-        [props.updateVars]
-    );
 
     const [comment, setComment] = useState("");
-    const changeComment = useCallback((e: ChangeEvent<HTMLInputElement>) => {
-        setComment(e.currentTarget.value);
-    }, []);
+    const changeComment = useCallback((e: ChangeEvent<HTMLInputElement>) => setComment(e.currentTarget.value), []);
 
     // on datanode change
     useEffect(() => {
         setLabel(dnLabel);
         setUserExpanded(expanded && valid);
         setHistoryRequested(false);
-        setDataRequested(true);
+        setDataRequested(showData);
+        setPropertiesRequested(!showData);
         setViewType(TableViewType);
         setComment("");
-    }, [dnId, dnLabel, valid, expanded]);
+    }, [dnId, dnLabel, valid, expanded, showData]);
 
     // Datanode data
-    const dtType = props.data && props.data[DatanodeDataProps.type];
-    const dtValue = (props.data && props.data[DatanodeDataProps.value]) ?? (dtType == "float" ? null : undefined);
-    const dtTabular = (props.data && props.data[DatanodeDataProps.tabular]) ?? false;
-    const dtError = props.data && props.data[DatanodeDataProps.error];
     const [dataValue, setDataValue] = useState<RowValue | Date>();
     const editDataValue = useCallback(
         (e: MouseEvent<HTMLElement>) => {
@@ -445,7 +562,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
             setFocusName("");
             dispatch(createSendActionNameAction(id, module, props.onLock, { id: dnId, lock: false }));
         },
-        [dtValue, dtType, dnId, id, dispatch, module, props.onLock, setDataValue, setFocusName]
+        [dtValue, dtType, dnId, id, dispatch, module, props.onLock]
     );
     const onDataValueChange = useCallback((e: ChangeEvent<HTMLInputElement>) => setDataValue(e.target.value), []);
     const onDataValueDateChange = useCallback((d: Date | null) => d && setDataValue(d), []);
@@ -460,11 +577,20 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
     const onViewTypeChange = useCallback(
         (e: MouseEvent, value?: string) => {
             if (value) {
-                dispatch(createSendActionNameAction(id, module, props.onIdSelect, { chart_id: dnId }));
+                const idVar = getUpdateVar(updateDnVars, "chart_id");
+                dispatch(
+                    createRequestUpdateAction(
+                        id,
+                        module,
+                        getUpdateVarNames(updateVars, "chartConfig"),
+                        true,
+                        idVar ? { [idVar]: dnId } : undefined
+                    )
+                );
                 setViewType(value);
             }
         },
-        [dnId, dispatch, id, module, props.onIdSelect]
+        [dnId, updateVars, updateDnVars, dispatch, id, module]
     );
 
     // base tabular columns
@@ -694,7 +820,11 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                     entityId={dnId}
                                     active={active}
                                     isDefined={valid}
-                                    entProperties={dnProperties}
+                                    entProperties={
+                                        propertiesRequested && Array.isArray(props.dnProperties)
+                                            ? props.dnProperties
+                                            : []
+                                    }
                                     show={showProperties}
                                     focusName={focusName}
                                     setFocusName={setFocusName}
@@ -757,161 +887,162 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                             id={`${uniqid}-dn-tabpanel-data`}
                             aria-labelledby={`${uniqid}-data`}
                         >
-                            {dataRequested ? (
-                                dtValue !== undefined ? (
-                                    <Grid container justifyContent="space-between" spacing={1}>
-                                        <Grid
-                                            item
-                                            container
-                                            xs={12}
-                                            justifyContent="space-between"
-                                            data-focus={dataValueFocus}
-                                            onClick={onFocus}
-                                            sx={hoverSx}
-                                        >
-                                            {active &&
-                                            dnEditable &&
-                                            dnEditInProgress &&
-                                            dnEditorId === editorId &&
-                                            focusName === dataValueFocus ? (
-                                                <>
+                            {dtValue !== undefined ? (
+                                <Grid container justifyContent="space-between" spacing={1}>
+                                    <Grid
+                                        item
+                                        container
+                                        xs={12}
+                                        justifyContent="space-between"
+                                        data-focus={dataValueFocus}
+                                        onClick={onFocus}
+                                        sx={hoverSx}
+                                    >
+                                        {active &&
+                                        dnEditable &&
+                                        dnEditInProgress &&
+                                        dnEditorId === editorId &&
+                                        focusName === dataValueFocus ? (
+                                            <>
+                                                {typeof dtValue == "boolean" ? (
+                                                    <>
+                                                        <Grid item xs={10}>
+                                                            <Switch
+                                                                value={dataValue as boolean}
+                                                                onChange={onDataValueChange}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={2}>
+                                                            <Tooltip title="Apply">
+                                                                <IconButton
+                                                                    onClick={editDataValue}
+                                                                    size="small"
+                                                                    sx={IconPaddingSx}
+                                                                >
+                                                                    <CheckCircle color="primary" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                            <Tooltip title="Cancel">
+                                                                <IconButton
+                                                                    onClick={cancelDataValue}
+                                                                    size="small"
+                                                                    sx={IconPaddingSx}
+                                                                >
+                                                                    <Cancel color="inherit" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                        </Grid>
+                                                    </>
+                                                ) : dtType == "date" &&
+                                                  (dataValue === null || dataValue instanceof Date) ? (
+                                                    <LocalizationProvider dateAdapter={AdapterDateFns}>
+                                                        <Grid item xs={10}>
+                                                            <DateTimePicker
+                                                                value={dataValue as Date}
+                                                                onChange={onDataValueDateChange}
+                                                                slotProps={textFieldProps}
+                                                            />
+                                                        </Grid>
+                                                        <Grid item xs={2}>
+                                                            <Tooltip title="Apply">
+                                                                <IconButton
+                                                                    onClick={editDataValue}
+                                                                    size="small"
+                                                                    sx={IconPaddingSx}
+                                                                >
+                                                                    <CheckCircle color="primary" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                            <Tooltip title="Cancel">
+                                                                <IconButton
+                                                                    onClick={cancelDataValue}
+                                                                    size="small"
+                                                                    sx={IconPaddingSx}
+                                                                >
+                                                                    <Cancel color="inherit" />
+                                                                </IconButton>
+                                                            </Tooltip>
+                                                        </Grid>
+                                                    </LocalizationProvider>
+                                                ) : (
+                                                    <TextField
+                                                        label="Value"
+                                                        variant="outlined"
+                                                        fullWidth
+                                                        sx={FieldNoMaxWidth}
+                                                        value={dataValue || ""}
+                                                        onChange={onDataValueChange}
+                                                        type={
+                                                            typeof dtValue == "number"
+                                                                ? "number"
+                                                                : dtType == "float" && dtValue === null
+                                                                ? "number"
+                                                                : undefined
+                                                        }
+                                                        InputProps={{
+                                                            endAdornment: (
+                                                                <InputAdornment position="end">
+                                                                    <Tooltip title="Apply">
+                                                                        <IconButton
+                                                                            sx={IconPaddingSx}
+                                                                            onClick={editDataValue}
+                                                                            size="small"
+                                                                        >
+                                                                            <CheckCircle color="primary" />
+                                                                        </IconButton>
+                                                                    </Tooltip>
+                                                                    <Tooltip title="Cancel">
+                                                                        <IconButton
+                                                                            sx={IconPaddingSx}
+                                                                            onClick={cancelDataValue}
+                                                                            size="small"
+                                                                        >
+                                                                            <Cancel color="inherit" />
+                                                                        </IconButton>
+                                                                    </Tooltip>
+                                                                </InputAdornment>
+                                                            ),
+                                                        }}
+                                                        disabled={!valid}
+                                                    />
+                                                )}
+                                                <TextField
+                                                    value={comment}
+                                                    onChange={changeComment}
+                                                    label="Comment"
+                                                ></TextField>
+                                            </>
+                                        ) : (
+                                            <>
+                                                <Grid item xs={4}>
+                                                    <Typography variant="subtitle2">Value</Typography>
+                                                </Grid>
+                                                <Grid item xs={8}>
                                                     {typeof dtValue == "boolean" ? (
-                                                        <>
-                                                            <Grid item xs={10}>
-                                                                <Switch
-                                                                    value={dataValue as boolean}
-                                                                    onChange={onDataValueChange}
-                                                                />
-                                                            </Grid>
-                                                            <Grid item xs={2}>
-                                                                <Tooltip title="Apply">
-                                                                    <IconButton
-                                                                        onClick={editDataValue}
-                                                                        size="small"
-                                                                        sx={IconPaddingSx}
-                                                                    >
-                                                                        <CheckCircle color="primary" />
-                                                                    </IconButton>
-                                                                </Tooltip>
-                                                                <Tooltip title="Cancel">
-                                                                    <IconButton
-                                                                        onClick={cancelDataValue}
-                                                                        size="small"
-                                                                        sx={IconPaddingSx}
-                                                                    >
-                                                                        <Cancel color="inherit" />
-                                                                    </IconButton>
-                                                                </Tooltip>
-                                                            </Grid>
-                                                        </>
-                                                    ) : dtType == "date" && (dataValue === null || dataValue instanceof Date)  ? (
-                                                        <LocalizationProvider dateAdapter={AdapterDateFns}>
-                                                            <Grid item xs={10}>
-                                                                <DateTimePicker
-                                                                    value={dataValue as Date}
-                                                                    onChange={onDataValueDateChange}
-                                                                    slotProps={textFieldProps}
-                                                                />
-                                                            </Grid>
-                                                            <Grid item xs={2}>
-                                                                <Tooltip title="Apply">
-                                                                    <IconButton
-                                                                        onClick={editDataValue}
-                                                                        size="small"
-                                                                        sx={IconPaddingSx}
-                                                                    >
-                                                                        <CheckCircle color="primary" />
-                                                                    </IconButton>
-                                                                </Tooltip>
-                                                                <Tooltip title="Cancel">
-                                                                    <IconButton
-                                                                        onClick={cancelDataValue}
-                                                                        size="small"
-                                                                        sx={IconPaddingSx}
-                                                                    >
-                                                                        <Cancel color="inherit" />
-                                                                    </IconButton>
-                                                                </Tooltip>
-                                                            </Grid>
-                                                        </LocalizationProvider>
-                                                    ) : (
-                                                        <TextField
-                                                            label="Value"
-                                                            variant="outlined"
-                                                            fullWidth
-                                                            sx={FieldNoMaxWidth}
-                                                            value={dataValue || ""}
-                                                            onChange={onDataValueChange}
-                                                            type={
-                                                                typeof dtValue == "number"
-                                                                    ? "number"
-                                                                    : dtType == "float" && dtValue === null
-                                                                    ? "number"
-                                                                    : undefined
-                                                            }
-                                                            InputProps={{
-                                                                endAdornment: (
-                                                                    <InputAdornment position="end">
-                                                                        <Tooltip title="Apply">
-                                                                            <IconButton
-                                                                                sx={IconPaddingSx}
-                                                                                onClick={editDataValue}
-                                                                                size="small"
-                                                                            >
-                                                                                <CheckCircle color="primary" />
-                                                                            </IconButton>
-                                                                        </Tooltip>
-                                                                        <Tooltip title="Cancel">
-                                                                            <IconButton
-                                                                                sx={IconPaddingSx}
-                                                                                onClick={cancelDataValue}
-                                                                                size="small"
-                                                                            >
-                                                                                <Cancel color="inherit" />
-                                                                            </IconButton>
-                                                                        </Tooltip>
-                                                                    </InputAdornment>
-                                                                ),
-                                                            }}
-                                                            disabled={!valid}
+                                                        <Switch
+                                                            defaultChecked={dtValue}
+                                                            disabled={true}
+                                                            title={`${dtValue}`}
                                                         />
+                                                    ) : (
+                                                        <Typography variant="subtitle2">
+                                                            {dtType == "date"
+                                                                ? (dataValue === null || dataValue instanceof Date) &&
+                                                                  format(dataValue as Date, "yyyy/MM/dd HH:mm:ss")
+                                                                : dtType == "float" && dtValue === null
+                                                                ? "NaN"
+                                                                : dtValue}
+                                                        </Typography>
                                                     )}
-                                                    <TextField
-                                                        value={comment}
-                                                        onChange={changeComment}
-                                                        label="Comment"
-                                                    ></TextField>
-                                                </>
-                                            ) : (
-                                                <>
-                                                    <Grid item xs={4}>
-                                                        <Typography variant="subtitle2">Value</Typography>
-                                                    </Grid>
-                                                    <Grid item xs={8}>
-                                                        {typeof dtValue == "boolean" ? (
-                                                            <Switch
-                                                                defaultChecked={dtValue}
-                                                                disabled={true}
-                                                                title={`${dtValue}`}
-                                                            />
-                                                        ) : (
-                                                            <Typography variant="subtitle2">
-                                                                {dtType == "date"
-                                                                    ? (dataValue === null || dataValue instanceof Date) &&
-                                                                      format(dataValue as Date, "yyyy/MM/dd HH:mm:ss")
-                                                                    : dtType == "float" && dtValue === null
-                                                                    ? "NaN"
-                                                                    : dtValue}
-                                                            </Typography>
-                                                        )}
-                                                    </Grid>
-                                                </>
-                                            )}
-                                        </Grid>
+                                                </Grid>
+                                            </>
+                                        )}
                                     </Grid>
-                                ) : dtError ? (
-                                    <Typography>{dtError}</Typography>
-                                ) : dtTabular ? (
+                                </Grid>
+                            ) : dtError ? (
+                                <Typography>{dtError}</Typography>
+                            ) : dtTabular ? (
+                                dataRequested ? (
                                     <>
                                         {viewType === TableViewType ? (
                                             <DataNodeTable
@@ -922,12 +1053,13 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                 nodeId={dnId}
                                                 configId={dnConfig}
                                                 onViewTypeChange={onViewTypeChange}
-                                                updateVarName={getUpdateVar(props.updateVars, "tabularData")}
+                                                updateVarName={getUpdateVar(updateVars, "tabularData")}
                                                 onEdit={props.onTabularDataEdit}
                                                 onLock={props.onLock}
                                                 editInProgress={dnEditInProgress && dnEditorId !== editorId}
                                                 editLock={editLock}
                                                 editable={dnEditable}
+                                                idVar={getUpdateVar(updateDnVars, "data_id")}
                                             />
                                         ) : (
                                             <DataNodeChart
@@ -937,7 +1069,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                                                 tabularData={props.tabularData}
                                                 configId={dnConfig}
                                                 defaultConfig={props.chartConfig}
-                                                updateVarName={getUpdateVar(props.updateVars, "tabularData")}
+                                                updateVarName={getUpdateVar(updateVars, "tabularData")}
                                                 chartConfigs={props.chartConfigs}
                                                 onViewTypeChange={onViewTypeChange}
                                             />
@@ -952,7 +1084,7 @@ const DataNodeViewer = (props: DataNodeViewerProps) => {
                         </div>
                     </AccordionDetails>
                 </Accordion>
-                <Box>{props.error}</Box>
+                {props.error ? <Alert severity="error">{props.error}</Alert> : null}
             </Box>
         </>
     );

+ 9 - 7
frontend/taipy/src/PropertiesEditor.tsx

@@ -36,12 +36,14 @@ type PropertiesEditPayload = {
     deleted_properties?: Array<Partial<Property>>;
 };
 
+export type DatanodeProperties = Array<[string, string]>;
+
 interface PropertiesEditorProps {
     id?: string;
     entityId: string;
     active: boolean;
     show: boolean;
-    entProperties: Array<[string, string]>;
+    entProperties: DatanodeProperties;
     onFocus: (e: MouseEvent<HTMLElement>) => void;
     focusName: string;
     setFocusName: (name: string) => void;
@@ -124,8 +126,8 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                     e.stopPropagation();
                 }
             }
-
-        }, [editProperty, cancelProperty]
+        },
+        [editProperty, cancelProperty]
     );
 
     const deleteProperty = useCallback(
@@ -187,7 +189,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                                   data-name="key"
                                                   data-id={property.id}
                                                   onChange={updatePropertyField}
-                                                  inputProps={{onKeyDown}}
+                                                  inputProps={{ onKeyDown }}
                                               />
                                           </Grid>
                                           <Grid item xs={5}>
@@ -200,7 +202,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                                   data-name="value"
                                                   data-id={property.id}
                                                   onChange={updatePropertyField}
-                                                  inputProps={{onKeyDown, "data-enter": true}}
+                                                  inputProps={{ onKeyDown, "data-enter": true }}
                                               />
                                           </Grid>
                                           <Grid
@@ -293,7 +295,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                     variant="outlined"
                                     sx={FieldNoMaxWidth}
                                     disabled={!isDefined}
-                                    inputProps={{onKeyDown}}
+                                    inputProps={{ onKeyDown }}
                                 />
                             </Grid>
                             <Grid item xs={5}>
@@ -305,7 +307,7 @@ const PropertiesEditor = (props: PropertiesEditorProps) => {
                                     variant="outlined"
                                     sx={FieldNoMaxWidth}
                                     disabled={!isDefined}
-                                    inputProps={{onKeyDown, "data-enter": true}}
+                                    inputProps={{ onKeyDown, "data-enter": true }}
                                 />
                             </Grid>
                             <Grid

+ 3 - 1
frontend/taipy/src/utils.ts

@@ -13,7 +13,7 @@
 import { Theme, alpha } from "@mui/material";
 import { PopoverOrigin } from "@mui/material/Popover";
 
-import { useDynamicProperty } from "taipy-gui";
+import { getUpdateVar, useDynamicProperty } from "taipy-gui";
 
 export type ScenarioFull = [
     string,     // id
@@ -218,3 +218,5 @@ export const DeleteIconSx = { height: 50, width: 50, p: 0 };
 
 
 export const EmptyArray = [];
+
+export const getUpdateVarNames = (updateVars: string, ...vars: string[]) => vars.map((v) => getUpdateVar(updateVars, v) || "").filter(v => v);

+ 12 - 10
taipy/gui/_renderers/builder.py

@@ -366,16 +366,18 @@ class _Builder:
         lov_name = self.__hashes.get(var_name)
         lov = self.__get_list_of_(var_name)
         default_lov = []
+
+        adapter = self.__attributes.get("adapter")
+        if adapter and isinstance(adapter, str):
+            adapter = self.__gui._get_user_function(adapter)
+        if adapter and not callable(adapter):
+            _warn(f"{self.__element_name}: adapter property value is invalid.")
+            adapter = None
+        var_type = self.__attributes.get("type")
+        if isclass(var_type):
+            var_type = var_type.__name__  # type: ignore
+
         if isinstance(lov, list):
-            adapter = self.__attributes.get("adapter")
-            if adapter and isinstance(adapter, str):
-                adapter = self.__gui._get_user_function(adapter)
-            if adapter and not callable(adapter):
-                _warn(f"{self.__element_name}: adapter property value is invalid.")
-                adapter = None
-            var_type = self.__attributes.get("type")
-            if isclass(var_type):
-                var_type = var_type.__name__  # type: ignore
             if not isinstance(var_type, str):
                 elt = None
                 if len(lov) == 0:
@@ -1009,7 +1011,7 @@ class _Builder:
                     self.__update_vars.append(f"{prop_name}={hash_name}")
                     self.__set_react_attribute(prop_name, hash_name)
 
-            self.__set_refresh_on_update()
+        self.__set_refresh_on_update()
         return self
 
     def set_attribute(self, name: str, value: t.Any):

+ 11 - 7
taipy/gui/_renderers/factory.py

@@ -327,19 +327,18 @@ class _Factory:
                 ("on_action", PropertyType.function),
                 ("inactive_ids", PropertyType.dynamic_list),
                 ("hover_text", PropertyType.dynamic_string),
-                ("lov", PropertyType.lov)
+                ("lov", PropertyType.lov),
             ]
         )
         ._set_propagate(),
         "navbar": lambda gui, control_type, attrs: _Builder(
             gui=gui, control_type=control_type, element_name="NavBar", attributes=attrs, default_value=None
-        )
-        .set_attributes(
+        ).set_attributes(
             [
                 ("id",),
                 ("active", PropertyType.dynamic_boolean, True),
                 ("hover_text", PropertyType.dynamic_string),
-                ("lov", PropertyType.single_lov)
+                ("lov", PropertyType.single_lov),
             ]
         ),
         "number": lambda gui, control_type, attrs: _Builder(
@@ -414,7 +413,7 @@ class _Factory:
                 ("on_change", PropertyType.function),
                 ("label",),
                 ("mode",),
-                ("lov", PropertyType.lov)
+                ("lov", PropertyType.lov),
             ]
         )
         ._set_propagate(),
@@ -526,7 +525,7 @@ class _Factory:
                 ("allow_unselect", PropertyType.boolean),
                 ("on_change", PropertyType.function),
                 ("mode",),
-                ("lov", PropertyType.single_lov)
+                ("lov", PropertyType.single_lov),
             ]
         )
         ._set_kind()
@@ -561,6 +560,8 @@ class _Factory:
     # TODO: process \" in property value
     _PROPERTY_RE = re.compile(r"\s+([a-zA-Z][\.a-zA-Z_$0-9]*(?:\[(?:.*?)\])?)=\"((?:(?:(?<=\\)\")|[^\"])*)\"")
 
+    __COUNTER = 0
+
     @staticmethod
     def set_library(library: "ElementLibrary"):
         from ..extension.library import Element, ElementLibrary
@@ -620,6 +621,7 @@ class _Factory:
         name = name[len(_Factory.__TAIPY_NAME_SPACE) :] if name.startswith(_Factory.__TAIPY_NAME_SPACE) else name
         builder = _Factory.__CONTROL_BUILDERS.get(name)
         built = None
+        _Factory.__COUNTER += 1
         with gui._get_autorization():
             if builder is None:
                 lib, element_name, element = _Factory.__get_library_element(name)
@@ -627,7 +629,9 @@ class _Factory:
                     from ..extension.library import Element
 
                     if isinstance(element, Element):
-                        return element._call_builder(element_name, gui, all_properties, lib, is_html)
+                        return element._call_builder(
+                            element_name, gui, all_properties, lib, is_html, counter=_Factory.__COUNTER
+                        )
             else:
                 built = builder(gui, name, all_properties)
             if isinstance(built, _Builder):

+ 18 - 1
taipy/gui/extension/library.py

@@ -42,6 +42,7 @@ class ElementProperty:
         property_type: t.Union[PropertyType, t.Type[_TaipyBase]],
         default_value: t.Optional[t.Any] = None,
         js_name: t.Optional[str] = None,
+        with_update: t.Optional[bool] = None,
     ) -> None:
         """Initializes a new custom property declaration for an `Element^`.
 
@@ -64,6 +65,7 @@ class ElementProperty:
         else:
             self.property_type = property_type
         self._js_name = js_name
+        self.with_update = with_update
         super().__init__()
 
     def check(self, element_name: str, prop_name: str):
@@ -75,7 +77,11 @@ class ElementProperty:
             _warn(f"Property type '{self.property_type}' is invalid for element property '{element_name}.{prop_name}'.")
 
     def _get_tuple(self, name: str) -> tuple:
-        return (name, self.property_type, self.default_value)
+        return (
+            (name, self.property_type, self.default_value)
+            if self.with_update is None
+            else (name, self.property_type, self.default_value, self.with_update)
+        )
 
     def get_js_name(self, name: str) -> str:
         return self._js_name or _to_camel_case(name)
@@ -90,6 +96,7 @@ class Element:
     """
 
     __RE_PROP_VAR = re.compile(r"<tp:prop:(\w+)>")
+    __RE_UNIQUE_VAR = re.compile(r"<tp:uniq:(\w+)>")
 
     def __init__(
         self,
@@ -152,9 +159,11 @@ class Element:
         properties: t.Optional[t.Dict[str, t.Any]],
         lib: "ElementLibrary",
         is_html: t.Optional[bool] = False,
+        counter: int = 0
     ) -> t.Union[t.Any, t.Tuple[str, str]]:
         attributes = properties if isinstance(properties, dict) else {}
         if self.inner_properties:
+            uniques: t.Dict[str, int] = {}
             self.attributes.update(self.inner_properties)
             for prop, attr in self.inner_properties.items():
                 val = attr.default_value
@@ -164,6 +173,14 @@ class Element:
                         var = attributes.get(m.group(1))
                         hash_value = "None" if var is None else gui._evaluate_expr(var)
                         val = val[: m.start()] + hash_value + val[m.end() :]
+                    # handling unique id replacement in inner properties <tp:uniq:...>
+                    while m := Element.__RE_UNIQUE_VAR.search(val):
+                        id = uniques.get(m.group(1))
+                        if id is None:
+                            id = len(uniques) + 1
+                            uniques[m.group(1)] = id
+                        val = f"{val[: m.start()]}'{counter}.{id}'{val[m.end() :]}"
+
                 attributes[prop] = val
         # this modifies attributes
         hash_names = _Builder._get_variable_hash_names(gui, attributes)  # variable replacement

+ 26 - 11
taipy/gui/gui.py

@@ -691,6 +691,7 @@ class Gui:
         propagate=True,
         holder: t.Optional[_TaipyBase] = None,
         on_change: t.Optional[str] = None,
+        forward: t.Optional[bool] = True,
     ) -> None:
         if holder:
             var_name = holder.get_name()
@@ -707,17 +708,22 @@ class Gui:
                 derived_vars.update(self._re_evaluate_expr(var_name))
         elif holder:
             derived_vars.update(self._evaluate_holders(hash_expr))
-        # if the variable has been evaluated then skip updating to prevent infinite loop
-        var_modified = self.__is_var_modified_in_context(hash_expr, derived_vars)
-        if not var_modified:
-            self._call_on_change(
-                var_name,
-                value.get() if isinstance(value, _TaipyBase) else value._dict if isinstance(value, _MapDict) else value,
-                on_change,
-            )
-        derived_modified = self.__clean_vars_on_exit()
-        if derived_modified is not None:
-            self.__send_var_list_update(list(derived_modified), var_name)
+        if forward:
+            # if the variable has been evaluated then skip updating to prevent infinite loop
+            var_modified = self.__is_var_modified_in_context(hash_expr, derived_vars)
+            if not var_modified:
+                self._call_on_change(
+                    var_name,
+                    value.get()
+                    if isinstance(value, _TaipyBase)
+                    else value._dict
+                    if isinstance(value, _MapDict)
+                    else value,
+                    on_change,
+                )
+            derived_modified = self.__clean_vars_on_exit()
+            if derived_modified is not None:
+                self.__send_var_list_update(list(derived_modified), var_name)
 
     def _get_real_var_name(self, var_name: str) -> t.Tuple[str, str]:
         if not var_name:
@@ -1044,12 +1050,20 @@ class Gui:
         # TODO: What if value == newvalue?
         self.__send_ws_update_with_dict(ws_dict)
 
+    def __update_state_context(self, payload: dict):
+        # apply state context if any
+        state_context = payload.get("state_context")
+        if isinstance(state_context, dict):
+            for var, val in state_context.items():
+                self._update_var(var, val, True, forward=False)
+
     def __request_data_update(self, var_name: str, payload: t.Any) -> None:
         # Use custom attrgetter function to allow value binding for _MapDict
         newvalue = _getscopeattr_drill(self, var_name)
         if isinstance(newvalue, _TaipyData):
             ret_payload = None
             if isinstance(payload, dict):
+                self.__update_state_context(payload)
                 lib_name = payload.get("library")
                 if isinstance(lib_name, str):
                     libs = self.__extensions.get(lib_name, [])
@@ -1073,6 +1087,7 @@ class Gui:
 
     def __request_var_update(self, payload: t.Any):
         if isinstance(payload, dict) and isinstance(payload.get("names"), list):
+            self.__update_state_context(payload)
             if payload.get("refresh", False):
                 # refresh vars
                 for _var in t.cast(list, payload.get("names")):

+ 44 - 42
taipy/gui_core/_GuiCoreLib.py

@@ -31,6 +31,18 @@ class _GuiCore(ElementLibrary):
     __SCENARIO_ADAPTER = "tgc_scenario"
     __DATANODE_ADAPTER = "tgc_datanode"
     __JOB_ADAPTER = "tgc_job"
+    __INNER_VARS = (
+        _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR,
+        _GuiCoreContext._SCENARIO_SELECTOR_ID_VAR,
+        _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR,
+        _GuiCoreContext._JOB_SELECTOR_ERROR_VAR,
+        _GuiCoreContext._DATANODE_VIZ_ERROR_VAR,
+        _GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR,
+        _GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR,
+        _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR,
+        _GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR,
+        _GuiCoreContext._DATANODE_VIZ_PROPERTIES_ID_VAR,
+    )
 
     __elts = {
         "scenario_selector": Element(
@@ -143,7 +155,7 @@ class _GuiCore(ElementLibrary):
                 "show_properties": ElementProperty(PropertyType.boolean, True),
                 "show_history": ElementProperty(PropertyType.boolean, True),
                 "show_data": ElementProperty(PropertyType.boolean, True),
-                "chart_config": ElementProperty(PropertyType.dict),
+                "chart_configs": ElementProperty(PropertyType.dict),
                 "class_name": ElementProperty(PropertyType.dynamic_string),
                 "scenario": ElementProperty(PropertyType.lov_value, "optional"),
                 "width": ElementProperty(PropertyType.string),
@@ -154,45 +166,55 @@ class _GuiCore(ElementLibrary):
                 "error": ElementProperty(PropertyType.react, f"{{{_GuiCoreContext._DATANODE_VIZ_ERROR_VAR}}}"),
                 "scenarios": ElementProperty(
                     PropertyType.lov,
-                    f"{{{__CTX_VAR_NAME}.get_scenarios_for_owner({_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR})}}",
+                    f"{{{__CTX_VAR_NAME}.get_scenarios_for_owner({_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
                 ),
                 "type": ElementProperty(PropertyType.inner, __SCENARIO_ADAPTER),
-                "on_id_select": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.select_id}}"),
-                "history": ElementProperty(
+                "dn_properties": ElementProperty(
                     PropertyType.react,
-                    f"{{{__CTX_VAR_NAME}.get_data_node_history("
-                    + f"<tp:prop:{_GuiCoreContext._DATANODE_VIZ_DATA_NODE_PROP}>, "
-                    + f"{_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR})}}",
+                    f"{{{__CTX_VAR_NAME}.get_data_node_properties("
+                    + f"{_GuiCoreContext._DATANODE_VIZ_PROPERTIES_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
                 ),
-                "data": ElementProperty(
+                "history": ElementProperty(
                     PropertyType.react,
-                    f"{{{__CTX_VAR_NAME}.get_data_node_data(<tp:prop:{_GuiCoreContext._DATANODE_VIZ_DATA_NODE_PROP}>,"
-                    + f" {_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR})}}",
+                    f"{{{__CTX_VAR_NAME}.get_data_node_history(" + f"{_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
                 ),
                 "tabular_data": ElementProperty(
                     PropertyType.data,
                     f"{{{__CTX_VAR_NAME}.get_data_node_tabular_data("
-                    + f"<tp:prop:{_GuiCoreContext._DATANODE_VIZ_DATA_NODE_PROP}>, "
-                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR})}}",
+                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
                 ),
                 "tabular_columns": ElementProperty(
                     PropertyType.dynamic_string,
                     f"{{{__CTX_VAR_NAME}.get_data_node_tabular_columns("
-                    + f"<tp:prop:{_GuiCoreContext._DATANODE_VIZ_DATA_NODE_PROP}>, "
-                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR})}}",
+                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
+                    with_update=True,
                 ),
                 "chart_config": ElementProperty(
                     PropertyType.dynamic_string,
                     f"{{{__CTX_VAR_NAME}.get_data_node_chart_config("
-                    + f"<tp:prop:{_GuiCoreContext._DATANODE_VIZ_DATA_NODE_PROP}>, "
-                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR})}}",
+                    + f"{_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR},"
+                    + "<tp:uniq:dn>)}",
+                    with_update=True,
                 ),
                 "on_data_value": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.update_data}}"),
                 "on_tabular_data_edit": ElementProperty(
                     PropertyType.function, f"{{{__CTX_VAR_NAME}.tabular_data_edit}}"
                 ),
                 "on_lock": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.lock_datanode_for_edit}}"),
-            },
+                "update_dn_vars": ElementProperty(
+                    PropertyType.string,
+                    f"data_id={_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR};"
+                    + f"history_id={_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR};"
+                    + f"owner_id={_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR};"
+                    + f"chart_id={_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR};"
+                    + f"properties_id={_GuiCoreContext._DATANODE_VIZ_PROPERTIES_ID_VAR}",
+                ),
+            }
         ),
         "job_selector": Element(
             "value",
@@ -230,19 +252,7 @@ class _GuiCore(ElementLibrary):
         return ["lib/taipy-gui-core.js"]
 
     def on_init(self, gui: Gui) -> t.Optional[t.Tuple[str, t.Any]]:
-        gui._get_default_locals_bind().update(
-            {
-                _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR: "",
-                _GuiCoreContext._SCENARIO_SELECTOR_ID_VAR: "",
-                _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR: "",
-                _GuiCoreContext._JOB_SELECTOR_ERROR_VAR: "",
-                _GuiCoreContext._DATANODE_VIZ_ERROR_VAR: "",
-                _GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR: "",
-                _GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR: "",
-                _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR: "",
-                _GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR: "",
-            }
-        )
+        gui._get_default_locals_bind().update({v: "" for v in _GuiCore.__INNER_VARS})
         ctx = _GuiCoreContext(gui)
         gui._add_adapter_for_type(_GuiCore.__SCENARIO_ADAPTER, ctx.scenario_adapter)
         gui._add_adapter_for_type(_GuiCore.__DATANODE_ADAPTER, ctx.data_node_adapter)
@@ -250,20 +260,12 @@ class _GuiCore(ElementLibrary):
         return _GuiCore.__CTX_VAR_NAME, ctx
 
     def on_user_init(self, state: State):
-        for var in [
-            _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR,
-            _GuiCoreContext._SCENARIO_SELECTOR_ID_VAR,
-            _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR,
-            _GuiCoreContext._JOB_SELECTOR_ERROR_VAR,
-            _GuiCoreContext._DATANODE_VIZ_ERROR_VAR,
-            _GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR,
-            _GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR,
-            _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR,
-            _GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR,
-        ]:
+        for var in _GuiCore.__INNER_VARS:
             state._add_attribute(var, "")
 
     def get_version(self) -> str:
         if not hasattr(self, "version"):
-            self.version = _get_version() + str(datetime.now().timestamp())
+            self.version = _get_version()
+            if "dev" in self.version:
+                self.version += str(datetime.now().timestamp())
         return self.version

+ 42 - 6
taipy/gui_core/_adapters.py

@@ -9,8 +9,12 @@
 # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 
+import math
 import typing as t
 from enum import Enum
+from numbers import Number
+
+import pandas as pd
 
 from taipy.core import (
     Cycle,
@@ -26,6 +30,7 @@ from taipy.core import (
     is_submittable,
 )
 from taipy.core import get as core_get
+from taipy.core.data._tabular_datanode_mixin import _TabularDataNodeMixin
 from taipy.gui._warnings import _warn
 from taipy.gui.gui import _DoNotUpdate
 from taipy.gui.utils import _TaipyBase
@@ -158,7 +163,42 @@ class _GuiCoreScenarioNoUpdate(_TaipyBase, _DoNotUpdate):
 
 
 class _GuiCoreDatanodeAdapter(_TaipyBase):
-    __INNER_PROPS = ["name"]
+
+    @staticmethod
+    def _is_tabular_data(datanode: DataNode, value: t.Any):
+        if isinstance(datanode, _TabularDataNodeMixin):
+            return True
+        if datanode.is_ready_for_reading:
+            return isinstance(value, (pd.DataFrame, pd.Series, list, tuple, dict))
+        return False
+
+    def __get_data(self, dn: DataNode):
+            if dn._last_edit_date:
+                if isinstance(dn, _TabularDataNodeMixin):
+                    return (None, None, True, None)
+                try:
+                    value = dn.read()
+                    if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
+                        return (None, None, True, None)
+                    val_type = (
+                        "date"
+                        if "date" in type(value).__name__
+                        else type(value).__name__
+                        if isinstance(value, Number)
+                        else None
+                    )
+                    if isinstance(value, float):
+                        if math.isnan(value):
+                            value = None
+                    return (
+                        value,
+                        val_type,
+                        None,
+                        None,
+                    )
+                except Exception as e:
+                    return (None, None, None, f"read data_node: {e}")
+            return (None, None, None, f"Data unavailable for {dn.get_simple_label()}")
 
     def get(self):
         data = super().get()
@@ -180,11 +220,7 @@ class _GuiCoreDatanodeAdapter(_TaipyBase):
                         else _EntityType.SCENARIO.value
                         if isinstance(owner, Scenario)
                         else -1,
-                        [
-                            (k, f"{v}")
-                            for k, v in datanode._get_user_properties().items()
-                            if k not in _GuiCoreDatanodeAdapter.__INNER_PROPS
-                        ],
+                        self.__get_data(datanode),
                         datanode._edit_in_progress,
                         datanode._editor_id,
                         is_readable(datanode),

+ 24 - 81
taipy/gui_core/_context.py

@@ -10,7 +10,6 @@
 # specific language governing permissions and limitations under the License.
 
 import json
-import math
 import typing as t
 from collections import defaultdict
 from numbers import Number
@@ -52,7 +51,6 @@ from taipy.core import (
 from taipy.core import delete as core_delete
 from taipy.core import get as core_get
 from taipy.core import submit as core_submit
-from taipy.core.data._tabular_datanode_mixin import _TabularDataNodeMixin
 from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event, EventOperation
 from taipy.core.notification.notifier import Notifier
@@ -61,7 +59,7 @@ from taipy.gui import Gui, State
 from taipy.gui._warnings import _warn
 from taipy.gui.gui import _DoNotUpdate
 
-from ._adapters import _EntityType
+from ._adapters import _EntityType, _GuiCoreDatanodeAdapter
 
 
 class _GuiCoreContext(CoreEventConsumerBase):
@@ -82,6 +80,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
     _DATANODE_VIZ_ERROR_VAR = "gui_core_dv_error"
     _DATANODE_VIZ_OWNER_ID_VAR = "gui_core_dv_owner_id"
     _DATANODE_VIZ_HISTORY_ID_VAR = "gui_core_dv_history_id"
+    _DATANODE_VIZ_PROPERTIES_ID_VAR = "gui_core_dv_properties_id"
     _DATANODE_VIZ_DATA_ID_VAR = "gui_core_dv_data_id"
     _DATANODE_VIZ_DATA_CHART_ID_VAR = "gui_core_dv_data_chart_id"
     _DATANODE_VIZ_DATA_NODE_PROP = "data_node"
@@ -680,7 +679,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         # we might be comparing naive and aware datetime ISO
         return entity.creation_date.isoformat()
 
-    def get_scenarios_for_owner(self, owner_id: str):
+    def get_scenarios_for_owner(self, owner_id: str, uid: str):
         cycles_scenarios: t.List[t.Union[Scenario, Cycle]] = []
         with self.lock:
             if self.scenario_by_cycle is None:
@@ -700,14 +699,8 @@ class _GuiCoreContext(CoreEventConsumerBase):
                         cycles_scenarios.append(entity)
         return sorted(cycles_scenarios, key=_GuiCoreContext.get_entity_creation_date_iso)
 
-    def get_data_node_history(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-        ):
+    def get_data_node_history(self, id: str, uid: str):
+        if id and (dn := core_get(id)) and isinstance(dn, DataNode):
             res = []
             for e in dn.edits:
                 job_id = e.get("job_id")
@@ -729,50 +722,6 @@ class _GuiCoreContext(CoreEventConsumerBase):
             return sorted(res, key=lambda r: r[0], reverse=True)
         return _DoNotUpdate()
 
-    @staticmethod
-    def __is_tabular_data(datanode: DataNode, value: t.Any):
-        if isinstance(datanode, _TabularDataNodeMixin):
-            return True
-        if datanode.is_ready_for_reading:
-            return isinstance(value, (pd.DataFrame, pd.Series, list, tuple, dict))
-        return False
-
-    def get_data_node_data(self, datanode: DataNode, id: str):
-        if (
-            id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
-            and (dn := core_get(id))
-            and isinstance(dn, DataNode)
-        ):
-            if dn._last_edit_date:
-                if isinstance(dn, _TabularDataNodeMixin):
-                    return (None, None, True, None)
-                try:
-                    value = dn.read()
-                    if _GuiCoreContext.__is_tabular_data(dn, value):
-                        return (None, None, True, None)
-                    val_type = (
-                        "date"
-                        if "date" in type(value).__name__
-                        else type(value).__name__
-                        if isinstance(value, Number)
-                        else None
-                    )
-                    if isinstance(value, float):
-                        if math.isnan(value):
-                            value = None
-                    return (
-                        value,
-                        val_type,
-                        None,
-                        None,
-                    )
-                except Exception as e:
-                    return (None, None, None, f"read data_node: {e}")
-            return (None, None, None, f"Data unavailable for {dn.get_simple_label()}")
-        return _DoNotUpdate()
-
     def __check_readable_editable(self, state: State, id: str, ent_type: str, var: str):
         if not is_readable(t.cast(ScenarioId, id)):
             state.assign(var, f"{ent_type} {id} is not readable.")
@@ -872,14 +821,26 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 state.assign(_GuiCoreContext._DATANODE_VIZ_ERROR_VAR, f"Error updating Datanode tabular value. {e}")
         setattr(state, _GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, dn_id)
 
+    def get_data_node_properties(self, id: str, uid: str):
+        if id and is_readable(t.cast(DataNodeId, id)) and (dn := core_get(id)) and isinstance(dn, DataNode):
+            try:
+                return (
+                    (
+                        (k, f"{v}")
+                        for k, v in dn._get_user_properties().items()
+                        if k != _GuiCoreContext.__PROP_ENTITY_NAME
+                    ),
+                )
+            except Exception:
+                return None
+        return None
+
     def __read_tabular_data(self, datanode: DataNode):
         return datanode.read()
 
-    def get_data_node_tabular_data(self, datanode: DataNode, id: str):
+    def get_data_node_tabular_data(self, id: str, uid: str):
         if (
             id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
             and is_readable(t.cast(DataNodeId, id))
             and (dn := core_get(id))
             and isinstance(dn, DataNode)
@@ -887,17 +848,15 @@ class _GuiCoreContext(CoreEventConsumerBase):
         ):
             try:
                 value = self.__read_tabular_data(dn)
-                if _GuiCoreContext.__is_tabular_data(dn, value):
+                if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
                     return value
             except Exception:
                 return None
         return None
 
-    def get_data_node_tabular_columns(self, datanode: DataNode, id: str):
+    def get_data_node_tabular_columns(self, id: str, uid: str):
         if (
             id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
             and is_readable(t.cast(DataNodeId, id))
             and (dn := core_get(id))
             and isinstance(dn, DataNode)
@@ -905,7 +864,7 @@ class _GuiCoreContext(CoreEventConsumerBase):
         ):
             try:
                 value = self.__read_tabular_data(dn)
-                if _GuiCoreContext.__is_tabular_data(dn, value):
+                if _GuiCoreDatanodeAdapter._is_tabular_data(dn, value):
                     return self.gui._tbl_cols(
                         True, True, "{}", json.dumps({"data": "tabular_data"}), tabular_data=value
                     )
@@ -913,11 +872,9 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 return None
         return None
 
-    def get_data_node_chart_config(self, datanode: DataNode, id: str):
+    def get_data_node_chart_config(self, id: str, uid: str):
         if (
             id
-            and isinstance(datanode, DataNode)
-            and id == datanode.id
             and is_readable(t.cast(DataNodeId, id))
             and (dn := core_get(id))
             and isinstance(dn, DataNode)
@@ -931,20 +888,6 @@ class _GuiCoreContext(CoreEventConsumerBase):
                 return None
         return None
 
-    def select_id(self, state: State, id: str, payload: t.Dict[str, str]):
-        args = payload.get("args")
-        if args is None or not isinstance(args, list) or len(args) == 0 and isinstance(args[0], dict):
-            return
-        data = args[0]
-        if owner_id := data.get("owner_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_OWNER_ID_VAR, owner_id)
-        elif history_id := data.get("history_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_HISTORY_ID_VAR, history_id)
-        elif data_id := data.get("data_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_ID_VAR, data_id)
-        elif chart_id := data.get("chart_id"):
-            state.assign(_GuiCoreContext._DATANODE_VIZ_DATA_CHART_ID_VAR, chart_id)
-
     def on_dag_select(self, state: State, id: str, payload: t.Dict[str, str]):
         args = payload.get("args")
         if args is None or not isinstance(args, list) or len(args) < 2:

+ 6 - 6
tests/gui_core/test_context_is_readable.py

@@ -328,7 +328,7 @@ class TestGuiCoreContext_is_readable:
     def test_get_scenarios_for_owner(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
             gui_core_context = _GuiCoreContext(Mock())
-            gui_core_context.get_scenarios_for_owner(a_scenario.id)
+            gui_core_context.get_scenarios_for_owner(a_scenario.id, '')
             mockget.assert_called_once()
             mockget.reset_mock()
 
@@ -406,18 +406,18 @@ class TestGuiCoreContext_is_readable:
     def test_get_data_node_tabular_data(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
             gui_core_context = _GuiCoreContext(Mock())
-            gui_core_context.get_data_node_tabular_data(a_datanode, a_datanode.id)
+            gui_core_context.get_data_node_tabular_data(a_datanode.id, "")
             mockget.assert_called_once()
             mockget.reset_mock()
 
             with patch("taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
-                gui_core_context.get_data_node_tabular_data(a_datanode, a_datanode.id)
+                gui_core_context.get_data_node_tabular_data(a_datanode.id, "")
                 mockget.assert_not_called()
 
     def test_get_data_node_tabular_columns(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
             gui_core_context = _GuiCoreContext(Mock())
-            gui_core_context.get_data_node_tabular_columns(a_datanode, a_datanode.id)
+            gui_core_context.get_data_node_tabular_columns(a_datanode.id, "")
             mockget.assert_called_once()
             mockget.reset_mock()
 
@@ -428,10 +428,10 @@ class TestGuiCoreContext_is_readable:
     def test_get_data_node_chart_config(self):
         with patch("taipy.gui_core._context.core_get", side_effect=mock_core_get) as mockget:
             gui_core_context = _GuiCoreContext(Mock())
-            gui_core_context.get_data_node_chart_config(a_datanode, a_datanode.id)
+            gui_core_context.get_data_node_chart_config(a_datanode.id, "")
             mockget.assert_called_once()
             mockget.reset_mock()
 
             with patch("taipy.gui_core._context.is_readable", side_effect=mock_is_readable_false):
-                gui_core_context.get_data_node_chart_config(a_datanode, a_datanode.id)
+                gui_core_context.get_data_node_chart_config(a_datanode.id, "")
                 mockget.assert_not_called()