Dashboard 2 - Experimenting with v-data-table

I've been playing with v-data-table and have gotten as far as can at the moment.
I've used the CSV file upload example and most the work has been on CCS styling of the table.

I used the headers option to pass up custom headers as I wanted to shorten column names.
This is done in a function node and reference as msg?.headers.

I've been able to reduce a "security key" col and use the expand option to view it. However every row expands at the moment where as it should only be the clicked row. The CCS below reduces the row hight to make a more compact search/table/footer.

The flow below might be of help to others that want a customised table.

image

// CSS

<style>
// define any styles here - supports raw CSS
.v-field__field {padding: 0 0px 0 6px !important; height:35px; min-height: 35px;}
.v-field__input{padding: 0 0px 0 6px !important; height:35px; min-height: 35px;}
.v-data-table { width: 1755px; }
.v-data-table-column--no-padding {padding: 0 0px 0 6px !important;}
.v-data-table-column--align-start {padding: 0 0px 0 6px !important;}
.v-data-table-header__content {color: black; font-weight: bold; height:40px; min-height: 40px; }
.v-data-table-header__content:hover {color: black; font-weight: bold;}
.v-data-table__th {background-color: #B3C1C6; white-space: nowrap;}
.v-data-table .v-data-table__td {white-space: nowrap; height: 40px !important; min-height: 40px !important;}
.v-table__wrapper tr:nth-child(even){background-color: #f2f2f2;}
.v-table__wrapper tr:hover {background-color: #ddd; color: black;}
.v-data-table-footer { background-color: #B3C1C6; color: black; height: 43px !important; min-height: 43px !important; padding:0;}
.v-data-table-footer .v-input__control {height: 35px !important; min-height: 30px !important; padding:0}
</style>

// Flow

[
    {
        "id": "3b1c8b1a52f7af8e",
        "type": "ui-template",
        "z": "ccbf14926ab59f7f",
        "group": "8a94c465ccc99808",
        "page": "",
        "ui": "",
        "name": "UPLOAD",
        "order": 0,
        "width": "3",
        "height": "2",
        "head": "",
        "format": "<v-file-input show-size multiple chips :rules=\"rules\" accept=\".csv\"\n    variant=\"underlined\" label=\"\" v-on:change=\"uploadFile\" v-model=\"value\"\n    active-color=\"primary\" @update:modelValue=\"send({payload: value})\"  prepend-icon=\"mdi-text-box-outline\" />\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 160,
        "y": 120,
        "wires": [
            [
                "c16c52e40cce67e4"
            ]
        ]
    },
    {
        "id": "c16c52e40cce67e4",
        "type": "change",
        "z": "ccbf14926ab59f7f",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload[0]",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 255,
        "y": 120,
        "wires": [
            [
                "fe397451602922bf"
            ]
        ],
        "l": false
    },
    {
        "id": "fe397451602922bf",
        "type": "function",
        "z": "ccbf14926ab59f7f",
        "name": "Convert Hex to String",
        "func": "const hexBuffer = msg.payload;\n\n// Convert the hex buffer to a Buffer object\nconst buffer = Buffer.from(hexBuffer, 'hex');\n\n// Convert the Buffer to a string\nconst string = buffer.toString();\n\n// Assign the string to msg.payload for further processing\nmsg.payload = string;\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 315,
        "y": 120,
        "wires": [
            [
                "fd9e67b2a0e7d8fd"
            ]
        ],
        "l": false
    },
    {
        "id": "fd9e67b2a0e7d8fd",
        "type": "csv",
        "z": "ccbf14926ab59f7f",
        "name": "",
        "sep": ",",
        "hdrin": true,
        "hdrout": "none",
        "multi": "mult",
        "ret": "\\n",
        "temp": "",
        "skip": "0",
        "strings": true,
        "include_empty_strings": "",
        "include_null_values": "",
        "x": 375,
        "y": 120,
        "wires": [
            [
                "0198aa546d9f0815"
            ]
        ],
        "l": false
    },
    {
        "id": "0198aa546d9f0815",
        "type": "change",
        "z": "ccbf14926ab59f7f",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "CSV",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 435,
        "y": 120,
        "wires": [
            [
                "953a43666f537f44",
                "9a140742e675322c",
                "c93eb9884699e05a"
            ]
        ],
        "l": false
    },
    {
        "id": "953a43666f537f44",
        "type": "function",
        "z": "ccbf14926ab59f7f",
        "name": "headers",
        "func": "msg.headers = [\n    {title: \"customer\", key:\"customer\"},\n    {title: \"project\", key: \"project\"},\n    {title: \"ver\", key: \"version\"},\n    {title: \"Gateway\", key:\"gateway_name\"},\n    {title: \"manf\", key:\"manufacturer\"},\n    {title: \"model\", key:\"model\"},\n    {title: \"devEUI\", key:\"devEUI\"},\n    {title: \"appEUI\", key:\"appEUI\"},\n    {title: \"appKey\", key:\"appkey\"},\n    {title: \"HW ver\", key:\"hardware\"},\n    {title: \"FW ver\", key:\"firmware\"},\n    {title: \"name\", key:\"device_name\"},\n    {title: \"tag\", key:\"tagging\"},\n    { title: '', key: 'data-table-expand' }\n]\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 535,
        "y": 120,
        "wires": [
            [
                "5ea7c9b33f60bd6e"
            ]
        ],
        "l": false
    },
    {
        "id": "5ea7c9b33f60bd6e",
        "type": "ui-template",
        "z": "ccbf14926ab59f7f",
        "group": "760f720c21457605",
        "page": "",
        "ui": "",
        "name": "Expandable Custom table",
        "order": 2,
        "width": "5",
        "height": "1",
        "head": "",
        "format": "<template>\n  <v-card\n    flat\n    width=1555\n  >\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\" hide-details ></v-text-field>\n    <v-data-table v-model:search=\"search\" :items=\"msg?.payload\" :headers=\"msg?.headers\"  v-model:custom-filter=\"customSearch\"  show-expand >\n        <template v-slot:header.current>\n            <div class=\"text-center\">Center-Aligned</div>\n        </template>\n        <template v-slot:item.appKey=\"{ item }\">\n            <div style=\"width: 40px; height:20px; overflow: hidden;\">{{item.appKey}} </div>\n        </template>\n        <template v-slot:expanded-row=\"{ columns, item }\">\n            <td :colspan=\"3\" style=\"text-align: left; height:50px; background-color:#F4F9FC;\">\n              <div style=\"padding-left:15px;\"> {{item.appKey }}</div>\n            </td>\n        </template>\n    </v-data-table>\n  </v-card>\n     \n</template>\n\n<script>\n    export default {\n    data () {\n      return {\n        search: ''\n      }\n    }\n  }\n</script>\n\n<style>\n// define any styles here - supports raw CSS\n.v-field__field {padding: 0 0px 0 6px !important; height:35px; min-height: 35px;}\n.v-field__input{padding: 0 0px 0 6px !important; height:35px; min-height: 35px;}\n.v-data-table { width: 1755px; }\n.v-data-table-column--no-padding  {padding: 0 0px 0 6px !important;}\n.v-data-table-column--align-start {padding: 0 0px 0 6px !important;}\n.v-data-table-header__content {color: black; font-weight: bold; height:40px; min-height: 40px; }\n.v-data-table-header__content:hover {color: black; font-weight: bold;}\n.v-data-table__th {background-color: #B3C1C6; white-space: nowrap;}\n.v-data-table .v-data-table__td {white-space: nowrap; height: 40px !important; min-height: 40px !important;}\n.v-table__wrapper tr:nth-child(even){background-color: #f2f2f2;}\n.v-table__wrapper tr:hover {background-color: #ddd; color: black;}\n.v-data-table-footer { background-color: #B3C1C6; color: black; height: 43px !important; min-height: 43px !important; padding:0;}\n.v-data-table-footer .v-input__control {height: 35px !important; min-height: 30px !important; padding:0}\n</style>\n",
        "storeOutMessages": true,
        "passthru": false,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 600,
        "y": 144,
        "wires": [
            []
        ],
        "l": false
    },
    {
        "id": "8a94c465ccc99808",
        "type": "ui-group",
        "name": "CSV upload",
        "page": "76f6b77509a74a17",
        "width": "3",
        "height": "2",
        "order": -1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "760f720c21457605",
        "type": "ui-group",
        "name": "Devices",
        "page": "76f6b77509a74a17",
        "width": "9",
        "height": "1",
        "order": -1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "76f6b77509a74a17",
        "type": "ui-page",
        "name": "Device CSV Upload",
        "ui": "42751b5153d87eac",
        "path": "/",
        "icon": "tray-arrow-up",
        "layout": "grid",
        "theme": "0d92c765bfad87e6",
        "order": 1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "42751b5153d87eac",
        "type": "ui-base",
        "name": "baseNode",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false,
        "navigationStyle": "icon"
    },
    {
        "id": "0d92c765bfad87e6",
        "type": "ui-theme",
        "name": "Basic Blue Theme",
        "colors": {
            "surface": "#506f86",
            "primary": "#b3c1c6",
            "bgPage": "#ffffff",
            "groupBg": "#ffffff",
            "groupOutline": "#f0f0f0"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    }

]

3 Likes

Nice, it looks very clean & easy to view

1 Like

Paul your comment prompted me to update post as I realised I hadn't stated what my aim was.
To make a more compact table as the default creates a fair amount of white space.

1 Like

Regarding Tables (and almost all other component in VUE), is possible to add a directive density="compact" to the elements.

This makes quite a difference in optimizing "space", I would wish this would be an option for all vue components used in dashboard2, dashboard 2 seems to "big" lot's of wasted space specially in screens with resolution higher than 1080p.

Table on the right without the density="compact", table on the left with that directive.

Example:

<template>
  <!-- Provide an input text box to search the content -->
  <v-text-field v-model="search" label="Search" prepend-inner-icon="mdi-magnify" density="compact"  single-line variant="outlined"
    hide-details></v-text-field>
  <v-data-table density="compact" v-model:search="search" :items="msg?.payload" :headers="headers" items-per-page=5>
                  <template #bottom>
                    <!-- Leave this slot empty to hide pagination controls -->
                    <hr>
                  </template>
    <!-- <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 }" filterable: false>
      <!-- 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>

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

Another one useful in some vue components (labels, text inputs) is also to add hide-details="auto"

3 Likes

@arturv2000 wow thanks for that I have to say I get lost in the docs and where to find these directives.
So resort to good old CCS. Thanks

1 Like

Ok for individual expansion of a given row. Each row must have a unique id key.

1 Like