How to use editor:true feature in uitable?

Hi guys,

I would be highly interested in learning how how my flow can be properly notified when a user changes a star rating (that is when my tabulator column is configured to use an editor via "editor":true, like in this example here.

image

Here's my uitable configuration:

msg.ui_control = {
    "tabulator": {
        autoResize: true,
        height: "100%",
        index:"ID",
        keybindings:{ // does not work at all.. :(
            "navUp" : "87", // page up
            "navDown" : "83", // page down
        },
        layout: "fitColumns",
        maxHeight:"100%",
        movableColumns: false,
        pagination: "local",
        paginationSize:19,
        responsiveLayout: "hide",
        resizableColumns: false,
        resizableRows: false,
        selectable: 1,
        selectableRollingSelection: true, // disable rolling selection
        columns: [
                    // ...
                    {
                        "title": "Rating",
                        "field": "RATING",
                        "align": "center",
                        "editor": true,
                        "formatter": "star",
                        "formatterParams": {
                            "stars":5
                        },
                        "width": 120
                    },
                   // ...
        ]
    }
}

Also, what I notice is that when I change the raiting the uitable node is not generating any message at all. I expected there'd be some information about that row or cell being generated.

So does anyone have a hint or idea on what could be the missing piece in this puzzle?

Hi,
I think what is missing is a callback to get your data back

Or

Only the onClick callback is defined by default

And another tip: to easily write callbacks use a function node as an editor. The. Copy paste it into your JSON using the visual editor. This handles converting of the inverted commas for you

1 Like

Sorry & Thanks much for that reminder. I once copied your flow into a sandbox area and forgot about your examples! I'll check them out right now..

No problem ... happy to help.

Just wondering: Your examples with injecting callback functions via buttons dynamically work nicely.
I could get the examples transferred into my table and the tooltips, etc. were showing up as well. (On a side note: I have like 50k or so rows, so injecting that tooltips function did not always work right away.. I had to click and "patiently" wait,.. but sometimes it never worked so I had to reload the page.. bit odd...)

Anyway, when I'm assigning a callback function statically in the configuration of a column right away, that function is not being triggered (tried both with cellClick and cellDblClick). Am I doing something basically wrong here or why do they not work?

Here's the code snippet from what I mean:

                    ...
                    {
                        "title": "Approved",
                        "field": "APPROVED_BY_MFI",
                        "align": "center",
                        "editor": false,
                        "cellDblClick": function(e, cell){
                            debugger;
                            this.send({
                                ui_control:{callback:'cellEdited'},
                                payload:cell.getValue(),
                                oldValue:cell.getOldValue(),
                                field:cell.getColumn().getField(),
                                id:cell.getRow().getIndex()
                            });
                        },
                        "formatter": "traffic",
                        "formatterParams": {
                            "min": -1,
                            "max": 1,
                            "color":["red", "orange", "green"]
                        },
                        "width": 40
                    },
                    ...

I also tried to assign a callback function globally, like this:

var cellDebugFunc = function(e, cell){
    debugger;
    this.send({
        ui_control:{callback:'cellEdited'},
        payload:cell.getValue(),
        oldValue:cell.getOldValue(),
        field:cell.getColumn().getField(),
        id:cell.getRow().getIndex()
    });
}

msg.ui_control = {
    tabulator : {
        ...
        cellClick:cellDebugFunc,
        ...
}

Whamming the mouse in any cell, but no breakpoint is being triggered. Probably I'm just too tired. Calling a night here :slight_smile: :zzz:

Cheers,
Marcel

Where did you placed this? In a function node? This won't work as a function node runs on the backend but tabulator in the front end. This is why the call back function has to be send inside a msg.ui_control structure as a string to the frontend and will be "handed over" to tabulator by ui-table (using the new Function ('','') constructor)

{
    "tabulator": {
        "cellEdited": "function(cell){     debugger;     this.send({         ui_control:{callback:'cellEdited'},                  payload:cell.getValue(),         oldValue:cell.getOldValue(),         field:cell.getColumn().getField(),         id:cell.getRow().getIndex()     });  }"
    }
}

See the little difference? your code snippet defines the function in the backend where the example nodes send the code as a string:

image

done correctly it should trigger the debugger (browser):

image

here are the modified nodes form example #6

[{"id":"7b6490b3.cd9c7","type":"function","z":"c4712650.59b5e8","name":"callback function(s)","func":"// how to use the editor to write callback functions\n// DO NOT wire this into your flow!\n// copy / paste \"function( ... }\" into the correct calback parameter\n// use the visual JSON Editor in order to take care formating inverted commas\n// use the \"debugger\" statement to debug your callback inside your browser\n\nvar topCalc = function(values, data, calcParams){\n    var total = 0;\n    var calc = 0;\n    var count = 0;\n    data.forEach(value => {\n        total+=Number(value.numberValue);\n        count++;\n    });\n    if (count>0) calc=(total/count).toFixed(2);\n    return `${calc} (avg)`;\n}\n\nvar legend = function (value) {\n    if (value<100) return \"<span style='color:#FF0000;'>\"+value+\"</span>\";\n    else return \"<span style='color:#000000;'>\"+value+\"</span>\"; \n}\n\nvar cellEdited = function(cell){\n    debugger;\n    this.send({\n        ui_control:{callback:'cellEdited'},         \n        payload:cell.getValue(),\n        oldValue:cell.getOldValue(),\n        field:cell.getColumn().getField(),\n        id:cell.getRow().getIndex()\n    }); \n}\n\n// used by the button column\nvar btnFormatter = function(cell, formatterParams, onRendered){\n    return '<button class=\"md-raised md-button md-ink-ripple\">Activate</button>';\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":923,"y":782,"wires":[[]]},{"id":"ce8d3904.be1b08","type":"ui_button","z":"c4712650.59b5e8","name":"","group":"ff9fdb9a.7da098","order":21,"width":"4","height":"1","passthru":false,"label":"inject cellEdited callback","tooltip":"","color":"","bgcolor":"","icon":"","payload":"function(cell){     debugger;     this.send({         ui_control:{callback:'cellEdited'},                  payload:cell.getValue(),         oldValue:cell.getOldValue(),         field:cell.getColumn().getField(),         id:cell.getRow().getIndex()     });  }","payloadType":"str","topic":"cellEdited","topicType":"str","x":280,"y":1071,"wires":[["16664cef.5b26b3"]]},{"id":"ff9fdb9a.7da098","type":"ui_group","name":"TEST","tab":"7dcc246f.ee661c","order":1,"disp":false,"width":"8","collapse":false},{"id":"7dcc246f.ee661c","type":"ui_tab","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

Perhaps take a look on the browser debug console for any errors.

Perhaps you can send me your node(s) how you plan to do this.

The tooltip peformance problem is totally tabulator internal - I used tabulator with more than 500k lines until chrome quits with out of memory error (but without tooltips) .... perhaps the upcoming upgrade can help here (Failure to create my first UI table - #23 by Christian-Me but not easy to install from my fork)

Hi Christian,

thx for taking your time to reply to my problem! I won't forget that... :slight_smile:

The code snippet I've shown you is indeed part of a function node which contains that definition
msg.ui_control = { "tabulator": { ... } }. So I was right in my assumption that assigning a function statically, i.e. in a function node on the Node Red backend won't come into effect as it requires to be assigned in the browser environment with instantiated objects like that tabulator object?

What I've been implementing with that Tabulator table is both an approval & rating system for a set of interesting stocks that come in on a daily basis. Next to the table is a separate area with an LED button (that toggles through red, green & gray) and a submit button to send the SQL update statement to the database.

While this works all nice and fine, it becomes more and more tedious with the amount of data to be reviewed and rated and approved because my wife and me have to do too many clicks all over the screen in order to rate one entry per row. The more clicks there are the more you start to think about how to optimize the UX.

Rotating through the colors by clicking the traffic lights in the table would be cool (+ it sends an update to the db). Finding a way to avoid one unnecessary update because for an approved data item the "traffic light" would first turn to red (1st update to DB) and then green (2nd update), would be amazing.. I'll have to brainstorm ideas later on .. :thinking:

From your last comment about having a good performance even with 500k lines, I'm wondering if there's some poor design in my flow.

Last night I've already implemented the very same cellEdited function like in your example #6.
It works nicely. Now the only way, I can think, to dynamically "bind" this function to the tabulator table, is: Injecting this function like 0.1 seconds after the page has been loaded. Or do you know a better way?

Cheers,
Marcel

Woha, I got it working: Rather than dynamically assigning (e.g. through a button), but by assigning that callback function directly to the tabulator. However, what I did wrong, and you'll probably laugh now, is I forgot to use quotes on that function... lol

So here it goes (again, properly though):

msg.ui_control = {
    tabulator : {
                    ...
                    columns: [
                    // ...
                    {
                        "title": "Approved",
                        "field": "APPROVED_BY_MFI",
                        "align": "center",
                        "editor": false,          // v----- below must be in quotes and all in one line.
                        "cellDblClick" : "function(e, cell) { this.send({ ui_control:{callback:'cellEdited_Nice'}, payload:cell.getValue(), oldValue:cell.getOldValue(), field:cell.getColumn().getField(), id:cell.getRow().getIndex() }); }", 
                        "formatter": "traffic",
                        "formatterParams": {
                            "min": -1,
                            "max": 1,
                            "color":["red", "orange", "green"]
                        },
                        "width": 40
                    },
                    ...
                    ]
}

Double clicking the traffic light in the corresponding column now triggers a msg with the cellEdited_Nice callback :slight_smile:

Right! It took a little while to get used to the back and frontend when working with the UI.

have you tried tabulator filters to limit rows of interest? perhaps this can help. Together with the row selection Feature you can perhaps manage bulk edits with your cellDblClick call back by retrieving all selected rows. Once you get the concept of callbacks and commands everything tabulator offers should be possible.

Form a performance point of view I use dynamic updates. So I don't send the hole table all the time when something changes. Only the changed cells are updated per commands (mostly by updateOrAddData) (you can find a lot of posts here how this works). The main advantage is that the traffic between the backend and frontend is minimal. But you have to take care of all your data. If you change a tab(!) or connect to the UI tabulator is created. So you have to replay all your data if it was created by single messages. (as your data sits on as SQL server this should not be a problem) I've done a syslog server sending a ton of messages ... the browser quits somewhere beween 500k and one million lines. So perhaps connect your data source directly to ui-table (and your SQL server) and keep the database updated with all edits. As long as there is an unique id the table can show live data (like my device table) in real time.

Ui-table does it's best to hold the tabulator object as long as possible, so in best case the definition has only to be send once after restart. But if you do partial deployments things could get messy. So you have to experiment. I use a ui control node:
image
To send on connect only triggering a change node sending the JSON. During development I change it to all events (and a inject node to trigger it from the editor by hand).

But in the end I use my ui-table handler subflow for all this ... good that it is working fine for all my use cases because editing "legacy code" even if it is own code is no fun.

Enough for now ... good night :wink:

Yes :joy_cat: because

and

So always make sure you don't use " in your functions. If so escape them or use the visual json editor.

1 Like

Yes, I think that is exactly the reason why the performance is slow sometimes: When a button sends a command back to the table the whole data is coming back and is being processed by the flow. I'll have to look at that more critically, maybe I can simply filter that out..

Yes, I have utilized many of your great examples in other threads here where you demonstrate the updateOrAddData command and that just works wonderfully.

Slowly, but surely, the whole concept of callbacks and commands sinks in and I'm really looking forward to implement more cool features like - as you mentioned the bulk editing which is another task on my huge todo list.. :slight_smile:

So far I see no performance issues with loading the data. A default filter (that applies a where clause directly into the SQL select) caps the result set to only items from the last week, so that's helping a lot. In the rare case, where I wanna see data of an unlimited timespan the table still loads fine. I'll have to wait for the day when it reaches 500k entries :smiley:
One nice feature I often use are global or flow context variables which hold computational results that would otherwise be way too expensive to be re-calculated every time a flow (with a table or chart) is being loaded. But for the table with all the stock data there was so far no problem yet.

Looking forward to learn more knowledge & good practices, so it becomes a natural thing to develop amazing flows!

Seriously, without Node Red I wouldn't have come that far* :pray:
(*So far my flows contain several dozen REST endpoints (using GET & POST), 2 different databases are employed (MySQL and Mongo, as you might remember :wink: ), a whole load of flows with tons of tables, forms and charts. The development speed finds no comparison to common Java programming! Over 20 pages with a lot of complexity, good looking UI (special thanks to @TotallyInformation for his amazing UIBuilder!) and a very easy way to achieve good UX... all done within less than 5 months!
And yet, during just 1 week my Trello board is gaining more tasks than I can work them off .. hahaha

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.