Using Tabulator grids in Dashboard v2.0 templates

Thanks @joepavitt , this is very helpful

Hi @joepavitt,
The custom node is progressing nicely, and I am succeeding to load & interact with the table.
2 quick questions:

  1. What is the best way to import the Tabulator's CSS file. I currently import it as an href from a URL, but want the node to be self -contained and not rely on external locations. @import doesn't work - it is rejected by the build process

  2. I notice that the onInput() handler receives a done() function.
    In regular custom nodes, done() should be called after the node finishes processing the incoming message (including all async callbacks).
    Should I call done() once onInput() completes? what if the client-side node is still running? For example, many of the Tabulator methods are async, and I send their response only after they complete processing. Should I also issue a done() after that? and if yes, how do I get this function in the this.$socket.on() listener?

I would expect that you can put the css file into a /ui/stylesheets folder, then in your component's .vue file in the <style></sctyle> you should be able to @import "".

Noticed you've tried this though, so I'll need to investigate further. I've opened Third Party: Cannot @import in CSS Ā· Issue #663 Ā· FlowFuse/node-red-dashboard Ā· GitHub to track

Tried to reproduce locally, and it's working for me. Have opened a PR (hopefully merging soon) which demonstrates in the base ui-example how to do it.

Solved!
@import did not work when inside the default <style scoped> section.
when I put it in a separate <style>...</style> section, it imports successfully.
Thanks @joepavitt

1 Like

Hi @joepavitt, another quick question:
Each Tabulator instance maps to a unique div id in the ui page, for example:

<template>
<div id="abcdef"></div>
</template>

<script>
...
this.table = new Tabulator('#abcdef',InitObj);
...
</script>

In order to support multiple table nodes on the same page, I need to ensure div id uniqueness. The solution I found is to use a generic div id and dynamically update it with a unique value, as follows:

<template>
<div id="tableContainer"></div>
</template>
<script>
...
mounted () {
   this.tblId = this.props.tblId || 'tbl'+Math.random().toString(12).substr(2, 12);
   let tblDiv = document.getElementById('tblContainer');
   tblDiv.id = this.tblId;
...
</script>

This works well. Yet I understand that behind the scenes the <template> sections of the various table nodes are combined into a single HTML page, resulting in multiple divs, with the same id ('TblContainer'), which then get unique Ids upon table node mounting.

I know that document.getElementById() returns the first element it finds (hence, after changing its id, next time it will retrieve the following one etc.).
To ensure the table nodes do not get mixed up - am I safe to assume that these divs are retrieved from the DOM in the same order they were defined in the page, i.e. the node order in the group hierarchy?

No need, Vue comes built in with a cleaner alternative call ref.

You can assign ref="myDiv" in your HTML, then in your JS, you can this.$refs.myDiv and it just works, tou can copy and paste it as much as you like, and this.$refs will always stay constrained to that instance of the ui-template

1 Like

Thanks @joepavitt, I will try it.
Does this still give me the ability to set a specified div id for each table instance?

The reason I am asking, is that in addition to the "classic" Node-red way of configuring, writing & reading the table via flow messages, I want to also enable direct API access (where makes sense), which can simplify the flows.

For example, allowing a supporting template node with button & drop-down controls to set the table appearance, e.g. group by, sort, filter etc. Tabulator can retrieve a reference to a table object by its div id, from anywhere on the page. This would allow manipulating that table directly from within template callbacks without having to send messages.

Another example is advanced (transactional) Tabulator commands which require multiple steps. It would be more complex to do it via stateful message loops, vs. direct API calls.

Hmmm, that makes it quite a bit trickier as that's quite anti-pattern with respect to how Node-RED operates, i.e. not sending messages in order to drive that.

You could set id on mounted, but then that would update every time your Dashboard mounts.

Thanks @joepavitt , the ref works beautifully!

I still have to set a unique Id to each table div (else the tables override each other), but don't have to worry about getElementById() anymore.

I'm auto-generating a random unique Id for each div upon mount. The user still has the ability to set a specified Id via an optional node-config property.

<template>
<div ref="tblDiv"></div>
</template>

<script>
mounted () {
   this.tblId = this.props.tblId || 'tbl'+Math.random().toString(12).substr(2, 12);
   this.$refs.tblDiv.id = this.tblId;
1 Like

For what it's worth, Tabulator does allow you to pass in the actual element, not just a #id selector: Vue Table / Data Grid | Tabulator

Thanks! :+1: :+1: :+1:

Hi @joepavitt,
I'm providing a configuration option for controlling multi-user behavior (for those not on flowfuse :slightly_smiling_face:), i.e. whether a table node should be shared (replicated across users) or per-client.

In per-user mode, msg._client.socketId enables me to determine if the incoming message is from another client and drop such messages. However, I am concerned this may impact patterns which rely on retaining the original message (like link-call).

Yet it seems like link-call knows how to handle this and doesn't timeout on missing responses from dashboard objects. In your opinion, can I rely on this link-call behavior, or add an "ignore & forward" option?

Can you show us a simple example where the link call situation might occur?

No situation. Link-call works well. Just wanted to be reassured that I can count on this behavior, as described below:

Assum that we have a dashboard node (e.g. button) sending messages to the contrib-table node through call-link.
image

The button message is replicated by the server (with socket id) and sent to the respective table nodes in all open clients:

  • If all the table nodes respond - all responses are returned to caller successfully
  • If no response from any table node - single timeout
  • If the table node responds only to the msg with same socket Id, its response is returned to the caller, with no timeout for the other (dropped) messages.

That is an interesting usage, multiple returns from a single call of the link call node, but I can't see any reason why it would not work, providing the tabulator passes through the original message properties that tell it where to return to.

Thanks @Colin, I think I figured it out.
The Link caller sets a timer and sends the message to its assigned Link-in. If the message is replicated (while retaining the return address) all the replicas return to the caller and are sent forward (the first returning message cancels the timer).

So no issue with multi-user dashboard nodes.

Hi @joepavitt , @BartButenaers and anyone else who can help,
The development of the tabulator node progresses well and is over >80% complete.

So far, I have done my development by modifying the ui-example node. I now tried to migrate it to its own new node. I have made a new clean copy of UI-example and changed the naming (from ui-example to ui-tabulator and from UIExample to UITabulator). I also changed the file names, build scripts etc.
NPM builds the umd file with no errors, and the new node loads & shows properly in the Node-red editor.

However, the node is not showing on the dashboard, and the group comes up empty. Other than naming, I have not changed anything in the original ui-example code, and I made sure the node registers itself in the dashboard (in ui-tabulator.js).

        if (group) {
            group.register(node, config, evts)

Did I miss any steps in the migration?

I open the consoles of both editor and dashboard client and am not receiving any errors or exceptions. I also tried uninstalling the existing ui-example node (in case it clashes with the new one), but the problem persists.

The first thing I'd check is our debug screen at http://<host>:<ip>/dashboard/_debug

Head to the "Widgets" tab and search for your widget type, ui-tabulator, does it show there?

Yes it shows.

Is there a channel I can communicate with you directly?