Here, we will learn about how to create a custom datatable using Vue.js
Lets start the code:
Step 1: Open CustomDataTable.vue and add the following in it:
<template> <div class="custom-data-table"> <div class="wrapper"> <main> <section class="content"> <section class="customer-table"> <div class="helpfinancehead"> <div class="helptable"> <a id="helpt" href="javascript: void(0);" ><img id="helpimg" src="./../assets/images/Help_Icon.svg" />Help</a > </div> <div class="financetable"> <div class="financetext"> <a id="fintextme" href="javascript: void(0);">Finance</a ><img id="finimg" src="./../assets/images/Finance_Icon.svg" /> </div> <div class="finimage"></div> </div> </div> <table> <thead> <tr> <th v-for="(item, index) of headerColumns" :key="index"> <span v-if=" item.isImage && item.columnName === 'Download_Icon.svg' " > <img src="./../assets/images/Download_Icon.svg" /> </span> <span v-if=" item.isImage && item.columnName === 'Upload_Icon.svg' " > <img src="./../assets/images/Upload_Icon.svg" /> </span> <span v-if="!item.isImage" v-html="item.columnName"></span> </th> </tr> </thead> <tbody> <tr> <td v-for="(item, index) of filterColumns" :key="index"> <input v-if="item.type === 'text'" type="text" :id="item.fieldName" v-model="search" class="text-box-ui" placeholder="Search.." /> <div class="select" v-if="item.type === 'dropdown'"> <select> <option :value="data.value" v-for="(data, count) of item.options" :key="count" @change=" searchData({ columnName: item.fieldName, value: data.value, }) " > {{ data.text }} </option> </select> </div> </td> </tr> <tr v-for="(data, count) of perPageData" :key="count"> <td v-for="(item, index) of dataColumns" :key="index"> <div class="checkboxies" v-if="item.type === 'checkbox'"> <label class="container"> <input type="checkbox" :disabled="!data.Editable" @change=" $emit('value-changed', { payload: data, field: item.fieldName, value: $event.target.checked, }) " checked v-if="data[item.fieldName]" /> <input type="checkbox" @change=" $emit('value-changed', { payload: data, field: item.fieldName, value: $event.target.checked, }) " :disabled="!data.Editable" v-if="!data[item.fieldName]" /> <span class="checkmark"></span> </label> </div> <a href="javascript: void(0);" v-if="item.type === 'img'" ><img v-if="item.icon === 'Download_Icon.svg'" src="./../assets/images/Download_Icon.svg" /><img v-if="item.icon === 'Upload_Icon.svg'" src="./../assets/images/Upload_Icon.svg" /></a> <span v-if="item.type === 'text' && !data.Editable"> {{ data[item.fieldName] }} </span> <input v-if="item.type === 'text' && data.Editable" type="text" @change=" $emit('value-changed', { payload: data, field: item.fieldName, value: $event.target.value, }) " class="text-box-ui" :value="data[item.fieldName]" /> </td> <td> <button class="deleteicon cursor-pointer"> <img src="./../assets/images/Delete_Icon.svg" @click="deleteItem(data)" /> </button> </td> <td> <button class="cursor-pointer"> <img src="./../assets/images/Edit_Icon.svg" v-if="!data.Editable" @click=" $emit('enable-edit', { payload: data, edit: true, }) " /> <img src="./../assets/images/Save_Icon.svg" v-if="data.Editable" @click=" $emit('enable-edit', { payload: data, edit: false, }) " /> </button> </td> </tr> </tbody> </table> <div class="tablefoot"> <div class="totitems"> <p style="text-align: left"> [ Total Items - {{ payloadData.length }} ] </p> </div> <div class="pagechanger"> <div class="previousclass cursor-pointer" :class="{ disabledEffect: isInFirstPage }" @click="onClickPreviousPage" > <a id="previousbtn" href="javascript: void(0);" ><img src="./../assets/images/Previous_Active.svg" alt="" /></a> </div> <div class="cursor-pointer" v-for="(item, index) of pages" :key="index" > <p @click="onClickPage(item.name)" :class="{ activePage: currentPage == item.name }" > [{{ item.name }}] </p> </div> <div class="nextclass cursor-pointer" :class="{ disabledEffect: isInLastPage }" @click="onClickNextPage" > <a id="nextbtn" href="javascript: void(0);" ><img src="./../assets/images/Next_Active.svg" alt="" /></a> </div> </div> <div class="add"> <button id="addbutton"> <img src="./../assets/images/Add_Icon.svg" @click="$emit('add-item')" /> </button> </div> </div> </section> </section> </main> </div> </div> </template> <script> export default { name: "CustomDataTable", data() { return { search: "" }; }, props: { headerColumns: { typeof: Object, default: () => [], }, filterColumns: { typeof: Object, default: () => [], }, dataColumns: { typeof: Object, default: () => [], }, payloadData: { typeof: Object, default: () => [], }, maxVisibleButtons: { type: Number, required: false, default: 3, }, totalPages: { type: Number, required: true, }, total: { type: Number, required: true, }, perPage: { type: Number, required: true, }, currentPage: { type: Number, required: true, }, }, computed: { perPageData() { return JSON.parse(JSON.stringify(this.payloadData)).splice( (this.currentPage - 1) * this.perPage, this.perPage ); }, startPage() { if (this.currentPage === 1) { return 1; } if (this.currentPage === this.totalPages) { return this.totalPages - this.maxVisibleButtons + 1; } return this.currentPage - 1; }, endPage() { return Math.min( this.startPage + this.maxVisibleButtons - 1, this.totalPages ); }, pages() { const range = []; for (let i = this.startPage; i <= this.endPage; i += 1) { range.push({ name: i, isDisabled: i === this.currentPage, }); } return range; }, isInFirstPage() { return this.currentPage === 1; }, isInLastPage() { return this.currentPage === this.totalPages; }, }, methods: { deleteItem(payload) { if (confirm("Are you sure you want to delete?")) { this.$emit("delete-item", payload); } }, onClickPreviousPage() { this.$emit("page-changed", this.currentPage - 1); }, onClickPage(page) { this.$emit("page-changed", page); }, onClickNextPage() { this.$emit("page-changed", this.currentPage + 1); }, }, }; </script> <style scoped> @import url("./../assets/styles/discharge.css"); @import url("./../assets/styles/table.css"); .cursor-pointer { cursor: pointer; } .disabledEffect { opacity: 0.5; pointer-events: none; } .activePage { background-color: rgba(23, 128, 140, 0.847); padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-block: 2px; color: white; border-radius: 5px; } </style>
Step 2: Open app.vue and add the following in it:
<template> <div id="app"> <CustomDataTable :headerColumns="headerColumns" :filterColumns="filterColumns" :payloadData="tableData" :dataColumns="dataColumns" :total-pages="Math.ceil(tableData.length / perPage)" :total="tableData.length" :per-page="perPage" :current-page="currentPage" @page-changed="onPageChange" @delete-item="deleteItem" @enable-edit="enableEdit" @add-item="addItem" @value-changed="valueChanged" /> </div> </template> <script> import CustomDataTable from "./components/CustomDataTable.vue"; export default { name: "App", components: { CustomDataTable, }, computed: { headerColumns() { return [ { columnName: "Protection", fieldName: "Protection", }, { columnName: "Status", fieldName: "Status", }, { columnName: "Sequence Number", fieldName: "SequenceNumber", }, { columnName: `Download_Icon.svg`, isImage: true, fieldName: "Download", }, { columnName: `Upload_Icon.svg`, isImage: true, fieldName: "Upload", }, { columnName: "Short Name", fieldName: "ShortName", }, { columnName: "Name In English", fieldName: "NameInEnglish", }, { columnName: "Name In Arabic", fieldName: "NameInArabic", }, ]; }, payloadData() { const tempData = []; for (var i = 0; i < 10; i++) { tempData.push({ Id: i, Protection: true, Status: false, SequenceNumber: "Test" + i, Download: "", Upload: "", ShortName: "Test" + i, NameInEnglish: "Test" + i, NameInArabic: "Test" + i, Editable: false, }); } return tempData; }, dataColumns() { return [ { type: "checkbox", fieldName: "Protection", }, { type: "checkbox", fieldName: "Status", }, { type: "text", fieldName: "SequenceNumber", }, { type: "img", icon: "Download_Icon.svg", fieldName: "Download", }, { type: "img", icon: "Upload_Icon.svg", fieldName: "Upload", }, { type: "text", fieldName: "ShortName", }, { type: "text", fieldName: "NameInEnglish", }, { type: "text", fieldName: "NameInArabic", }, ]; }, filterColumns() { return [ { type: "dropdown", fieldName: "Protection", options: [ { text: "Active", value: 1, }, { text: "In active", value: 2, }, ], }, { type: "dropdown", fieldName: "Status", options: [ { text: "Active", value: 1, }, { text: "In active", value: 2, }, ], }, { type: "text", fieldName: "SequenceNumber", }, { type: "", }, { type: "", }, { type: "text", fieldName: "ShortName", }, { type: "text", fieldName: "NameInEnglish", }, { type: "text", fieldName: "NameInArabic", }, ]; }, }, data() { return { tableData: [], currentPage: 1, perPage: 3, }; }, mounted() { this.tableData = this.payloadData; }, methods: { deleteItem(payload) { this.tableData = this.tableData.filter((x) => x.Id !== payload.Id); }, addItem() { const i = Math.ceil(Math.random() * 100); this.tableData.unshift({ Id: i, Protection: true, Status: false, SequenceNumber: "Test" + i, Download: "", Upload: "", ShortName: "Test" + i, NameInEnglish: "Test" + i, NameInArabic: "Test" + i, Editable: true, }); }, findData(payload) { return this.tableData.find((x) => x.Id == payload.Id); }, enableEdit({ payload, edit }) { this.findData(payload).Editable = edit; }, valueChanged({ payload, field, value }) { this.findData(payload)[field] = value; }, onPageChange(page) { this.currentPage = page; }, }, }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
Code in action: