Stop ui-table from auto-numbering column

I'm using the ui-table node to create a sorted list of electrical loads for a dashboard display. The first column in the table is the Priority number, which is loaded from another source for display. The priority number is unique in each record and I want to use it to replace row data in the table when information is updated. I'm currently using this code in a function node to inject new data into the table (or update existing data)

var tableRow = {
    command: "updateOrAddData",
    arguments: [
        [
            {
                priority: msg.payload.priority,                
                loadName: msg.payload.name,
                type: msg.payload.type,
                ratedPower: msg.payload.ratedPower,
                powerUsage: msg.payload.powerUsage,
                state: msg.payload.state
            }  
        ]
    ],
    returnPromise: true
};
msg.payload = tableRow;
return msg;

When I run this on the table, it does add the rows with the data, but the "priority" column is being auto-numbered by the table regardless of what value I give to the priority column in the input data. Here's what the table displays:

image

NOTE: that the priority numbers displayed are not the same numbers I'm passing into the table.

Here's an example flow that illustrates the basic problem I'm having

[{"id":"ea700dd5acd0970d","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Stove\",\"priority\":0,\"type\":\"AC\",\"ratedPower\":800,\"powerUsage\":735,\"state\":\"active\"}","payloadType":"json","x":970,"y":4420,"wires":[["5b9ce9155c66e437"]]},{"id":"02824747a9095989","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Microwave\",\"priority\":4,\"type\":\"AC\",\"ratedPower\":1100,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":970,"y":4460,"wires":[["5b9ce9155c66e437"]]},{"id":"9f16c188d6f4ae7d","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 3","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"TV\",\"priority\":5,\"type\":\"AC\",\"ratedPower\":150,\"powerUsage\":150,\"state\":\"disabled\"}","payloadType":"json","x":970,"y":4500,"wires":[["5b9ce9155c66e437"]]},{"id":"69b0b1cc8d07a904","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 4","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Refrigerator\",\"priority\":1,\"type\":\"DC\",\"ratedPower\":600,\"powerUsage\":25,\"state\":\"active\"}","payloadType":"json","x":970,"y":4540,"wires":[["5b9ce9155c66e437"]]},{"id":"a6db6cf161f2417a","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 5","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Kitchen Light\",\"priority\":2,\"type\":\"DC\",\"ratedPower\":25,\"powerUsage\":24.8,\"state\":\"active\"}","payloadType":"json","x":970,"y":4580,"wires":[["5b9ce9155c66e437"]]},{"id":"4ae69d337884f3f2","type":"inject","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Load 6","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Laptop\",\"priority\":3,\"type\":\"AC\",\"ratedPower\":90,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":970,"y":4620,"wires":[["5b9ce9155c66e437"]]},{"id":"5b9ce9155c66e437","type":"function","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","name":"Format For Table","func":"var tableRow = {\n    command: \"updateOrAddData\",\n    arguments: [\n        [\n            {\n                priority: msg.payload.priority,                \n                loadName: msg.payload.name,\n                type: msg.payload.type,\n                ratedPower: msg.payload.ratedPower,\n                powerUsage: msg.payload.powerUsage,\n                state: msg.payload.state\n            }  \n        ]\n    ],\n    returnPromise: true\n};\n\n// Replace the status strings with icons to go in the table\nif (tableRow.arguments[0][0].state == \"active\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: navy;\">check_circle</i>';\nelse if (tableRow.arguments[0][0].state == \"inactive\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">check_circle_outline</i>';\nelse if (tableRow.arguments[0][0].state == \"disabled\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: red;\">dnd_forwardslash</i>';\nelse\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">radio_button_unchecked</i>';\n    \nmsg.payload = tableRow;\nnode.warn(tableRow.arguments[0][0].priority);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1230,"y":4520,"wires":[["b49f7c86dfee2597"]]},{"id":"b49f7c86dfee2597","type":"ui_table","z":"cd2cc43efd12a00d","g":"3b8fa4137cc9c9bb","group":"be4515129be2c1be","name":"Load Priority Table","order":6,"width":20,"height":8,"columns":[{"field":"priority","title":"Priority","width":"11%","align":"center","formatter":"rownum","formatterParams":{"target":"_blank"}},{"field":"loadName","title":"Load Name","width":"28%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"type","title":"Type","width":"9%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"ratedPower","title":"Rated Power (W)","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"powerUsage","title":"Power Usage (W)","width":"22%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"state","title":"State","width":"9%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":1,"cts":true,"x":1470,"y":4520,"wires":[["0367ee1f7622fb88","e67a92aa3fc7f0fc"]]},{"id":"be4515129be2c1be","type":"ui_group","name":"Controls","tab":"11bccd3d1bb29d0f","order":2,"disp":false,"width":"22","collapse":false,"className":"controls"},{"id":"11bccd3d1bb29d0f","type":"ui_tab","name":"Power - Load Prioritization","icon":"dashboard","order":13,"disabled":false,"hidden":false}]

My primary objective is to be able to send data to the table and have it either add a new row if it isn't there, or update the data in the existing row if available. Is there some way to disable the auto-numbering in the table?

You have to specify an id property in your objects which can be used to uniquely identify a row.
If you dont call it id - you must config this with a separate command.

I you have not an id in your object may be the ui-Table adds an unique identifier. The id property must not be displayed in the table, but has to exist in an object (row).

If you specify nothing with property id - the first column will be used as identifier.

Tabulator (I know the ui-table node uses tabulator 4.4 but I guess it is the same).

So the primary problem in your objects is that you have not at least one id property in your objects and not a unique key per row.

But as I said you need not assign the id property to a column, so it is hidden, but must be existent in each object.

You can try to add a dummy property at the beginning - may be it is used then automatically uses as id in the same way as your priority property.

You defined your priority column as row number! So it does what you asked for :wink:

image

Using plain text I think it work as expected

Only to avoid confusion: You "should" provide a index column (defaults to "id" or defined by msg.ui_control.tabulator.index). But you don't have to. You need the index to update rows / cells. If you don't defined it you can only add rows but not update them because there will be no match between the index and the "undefined" index property (i.e. id)

I don't think so. It will always defaults to the internal id column unless set by index. If you don't provide it it will be auto generated internal. You can think of the index is the row identifier normally numbers but can be something else too. Only if you want to update items in a row you have to identify the row by the row index. (or delete or do other manipulations)

@Jdo300 As I see your table you might whant to update new measurements or states in the future. So defining a index column with unique identifiers is a good idea.

Thank you all for the replies. I must have set that Row Number setting a while ago when I was first playing with the ui-table and completely missed it again later.

So this brings up a more fundamental question. I can add an id variable to my incoming load objects, but I need to sort them according to their priority value so they display in ascending order. But I'm not sure how to best go about this. The incoming object updates will not be in order (and subssequent updates definitely will not).

Is there a way to auto-sort the table as items are added/updated?

The second challenge is that I want to be able to rearrange the items in the table by selecting an item and shifting it up or down (to effectively change the priority number).

So far, what I've come up with is to make another function node that keeps track of the incoming objects from the "Format for Table" as they are sent to the ui-table. But I'm not sure of the best approach to implement the sorting and shifting operations. Are there any common approaches to this?

Hi,

As I see your table the data comes from measuring devices which should have any kind of unique id (I use the mqtt topic for all data comming in from mqtt devices or the serial number of others. Send this id with all your data {id:"device001",value:123.12}
So if you send a new value later {id:"device001",value:999.99} using the updateOrAddData function call the value will be updated. Best practice is to send only the changed values to keep the traffic load low and be able to use something like this Ui-table: highlight updated cells with animations

Using your demo if you inject your data you always get a new row. I did a little modification so you can see how to use the id field to be able to send updates (see the "Power Usage" column each time you inject the row.

[{"id":"ea700dd5acd0970d","type":"inject","z":"790d116ed3ca1bf0","name":"Load 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Stove\",\"priority\":0,\"type\":\"AC\",\"ratedPower\":800,\"powerUsage\":735,\"state\":\"active\"}","payloadType":"json","x":230,"y":220,"wires":[["5b9ce9155c66e437"]]},{"id":"02824747a9095989","type":"inject","z":"790d116ed3ca1bf0","name":"Load 2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Microwave\",\"priority\":4,\"type\":\"AC\",\"ratedPower\":1100,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":230,"y":260,"wires":[["5b9ce9155c66e437"]]},{"id":"9f16c188d6f4ae7d","type":"inject","z":"790d116ed3ca1bf0","name":"Load 3","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"TV\",\"priority\":5,\"type\":\"AC\",\"ratedPower\":150,\"powerUsage\":150,\"state\":\"disabled\"}","payloadType":"json","x":230,"y":300,"wires":[["5b9ce9155c66e437"]]},{"id":"69b0b1cc8d07a904","type":"inject","z":"790d116ed3ca1bf0","name":"Load 4","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Refrigerator\",\"priority\":1,\"type\":\"DC\",\"ratedPower\":600,\"powerUsage\":25,\"state\":\"active\"}","payloadType":"json","x":230,"y":340,"wires":[["5b9ce9155c66e437"]]},{"id":"a6db6cf161f2417a","type":"inject","z":"790d116ed3ca1bf0","name":"Load 5","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Kitchen Light\",\"priority\":2,\"type\":\"DC\",\"ratedPower\":25,\"powerUsage\":24.8,\"state\":\"active\"}","payloadType":"json","x":230,"y":380,"wires":[["5b9ce9155c66e437"]]},{"id":"4ae69d337884f3f2","type":"inject","z":"790d116ed3ca1bf0","name":"Load 6","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Laptop\",\"priority\":3,\"type\":\"AC\",\"ratedPower\":90,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":230,"y":420,"wires":[["5b9ce9155c66e437"]]},{"id":"5b9ce9155c66e437","type":"function","z":"790d116ed3ca1bf0","name":"Format For Table","func":"var tableRow = {\n    command: \"updateOrAddData\",\n    arguments: [\n        [\n            {\n                id: msg.payload.priority,\n                priority: msg.payload.priority,                \n                loadName: msg.payload.name,\n                type: msg.payload.type,\n                ratedPower: msg.payload.ratedPower,\n                powerUsage: (Math.random()*100).toFixed(1),\n                state: msg.payload.state\n            }  \n        ]\n    ],\n    returnPromise: true\n};\n\n// Replace the status strings with icons to go in the table\nif (tableRow.arguments[0][0].state == \"active\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: navy;\">check_circle</i>';\nelse if (tableRow.arguments[0][0].state == \"inactive\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">check_circle_outline</i>';\nelse if (tableRow.arguments[0][0].state == \"disabled\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: red;\">dnd_forwardslash</i>';\nelse\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">radio_button_unchecked</i>';\n    \nmsg.payload = tableRow;\nnode.warn(tableRow.arguments[0][0].priority);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":320,"wires":[["b49f7c86dfee2597"]]},{"id":"b49f7c86dfee2597","type":"ui_table","z":"790d116ed3ca1bf0","group":"be4515129be2c1be","name":"Load Priority Table","order":6,"width":20,"height":8,"columns":[{"field":"priority","title":"Priority","width":"11%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"loadName","title":"Load Name","width":"28%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"type","title":"Type","width":"9%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"ratedPower","title":"Rated Power (W)","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"powerUsage","title":"Power Usage (W)","width":"22%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"state","title":"State","width":"9%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":1,"cts":true,"x":730,"y":320,"wires":[["6ddf301a207840fa"]]},{"id":"6ddf301a207840fa","type":"debug","z":"790d116ed3ca1bf0","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":930,"y":320,"wires":[]},{"id":"be4515129be2c1be","type":"ui_group","name":"Controls","tab":"11bccd3d1bb29d0f","order":2,"disp":false,"width":"22","collapse":false},{"id":"11bccd3d1bb29d0f","type":"ui_tab","name":"Power - Load Prioritization","icon":"dashboard","order":13,"disabled":false,"hidden":false}]

Adding the id don't change the sorting. you can always sort the table as you wish, either by using the header controls or by command, config ... more info here

msg.payload = {
    command: "setSort",
    arguments: [
        [
            {
                column: "priority",                
                dir: "asc",
            }  
        ]
    ],
    returnPromise: false
};

(not tested, I think you get the idea)

To enable sorting you have to configure your table. As the config dialog don't include this option you have to add this by sending a ui_control message Ui-table supports ui_control

I found out that you have to trigger sorting after each new row (don't know if I did something wrong but I could not see any sorting after a updateOrAddData command

Yeah! That's a challenge but possible:
image

But before that have you recognized that you loose all table data when you switch tabs? That's because the table is destroyed when disconnected or another tab is selected!

You have to replay your data every time the table becomes visible (unless you you have sent it as a complete table. But then you already have collected all data and changes before). And if you like to have movable rows you have to track the order too. That is where I can suggest to take a look into my ui-table-handler subflow which take care of all of these (and much more):

image

You can find the latest incarnation + demo here: Get ui-table data in JSON format - #11 by Christian-Me (a basic documentation is included)

Thank you so much for all the great information! I need to take a closer look at the details for the sorting, but I wanted to share what I was able to accomplish after you pointed out the issue with the Row number setting in the ui-table node.

I redid my logic so that I collect the object data as it is coming in, pre-sort it into an array by Priority and also add the ID numbers for the ui-table to display. Then the full array is pushed to the table using the updateOrAddData command. the function node called Combine + Sort is designed to output the full array whenever a new item is added to the list, but if it receives data for an object that is already in the array, I will keep track of how many updates were made and only push out the array once all the objects are updated. I did it this way since, in the real application, I am subscribing to an MQTT topic which sends me the data for all the objects (which are represented as sub-topics in the broker), so after the first time that the topics populate the array, I will always know how many objects to expect when updates come in.

The issue I'm having now is that the code will populate the table once or twice, but after that, it stops responding even though I'm pretty sure I'm sending it the same data to populate. Then it won't work again until I restart node-RED (or in some cases, the raspberry Pi hosting Node-RED). I have attached an udpated flow showing the current setup. I replaced the incoming MQTT objects with inject nodes that you can press in sequence to simulate the incoming object data. The bottom node clears the flow context object containing the array to allow it to be reset again.

[{"id":"b49f7c86dfee2597","type":"ui_table","z":"cd2cc43efd12a00d","group":"be4515129be2c1be","name":"Load Priority Table","order":6,"width":20,"height":8,"columns":[{"field":"priority","title":"Priority","width":"11%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"name","title":"Load Name","width":"28%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"type","title":"Type","width":"9%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"ratedPower","title":"Rated Power (W)","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"powerUsage","title":"Power Usage (W)","width":"22%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"state","title":"State","width":"9%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":1,"cts":true,"x":1190,"y":4560,"wires":[["0367ee1f7622fb88","df873518fba60ef6"]]},{"id":"0367ee1f7622fb88","type":"debug","z":"cd2cc43efd12a00d","name":"Table Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1450,"y":4500,"wires":[]},{"id":"df873518fba60ef6","type":"function","z":"cd2cc43efd12a00d","name":"Priority Table Context","func":"var loads = flow.get(\"loads\");\nvar selectedItem = context.get(\"selectedItem\");\n\n//node.warn(loads);\nif (msg.payload == \"update\")\n{\n    if (loads != null)\n    {\n        msg.payload = formatForTable();\n        //node.warn(msg.payload);\n        return msg;\n    }\n}\nelse if (msg.payload == \"up\")\n{\n    if (loads != null && selectedItem != null)\n    {\n        var index = selectedItem.priority - 1;\n        \n        // If there is room to shift the priority up, then adjust it and the item above it and update the values on the MQTT server\n        if (index < loads.length)\n        {\n            // Update the prioriry of the selected item (subtract 1 from current value)\n            var msg2 = {\n                topic: `loads/${loads[index].name}/priority`,\n                payload: loads[index].priority - 1,\n            };\n            \n            // Send the update out to the MQTT broker\n            node.send([null, msg2]);\n            \n            // Update the prioriry of the previous item (add 1 to current value)\n            msg2 = {\n                topic: `loads/${loads[index - 1].name}/priority`,\n                payload: loads[index].priority,\n            };\n            \n            // Send the update out to the MQTT broker\n            node.send([null, msg2]);\n        }\n    }\n}\nelse if (msg.payload == \"down\")\n{\n    \n}\nelse // Only other possible case is object from table output is selected\n{\n    // Save the selected Item\n    selectedItem = msg.payload;\n    context.set(\"selectedItem\", selectedItem);\n    msg.payload = undefined;\n    msg.enabled = true;\n    node.send(msg);\n    \n    // Highlight the selected row\n    selectRow();\n}\n\n\nfunction formatForTable()\n{\n    // Add an \"id\" field to the array and insert the formattnig for the status icons\n    for (let i = 0; i < loads.length; i++) {\n        loads[i].id = i + 1;\n        loads[i].state = formatStateIcon(loads[i].state);\n    }\n    \n    // Create object to format data for sending to the table\n    var tableRow = {\n        command: \"updateOrAddData\",\n        arguments: [loads],\n        returnPromise: true\n    };\n    \n    /*var tableRow = {\n        command: \"updateOrAddData\",\n        arguments: [\n            [\n                {\n                    id: 1,\n                    priority: 1,                \n                    loadName: \"TV\",\n                    type: \"AC\",\n                    ratedPower: 300,\n                    powerUsage: 100,\n                    state: \"active\"\n                }  \n            ]\n        ],\n        returnPromise: true\n    };*/\n    \n    return tableRow;\n}\n\nfunction formatStateIcon(state)\n{\n    // Replace the status strings with icons to go in the table\n    if (state == \"active\")\n        return '<i class=\"material-icons icon-2x\" style=\"color: navy;\">check_circle</i>';\n    else if (state == \"inactive\")\n        return '<i class=\"material-icons icon-2x\" style=\"color: gray;\">check_circle_outline</i>';\n    else if (state == \"disabled\")\n        return '<i class=\"material-icons icon-2x\" style=\"color: red;\">dnd_forwardslash</i>';\n    else if (!state.startsWith('<i class'))\n        return '<i class=\"material-icons icon-2x\" style=\"color: gray;\">radio_button_unchecked</i>';\n    else\n        return state;\n}\n\nfunction selectRow()\n{\n    var m = {\n        payload: {\n            command: \"deselectRow\",\n            arguments: [context.get(\"oldrow\") || 0],\n            returnPromise: false\n        }\n    };\n    \n    msg.payload = {\n        command: \"selectRow\",\n        arguments: [msg.row],\n        returnPromise: false\n    }\n    \n    context.set(\"oldrow\", msg.row);\n    return [[m,msg]];\n}\n\nreturn msg;","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1200,"y":4640,"wires":[["b49f7c86dfee2597","8e9346d7ba10ea11","76a540f93374b0f1","b032ad309c99886a","eb67c42059fe6cb5"],[]],"icon":"font-awesome/fa-database"},{"id":"b032ad309c99886a","type":"debug","z":"cd2cc43efd12a00d","name":"Context Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1460,"y":4640,"wires":[]},{"id":"ea700dd5acd0970d","type":"inject","z":"cd2cc43efd12a00d","name":"Stove","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Stove\",\"priority\":1,\"type\":\"AC\",\"ratedPower\":800,\"powerUsage\":735,\"state\":\"active\"}","payloadType":"json","x":670,"y":4500,"wires":[["17b031b8630f6262"]]},{"id":"02824747a9095989","type":"inject","z":"cd2cc43efd12a00d","name":"Microwave","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Microwave\",\"priority\":5,\"type\":\"AC\",\"ratedPower\":1100,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":680,"y":4540,"wires":[["17b031b8630f6262"]]},{"id":"9f16c188d6f4ae7d","type":"inject","z":"cd2cc43efd12a00d","name":"TV","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"TV\",\"priority\":6,\"type\":\"AC\",\"ratedPower\":150,\"powerUsage\":150,\"state\":\"disabled\"}","payloadType":"json","x":670,"y":4580,"wires":[["17b031b8630f6262"]]},{"id":"69b0b1cc8d07a904","type":"inject","z":"cd2cc43efd12a00d","name":"Refrigerator","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Refrigerator\",\"priority\":2,\"type\":\"DC\",\"ratedPower\":600,\"powerUsage\":25,\"state\":\"active\"}","payloadType":"json","x":690,"y":4620,"wires":[["17b031b8630f6262"]]},{"id":"a6db6cf161f2417a","type":"inject","z":"cd2cc43efd12a00d","name":"Kitchen","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Kitchen Light\",\"priority\":3,\"type\":\"DC\",\"ratedPower\":25,\"powerUsage\":24.8,\"state\":\"active\"}","payloadType":"json","x":670,"y":4660,"wires":[["17b031b8630f6262"]]},{"id":"4ae69d337884f3f2","type":"inject","z":"cd2cc43efd12a00d","name":"Laptop","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Laptop\",\"priority\":4,\"type\":\"AC\",\"ratedPower\":90,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":670,"y":4700,"wires":[["17b031b8630f6262"]]},{"id":"17b031b8630f6262","type":"function","z":"cd2cc43efd12a00d","name":"Combine + Sort","func":"var loads = flow.get(\"loads\") || [];\nvar count = context.get(\"loadCount\") || 0; // keeps track of how many objects are added to the array with each call of the functions\nvar added = false;\n\n// If the loads array is empty, add the new element to it\nif (loads.length == 0)\n{\n    loads.push(msg.payload);\n    //node.warn(\"Added new \" + msg.payload.name);\n    added = true;\n}\nelse\n{\n    // If the object doesn't exist in the current array, increas the count\n    for (let i = 0; i < loads.length; i++) {\n        // If the load already exists in the table, then update it\n        if (loads[i].name == msg.payload.name)\n        {\n            loads[i] = msg.payload;\n            //node.warn(\"Updating \" + msg.payload.name);\n            break;\n        }\n        \n        // If we're on the last item and it still doesn't match, then add it to the array\n        if (i == loads.length - 1)\n        {\n            if (loads[i].name != msg.payload.name)\n            {\n                loads.push(msg.payload);\n                //node.warn(\"Added new \" + msg.payload.name);\n                added = true;\n                break;\n            }\n        }\n    }\n}\n\n// Increment the count\ncount++;\n//node.warn(count);\n\n// Sort the array by priority number and store in the context\nloads = loads.sort((a, b) => a.priority-b.priority);\nflow.set(\"loads\", loads);\ncontext.set(\"loadCount\", count);\n\n// If the count value = the array count, then send out the array to update the table\nif (count == loads.length || added == true)\n{\n    count = 0;\n    context.set(\"loadCount\", 0);\n    \n    msg.payload = \"update\";\n    return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":4600,"wires":[["df873518fba60ef6","d926b663b2e10030"]]},{"id":"d926b663b2e10030","type":"debug","z":"cd2cc43efd12a00d","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1170,"y":4500,"wires":[]},{"id":"7c202aa5ae4d96ab","type":"inject","z":"cd2cc43efd12a00d","name":"Clear Flow Context","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":710,"y":4740,"wires":[["147f1f30cd53301d"]]},{"id":"147f1f30cd53301d","type":"function","z":"cd2cc43efd12a00d","name":"Clear Loads Context","func":"flow.set(\"loads\", undefined);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":940,"y":4740,"wires":[[]]},{"id":"be4515129be2c1be","type":"ui_group","name":"Controls","tab":"11bccd3d1bb29d0f","order":2,"disp":false,"width":"22","collapse":false,"className":"controls"},{"id":"11bccd3d1bb29d0f","type":"ui_tab","name":"Power - Load Prioritization","icon":"dashboard","order":13,"disabled":false,"hidden":false}]

I've been pulling my hair out on this one. It seems that what needs to be done should be rather simple, but I'm not sure what I'm missing (again). Thank you once again for your help!

Please note that the code for the "up" and "down" commands inside the Priority Context Node can be ignored (I'm still writing it, and it hasn't been tested yet).

Hi,

happy to help. On the first inspection of your flow your setup has one major problem
image

It seams that it relies on the promise returned by ui-table. But you only get the promise back if the table is visible on one dashboard (and you will get multiple promises if you have many instances open).

As it seams that you don't whant to go the ui-table-handler route ... so lets work on your flow

Because of the first issue I didn't dive into your code. But here is a minimal first fix to your first flow.

As your data comes form a mqtt broker it carries a unique topic. So I use that for the id. The store node simply capture your data and put it into the flow context.

var tableData = flow.get('tableData') || {};
tableData[msg.topic]=msg.payload;
flow.set('tableData',tableData)
return msg;

So the replay function can replay it triggerd by ui-control (note the delay to make sure the table exists. Not elegant and can be done better but will be far more complicated)

var tableData = flow.get('tableData') || {};

Object.keys(tableData).forEach(key =>{
    node.send({
        topic: key,
        payload: tableData[key]
    })
})

and the add sort simply adds the sort command after each message. A "little" bit to much traffic but to keep things easy for now ok.

// first let the update pass 
node.send(msg);

// than send a sort command
return {payload:{
    command: "setSort",
    arguments: [
        [
            {
                column: "priority",
                dir: "asc",
            }
        ]
    ],
    returnPromise: false
}};

So now the data can be updated, is always sorted by priority and replays on connect and tab changes.

What can / should be done from here:

  • change the replay node to output an array directly for ui-table
  • sort the array to avoid sorting after each row
  • format the state column with an tabulator formatter callback to get rid of the formatting code of the Format For Table function otherwise step 1 and 2 leave the State column unformatted.
  • decide if you like to have manual sorting or automatic sorting. Having both is complicated to decide when to trigger automatic sorting and what to do with the manual sort

I hat read through the release notes of tabulator after 4.3 and found there where issues in the sorting functions. That might explain that we have to trigger sorting after each update.

[{"id":"70058d87226ab02e","type":"inject","z":"790d116ed3ca1bf0","name":"Stove","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Stove","payload":"{\"name\":\"Stove\",\"priority\":1,\"type\":\"AC\",\"ratedPower\":800,\"powerUsage\":735,\"state\":\"active\"}","payloadType":"json","x":150,"y":1040,"wires":[["830b8ac10c7c54ea"]]},{"id":"d52ef09e54c98e7d","type":"inject","z":"790d116ed3ca1bf0","name":"Microwave","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Microwave","payload":"{\"name\":\"Microwave\",\"priority\":5,\"type\":\"AC\",\"ratedPower\":1100,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":160,"y":1080,"wires":[["830b8ac10c7c54ea"]]},{"id":"7b622c0a24a31857","type":"inject","z":"790d116ed3ca1bf0","name":"TV","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"TV","payload":"{\"name\":\"TV\",\"priority\":6,\"type\":\"AC\",\"ratedPower\":150,\"powerUsage\":150,\"state\":\"disabled\"}","payloadType":"json","x":150,"y":1120,"wires":[["830b8ac10c7c54ea"]]},{"id":"5e42d30ce301f64d","type":"inject","z":"790d116ed3ca1bf0","name":"Refrigerator","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Refrigerator","payload":"{\"name\":\"Refrigerator\",\"priority\":2,\"type\":\"DC\",\"ratedPower\":600,\"powerUsage\":25,\"state\":\"active\"}","payloadType":"json","x":170,"y":1160,"wires":[["830b8ac10c7c54ea"]]},{"id":"f6ba805fa8d29b27","type":"inject","z":"790d116ed3ca1bf0","name":"Kitchen","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Kitchen","payload":"{\"name\":\"Kitchen Light\",\"priority\":3,\"type\":\"DC\",\"ratedPower\":25,\"powerUsage\":24.8,\"state\":\"active\"}","payloadType":"json","x":150,"y":1200,"wires":[["830b8ac10c7c54ea"]]},{"id":"695485ee4f7730ed","type":"inject","z":"790d116ed3ca1bf0","name":"Laptop","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Laptop","payload":"{\"name\":\"Laptop\",\"priority\":4,\"type\":\"AC\",\"ratedPower\":90,\"powerUsage\":0,\"state\":\"inactive\"}","payloadType":"json","x":150,"y":1240,"wires":[["830b8ac10c7c54ea"]]},{"id":"830b8ac10c7c54ea","type":"function","z":"790d116ed3ca1bf0","name":"store","func":"var tableData = flow.get('tableData') || {};\ntableData[msg.topic]=msg.payload;\nflow.set('tableData',tableData)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":1140,"wires":[["24104bf1122fdbd6","819cd6c4f6d0fca9"]]},{"id":"24104bf1122fdbd6","type":"function","z":"790d116ed3ca1bf0","name":"Format For Table","func":"var tableRow = {\n    command: \"updateOrAddData\",\n    arguments: [\n        [\n            {\n                id: msg.topic, // added this\n                priority: msg.payload.priority,                \n                loadName: msg.payload.name,\n                type: msg.payload.type,\n                ratedPower: msg.payload.ratedPower,\n                powerUsage: (Math.random()*100).toFixed(1), // for testing purpose only\n                state: msg.payload.state\n            }  \n        ]\n    ],\n    returnPromise: true\n};\n\n// TODO: get code below into a tabulator formatter for column State\n\n// Replace the status strings with icons to go in the table\nif (tableRow.arguments[0][0].state == \"active\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: navy;\">check_circle</i>';\nelse if (tableRow.arguments[0][0].state == \"inactive\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">check_circle_outline</i>';\nelse if (tableRow.arguments[0][0].state == \"disabled\")\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: red;\">dnd_forwardslash</i>';\nelse\n    tableRow.arguments[0][0].state = '<i class=\"material-icons icon-2x\" style=\"color: gray;\">radio_button_unchecked</i>';\n    \nmsg.payload = tableRow;\n// node.warn(tableRow.arguments[0][0].priority);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":1140,"wires":[["ea02a266eb13afdb"]]},{"id":"8cedfaba03af644e","type":"ui_table","z":"790d116ed3ca1bf0","group":"be4515129be2c1be","name":"Load Priority Table","order":6,"width":20,"height":8,"columns":[{"field":"priority","title":"Priority","width":"11%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"loadName","title":"Load Name","width":"28%","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"type","title":"Type","width":"9%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"ratedPower","title":"Rated Power (W)","width":"20%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"powerUsage","title":"Power Usage (W)","width":"22%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"state","title":"State","width":"9%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":1,"cts":true,"x":990,"y":1140,"wires":[["213e7d81abf9c54c"]]},{"id":"213e7d81abf9c54c","type":"debug","z":"790d116ed3ca1bf0","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1170,"y":1140,"wires":[]},{"id":"140dfb40496b17f4","type":"ui_ui_control","z":"790d116ed3ca1bf0","name":"","events":"change","x":140,"y":1300,"wires":[["4d97b353b2bf2cb9"]]},{"id":"a984f94ce8fb88f1","type":"function","z":"790d116ed3ca1bf0","name":"replay","func":"var tableData = flow.get('tableData') || {};\n\nObject.keys(tableData).forEach(key =>{\n    node.send({\n        topic: key,\n        payload: tableData[key]\n    })\n})","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":1300,"wires":[["24104bf1122fdbd6","428897cd85ff5dd4"]]},{"id":"428897cd85ff5dd4","type":"debug","z":"790d116ed3ca1bf0","name":"replay","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":570,"y":1300,"wires":[]},{"id":"510c82d39589a0b6","type":"debug","z":"790d116ed3ca1bf0","name":"to Table","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":980,"y":1100,"wires":[]},{"id":"819cd6c4f6d0fca9","type":"debug","z":"790d116ed3ca1bf0","name":"live","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":570,"y":1100,"wires":[]},{"id":"631ecc3ed9dccaed","type":"inject","z":"790d116ed3ca1bf0","name":"trigger","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":230,"y":1340,"wires":[["a984f94ce8fb88f1"]]},{"id":"4d97b353b2bf2cb9","type":"delay","z":"790d116ed3ca1bf0","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":290,"y":1300,"wires":[["a984f94ce8fb88f1"]]},{"id":"ea02a266eb13afdb","type":"function","z":"790d116ed3ca1bf0","name":"add sort","func":"// first let the update pass \nnode.send(msg);\n\n// than send a sort command\nreturn {payload:{\n    command: \"setSort\",\n    arguments: [\n        [\n            {\n                column: \"priority\",\n                dir: \"asc\",\n            }\n        ]\n    ],\n    returnPromise: false\n}};\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":1140,"wires":[["8cedfaba03af644e","510c82d39589a0b6"]]},{"id":"be4515129be2c1be","type":"ui_group","name":"Controls","tab":"11bccd3d1bb29d0f","order":2,"disp":false,"width":"22","collapse":false},{"id":"11bccd3d1bb29d0f","type":"ui_tab","name":"Power - Load Prioritization","icon":"dashboard","order":13,"disabled":false,"hidden":false}]

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