Ui-template to create a custom table with texts and buttons

Hi, I have a problem with this code. The table displays correctly, but when I click on one of the buttons, it is always the last one that responds.
I use DB2.
Here is the flow:

[
    {
        "id": "ab58ae07fd1bce02",
        "type": "tab",
        "label": "Flux 3",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "dcf53187a96c3ca4",
        "type": "ui-template",
        "z": "ab58ae07fd1bce02",
        "group": "26de8d07b6d3a7d8",
        "page": "",
        "ui": "",
        "name": "tableau2",
        "order": 11,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n    <!-- Provide an input text box to search the content -->\n    <v-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n        hide-details></v-text-field>\n    <v-data-table v-model:search=\"search\" :items=\"msg?.payload\">\n        <template v-slot:header.current>\n            <!-- Override how we render the header for the \"current\" column -->\n            <div class=\"text-center\">Center-Aligned</div>\n        </template>\n\n        <template v-slot:item.target=\"{ item }\">\n            <!-- Add a custom suffix to the value for the \"target\" column -->\n            {{ item.target }}°C\n        </template>\n\n        <template v-slot:item.current=\"{ item }\">\n            <!-- Render a Linear Progress Bar for the \"current\" column -->\n            <v-progress-linear v-model=\"item.current\" min=\"15\" max=\"25\" height=\"25\" :color=\"getColor(item)\">\n                <template v-slot:default=\"{ value }\">\n                    <strong>{{ item.current }}°C</strong>\n                </template>\n            </v-progress-linear>\n        </template>\n        <template v-slot:item.button=\"{ item }\">\n            <!-- Add a custom suffix to the value for the \"target\" column -->\n            <!-- Any HTML can go here -->\n            <v-btn class=\"button{ item.button }\" ref=\"item.button\" stacked @click=\"change\">\n                <div class=\"title\">{{item.button}} </div>\n                <v-icon class=\"icon\" ref=\"icon\">{{icon}}</v-icon>\n            </v-btn>\n        </template>\n\n    </v-data-table>\n</template>\n\n<script>\n    export default {\n    data () {\n      return {\n        value: \"OFF\",\n        icon: \"mdi-power\",//\"mdi-lightbulb-outline\"\n        search: ''\n      }\n    },\n    methods: {\n                // add a function to determine the color of the progress bar given the row's item\n        getColor: function (item) {\n            if (item.current > item.target) {\n                return 'red'\n            } else {\n                return 'green'\n            }\n        },\n        change: function () {\n            if (this.value === 'ON') {\n                this.disconnect();\n            } else if (this.value === 'OFF') {\n                this.connect();\n            } else {\n                this.disconnect();\n            }\n        },\n        connect: function () {\n            this.icon = \"mdi-power\";//\"mdi-lightbulb-on-outline\";\n            this.$refs.icon.$el.style.color = '#BD9608';\n            this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';\n            //this.$refs.button.$el.style.backgroundColor = '#FF8C00';\n            if (this.value === 'OFF') {\n                this.send({ payload: 'ON' });\n                this.value = \"ON\"; \n            }\n        },\n        disconnect: function () {\n            this.icon = \"mdi-power\";//\"mdi-lightbulb-outline\";\n            this.$refs.icon.$el.style.color = '#A9A9A9';\n            this.$refs.icon.$el.style.textShadow = '0px 0px 0px';\n            //this.$refs.button.$el.style.backgroundColor = '#BD9608';\n            if (this.value === 'ON') {\n                this.send({ payload: 'OFF' });\n                this.value = \"OFF\"; \n            }\n        }\n    }\n\n}\n</script>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 300,
        "y": 300,
        "wires": [
            [
                "39c80e60c62842d5"
            ]
        ]
    },
    {
        "id": "88f79f61142b3dd0",
        "type": "inject",
        "z": "ab58ae07fd1bce02",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"room\":\"Living Room\",\"id\":\"1234\",\"target\":18.1,\"current\":20,\"toto\":10,\"button\":1},{\"room\":\"Bathroom Room\",\"id\":\"5678\",\"target\":19.5,\"current\":18,\"toto\":10,\"button\":0},{\"room\":\"Kitchen Room\",\"id\":\"9101\",\"target\":18.1,\"current\":17.6,\"toto\":10,\"button\":2}]",
        "payloadType": "json",
        "x": 120,
        "y": 240,
        "wires": [
            [
                "dcf53187a96c3ca4"
            ]
        ]
    },
    {
        "id": "fb285de370f1bc42",
        "type": "inject",
        "z": "ab58ae07fd1bce02",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[]",
        "payloadType": "json",
        "x": 110,
        "y": 340,
        "wires": [
            [
                "dcf53187a96c3ca4"
            ]
        ]
    },
    {
        "id": "39c80e60c62842d5",
        "type": "debug",
        "z": "ab58ae07fd1bce02",
        "name": "debug 8",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 600,
        "y": 300,
        "wires": []
    },
    {
        "id": "26de8d07b6d3a7d8",
        "type": "ui-group",
        "name": "Table examples",
        "page": "f01f7f9fa8bf747e",
        "width": "6",
        "height": "1",
        "order": 1,
        "showTitle": false,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "f01f7f9fa8bf747e",
        "type": "ui-page",
        "name": "Tables",
        "ui": "22b797734314d77c",
        "path": "/tables",
        "icon": "table",
        "layout": "notebook",
        "theme": "0d92c765bfad87e6",
        "order": 1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "22b797734314d77c",
        "type": "ui-base",
        "name": "myDashboard",
        "path": "/dashboard",
        "appIcon": "",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false,
        "headerContent": "page",
        "navigationStyle": "default",
        "titleBarStyle": "default",
        "showReconnectNotification": true,
        "notificationDisplayTime": 1,
        "showDisconnectNotification": true,
        "allowInstall": true
    },
    {
        "id": "0d92c765bfad87e6",
        "type": "ui-theme",
        "name": "Basic Blue Theme",
        "colors": {
            "surface": "#4d58ff",
            "primary": "#0094ce",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    }
]

and the content of the ui-template:

<template>
    <!-- Provide an input text box to search the content -->
    <v-text-field v-model="search" label="Search" prepend-inner-icon="mdi-magnify" single-line variant="outlined"
        hide-details></v-text-field>
    <v-data-table v-model:search="search" :items="msg?.payload">
        <template v-slot:header.current>
            <!-- Override how we render the header for the "current" column -->
            <div class="text-center">Center-Aligned</div>
        </template>

        <template v-slot:item.target="{ item }">
            <!-- Add a custom suffix to the value for the "target" column -->
            {{ item.target }}°C
        </template>

        <template v-slot:item.current="{ item }">
            <!-- Render a Linear Progress Bar for the "current" column -->
            <v-progress-linear v-model="item.current" min="15" max="25" height="25" :color="getColor(item)">
                <template v-slot:default="{ value }">
                    <strong>{{ item.current }}°C</strong>
                </template>
            </v-progress-linear>
        </template>
        <template v-slot:item.button="{ item }">
            <!-- Add a custom suffix to the value for the "target" column -->
            <!-- Any HTML can go here -->
            <v-btn class="button{ item.button }" ref="item.button" stacked @click="change">
                <div class="title">{{item.button}} </div>
                <v-icon class="icon" ref="icon">{{icon}}</v-icon>
            </v-btn>
        </template>

    </v-data-table>
</template>

<script>
    export default {
    data () {
      return {
        value: "OFF",
        icon: "mdi-power",//"mdi-lightbulb-outline"
        search: ''
      }
    },
    methods: {
                // add a function to determine the color of the progress bar given the row's item
        getColor: function (item) {
            if (item.current > item.target) {
                return 'red'
            } else {
                return 'green'
            }
        },
        change: function () {
            if (this.value === 'ON') {
                this.disconnect();
            } else if (this.value === 'OFF') {
                this.connect();
            } else {
                this.disconnect();
            }
        },
        connect: function () {
            this.icon = "mdi-power";//"mdi-lightbulb-on-outline";
            this.$refs.icon.$el.style.color = '#BD9608';
            this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';
            //this.$refs.button.$el.style.backgroundColor = '#FF8C00';
            if (this.value === 'OFF') {
                this.send({ payload: 'ON' });
                this.value = "ON"; 
            }
        },
        disconnect: function () {
            this.icon = "mdi-power";//"mdi-lightbulb-outline";
            this.$refs.icon.$el.style.color = '#A9A9A9';
            this.$refs.icon.$el.style.textShadow = '0px 0px 0px';
            //this.$refs.button.$el.style.backgroundColor = '#BD9608';
            if (this.value === 'ON') {
                this.send({ payload: 'OFF' });
                this.value = "OFF"; 
            }
        }
    }

}
</script>

If anyone can help me, that would be great.
Nicolas

Hi, welcome to the forum.

There are some vue concepts in your code you are missing (this.value is a single state and why your individual rows were not updated).

Anyhow, rather than go through that all, I will post an altered version of your flow that uses context to maintain state server side & only updates client side if the backend has received the new state (i.e. front end reflects state of backend for end to end state true state reflection)

Perhaps it will help you achieve what you need?

Here goes.

The flow:

[{"id":"dcf53187a96c3ca4","type":"ui-template","z":"ab58ae07fd1bce02","group":"26de8d07b6d3a7d8","page":"","ui":"","name":"tableau2","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <!-- Provide an input text box to search the content -->\n    <v-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n        hide-details></v-text-field>\n    <v-data-table v-model:search=\"search\" :items=\"msg?.payload\" :headers=\"headers\">\n        <template v-slot:item.target=\"{ item }\">\n            <!-- Add a custom suffix to the value for the \"target\" column -->\n            {{ item.target }}°C\n        </template>\n\n        <template v-slot:item.current=\"{ item }\">\n            <!-- Render a Linear Progress Bar for the \"current\" column -->\n            <v-progress-linear v-model=\"item.current\" min=\"15\" max=\"25\" height=\"25\" :color=\"getColor(item)\">\n                <template v-slot:default=\"{ value }\">\n                    <strong>{{ item.current }}°C</strong>\n                </template>\n            </v-progress-linear>\n        </template>\n        <template v-slot:item.button=\"{ item }\">\n            <!-- Add a custom suffix to the value for the \"target\" column -->\n            <!-- Any HTML can go here -->\n            <v-btn :color=\"item.state ? 'green' : 'red' \" stacked @click=\"toggle(item)\">\n                <v-icon>{{item.state ? 'mdi-power' : 'mdi-power-off'}}</v-icon>\n            </v-btn>\n        </template>\n\n    </v-data-table>\n</template>\n\n<script>\n    export default {\n    data () {\n      return {\n        search: '',\n        headers: [\n            { title: 'Room', key: 'room', align: 'start' },\n            { title: 'Current', key: 'current', align: 'center' },\n            { title: 'Target', key: 'target', align: 'center' },\n            { title: '', key: 'button', align: 'center' }\n        ]\n      }\n    },\n    methods: {\n                // add a function to determine the color of the progress bar given the row's item\n        getColor: function (item) {\n            if (item.current > item.target) {\n                return 'red'\n            } else {\n                return 'green'\n            }\n        },\n        toggle: function (item) {\n            this.send({\n                payload: {\n                    id: item.id,\n                    state: !item.state // invert\n                }\n            });\n        }\n    }\n\n}\n</script>\n","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":480,"y":320,"wires":[["2ac571404b6a93d5","2c95426efe2fc9bf"]]},{"id":"88f79f61142b3dd0","type":"inject","z":"ab58ae07fd1bce02","name":"init","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":110,"y":240,"wires":[["7235c71551dc6882"]]},{"id":"fb285de370f1bc42","type":"inject","z":"ab58ae07fd1bce02","name":"clear","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":330,"y":200,"wires":[["ee8bf75e9248dca9"]]},{"id":"7235c71551dc6882","type":"template","z":"ab58ae07fd1bce02","name":"setup dummy data","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"[\n    {\n        \"room\": \"Living Room\",\n        \"id\": \"1234\",\n        \"target\": 18.1,\n        \"current\": 20,\n        \"toto\": 10,\n        \"button\": 1,\n        \"state\": false\n    },\n    {\n        \"room\": \"Bathroom Room\",\n        \"id\": \"5678\",\n        \"target\": 19.5,\n        \"current\": 18,\n        \"toto\": 10,\n        \"button\": 0,\n        \"state\": false\n    },\n    {\n        \"room\": \"Kitchen Room\",\n        \"id\": \"9101\",\n        \"target\": 18.1,\n        \"current\": 17.6,\n        \"toto\": 10,\n        \"button\": 2,\n        \"state\": false\n    }\n]","output":"json","x":290,"y":240,"wires":[["ee8bf75e9248dca9"]]},{"id":"ee8bf75e9248dca9","type":"change","z":"ab58ae07fd1bce02","name":"Store data in flow.tableData","rules":[{"t":"set","p":"tableData","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":240,"wires":[["9d6449a8cc36a5b5"]]},{"id":"9d6449a8cc36a5b5","type":"change","z":"ab58ae07fd1bce02","name":"get tableData","rules":[{"t":"set","p":"payload","pt":"msg","to":"tableData","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":320,"wires":[["dcf53187a96c3ca4"]]},{"id":"2ac571404b6a93d5","type":"function","z":"ab58ae07fd1bce02","name":"update tableData","func":"\nconst tableData = flow.get('tableData')\nconst itemId = msg.payload.id\nconst foundItem = tableData.find(e => e.id === itemId)\nif (!foundItem) {\n    node.warn(`Item with ID ${itemId} not found!`);\n}\nconst targetState = !!msg.payload.state\nconst currentState = !!foundItem.state\nif (currentState === targetState) {\n    // no change\n    return null // halt flow\n}\n\nfoundItem.state = targetState\nflow.set('tableData', tableData)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":400,"wires":[["9d6449a8cc36a5b5"]]},{"id":"2c95426efe2fc9bf","type":"debug","z":"ab58ae07fd1bce02","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":635,"y":320,"wires":[],"l":false},{"id":"26de8d07b6d3a7d8","type":"ui-group","name":"Table examples","page":"f01f7f9fa8bf747e","width":"6","height":"1","order":1,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"f01f7f9fa8bf747e","type":"ui-page","name":"Tables","ui":"03482f22997a66ac","path":"/tables","icon":"table","layout":"notebook","theme":"0d92c765bfad87e6","order":2,"className":"","visible":"true","disabled":"false"},{"id":"03482f22997a66ac","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control","ui-chat"],"showPathInSidebar":false,"headerContent":"page","navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true,"allowInstall":false},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#4d58ff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"74bcbabb7199aa26","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.29.0"}}]

Demo:

chrome_aybvwfAH61

Thank you very much.
I'll give it a try.