@totallyInformation has given you a way to get this running yourself but if you want to see his original code (more or less) - and this works as I am using it myself - I have included the three comment nodes
with the code in them that I use for information. [note- copyright goes to totallyInformation
)
[{"id":"1e87c86c97f26515","type":"comment","z":"cd281ebd2c4a291e","name":"index.html","info":"<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n <title>Home1: Network Devices</title>\n <meta name=\"description\" content=\"Home1: Network Devices\">\n\n <link rel=\"icon\" href=\"./images/node-blue.ico\">\n\n <link href=\"../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css\" type=\"text/css\" rel=\"stylesheet\" />\n <link href=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css\" type=\"text/css\" rel=\"stylesheet\" />\n\n <link href=\"./index.css\" media=\"all\" type=\"text/css\" rel=\"stylesheet\" />\n</head>\n\n<body>\n <!-- Hidden icon data, <use xlink:href=\"/path/to/icons.svg#play\"></use> if IE not needed -->\n <svg aria-hidden=\"true\" focusable=\"false\" style=\"display:none\">\n <symbol id=\"icon-edit\" viewBox=\"0 0 576 512\">\n <path fill=\"currentColor\"\n d=\"M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z\" />\n \n </symbol>\n\n <symbol id=\"icon-delete\" viewBox=\"0 0 448 512\">\n <path fill=\"currentColor\"\n d=\"M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z\">\n \n </path>\n\n </symbol>\n\n </svg>\n <div id=\"app\" v-cloak>\n <b-container id=\"app_container\" fluid>\n <b-table striped hover small bordered caption-top :items=\"devs.items\" :fields=\"devs.cols\"\n :primary-key=\"devs.key\" :busy=\"devs.busy\" :head-row-variant=\"devs.headRowVariant\" :sort-by=\"devs.key\"\n :filter=\"devs.filter\" :per-page=\"devs.perPage\" :current-page=\"devs.currentPage\" @filtered=\"onFiltered\"\n @row-dblclicked=\"rowDblClicked\" v-model=\"devs.model\">\n <template v-slot:table-caption>\n <h2>Network Devices Table</h2>\n\n <!-- Filter (search) input - searches whole row - TODO: Add field selector dropdown -->\n <b-form-group label=\"Filter\" label-cols-sm=\"3\" label-align-sm=\"right\" label-size=\"sm\"\n label-for=\"filterInput\" class=\"mb-0\">\n <b-input-group size=\"sm\">\n <b-form-input v-model=\"devs.filter\" type=\"search\" id=\"filterInput\"\n placeholder=\"Type to search ...\"></b-form-input>\n\n </b-input-group>\n\n </b-form-group>\n\n </template>\n\n <template v-slot:table-busy>\n <div class=\"text-center text-danger my-2\">\n <b-spinner class=\"align-middle\"></b-spinner>\n\n <strong>Loading...</strong>\n\n </div>\n\n </template>\n\n <!-- Custom Header scoped slot -->\n <!--<template v-slot:thead-top=\"data\"><b-tr><b-td :colspan=\"data.columns\">\n </b-td></b-tr></template>-->\n\n <!-- Custom Footer scoped slot -->\n <template v-slot:custom-foot=\"data\">\n <b-tr>\n <b-td :colspan=\"data.columns\">\n <b-container fluid class=\"m-0 p-0\">\n <b-form-row align-h=\"between\" align-v=\"center\">\n <b-col>\n <b-button @click=\"tblItemAdd(data, $event.target)\">\n Add New Entry\n </b-button>\n\n </b-col>\n <b-col cols=\"1\">\n <b-form-input id=\"devs-rowsperpage\" type=\"number\" v-model=\"devs.perPage\"\n align=\"right\" title=\"Rows per page. Use 0 to show whole table.\">\n </b-form-input>\n\n </b-col>\n <b-col cols=\"3\">\n <b-pagination v-model=\"devs.currentPage\" :total-rows=\"devs.totalRows\"\n :per-page=\"devs.perPage\" first-number last-number align=\"right\"\n class=\"m-0 p-0\" title=\"Select page\"></b-pagination>\n\n </b-col>\n\n </b-row>\n\n </b-container>\n <!--\n <b-button class=\"float-left\"\n @click=\"tblItemAdd(data, $event.target)\"\n >\n Add New Entry\n </b-button>\n <div class=\"float-right\">\n <b-form-input id=\"devs-rowsperpage\" type=\"number\" v-model=\"devs.perPage\"></b-form-input>\n <b-pagination\n v-model=\"devs.currentPage\"\n :total-rows=\"devs.totalRows\"\n :per-page=\"devs.perPage\"\n first-number last-number\n ></b-pagination>\n </div>\n -->\n </b-td>\n\n </b-tr>\n\n </template>\n\n <!-- Default data cell scoped slot (only used if allowing inline edits) -->\n <template v-if = \"devs.inlineEdit\" v-slot:cell() = \"row\">\n <!--<textarea v-if=\"row.field.editable\"\n @change=\"cellEdit('change', row, $event.target, devs.model)\"\n >{{row.value}}</textarea>-->\n\n <b-form-textarea v-if=\"row.field.editable\" :id=\"'inpta-'+row.field.key+'-'+row.index\"\n v-model=\"row.item[row.field.key]\" trim lazy\n @change=\"cellEdit('change', row, $event, devs.model)\"></b-form-textarea>\n\n <!--<input type=\"textarea\" v-if=\"row.field.editable\" :value=\"row.value\"></input>-->\n \n <span v-else>{{ row.value }}</span>\n\n </template>\n\n <!-- Actions column -->\n <template v-slot:cell(actions)=\"row\">\n <!--<b-button variant=\"outline-secondary\" class=\"p-0\" v-b-modal.modal1>\n <svg class=\"icon m-2\" aria-hidden=\"true\" focusable=\"false\">\n <use xlink:href=\"#icon-edit\"></use>\n </svg>\n </b-button>-->\n <b-button variant=\"outline-secondary\" class=\"p-0\" @click=\"tblRowEdit(row, $event, devs.model)\"\n v-if=\"!devs.inlineEdit\">\n <svg class=\"icon m-2\" aria-hidden=\"true\" focusable=\"false\">\n <use xlink:href=\"#icon-edit\"></use>\n\n </svg>\n\n </b-button>\n\n <span class=\"access-label\">Edit</span>\n <b-button variant=\"outline-secondary\" class=\"p-0\"\n @click=\"tblRowDelete(row.item, row.index, $event.target)\">\n <svg class=\"icon m-2\" aria-hidden=\"true\" focusable=\"false\">\n <use xlink:href=\"#icon-delete\"></use>\n\n </svg>\n\n <span class=\"access-label\">Delete</span>\n\n </b-button>\n\n </template>\n\n </b-table>\n\n <!-- Modal Edit Dialog box-->\n <b-modal v-model=\"devs.showEdit\" id=\"modal1\" title=\"Device Edit\" header-bg-variant=\"primary\"\n header-text-variant=\"light\" @ok=\"modalEditOk\" modal-ok=\"Save\">\n <b-form>\n <div v-for=\"(column, index) in devs.cols\" :key=\"column.key\">\n <b-form-group v-if=\"column.key !== 'actions'\" label-cols=\"3\" :id=\"'mefg-'+index\"\n :label=\"column.label+': '\" :label-for=\"'input-'+index\"\n :description=\"(column.description||'')+' e.g. '+column.eg\">\n <b-form-input :id=\"'input-'+index\" v-model=\"editFrm[column.key]\" lazy trim\n :type=\"column.dataType || 'text'\" :readonly=\"column.editable === true ? false : true\">\n\n </b-form-input>\n <!--<b-form-input :id=\"'input-'+index\"\n @input=\"frmEdit('input', index, column, $event.target)\"\n @change=\"frmEdit('change', index, column, $event.target)\"\n v-model=\"frmCol('model', index, column, $event.target)\"\n lazy trim\n :type=\"column.dataType || 'text'\"\n :readonly=\"column.editable === true ? false : true\"\n >-->\n </b-form-input>\n\n </b-form-group>\n\n <!--<p v-else>{{column.key}} :: {{editFrm[column.key]}}</p>-->\n </div>\n\n </b-form>\n\n </b-modal>\n\n </b-container>\n\n </div>\n\n <script src=\"../uibuilder/vendor/socket.io/socket.io.js\"></script>\n <script src=\"../uibuilder/vendor/vue/dist/vue.js\"></script>\n <script src=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js\"></script>\n <script src=\"./uibuilderfe.min.js\"></script>\n <script src=\"./index.js\"></script>\n\n</body>\n\n</html>","x":180,"y":840,"wires":[]},{"id":"f4b92b1dd6d1c780","type":"comment","z":"cd281ebd2c4a291e","name":"index.js","info":"/* jshint browser: true, esversion: 5, asi: true */\n/*globals Vue, uibuilder */\n// @ts-nocheck\n/*\nCopyright (c) 2020 Julian Knight (Totally Information)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n'use strict'\n\n/** @see\nhttps://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods\n*/\n\n// eslint-disable-next-line no-unused-vars\nvar vueApp = new Vue({\n el: '#app',\n data: {\n\n // Devices table\n devs: {\n items: [], // table content\n cols: [], // column definitions\n key: '', // primary key field name\n headVariant: 'dark',\n headRowVariant: 'primary',\n busy: false, // Table busy?\n perPage: 10,\n currentPage: 1,\n totalRows: 1,\n filter: null,\n showEdit: false,\n inlineEdit: true, // Allow edits of editable fields in-line?\n /** Holds visible items from devs.items (b-table v-model)\n * Indexing on the model matches the index returned by b-table\n */\n model: null,\n },\n\n editFrm: {},\n\n }, // --- End of data --- //\n\n computed: {\n\n frmCol: function (chg, index, column, event) {\n console.log({ chg, index, column, event })\n return devices\n },\n\n }, // --- End of computed --- //\n methods: {\n\n /** Called from modal form\n * NOTE: If no params were passed, the system would provide the new value\n */\n cellEdit(chg, row, event, devsModel) {\n console.log({ chg, row, event, devsModel })\n //devsModel[row.index][row.field.key] = event.target.value\n uibuilder.send({ topic: 'admin/change', payload: devsModel[row.index] })\n }, // --- End cellEdit --- //\n\n // Trigger pagination to update the number of buttons/pages due to filtering\n rowDblClicked(item, index, event) {\n console.log({ item, index, event }, this.devsModel)\n this.editFrm = item\n this.devs.showEdit = true // !this.devs.showEdit\n }, // --- End of rowDblClicked --- //\n\n /** Handle table edit button\n * @param {Object} item The data content of the row being edited\n * @param {number} index The filtered/paginated row index (not the full data index)\n * @param {Object} event The event object\n * @param {Object} devsModel Reference to the table v-model data (FILTERED)\n */\n tblRowEdit(row, event, devsModel) {\n /** editFrm referenced by input's in form\n * maps to table data model.\n */\n this.editFrm = row.item\n this.devs.showEdit = true\n console.log('Table Row Edit Button Pressed', { row, event, devsModel })\n }, // --- End of tblRowEdit --- //\n\n frmEdit: function (chg, index, column, event) {\n const value = event.target.value\n console.log({ chg, value, index, column, event })\n //this.$emit(chg, { ...this.local, [key]: value })\n }, // --- End frmEdit --- //\n \n // Handle OK button on edit modal dialog\n modalEditOk(bvModalEvt) {\n console.log('Edit Modal Dialog: OK Button Pressed', 'DATA: ', this.editFrm)\n // Update main data table\n this.devs.items[this.editFrm._idx] = this.editFrm\n // Send to NR\n uibuilder.send(this.editFrm)\n // If error, prevent modal from closing\n //bvModalEvt.preventDefault()\n }, // --- End of modalEditOk --- //\n\n // Handle table delete button\n tblRowDelete(item, index, event) {\n console.log('Table Row Delete Button Pressed', { item, index, event })\n this.devs.items = this.devs.items.filter(\n device => device.id !== item.id\n\n )\n uibuilder.send({ topic: 'admin/change', payload: this.devs.items })\n\n }, // --- End of tblRowDelete --- //\n\n // Handle table add new button\n tblItemAdd(data, event) {\n this.devs.showEdit = true\n console.log('Table Item Add Button Pressed', { data, event })\n }, // --- End of tblItemAdd --- //\n\n // Trigger pagination to update the number of buttons/pages due to filtering\n onFiltered(filteredItems) {\n this.devs.totalRows = filteredItems.length\n this.devs.currentPage = 1\n }, // --- End of onFiltered --- //\n\n // Return formatted HTML version of JSON object\n syntaxHighlight: function (json) {\n json = JSON.stringify(json, undefined, 4)\n json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n json =\n json.replace(/(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g,\n function (match) {\n var cls = 'number'\n if (/^\"/.test(match)) {\n if (/:$/.test(match)) {\n cls = 'key'\n } else {\n cls = 'string'\n }\n } else if (/true|false/.test(match)) {\n cls = 'boolean'\n } else if (/null/.test(match)) {\n cls = 'null'\n }\n return '<span class=\"' + cls + '\">' + match + '</span>'\n })\n return json\n }, // --- End of syntaxHighlight --- //\n\n }, // --- End of methods --- //\n\n // Available hooks: init,mounted,updated,destroyed\n mounted: function () {\n uibuilder.start()\n\n var vueApp = this\n\n // Example of retrieving data from uibuilder\n vueApp.feVersion = uibuilder.get('version')\n\n uibuilder.onChange('msg', function (msg) {\n //console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)\n vueApp.msgRecvd = msg\n\n // May need to use Vue.set(vm.items, indexOfItem, newValue)\n\n if (msg.topic === 'network/status') {\n vueApp.devs.busy = true\n console.log('NEW NETWORK DATA RECEIVED')\n\n //#region ------ COLUMNS ---- //\n // Grab the schema to define the columns\n if (msg.schema) {\n // Convert from object to array if needed\n if (Array.isArray(msg.schema)) vueApp.devs.cols = msg.schema\n else vueApp.devs.cols = Object.values(msg.schema)\n }\n\n // Enrich the columns definitions as required\n vueApp.devs.cols.forEach(function (column, i, arr) {\n // If col not specified as object, make it one\n if (!(column != null && column.constructor.name === \"Object\")) column = arr[i] = { 'key': column }\n \n // Add sortable to column spec unless already specified\n if (!column.sortable) column.sortable = true\n \n // Default headerTitle addrib to key if not present\n if (column.label && !column.headerTitle) column.headerTitle = column.key\n \n // Set primary key for table\n if (column.primary) {\n vueApp.devs.key = column.key\n column.headerTitle += ' [Primary Key]'\n\n }\n \n //TODO Format or dataType specified?\n if (column.dataType.toLowerCase() === 'date' && !column.formatter) {\n column.dataType = 'text'\n column.formatter = function (value, key, item) {\n if (value === '') {\n var d = new Date('fred')\n consonsole.log(d)\n\n return ''\n\n } else {\n var d = new Date(value)\n return d.toLocaleString()\n\n }\n }\n }\n\n /* \n if ( column.format || column.dataType ) {\n\n }\n */\n\n // TODO Editable?\n if (column.editable) {\n column.headerTitle += ' (EDITABLE)'\n\n }\n\n })\n\n // If col definitions don't specify a primary key, choose the 1st col\n if (vueApp.devs.key === '') {\n vueApp.devs.key = vueApp.devs.cols[0]\n vueApp.devs.cols[0].headerTitle += ' [Primary Key]'\n }\n\n // Add actions column WARN: Assumes cols table rebuilt so action col not exist\n vueApp.devs.cols.push({\n key: 'actions',\n label: '',\n sortable: false,\n })\n\n //#endregion ---- COLUMNS ---- //\n\n //#region ------- DATA ------- //\n\n // Set the data (convert from object to array if needed)\n if (Array.isArray(msg.payload)) vueApp.devs.items = msg.payload\n else vueApp.devs.items = Object.values(msg.payload)\n\n vueApp.devs.items.forEach(function (row, i) {\n // Add an index field to make editing easier when using filtered view\n row._idx = i\n\n })\n\n // Set row count for pagination\n vueApp.devs.totalRows = vueApp.devs.items.length\n\n //#endregion ----- DATA ----- //\n\n //console.log(vueApp.devCols,vueApp.devices, vueApp.devs.key)\n vueApp.devs.busy = false\n\n } // --- End of devices --- //\n\n }) // ----- End of msg received ----- //\n\n } // --- End of mounted hook --- //\n\n}) // --- End of vueApp --- //\n\n// EOF","x":350,"y":840,"wires":[]},{"id":"0b492baa66cf45ba","type":"comment","z":"cd281ebd2c4a291e","name":"index.css","info":"/* Cloak elements on initial load to hide the possible display of {{ ... }} \n * Add to the app tag or to specific tags\n * To display \"loading...\", change to the following:\n * [v-cloak] > * { display:none }\n * [v-cloak]::before { content: \"loading…\" }\n */\n[v-cloak] { display: none; }\n\n/**\n * Visually hidden accessible label\n * Using styles that do not hide the text in screen readers\n * We use !important because we should not apply other styles to this hidden alternative text\n */\n .access-label {\n position: absolute !important;\n width: 1px !important;\n height: 1px !important;\n overflow: hidden !important;\n white-space: nowrap !important;\n }\n \n/**\n * Default icon style\n */\n.icon {\n /* Use the current text color as the icon’s fill color. */\n fill: currentColor;\n /* Inherit the text’s size too. Also allows sizing the icon by changing its font-size. */\n width: 1.2em; height: 1.2em;\n /* The default vertical-align is `baseline`, which leaves a few pixels of space below the icon. Using `center` prevents this. For icons shown alongside text, you may want to use a more precise value, e.g. `vertical-align: -4px` or `vertical-align: -0.15em`. */\n vertical-align: middle;\n /* Paths and strokes that overflow the viewBox can show in IE11. */\n overflow: hidden;\n}\n\n/* Colours for Syntax Highlighted pre's */\n.syntax-highlight {color:white;background-color:black;padding:5px 10px;}\n.syntax-highlight > .key {color:#ffbf35}\n.syntax-highlight > .string {color:#5dff39;}\n.syntax-highlight > .number {color:#70aeff;}\n.syntax-highlight > .boolean {color:#b993ff;}","x":520,"y":840,"wires":[]}]
PS I am now off to try the uiBuilder cache node