Node Red UI Table

Hello, I have a node-red-node-ui-table set up. I am confused because all of the examples simply use an inject node to inject data. I am reading data from an OPCUA server from a PLC. I am trying to log machine "feeds" into the table so it would display User, Time, and Pounds. Below is my flow...

[{"id":"eb706c2c.44138","type":"tab","label":"DASHBOARD","disabled":false,"info":""},{"id":"ff1fc00.1070c4","type":"ui_table","z":"eb706c2c.44138","group":"ef5cd596.a87958","name":"DIG TANK FEEDS","order":1,"width":"6","height":"11","columns":[{"field":"User","title":"User","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Pounds","title":"Pounds","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Time","title":"Time","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"id","title":"id","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}}],"outputs":1,"cts":true,"x":1790,"y":480,"wires":[["d950675b.8b7ae8"]]},{"id":"f335f23c.800a4","type":"ui_button","z":"eb706c2c.44138","name":"","group":"a17cfa8f.210cd","order":2,"width":0,"height":0,"passthru":false,"label":"PAUL","tooltip":"","color":"","bgcolor":"","icon":"","payload":"PAUL","payloadType":"str","topic":"User","x":1090,"y":520,"wires":[["259d7bf7.06e95c","7f38b60d.627f18","482c6cdb.f68a34"]]},{"id":"d19deb8e.a325a","type":"ui_button","z":"eb706c2c.44138","name":"","group":"a17cfa8f.210cd","order":4,"width":0,"height":0,"passthru":false,"label":"ZACK","tooltip":"","color":"","bgcolor":"","icon":"","payload":"ZACK","payloadType":"str","topic":"User","x":1090,"y":560,"wires":[["259d7bf7.06e95c","7f38b60d.627f18","482c6cdb.f68a34"]]},{"id":"a06aa785.73db48","type":"ui_button","z":"eb706c2c.44138","name":"","group":"a17cfa8f.210cd","order":6,"width":0,"height":0,"passthru":false,"label":"IAN","tooltip":"","color":"","bgcolor":"","icon":"","payload":"IAN","payloadType":"str","topic":"User","x":1090,"y":600,"wires":[["259d7bf7.06e95c","7f38b60d.627f18","482c6cdb.f68a34"]]},{"id":"a4a4fc32.c86fc","type":"change","z":"eb706c2c.44138","name":"Weight Difference","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[1]-payload[0]","tot":"jsonata"},{"t":"set","p":"topic","pt":"msg","to":"Pounds","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1310,"y":360,"wires":[["89286469.46501","7f38b60d.627f18"]]},{"id":"43e6b4c7.cadb6c","type":"join","z":"eb706c2c.44138","name":"Join Weights","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1090,"y":360,"wires":[["a4a4fc32.c86fc"]]},{"id":"7f38b60d.627f18","type":"join","z":"eb706c2c.44138","name":"FEED PACKAGE","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1330,"y":480,"wires":[["3910d07e.afe8b"]]},{"id":"3910d07e.afe8b","type":"join","z":"eb706c2c.44138","name":"TO TABLE","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"1","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1570,"y":480,"wires":[["ff1fc00.1070c4"]]},{"id":"cc587615.a1a99","type":"OpcUa-Client","z":"eb706c2c.44138","endpoint":"3d125360.2347bc","action":"write","deadbandtype":"a","deadbandvalue":1,"time":"2","timeUnit":"s","certificate":"n","localfile":"","localkeyfile":"","securitymode":"None","securitypolicy":"None","name":"OPCUA WRITE","x":1800,"y":580,"wires":[[]]},{"id":"5e49f7fa.8d6b18","type":"OpcUa-Item","z":"eb706c2c.44138","item":"ns=4;i=131","datatype":"Int16","value":"","name":"PREFEED WEIGHT","x":430,"y":340,"wires":[["73385b0c.126b84"]]},{"id":"bbc74521.4465d","type":"OpcUa-Item","z":"eb706c2c.44138","item":"ns=4;i=132","datatype":"Int16","value":"","name":"POSTFEED WEIGHT","x":440,"y":380,"wires":[["73385b0c.126b84"]]},{"id":"fb77cc64.f75868","type":"OpcUa-Item","z":"eb706c2c.44138","item":"ns=4;i=127","datatype":"Boolean","value":"","name":"FEED REQ","x":1590,"y":580,"wires":[["cc587615.a1a99"]]},{"id":"259d7bf7.06e95c","type":"change","z":"eb706c2c.44138","name":"TRIGGER FEED REQ","rules":[{"t":"set","p":"payload","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":1320,"y":580,"wires":[["fb77cc64.f75868"]]},{"id":"73385b0c.126b84","type":"OpcUa-Client","z":"eb706c2c.44138","endpoint":"3d125360.2347bc","action":"read","deadbandtype":"a","deadbandvalue":1,"time":"2","timeUnit":"s","certificate":"n","localfile":"","localkeyfile":"","securitymode":"None","securitypolicy":"None","name":"OPCUA READ","x":700,"y":360,"wires":[["f515e068.bb88a"]]},{"id":"f515e068.bb88a","type":"switch","z":"eb706c2c.44138","name":"","property":"browseName","propertyType":"msg","rules":[{"t":"cont","v":"PREFEED WEIGHT","vt":"str"},{"t":"cont","v":"POSTFEED WEIGHT","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":870,"y":360,"wires":[["43e6b4c7.cadb6c"],["43e6b4c7.cadb6c"]]},{"id":"82882588.b2b578","type":"delay","z":"eb706c2c.44138","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":360,"y":140,"wires":[["e61962af.f25eb"]]},{"id":"e61962af.f25eb","type":"OpcUa-Item","z":"eb706c2c.44138","item":"ns=4;i=136","datatype":"Boolean","value":"","name":"FEED COMPLETE","x":570,"y":140,"wires":[["bd14d2f7.dbdfc8"]]},{"id":"bd14d2f7.dbdfc8","type":"OpcUa-Client","z":"eb706c2c.44138","endpoint":"3d125360.2347bc","action":"read","deadbandtype":"a","deadbandvalue":1,"time":"2","timeUnit":"s","certificate":"n","localfile":"","localkeyfile":"","securitymode":"None","securitypolicy":"None","name":"OPCUA READ","x":800,"y":140,"wires":[["617bc976.cc986"]]},{"id":"7f9ef986.6089d8","type":"inject","z":"eb706c2c.44138","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"2","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":140,"wires":[["82882588.b2b578"]]},{"id":"617bc976.cc986","type":"rbe","z":"eb706c2c.44138","name":"","func":"rbei","gap":"","start":"","inout":"out","property":"payload","x":990,"y":140,"wires":[["5e4cbb8b.e157ec"]]},{"id":"5e4cbb8b.e157ec","type":"switch","z":"eb706c2c.44138","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":1150,"y":140,"wires":[["d6f37466.aeea08"]]},{"id":"44da13c8.9323f4","type":"ui_button","z":"eb706c2c.44138","name":"","group":"67aef144.f70dd8","order":2,"width":"2","height":"1","passthru":false,"label":"FEED","tooltip":"","color":"white","bgcolor":"5f833a","icon":"fa-leaf","payload":"3","payloadType":"num","topic":"Feed Request","x":370,"y":880,"wires":[["e6afcc5d.0f2ac8"]]},{"id":"e6afcc5d.0f2ac8","type":"ui_ui_control","z":"eb706c2c.44138","name":"FEED","events":"change","x":650,"y":880,"wires":[[]]},{"id":"89286469.46501","type":"debug","z":"eb706c2c.44138","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1650,"y":220,"wires":[]},{"id":"d6f37466.aeea08","type":"function","z":"eb706c2c.44138","name":"","func":"msg.payload.time = new Date()\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":180,"y":360,"wires":[["5e49f7fa.8d6b18","bbc74521.4465d"]]},{"id":"2ef22df6.5d273a","type":"debug","z":"eb706c2c.44138","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":870,"y":620,"wires":[]},{"id":"9487048d.b24c4","type":"change","z":"eb706c2c.44138","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"date"},{"t":"set","p":"topic","pt":"msg","to":"Time","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":880,"y":460,"wires":[["7f38b60d.627f18","2ef22df6.5d273a"]]},{"id":"60bc99f0.063ea8","type":"complete","z":"eb706c2c.44138","name":"","scope":["a4a4fc32.c86fc"],"uncaught":false,"x":430,"y":460,"wires":[["fe0f06a8.8c63a"]]},{"id":"fe0f06a8.8c63a","type":"function","z":"eb706c2c.44138","name":"","func":"msg.payload.time = new Date()\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":620,"y":460,"wires":[["9487048d.b24c4"]]},{"id":"d950675b.8b7ae8","type":"function","z":"eb706c2c.44138","name":"table recorder","func":"var status = {fill:\"red\",shape:\"ring\",text:\"an error occured\"};\nvar success = (msg.topic && msg.topic===\"success\") || false;\nvar tableData = flow.get(\"tableData\");\nif (tableData === undefined) {\n    tableData = [];\n    flow.set(\"tableData\",tableData);\n}\n\n// find the index for a row in tableData for a given index (id)\nfunction checkIndex(id) {\n    let matchRow=-1\n    tableData.forEach(function (row,index){\n        if (row.id === id){\n            matchRow=index;\n            return matchRow;\n        }\n    })\n    return matchRow;\n}\n\n// flat merge one row \nfunction mergeRow(dest,source) {\n    Object.keys(source).forEach(function(key) {\n        dest[key]=source[key];\n    })\n}\n\n//merge or add one or many rows into tableData \nfunction mergeData(newData,toTop) {\n    newData.forEach(function (item,index) {\n        node.warn([\"findIndex\",item]);\n        let row=checkIndex(item.id);\n        if (row<0) { // row do not existst in tableData\n            if (toTop) {\n                tableData.push(item);\n                status.text+=\"newRow @ top\";\n            } else {\n                tableData.unshift(item);\n                status.text+=\"newRow @ bottom\";\n            }\n            return;\n        } else { // row exists so update\n            mergeRow(tableData[row],item);\n            status.text+=\"row updated\";\n            return;\n        }\n        if (status.text!==\"\") node.status(status);\n    });\n}\n\nswitch (typeof msg.payload){\n    case \"string\":\n        node.warn([\"[table recorder] \",(typeof msg.payload),msg.payload]);\n        switch (msg.payload){\n            case \"change\":\n                status={fill:\"green\",shape:\"dot\",text:\"table restored \"+tableData.length+\" rows\"};\n                msg.payload=tableData;\n                break;\n        }\n        break;\n    case \"object\":\n        node.warn([\"[table recorder] \",(typeof msg.payload),msg.payload]);\n        if (Array.isArray(msg.payload)) { // replace all tableData\n            status={fill:\"green\",shape:\"dot\",text:\"table replaced \"+msg.payload.length+\" rows\"};\n            tableData=RED.util.cloneMessage(msg.payload); \n        } else {\n            switch (msg.payload.command) { // clearData does not return a promise!\n                case \"clearData\":\n                    status={fill:\"green\",shape:\"dot\",text:\"clearData: done\"};\n                    tableData=[];\n                    flow.set(\"lastId\",0);\n                    break;                \n            }\n        }\n        break;\n    default: // likely a msg fom a ui-table command or callback\n        if (msg.hasOwnProperty(\"topic\")&&\n            msg.hasOwnProperty(\"ui_control\") && \n            msg.ui_control.hasOwnProperty(\"callback\") &&\n            msg.hasOwnProperty(\"return\")) { // message originates from a ui-table callback\n            if (success) {\n                switch(msg.return.command) {\n                    case \"addRow\":\n                        status.text=\"addRow: \";\n                        mergeData(msg.return.arguments[0],msg.return.arguments[1]);\n                        status.shape=\"dot\";\n                        break;\n                    case \"updateOrAddData\":\n                        status.text=\"updateOrAddData: \";\n                        mergeData(msg.return.arguments[0]);\n                        break;\n                    case \"deleteRow\":\n                        let row=checkIndex(msg.return.arguments[0]);\n                        tableData.splice(row,1);\n                        status.shape=\"dot\";\n                        status.text=\"deleteRow: \"+row+\" deleted\";\n                        break;\n                    default:\n                        status={fill:\"yellow\",shape:\"dot\",text:msg.return.command + \" unknown!\"};\n                        break;         \n                }\n            } else {\n                status.text=msg.topic+\" \"+msg.error;\n            }\n        }\n        break;\n}\nif (success) status.fill=\"green\";\nflow.set(\"tableData\",tableData);\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1780,"y":540,"wires":[["ff1fc00.1070c4"]],"icon":"font-awesome/fa-database","info":"# simple ui-table handler\n## abstract\nUsing ui-table with commands offer the hole flexibilty of tabulator. The table can be manipulated down to cell level.\nAs the ui-table node only passes the commands to tabulator and receives promises back the node does not hold the table data. If the data should be available after refresh, tab change, new connections the flow is responsible to cache the data and all the manipulations.\nThis node takes care of most simple data manipulation commands and holds a copy of the data in `flow.context.tabledata`\n\n## details\n\n### row index (id)\n\nTo identify a [row a index](http://tabulator.info/docs/4.5/data#overview) column has to be defined. This colum defaults to `id` but can be changed by specifing a **field** by using `msg.ui_control`. In this example the row index is a simple counter adding up by one if a new line is added.\n\n### addRow command\n\n[details @ tabulator addRow docs](http://tabulator.info/docs/4.5/update#alter-add)\n\nYou can add a row by sending the `addRow` command. You can decide if the row adds on the top or at the bottom of table.\n\n### addOrUpdate command\n\n[details @ tabulator addOrUpdate docs](http://tabulator.info/docs/4.5/update#alter-update)\n\nTo update data the best way is to use the `addOrUpdate` command. If the row indetified by the index is not exeisting a new row will be added automatically\n\n### deleteRow command\n\n[details @ tabulator deleteRow docs](http://tabulator.info/docs/4.5/update#row)\n\nDelete one or more rows (passing an array always results in \"row not found error\"! I think there is an issue in tabulator)\n\n### clearData\n\n[details @ tabulator clearData docs](http://tabulator.info/docs/4.5/update#alter-empty)\n\nunfortunately this command (currently) do not send a promise back! So we have to pass it directly to the table handler"},{"id":"482c6cdb.f68a34","type":"function","z":"eb706c2c.44138","name":"updateOrAddData (add)","func":"var id=flow.get(\"lastId\") || 0;\n++id;\nmsg.payload={\n    command:\"updateOrAddData\",\n    arguments: [\n        [\n            {\n            \"id\":id,\n            \"timestamp\":msg.payload,\n            \"text\":\"updateOrAddData (add) (#\"+id+\")\"\n            }\n        ]\n    ],\n    returnPromise: true\n}\nflow.set(\"lastId\",id);\nreturn msg;","outputs":1,"noerr":0,"x":1330,"y":540,"wires":[["ff1fc00.1070c4"]],"info":"# updateOrAddData([row])\n\nadds a the row with a new id (same as addRow)"},{"id":"c9d9c5c6.834d68","type":"ui_ui_control","z":"eb706c2c.44138","name":"","events":"all","x":1580,"y":540,"wires":[["d950675b.8b7ae8"]]},{"id":"ef5cd596.a87958","type":"ui_group","name":"HISTORY","tab":"5e5266f4.85f33","order":2,"disp":true,"width":"6","collapse":false},{"id":"a17cfa8f.210cd","type":"ui_group","name":"FEEDS","tab":"5e5266f4.85f33","order":1,"disp":true,"width":"6","collapse":false},{"id":"3d125360.2347bc","type":"OpcUa-Endpoint","endpoint":"opc.tcp://192.168.0.8:4840","secpol":"None","secmode":"None","login":false},{"id":"67aef144.f70dd8","type":"ui_group","name":"ACTIONS","tab":"b7e83331.d5bc4","order":2,"disp":true,"width":"8","collapse":false},{"id":"5e5266f4.85f33","type":"ui_tab","name":"FEEDS","icon":"fa-leaf","order":3,"disabled":false,"hidden":false},{"id":"b7e83331.d5bc4","type":"ui_tab","name":"HOME","icon":"fa-home","order":1,"disabled":false,"hidden":false}]

Every time we complete a feed, it overwrites the data in the previous feed. The "updateorAddData" and "table recorder" nodes were taken from the ui-table example on commands...

@zenofmud or @Christian-Me can either of you speak to this? I know you've worked on some of the table stuff. The big issue I have is none of the examples use a msg.payload, simply pre written functions that inject perfect data.

Hi,

sorry I'm very busy currently ... perhaps this subflow can help you.

https://flows.nodered.org/flow/35d0480ce9151b2a722fa9d185a37825

I use it for all my tables.

I had no time to import your flow (and no safe sandbox available running code downloaded from the internet :wink:) but it seems that you don't have unique ids for each (new) row. Define a column as index. Best would be a timestamp or use addRow instead.

Tabulator addRow

Otherwise it does what it says it will do updateOrAdd!