Ui-table (Dashboard 1) vs ui-tabulator (Dashboard 2) vs ui-table (Dashboard 2)

I am starting to plan on moving my Dashboard 1 to Dashboard 2. Currently, I have quite a few pages displaying data in table format using ui-table. If ui-table (Dashboard 1) and ui-tabulator (Dashboard 2) both use the tabulator module, would it be simpler for me to use ui-tabulator instead of the built in Dashboard 2 table node? Or are the differences between ui-table (1.0) and ui-tabulator (2.0) significant enough that I would not be able to leverage much of my existing setup?

My use of data tables is primarily for displaying and sorting data (no dynamic update, no data entry, no double-clicks on rows etc). I do format a number of columns (date/time, decimal formatting, column alignment etc.)

Thanks for your help.

Note: I have looked at the documentation for ui-tabulator but could not find many examples.

ui-tabulator in dashboard-2 follows the same concept of ui-table in dashboard-1, whereas the dashboard-2 ui-table is based on a Vue template and behaves differently. Hence, the migration to ui-tabulator should be easier.

D-2 ui-tabulator supports all the basic capabilities of D-1 ui-table, plus a quantum leap of new functionality, such as;

  1. Table data & styles are automatically saved in the Node-red datastore, automatically (re)populating the table upon page load/refresh
  2. Support for both shared and multi-user modes
  3. Ability to set functions in the table configuration (and runtime commands) enabling conditional-formatting, as well as dynamic event-handlers, mutators, filters, sorters, grouping etc.
  4. Ability to retrieve/search data from the table
  5. The table node can send notifications for user-selected table events, e.g. clicks, data-changes etc.
  6. In-cell editing
  7. Multiple options for cell, row, column & table styling

ui-tabulator is fully maintained. it includes comprehensive on-line documentation (in the editor), and example flow covering most of the functionality. The node follows a live development roadmap, and reported issues & feature requests are handled promptly.

3 Likes

Thanks so much! Will move ahead with ui-tabulator. Looking forward to trying the enhancements!

I prefer the ui-table in Dashboard 2 personally. It is much better than ui-table in Dashboard 1 in terms of performance and features. The ui-table in Dashboard 2 automatically formats data from database queries, so that there is minimum work for a quick start.

1 Like

Thanks @davidz. Since I am migrating an existing Dashboard1 setup, it appears I would have to undo my existing code (setup, formatting etc.) to use ui-table. Is that not the case?

@omrid - I ran into some issues using ui-tabulator (could just be my lack of understanding) and have created a GitHub issue. GitHub Issue #15. Can you take a look at it when you get a chance?

Your configuration includes the directive

"layout": "fitDataTable",

Which overrides the column width %. If you take it out, the % will work.

Regarding the date field, it is due to a missing dependency (Tabulator relies on the luxon datetime package). I will add it into the node on the next ui-tabulator release. For now, you can import it manually by adding a ui-template node to the page, with the following:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js"></script>

(You can download the package and load it from a local location).

Please note, that ui-Tabulator uses the latest Tabulator version (6.3) whereas D1 ui-table uses a very old Tabulator version, so always check the Tabulator documentation to see if anything changed.

2 Likes

Thanks that did it. It was a copy/paste from ui-table (DB1) and it worked there!

I'm getting inconsistent results in displaying data. Sometimes only one row will show, sometimes two or three, but never the whole data set. I tried to create a test case by passing in data from an inject node (using msg.tbSetData = array of values, and also msg.tbCmd = "setData", msg.tbArgs = array of values) and nothing showed. I've been looking at the Tabulator documentation and examples but nothing seems to work. Below is the test flow:

[{"id":"0316cd25d00c5e0d","type":"tab","label":"Test ui-tabulator","disabled":false,"info":"","env":[]},{"id":"2d823c1347018a71","type":"ui-tabulator","z":"0316cd25d00c5e0d","name":"","group":"add4028b768d1c47","initObj":"{\n\t\"columns\": [\n\t\t{\n\t\t\t\"title\": \"Row\",\n\t\t\t\"field\": \"row_id\",\n\t\t\t\"formatter\": \"rownum\",\n\t\t\t\"width\": \"8%\"\n\t\t},\n\t\t{\n\t\t\t\"formatter\": \"datetime\",\n\t\t\t\"formatterParams\": {\n\t\t\t\t\"inputFormat\": \"yyyy-MM-dd hh:mm:ss\",\n\t\t\t\t\"outputFormat\": \"yyyy-MM-dd\",\n\t\t\t\t\"invalidPlaceholder\": \"(invalid date)\"\n\t\t\t},\n\t\t\t\"title\": \"Date\",\n\t\t\t\"field\": \"Date\",\n\t\t\t\"width\": \"12%\"\n\t\t},\n\t\t{\n\t\t\t\"formatter\": \"datetime\",\n\t\t\t\"formatterParams\": {\n\t\t\t\t\"inputFormat\": \"yyyy-MM-dd hh:mm:ss\",\n\t\t\t\t\"outputFormat\": \"hh:mm:ss a\",\n\t\t\t\t\"invalidPlaceholder\": \"(invalid time)\"\n\t\t\t},\n\t\t\t\"title\": \"Time\",\n\t\t\t\"field\": \"Time\",\n\t\t\t\"width\": \"12%\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Source\",\n\t\t\t\"field\": \"Source\",\n\t\t\t\"width\": \"15%\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Event\",\n\t\t\t\"field\": \"Event\",\n\t\t\t\"width\": \"15%\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Device\",\n\t\t\t\"field\": \"Device\",\n\t\t\t\"width\": \"20%\"\n\t\t},\n\t\t{\n\t\t\t\"title\": \"Value\",\n\t\t\t\"field\": \"Value\",\n\t\t\t\"width\": \"18%\"\n\t\t}\n\t]\n}","funcs":"","allowMsgFuncs":false,"maxWidth":"","events":"","order":2,"multiUser":false,"validateRowIds":false,"themeCSS":"","themeFile":"","tblDivId":"","printToLog":false,"width":"26","height":"14","x":724.8346862792969,"y":307.82719802856445,"wires":[[]]},{"id":"2ac511ca77dd0298","type":"inject","z":"0316cd25d00c5e0d","name":"setData (no Args)","props":[{"p":"setData","v":"[{\"Date\":\"2025-07-09 06:54:17\",\"Time\":\"2025-07-09 06:54:17\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"closed\"},{\"Date\":\"2025-07-09 06:52:40\",\"Time\":\"2025-07-09 06:52:40\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:57:39 CDT\"},{\"Date\":\"2025-07-09 06:52:40\",\"Time\":\"2025-07-09 06:52:40\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061962219\"},{\"Date\":\"2025-07-09 06:51:16\",\"Time\":\"2025-07-09 06:51:16\",\"Source\":\"events\",\"Event\":\"dewPoint\",\"Device\":\"Backyard Temp and Humidity\",\"Value\":\"69.4\"},{\"Date\":\"2025-07-09 06:51:16\",\"Time\":\"2025-07-09 06:51:16\",\"Source\":\"events\",\"Event\":\"heatIndex\",\"Device\":\"Backyard Temp and Humidity\",\"Value\":\"74.5\"},{\"Date\":\"2025-07-09 06:49:24\",\"Time\":\"2025-07-09 06:49:24\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"open\"},{\"Date\":\"2025-07-09 06:49:24\",\"Time\":\"2025-07-09 06:49:24\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"on\"},{\"Date\":\"2025-07-09 06:47:39\",\"Time\":\"2025-07-09 06:47:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:52:38 CDT\"},{\"Date\":\"2025-07-09 06:47:39\",\"Time\":\"2025-07-09 06:47:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061662134\"},{\"Date\":\"2025-07-09 06:42:39\",\"Time\":\"2025-07-09 06:42:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:47:38 CDT\"},{\"Date\":\"2025-07-09 06:42:39\",\"Time\":\"2025-07-09 06:42:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061361942\"},{\"Date\":\"2025-07-09 06:41:40\",\"Time\":\"2025-07-09 06:41:40\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"off\"},{\"Date\":\"2025-07-09 06:37:39\",\"Time\":\"2025-07-09 06:37:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:42:38 CDT\"},{\"Date\":\"2025-07-09 06:37:39\",\"Time\":\"2025-07-09 06:37:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061061759\"},{\"Date\":\"2025-07-09 06:36:37\",\"Time\":\"2025-07-09 06:36:37\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"closed\"},{\"Date\":\"2025-07-09 06:33:09\",\"Time\":\"2025-07-09 06:33:09\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"on\"},{\"Date\":\"2025-07-09 06:32:39\",\"Time\":\"2025-07-09 06:32:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:37:38 CDT\"},{\"Date\":\"2025-07-09 06:32:39\",\"Time\":\"2025-07-09 06:32:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752060761365\"}]","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":398.8345642089844,"y":172.82720947265625,"wires":[["2d823c1347018a71","b65678d49725e839"]]},{"id":"b65678d49725e839","type":"debug","z":"0316cd25d00c5e0d","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":448.6470031738281,"y":299.31798571875,"wires":[]},{"id":"5c4373048b773f96","type":"inject","z":"0316cd25d00c5e0d","name":"setData (wtih Args)","props":[{"p":"tbArgs","v":"[{\"Date\":\"2025-07-09 06:54:17\",\"Time\":\"2025-07-09 06:54:17\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"closed\"},{\"Date\":\"2025-07-09 06:52:40\",\"Time\":\"2025-07-09 06:52:40\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:57:39 CDT\"},{\"Date\":\"2025-07-09 06:52:40\",\"Time\":\"2025-07-09 06:52:40\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061962219\"},{\"Date\":\"2025-07-09 06:51:16\",\"Time\":\"2025-07-09 06:51:16\",\"Source\":\"events\",\"Event\":\"dewPoint\",\"Device\":\"Backyard Temp and Humidity\",\"Value\":\"69.4\"},{\"Date\":\"2025-07-09 06:51:16\",\"Time\":\"2025-07-09 06:51:16\",\"Source\":\"events\",\"Event\":\"heatIndex\",\"Device\":\"Backyard Temp and Humidity\",\"Value\":\"74.5\"},{\"Date\":\"2025-07-09 06:49:24\",\"Time\":\"2025-07-09 06:49:24\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"open\"},{\"Date\":\"2025-07-09 06:49:24\",\"Time\":\"2025-07-09 06:49:24\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"on\"},{\"Date\":\"2025-07-09 06:47:39\",\"Time\":\"2025-07-09 06:47:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:52:38 CDT\"},{\"Date\":\"2025-07-09 06:47:39\",\"Time\":\"2025-07-09 06:47:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061662134\"},{\"Date\":\"2025-07-09 06:42:39\",\"Time\":\"2025-07-09 06:42:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:47:38 CDT\"},{\"Date\":\"2025-07-09 06:42:39\",\"Time\":\"2025-07-09 06:42:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061361942\"},{\"Date\":\"2025-07-09 06:41:40\",\"Time\":\"2025-07-09 06:41:40\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"off\"},{\"Date\":\"2025-07-09 06:37:39\",\"Time\":\"2025-07-09 06:37:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:42:38 CDT\"},{\"Date\":\"2025-07-09 06:37:39\",\"Time\":\"2025-07-09 06:37:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752061061759\"},{\"Date\":\"2025-07-09 06:36:37\",\"Time\":\"2025-07-09 06:36:37\",\"Source\":\"events\",\"Event\":\"contact\",\"Device\":\"Bedroom Closet Contact\",\"Value\":\"closed\"},{\"Date\":\"2025-07-09 06:33:09\",\"Time\":\"2025-07-09 06:33:09\",\"Source\":\"events\",\"Event\":\"switch\",\"Device\":\"Bedroom Closet Switch\",\"Value\":\"on\"},{\"Date\":\"2025-07-09 06:32:39\",\"Time\":\"2025-07-09 06:32:39\",\"Source\":\"events\",\"Event\":\"nextPoll\",\"Device\":\"Hub Details\",\"Value\":\"2025-07-09 06:37:38 CDT\"},{\"Date\":\"2025-07-09 06:32:39\",\"Time\":\"2025-07-09 06:32:39\",\"Source\":\"events\",\"Event\":\"lastUpdated\",\"Device\":\"Hub Details\",\"Value\":\"1752060761365\"}]","vt":"json"},{"p":"tbCmd","v":"addData","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":403.3051452636719,"y":438.003662109375,"wires":[["2d823c1347018a71","b65678d49725e839"]]},{"id":"12abcf748360dbd8","type":"ui-template","z":"0316cd25d00c5e0d","group":"add4028b768d1c47","page":"","ui":"","name":"","order":1,"width":0,"height":0,"head":"","format":"<script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/npm/luxon@2.3.1/build/global/luxon.min.js\"></script>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":547.8933715820312,"y":53.82720947265625,"wires":[[]]},{"id":"add4028b768d1c47","type":"ui-group","name":"Test Group","page":"5edb5b5cd718fa05","width":"26","height":"12","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"5edb5b5cd718fa05","type":"ui-page","name":"Test ui-tabulator","ui":"de5759a313e7ad79","path":"/page5","icon":"home","layout":"grid","theme":"e4b50d7892c4eb32","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":5,"className":"","visible":"true","disabled":"false"},{"id":"de5759a313e7ad79","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"fixed","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":5,"showDisconnectNotification":true,"allowInstall":true},{"id":"e4b50d7892c4eb32","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px","density":"default"}}]

In ui-table (DB1), the data was just sent in as msg.payload, so not sure what I am missing. Any help would be really appreciated. Thanks!

Update: I figured out how the rows are sometimes 1, 2, 3. It appears that only 1 row is actually shown. Every time I change the date, one row gets added to the table. What I don't know is WHY this is happening :frowning:

Update2: I figured out why it was appending rows - the tbCmd I was using is "addData", so it would add a row. When I changed that to "setData" nothing showed even though the tbArgs had the whole array.

tbArgs should not be a JSON, it must be an object of type array. See the following extract from the online help:

.

If you want to provide the tbArgs from an inject node, use a property of type "expression", not JSON.

One more thing: I see that you gave the Row column the field name row_id. If you intend to refer to specific rows by Id (e.g. in updateData or updateRow), you will need a row Id which is recognized by your table. So either use the default row identifier id, or set row_id is your row identifier using theindex property in the table configuration.

1 Like

Thanks @omrid ...

I changed it to expression and the data still did not populate :frowning:.

I also tried to trigger loading the data set from a "button" sending in the input from a change node


If I put the data in the table configuration, it works just fine. But since I need to feed the data in from a database, that solution is not workable.


I am probably missing something very basic but can't figure it out.

I don't really need this since I don't typically need to handle row selection, double-click etc. I think it's something left over from my ui-table (DB1) setup so will look into it.

Thanks again for your help. Hopefully, once I get this working, I can stop bothering you :folded_hands:t3:

EDIT: Do I need to convert this JSON object into an array using javascript or JSONata? I think the mySQL node returns a JSON object..

Look carefully at the example and help excerpt I sent you previously. You need to encapsulate your argument (the data array) within an array. (There is a reason behind this)

If your SQL returns a JSON object, convert it to an object using a JSON node, or convert it in a function node as follows:

const data = JSON.parse(...your SQL response...);
msg.tbArgs = [data];  // place the argument in an array
1 Like

That seems to have done the trick! Thanks so much. Just out of curiosity, what is the reason for this?

I would still need to put it "within an array", correct? If I declared a second array (array2) and put the converted element as array2[0]=array1, would that work? Or is there a simpler way of doing that?
EDIT: Just tried this in a function node and it seems to work. If you have another recommendation, I would really appreciate having that as well.

Thanks a lot for your help.

Tabulator API calls have a varying number of arguments. So instead of maintaining a specific call signature for each Tabulator API (and having to update the node whenever Tabulator changes an API, e.g. adds an argument), sending arguments in an array makes the call generic & protects ui-tabulator from such Tabulator changes.

1 Like