uiBuilder Vue Table - Change Row Format

Ok here is something , that I think should be fairly simple , that I cant get my head around.
I am building a MQTT device tracker with a web front end. Fairly strait forward , I keep track of regular incoming payload via MQTT and get notified it the incoming payload timeout.
I am using SQLite to track the topic's and then run a query to track the time received. All and it works quite well but as per ususal this was/is not enough.
I created a Vue Table through uiBuilder where I can edit / add topic's to be monitored but now I would like to highlight the row of the device that goes offline for easier identification , should I have missed the notification.
This is what the interface looks like

How do I achieve this? From my understanding I should use use computed properties but I just can get the hang of it.
Here is the .js code

/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
  Copyright (c) 2021 Julian Knight (Totally Information)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at


  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  See the License for the specific language governing permissions and
  limitations under the License.
'use strict'

/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */

// eslint-disable-next-line no-unused-vars
new Vue({
    el: '#app',
    data: { 
//    return {
      fields: [
        { key: "id",        label: "ID"},
        { key: "date",      label: "Date Added" },
    //    { key: "updated",   label: "Last Update" },
        { key: "name",      label: "Name"},
        { key: "topic",     label: "Monitor Topic" },
        { key: "timeout",   label: "Time Out" ,sortable: true},
        { key: "status",    label: "Status" ,sortable: true },
        { key: "recieved",  label: "Last Trigger" },
        { key: 'edit',      label: ''}

       items: [

        selectedRow: {},
 //   };


    }, // --- End of data --- //
    methods: {  
        handleEditRow(data) {
         // uibuilder.send({
         //           "payload": data.item,
          //          "index":data.index
        //          })  

        this.selectedRow = {
          [data.index]: !this.selectedRow[data.index]
var edit = this.selectedRow[data.index];
//var old = context.old || "none"
if (edit == false){

                    "payload": data.item,

this.old = data.item   ;       

           switchOn: function(name) {
      //console.log('Button Press name: %s; value: %s',name, this[name])
                if (this[name] === false){
                var stat = true; 
                 var stat = false;   
                    "payload": {
                      "cmnd": name,
                      "value": stat,
                    //  "type":'switch'

   // },
    }, // --- End of methods --- //

    // Available hooks: beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed, activated,deactivated, errorCaptured

    /** Called after the Vue app has been created. A good place to put startup code */
    created: function() {
        // Example of retrieving data from uibuilder
       this.feVersion = uibuilder.get('version')

        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder
         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
         * e.g. uibuilder.start('/uib', '/uibuilder/vendor/socket.io') // change to use your paths/names
         * @param {Object=|string=} namespace Optional. Object containing ref to vueApp, Object containing settings, or String IO Namespace override. changes self.ioNamespace from the default.
         * @param {string=} ioPath Optional. changes self.ioPath from the default
         * @param {Object=} vueApp Optional. Reference to the VueJS instance. Used for Vue extensions.
        uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.


    /** Called once all Vue component instances have been loaded and the virtual DOM built */
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        var app = this  // Reference to `this` in case we need it for more complex functions

        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
        // newVal relates to the attribute being listened to.
        uibuilder.onChange('msg', function(msg){
            console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
            app.items = msg.payload 

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

// EOF

and the index.html code

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Device Tracker Page</title>
    <meta name="description" content="Device Status Tracker">

    <link rel="icon" href="./images/node-blue.ico">

    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    <script type="text/javascript" src=""></script>
    <link href="" rel="stylesheet">
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

  <!-- Your Site Start Here / -->    
    <div id="app" v-cloak>
        <b-container fluid>

            <h2>Device Tracker</h2>

    <b-button v-on:click="switchOn('new_record')">Add Record</b-button>
    <b-table striped hover small :items="items" :fields="fields">

      <template #cell(name)="data">
          <b-form-input v-if="selectedRow[data.index]" type="text" v-model="items[data.index].name"></b-form-input>
          <span v-else>{{data.value}}</span>
      <template #cell(topic)="data">
          <b-form-input v-if="selectedRow[data.index]" type="text" v-model="items[data.index].topic"></b-form-input>
          <span v-else>{{data.value}}</span>
        <template #cell(timeout)="data">
          <b-form-input v-if="selectedRow[data.index]" type="text" v-model="items[data.index].timeout"></b-form-input>
          <span v-else>{{data.value}}</span>
         <template #cell(recieved)="data">
          <b-form-input v-if="selectedRow[data.index]" type="number" v-model="items[data.index].recieved"></b-form-input>
          <span v-else>{{data.value}}</span>  

      <template #cell(status)="data">
          <b-form-input v-if="selectedRow[data.index]" type="text" v-model="items[data.index].var4"></b-form-input>
          <span v-else>{{data.value}}</span>

      <template #cell(edit)="data">
        <b-button size="sm" @click="handleEditRow(data)">
          <span v-if="!selectedRow[data.index]">Edit</span>
          <span v-else>Done</span>


  <!-- Table Ends Here / -->

    <!-- These MUST be in the right order. Note no leading / -->

    <!-- REQUIRED: Socket.IO is loaded only once for all instances. Without this, you don't get a websocket connection -->
    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>

    <!-- Vendor Libraries - Load in the right order, use minified, production versions for speed -->
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script> <!-- dev version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script>   prod version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js"></script>   prod version without component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script> <!-- Dev version -->
    <!-- <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.min.js"></script>   Prod version -->

    <!-- REQUIRED: Sets up Socket listeners and the msg object -->
    <script src="./uibuilderfe.js"></script> <!-- dev version -->
    <!-- <script src="./uibuilderfe.min.js"></script>     prod version -->

    <!-- OPTIONAL: You probably want this. Put your custom code here -->
    <script src="./index.js"></script>


any help / guidance will be much appreciated!

Hello .. there is an example on the bootstrap-vue website on how to style a table row ( link )

From what i see in your code in onChange app.items = msg.payload
you are receiving the whole table data with every change and re-drawing the table ? all good

so add the :tbody-tr-class="rowClass" prop to your table
add in methods() a method similar to their example

 rowClass(item, type) {
        if (!item || type !== 'row') return
        if (item.status === 'offline') return 'table-warning'

the tricky part may be on Node-red side to modify the payload table data that you send to uibuilder to include an extra status field.

items: [
          { age: 40, first_name: 'Dickerson', last_name: 'Macdonald', status: 'offline' },
1 Like

Yes, you need to pass something to the front-end to indicate whether the device is on or offline. Whether a flag, a status or at least the last update timestamp. Any of these could be used in the table to highlight things.

Ok I under stand but then if I use the status field it should work without the need to add a new paramater.
What I have tried was to commend out the status line

 fields: [
        { key: "id",        label: "ID"},
        { key: "date",      label: "Date Added" },
    //    { key: "updated",   label: "Last Update" },
        { key: "name",      label: "Name"},
        { key: "topic",     label: "Monitor Topic" },
        { key: "timeout",   label: "Time Out" ,sortable: true},
       // { key: "status",    label: "Status" },
        { key: "recieved",  label: "Last Trigger" },
        { key: 'edit',      label: ''}

and added the rowClass

methods: { 
 rowClass(item, type) {
        if (!item || type !== 'row') return
        if (item.status === 'offline') return 'table-warning'
        handleEditRow(data) {


Thinking being that I don't need to see "Online" if the row changes color , but this does not change the row when I refresh the page.

true, you are right .. since you already have that field .. i should have paid more attention to your table fields

dont comment it out if you need that field to show up in the table

just try to change offline to Offline with capital O for the comparison === to work
if (item.status === 'Offline') return 'table-warning'

Apologies , it was the first thing that I tried but with no luck. It seems to be a simple solution but with no luck.

Got it!

Missed the part that I needed to add in the index.html file.


Thank you for the help!!


i was starting to think that the use of templates inside the b-table were overiding that prop ..
im glad that it worked ..

nice design by the way .. all the best :+1:

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