Custom Datatable in Vue.js

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:

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe

Select Categories