Dashboard 2 / Tabulator table initialization issue when loading the dashboard

Hi everyone,

I'm experiencing a strange issue with @omrid01/node-red-dashboard-2-table-tabulator.

I created a empty table using ui-tabulator, which is populated automatically when the flow is deployed. To avoid timing issues, I already added a 2.5-second delay before sending the data to the table.

However, quite often when the flow starts—especially if the Dashboard is not currently open on the page containing the table—the table does not initialize correctly. Sometimes it loads without issues, but other times the cells are not clickable, even though they are configured to be.

I've checked and rechecked my flow and I can't find any obvious problems.

The table cells contain HTML content, and I'm also applying custom CSS through a ui-template.

I also tried resetting the table first using the tbResetTable command and then sending the data again, but the issue still occurs.

Has anyone experienced something similar, or is there a recommended way to ensure that the table is fully initialized before loading the data?

Any suggestions would be greatly appreciated. Thanks!

  • Are you listening to the node responses after sending commands?
  • Are you waiting for the "tableReady" event indicating that the table finished initializing?
  • Are there any HTML errors in the browser's console when you access the table?

Please share a flow that represents your scenario, so I can investigate it.

I am about to release a new version of the ui-tabulator node with some enhancements. If you want me to look into the problem you report, please share a representative flow

Hi, sorry for the late reply. Here's a representative flow from my Node-RED project, uploaded to GitHub:
https://github.com/PW-Developer6/Node-Red-dashboard-2.0-tabulator-issue
As you can see, I also tried adding a ui-template node to preload the table even if the dashboard is opened on a different page, but the clickable cells are still not working.

I started looking into it. Let's start with a few questions:

  1. What is the "preload the table" template node supposed to do?
  2. You say that the table is automatically populated when the flow is deployed. Maybe I'm missing something, but doesn't this mean that the table comes up with predefined data whenever Node-red starts? And if so, why not place this initial data (currently in your inject node) as part of the table configuration?
  3. In your project scenario, what happens once the user clicks a cell in the table?

The "preload the table" node preloads the Dashboard page containing the table, even if the user is currently on the Home page.

After the flow is deployed, the table data are updated by another flow that reads and processes data from a JSON file. The contents of this file may change depending on the most recently saved data. The table is then updated dynamically whenever new messages are received from the MQTT server. These messages are first processed by the same flow that handles the data read from the JSON file. The processed data are then sent to the table, replacing the previous contents, and the JSON file is rewritten with the updated values.

In the Inject node, I included an example of a payload that the table might receive.

In my actual project, when the user clicks on a table cell, a dialog opens showing information about the selected device. This part is not included in the shared flow because I didn't think it was relevant to the issue. However, if you click on any cell in the table, an empty dialog should still open.

Let me give some background:
every dashboard node, is actually composed of 2 parts. the "backend" node which is part of the flow, and the "widget" which is an HTML object living in the dashboard page and interacting with its backend node.
for example, a button node:
backend node: image widget: image

the widgets are created once their respective page opens, and destroyed when it closes. messages sent to a backend node while there is no open dashboard client, are ignored.

in our case, the actual ui-tabulator table object is a widget, so you cannot set data into it before the dashboard page is open and the table is initialized. The "preload" template you added is not useful since it opens an iframe (which has a different DOM & context), and moreover, this template is created only after the page is opened (i.e. chicken & egg).

What I propose to do here, is to listen to the notification "tableBuilt", and then set a flag which allows safe access to the table, as in the attached flow.

[
    {
        "id": "59f03b602ebe4496",
        "type": "ui-template",
        "z": "5130c95e050fe9b6",
        "group": "",
        "page": "3c88ebbd2d694eb8",
        "ui": "",
        "name": "Page Style PN STATION STATUS",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": ".tabulator{\n    font-size:16px !important;\n    background-color:White !important;\n}\n.tabulator-header{\n    background-color: White !important;\n    color:steelblue !important;\n}\n.tabulator-row{\n    background-color: White !important;\n}\n.tabulator-cell {\n        border: 3px solid transparent;\n        box-sizing: border-box;\n    }\n\n.tabulator-cell:hover {\n    border: 3px solid #585858;\n    cursor: pointer;\n}\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "page:style",
        "className": "",
        "x": 180,
        "y": 560,
        "wires": [
            []
        ]
    },
    {
        "id": "5a0a5c23725007be",
        "type": "debug",
        "z": "5130c95e050fe9b6",
        "name": "debug 2",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 855,
        "y": 700,
        "wires": [],
        "l": false
    },
    {
        "id": "3dedf938038a815e",
        "type": "ui-tabulator",
        "z": "5130c95e050fe9b6",
        "name": "PN STATION STATUS",
        "group": "f2df73be35a54955",
        "initObj": "{\n  \"height\": 900,\n  \"layout\": \"fitColumns\",\n  \"headerVisible\": true,\n  \"columns\": [\n    {\"title\":\"id\",\"field\":\"id\",\"visible\":false},\n    {\"title\":\"C1\",\"field\":\"C1\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C2\",\"field\":\"C2\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C3\",\"field\":\"C3\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C4\",\"field\":\"C4\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C5\",\"field\":\"C5\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C6\",\"field\":\"C6\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C7\",\"field\":\"C7\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C8\",\"field\":\"C8\",\"hozAlign\":\"left\",\"formatter\":\"html\"},\n    {\"title\":\"C9\",\"field\":\"C9\",\"hozAlign\":\"left\",\"formatter\":\"html\"}\n  ]\n}",
        "funcs": "",
        "allowMsgFuncs": false,
        "maxWidth": "",
        "events": "cellClick, tableBuilt",
        "order": 2,
        "multiUser": false,
        "validateRowIds": false,
        "themeCSS": "",
        "themeFile": "",
        "tblDivId": "",
        "printToLog": false,
        "width": "27",
        "height": "15",
        "x": 560,
        "y": 640,
        "wires": [
            [
                "cb474968c93d588f"
            ]
        ]
    },
    {
        "id": "c4b668eb7261b006",
        "type": "inject",
        "z": "5130c95e050fe9b6",
        "name": "",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": "0.3",
        "topic": "",
        "x": 75,
        "y": 640,
        "wires": [
            [
                "3e956c84e978d866"
            ]
        ],
        "l": false
    },
    {
        "id": "cb474968c93d588f",
        "type": "switch",
        "z": "5130c95e050fe9b6",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "jsonata_exp",
                "v": "msg.msgType = \"tbNotification\" and msg.event = \"tableBuilt\"",
                "vt": "jsonata"
            },
            {
                "t": "jsonata_exp",
                "v": "msg.msgType = \"tbNotification\" and msg.event = \"cellClick\"",
                "vt": "jsonata"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "false",
        "repair": false,
        "outputs": 3,
        "x": 750,
        "y": 640,
        "wires": [
            [
                "024a7b5da1563c9c"
            ],
            [
                "f57a163a11487675"
            ],
            [
                "5a0a5c23725007be"
            ]
        ]
    },
    {
        "id": "024a7b5da1563c9c",
        "type": "function",
        "z": "5130c95e050fe9b6",
        "name": "tableBuilt",
        "func": "flow.set(\"tables\", { \"PN_STATION_STATUS\": true });\nreturn null;  // or send a message to other initialization tasks",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "// Code added here will be run once\n// whenever the node is started.\nflow.set(\"tables\",{\"PN_STATION_STATUS\":false});",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 580,
        "wires": [
            []
        ]
    },
    {
        "id": "a44479a7ca51416e",
        "type": "function",
        "z": "5130c95e050fe9b6",
        "name": "Is table ready?",
        "func": "const isReady = flow.get(\"tables.PN_STATION_STATUS\");\nif (isReady)\n    return msg;\nelse\n    node.error(\"table is not ready\",msg)\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 640,
        "wires": [
            [
                "3dedf938038a815e"
            ]
        ]
    },
    {
        "id": "f57a163a11487675",
        "type": "function",
        "z": "5130c95e050fe9b6",
        "name": "cellClick",
        "func": "\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 640,
        "wires": [
            [
                "ae7e36415dd33546"
            ]
        ]
    },
    {
        "id": "ae7e36415dd33546",
        "type": "debug",
        "z": "5130c95e050fe9b6",
        "name": "Handle cell click...",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1010,
        "y": 780,
        "wires": []
    },
    {
        "id": "740f4f17ee12fec2",
        "type": "catch",
        "z": "5130c95e050fe9b6",
        "name": "",
        "scope": [
            "a44479a7ca51416e"
        ],
        "uncaught": false,
        "x": 330,
        "y": 700,
        "wires": [
            [
                "793c6daa45574e44"
            ]
        ]
    },
    {
        "id": "793c6daa45574e44",
        "type": "debug",
        "z": "5130c95e050fe9b6",
        "name": "Table not ready",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 540,
        "y": 700,
        "wires": []
    },
    {
        "id": "3e956c84e978d866",
        "type": "function",
        "z": "5130c95e050fe9b6",
        "name": "setData",
        "func": "msg.tbCmd = \"setData\";\nmsg.tbArgs = [\n    [\n        {\n            \"id\": 0,\n            \"C0\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: green; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.7</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev5</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">Buskoppler Profinet</div>\\n    </div>\\n  </div>\",\n            \"C1\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: green; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.6</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev4</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">IM151-3</div>\\n    </div>\\n  </div>\",\n            \"C2\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: green; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.2</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev1</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">IM151-3</div>\\n    </div>\\n  </div>\",\n            \"C3\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: green; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.1</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">pn-io</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">S7-300</div>\\n    </div>\\n  </div>\",\n            \"C4\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: yellow; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.4</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev2</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">SCALANCE X-200</div>\\n    </div>\\n  </div>\",\n            \"C5\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: yellow; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.5</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev3</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">SCALANCE X-200</div>\\n    </div>\\n  </div>\",\n            \"C6\": \"<div style=\\\"background-color: green; width: 100%; height: 75px; position: relative; color: white; font-family: sans-serif; font-size: 12px; overflow: hidden;\\\">\\n    <div style=\\\"background-color: green; width: 15px; height: 15px; position: absolute; top: 0; right: 0;\\\">\\n    </div>\\n    <div style=\\\"background-color: green !important; padding: 12px 5px 5px 5px; height: 100%; overflow: hidden;\\\">\\n        <div\\n            style=\\\"display: flex; align-items: center; gap: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">\\n            <span\\n                style=\\\" display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: green; flex-shrink: 0; \\\"></span>\\n            <span>192.168.0.8</span> </div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">dev6</div>\\n        <div style=\\\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\\\">KP8 PN 8KEYS 8DI/O DC24V</div>\\n    </div>\\n  </div>\"\n        }\n    ]\n];\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 180,
        "y": 640,
        "wires": [
            [
                "a44479a7ca51416e"
            ]
        ]
    },
    {
        "id": "3c88ebbd2d694eb8",
        "type": "ui-page",
        "name": "PN STATION STATUS",
        "ui": "ui_base_01",
        "path": "/pn_station_status",
        "icon": "view-dashboard",
        "layout": "grid",
        "theme": "cf6324aece5445ca",
        "breakpoints": [
            {
                "name": "Default",
                "px": "0",
                "cols": "3"
            },
            {
                "name": "Tablet",
                "px": "576",
                "cols": "6"
            },
            {
                "name": "Small Desktop",
                "px": "768",
                "cols": "9"
            },
            {
                "name": "Desktop",
                "px": "1024",
                "cols": "12"
            }
        ],
        "order": 2,
        "className": "",
        "visible": true,
        "disabled": false
    },
    {
        "id": "f2df73be35a54955",
        "type": "ui-group",
        "name": "PN Station Status",
        "page": "3c88ebbd2d694eb8",
        "width": "30",
        "height": "16",
        "order": 1,
        "showTitle": false,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "ui_base_01",
        "type": "ui-base",
        "name": "Dashboard Demo",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification"
        ],
        "showPathInSidebar": false,
        "navigationStyle": "default",
        "titleBarStyle": "default"
    },
    {
        "id": "cf6324aece5445ca",
        "type": "ui-theme",
        "name": "Default Theme",
        "colors": {
            "surface": "#ffffff",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "density": "default",
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    },
    {
        "id": "a31d09e1d6bc4c76",
        "type": "global-config",
        "env": [],
        "modules": {
            "@flowfuse/node-red-dashboard": "1.30.2",
            "@omrid01/node-red-dashboard-2-table-tabulator": "0.9.0"
        }
    }
]

[Edit] the table was configured for "Multi-user" mode. changed back to "Shared".

One more thing: I believe it would be easier & safer to format the table in the table configuration (using cssClass properties or formatter functions), or using the ui-tabulator command tbSetStyle, instead of overriding native tabulator classes. you can refer to the Table Styling section in the help.

Thank you so much!

We were focusing on initialization, but if relevant, you will also need to reset the flag to false upon page close.