Dashboard 2.0 ui-table with Export function

Hello everyone,

i am missing an important feature on the ui-table node and it's the ability to export data primarily into XLSX format and less important into PDF.

does anyone have an example perhaps using the UI-template that works exactly like the ui-table node but adds export functionality?

With lots of trials thanks to ChatGPT i was able to get a button that DOES allow the user to export an .xlsx file however the "filtered data" is always empty.

does anyone know how to fix this?

below is my sample flow:

[{"id":"18695d58fa2475b7","type":"inject","z":"c51b722a0574c241","name":"Inject sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"[{\"name\":\"abc\",\"age\":30,\"city\":\"New York\"},{\"name\":\"Mary\",\"age\":25,\"city\":\"Paris\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"}]","payloadType":"json","x":190,"y":300,"wires":[["888397d4f835d70a","29cf34c43c1d3977","e94b31137f2970c6","d24359765a95cf74","9dd98e65eea34da7"]]},{"id":"9dd98e65eea34da7","type":"ui-template","z":"c51b722a0574c241","group":"f56814f95b0bd143","page":"","ui":"","name":"Data Table with Export - exports an empty xlsx","order":2,"width":"12","height":"10","format":"\n\n<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    \n    <v-btn @click=\"exportToExcel\">Export to Excel</v-btn>\n\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    \n    </v-data-table>\n</template>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js\"></script>\n    <link href=\"https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css\" rel=\"stylesheet\">\n    <link href=\"https://cdn.jsdelivr.net/npm/vuetify@2.5.10/dist/vuetify.min.css\" rel=\"stylesheet\">\n    <script src=\"https://cdn.jsdelivr.net/npm/vue@2.6.14\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/vuetify@2.5.10\"></script>\n<script>\nexport default {\n    data() {\n        return {\n            search: '',\n            items: [],\n            headers: [\n                { text: 'Name', value: 'name' },\n                { text: 'Age', value: 'age' },\n                { text: 'City', value: 'city' }\n            ]\n        }\n    },\n    computed: {\n        filteredItems() {\n            return this.items.filter(item => {\n                return Object.values(item).some(val =>\n                    String(val).toLowerCase().includes(this.search.toLowerCase())\n                );\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    mounted() {\n        this.$scope.$watch('msg.payload', (newVal) => {\n            if (Array.isArray(newVal)) {\n                this.items = newVal;\n            } else {\n                this.items = [];\n            }\n        });\n    },\n    exportToExcel() {\n      //if you comment the following 3 lines, then an XLSX file is exported but has no data\n       if (this.filteredItems.length === 0) {\n                alert(\"No data to export.\");\n                return;\n            }\n            let wb = XLSX.utils.book_new();\n            let ws = XLSX.utils.json_to_sheet(this.filteredItems);\n            XLSX.utils.book_append_sheet(wb, ws, 'Data');\n            XLSX.writeFile(wb, 'ExportedData.xlsx');\n        }\n    }\n    \n\n  }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":520,"y":300,"wires":[[]]},{"id":"7614498695369594","type":"comment","z":"c51b722a0574c241","name":"the filetered data is \"empty\" when exported","info":"","x":500,"y":260,"wires":[]},{"id":"f56814f95b0bd143","type":"ui-group","name":"Data Table","page":"2217a2c3c7323911","width":"12","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"2217a2c3c7323911","type":"ui-page","name":"My Dashboard","ui":"3967ee137a9c95c6","path":"","icon":"","layout":"grid","theme":"f75e5d8cd6d911d2","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":3,"className":"","visible":"true","disabled":"false"},{"id":"3967ee137a9c95c6","type":"ui-base","name":"Console Center","path":"/dashboard","appIcon":"https://cdn.icon-icons.com/icons2/2699/PNG/512/siemens_logo_icon_170741.png","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":false,"notificationDisplayTime":"5","showDisconnectNotification":true},{"id":"f75e5d8cd6d911d2","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#15617e","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

Hello ..

  1. the exportToExcel() wasnt inside the methods section of your Vue app ?
  2. According to the docs the new and cleaner way of listening for new msgs is in watch section and i believe you werent checking whether msg.payload isArray but the whole newVal instead ? (that is an obj)
  3. Assign filteredItems computed property to the table instead of msg?.payload
  4. No need for the script tags for vuetify or vue .. it comes with 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-btn @click="exportToExcel">Export to Excel</v-btn>

  <v-data-table v-model:search="search" :items="filteredItems">
    <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>

  </v-data-table>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">
<!-- <link href="https://cdn.jsdelivr.net/npm/vuetify@2.5.10/dist/vuetify.min.css" rel="stylesheet"> -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script> -->
<!-- <script src="https://cdn.jsdelivr.net/npm/vuetify@2.5.10"></script> -->
<script>
  export default {
    data() {
        return {
            search: '',
            items: [],
            headers: [
                { text: 'Name', value: 'name' },
                { text: 'Age', value: 'age' },
                { text: 'City', value: 'city' }
            ]
        }
    },

    watch: {
       msg: function(newVal) {
          console.log("Msg received from NR", newVal)
            if (Array.isArray(newVal.payload)) {
                this.items = newVal.payload;

            } else {
                this.items = [];
            }
        }
    },


    computed: {
        filteredItems() {
            return this.items.filter(item => {
                return Object.values(item).some(val =>
                    String(val).toLowerCase().includes(this.search.toLowerCase())
                );
            });
        }
    },
    
    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'
        }
      },

      exportToExcel() {
      //if you comment the following 3 lines, then an XLSX file is exported but has no data
       if (this.filteredItems.length === 0) {
                console.log("No data to export.", this.filteredItems);
                return;
            }
            let wb = XLSX.utils.book_new();
            let ws = XLSX.utils.json_to_sheet(this.filteredItems);
            XLSX.utils.book_append_sheet(wb, ws, 'Data');
            XLSX.writeFile(wb, 'ExportedData.xlsx');
        }
    }
    

  }
</script>
1 Like

Thank you SO much for the great help!!!!!!!!!!!!!

Enhanced version with Pagination of items, Search bar & the export button has an icon.

once again big thank you to @UnborN

[{"id":"af3cd0f6fd192b54","type":"inject","z":"c51b722a0574c241","name":"Inject sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"[{\"name\":\"  cccc\",\"age\":30,\"city\":\"New York\"},{\"name\":\"Mary\",\"age\":25,\"city\":\"Paris\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Peter\",\"age\":45,\"city\":\"London\"},{\"name\":\"Daryl\",\"age\":45,\"city\":\"Bogota\"},{\"name\":\"John\",\"age\":45,\"city\":\"Munich\"},{\"name\":\"Mary\",\"age\":45,\"city\":\"London\"}]","payloadType":"json","x":210,"y":420,"wires":[["daec15af75dc4432"]]},{"id":"daec15af75dc4432","type":"ui-template","z":"c51b722a0574c241","group":"f56814f95b0bd143","page":"","ui":"","name":"Data Table with Export","order":5,"width":"0","height":"0","format":"<template>\n  <!-- Provide an input text box to search the content -->\n<div class=\"d-flex justify-end mt-4\">\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-btn size=\"large\" width=\"200px\" @click=\"exportToExcel\">\n    <v-icon icon=\"mdi-file-export-outline\"></v-icon>\n    Export to Excel\n  </v-btn>\n</div>\n\n \n<!-- <v-app>\n    <v-container>\n   <v-data-table v-model:search=\"search\" :items=\"filteredItems\"></v-data-table>\n    </v-container>\n  </v-app>\n-->\n\n   <v-data-table v-model:search=\"search\" :items=\"filteredItems\"></v-data-table>\n\n</template>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js\"></script>\n<link href=\"https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css\" rel=\"stylesheet\">\n<!-- <link href=\"https://cdn.jsdelivr.net/npm/vuetify@2.5.10/dist/vuetify.min.css\" rel=\"stylesheet\"> -->\n<!-- <script src=\"https://cdn.jsdelivr.net/npm/vue@2.6.14\"></script> -->\n<!-- <script src=\"https://cdn.jsdelivr.net/npm/vuetify@2.5.10\"></script> -->\n<script>\n  export default {\n    data() {\n        return {\n            search: '',\n            items: [],\n            headers: [\n            { text: 'Name', value: 'name' },\n            { text: 'Age', value: 'age' },\n            { text: 'City', value: 'city' }\n            ]\n        }\n    },\n\n    watch: {\n       msg: function(newVal) {\n          console.log(\"Msg received from NR\", newVal)\n            if (Array.isArray(newVal.payload)) {\n                this.items = newVal.payload;\n\n            } else {\n                this.items = [];\n            }\n        }\n    },\n\n\n    computed: {\n        filteredItems() {\n            return this.items.filter(item => {\n                return Object.values(item).some(val =>\n                    String(val).toLowerCase().includes(this.search.toLowerCase())\n                );\n            });\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\n      exportToExcel() {\n\n       if (this.filteredItems.length === 0) {\n                console.log(\"No data to export.\", this.filteredItems);\n                return;\n            }\n            let wb = XLSX.utils.book_new();\n            let ws = XLSX.utils.json_to_sheet(this.filteredItems);\n            XLSX.utils.book_append_sheet(wb, ws, 'Data');\n            XLSX.writeFile(wb, 'ExportedData.xlsx');\n        }\n    }\n    \n\n  }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":480,"y":420,"wires":[[]]},{"id":"f56814f95b0bd143","type":"ui-group","name":"Data Table","page":"2217a2c3c7323911","width":"10","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"2217a2c3c7323911","type":"ui-page","name":"My Dashboard","ui":"3967ee137a9c95c6","path":"","icon":"","layout":"grid","theme":"f75e5d8cd6d911d2","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":2,"className":"","visible":"true","disabled":"false"},{"id":"3967ee137a9c95c6","type":"ui-base","name":"Console Center","path":"/dashboard","appIcon":"https://cdn.icon-icons.com/icons2/2699/PNG/512/siemens_logo_icon_170741.png","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":false,"notificationDisplayTime":"5","showDisconnectNotification":true},{"id":"f75e5d8cd6d911d2","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#15617e","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]
1 Like

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