# Dashboard 2.0 ui-table with Export function

**URL:** <https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640>\
**Category:** Dashboard\
**Created:** [15 January 2025 11:10 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640 "2025-01-15T11:10:33Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![akd02](https://avatars.discourse-cdn.com/v4/letter/a/5e9695/32.png) [@akd02](https://discourse.nodered.org/u/akd02)\
**Post date:** [15 January 2025 11:10 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/1 "2025-01-15T11:10:33Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![akd02](https://avatars.discourse-cdn.com/v4/letter/a/5e9695/32.png) [@akd02](https://discourse.nodered.org/u/akd02)\
**Post date:** [17 January 2025 13:52 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/2 "2025-01-17T13:52:45Z")

</div>

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:

```auto
[{"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"}}]

```

---

<div class="post-metadata">

**Author:** ![UnborN](https://avatars.discourse-cdn.com/v4/letter/u/4491bb/32.png) [@UnborN](https://discourse.nodered.org/u/UnborN)\
**Post date:** [17 January 2025 20:33 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/3 "2025-01-17T20:33:03Z")

</div>

Hello ..

1. the `exportToExcel()` wasnt inside the `methods` section of your Vue app ?
2. According to the [docs](https://dashboard.flowfuse.com/nodes/widgets/ui-template.html#receiving-data) 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

```html
<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>

```

---

<div class="post-metadata">

**Author:** ![akd02](https://avatars.discourse-cdn.com/v4/letter/a/5e9695/32.png) [@akd02](https://discourse.nodered.org/u/akd02)\
**Post date:** [20 January 2025 06:46 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/4 "2025-01-20T06:46:24Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![akd02](https://avatars.discourse-cdn.com/v4/letter/a/5e9695/32.png) [@akd02](https://discourse.nodered.org/u/akd02)\
**Post date:** [20 January 2025 08:52 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/5 "2025-01-20T08:52:44Z")

</div>

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

once again big thank you to @UnborN

```auto
[{"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"}}]

```

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [3 February 2025 08:53 UTC](https://discourse.nodered.org/t/dashboard-2-0-ui-table-with-export-function/94640/6 "2025-02-03T08:53:35Z")

</div>

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