Skip to content

Instantly share code, notes, and snippets.

@AbdullahiAbdulkabir
Last active November 24, 2021 12:19
Show Gist options
  • Save AbdullahiAbdulkabir/72442f86d7359985f5184c88833bd4d6 to your computer and use it in GitHub Desktop.
Save AbdullahiAbdulkabir/72442f86d7359985f5184c88833bd4d6 to your computer and use it in GitHub Desktop.
Data table component for vue expecting data been paginated informations
<template>
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
<spinner
:showFull="true"
:loadingText="actionTypeText"
v-if="processingStuff || showFullLoader"
></spinner>
<div class="row">
<div class="col-sm-12 col md-6">
<div class="btn-group" role="group" aria-label="Actions">
<button
type="button"
class="btn btn-primary"
@click="exportToCSV"
:disabled="filteredList.length === 0"
v-bind:class="{disabled: filteredList.length === 0}"
>
<i class="mdi mdi-file-export"></i> Export
</button>
</div>
</div>
<div class="col-sm-12 col-md-6"></div>
</div>
<div class="gap"></div>
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="dataTables_length" id="order-listing_length">
<label>
Show
<select
name="order-listing_length"
aria-controls="order-listing"
class="custom-select custom-select-sm form-control"
:disabled="filteredList.length === 0"
v-model="entriesPerPage"
>
<option value="6">6</option>
<option value="12">12</option>
<option value="18">18</option>
<option value="24">24</option>
<option :value="totalData">All</option>
</select> entries
</label>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="dataTables_filter">
<label>
<input
type="text"
class="form-control"
v-model="search"
placeholder="Search"
ref="tableOne"
:disabled="loading && filteredList.length === 0"
/>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 table-responsive">
<table class="table dataTable no-footer" role="grid" style=" width:100%">
<thead>
<tr>
<th scope="col" v-for="(column, columnIndex) in columns" :key="columnIndex">
{{column.label}}
<i
class="mdi"
:title="'Sort by '+ column.label+ ' in ascending order'"
@click="sort(column.field, 'asc')"
:class="[currentSort === column.field && currentSortDir === 'asc' ? 'mdi-arrow-up-bold disabled' : 'mdi-arrow-up mdi-pointer']"
></i>
<i
class="mdi"
:title="'Sort by '+ column.label+ ' in descending order'"
@click="sort(column.field, 'desc')"
:class="[currentSort === column.field && currentSortDir === 'desc' ? 'mdi-arrow-down-bold disabled' : 'mdi-arrow-down mdi-pointer']"
></i>
</th>
<th v-if="actions.length > 0"></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in (sortedActivity, filteredList)" v-bind:key="index">
<td
v-for="(col, colIndex) in columns"
:key="colIndex"
v-html="itemValue(item, col.field)"
>{{itemValue(item, col.field)}}</td>
<!--v-if="dontShowKey === action && dontShowValue === action.dontShowKey"-->
<td v-if="actions.length > 0">
<span v-for="(action, actionIndex) in actions" :key="actionIndex">
<button
v-if="(typeof action.showKey === 'undefined' || action.showKey.length === 0 || action.showWhen.indexOf(item[action.showKey]) !== -1)
&& !action.hasOwnProperty('dropdown')"
:title="action.title || ''"
:class="action.class"
v-bind="$attrs"
@click="handleCallback(action.callback, filterObject(item, action.args))"
>{{ action.text }}</button>
<div
class="btn-group"
v-else-if="(typeof action.showKey === 'undefined' || action.showKey.length === 0 || action.showWhen.indexOf(item[action.showKey]) !== -1)
&& action.hasOwnProperty('dropdown')"
>
<button
:class="action.class"
class="dropdown-toggle"
:aria-expanded="true"
data-toggle="dropdown"
:title="action.title || ''"
>{{ action.title }}</button>
<div class="dropdown-menu">
<span
v-for="(dropDown, dropDownIndex) in action.dropdown"
:key="dropDownIndex"
>
<a
class="dropdown-item"
v-bind="$attrs"
v-if="typeof dropDown.showKey === 'undefined' || dropDown.showKey.length === 0 || dropDown.showWhen.indexOf(item[dropDown.showKey]) !== -1"
@click="handleCallback(dropDown.callback, filterObject(item, dropDown.args))"
:title="dropDown.title || ''"
>{{dropDown.text}}</a>
</span>
</div>
</div>
</span>
</td>
</tr>
<tr v-if="filteredList.length === 0">
<td
v-if="search.length > 0"
colspan="100"
style="text-align: center"
>Nothing was found for {{search}}</td>
</tr>
<tr v-if="loading">
<td style="text-align: center" colspan="100">
<spinner></spinner>
</td>
</tr>
<tr v-else-if="!loading && data.length === 0">
<td style="text-align: center" colspan="100">
<span>No data yet!</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row" v-if="filteredList.length > 0">
<div class="col-sm-12 col-md-5" v-if="data.length > 0">
<div
class="dataTables_info"
>Showing page {{ currentPage }} of {{availableTotalPage}} page(s)</div>
</div>
<div class="col-sm-12 col-md-7">
<div class="dataTables_paginate paging_simple_numbers">
<ul class="pagination">
<li
class="paginate_button page-item previous"
v-bind:class="{disabled: (currentPage === 1)}"
>
<a tabindex="0" class="page-link" @click="prevPage">Previous</a>
</li>
<li
class="paginate_button page-item next"
v-bind:class="{disabled: (availableTotalPage === 1)}"
>
<a tabindex="0" class="page-link" @click="nextPage">Next</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import Spinner from "./../Loading";
import { ExportToCsv } from "export-to-csv";
export default {
name: "TabulatedData",
components: { Spinner },
mounted: function() {
this.entriesPerPage = this.perPageEntry;
this.showPerPage = this.entriesPerPage;
},
watch: {
entriesPerPage: function(val) {
this.entriesPerPage = val === 0 ? this.data.length : val;
this.showPerPage = this.entriesPerPage;
},
filteredList: function(val) {
this.filteredList = val;
}
},
props: {
sortBy: {
type: String,
default: "default"
},
perPageEntry: {
type: Number,
default: 12
},
sortDir: {
type: String,
default: "default"
},
columns: {
type: Array,
default: () => {
return [];
}
},
data: {
type: Array,
default: () => {
return [];
}
},
actions: {
type: Array,
default: () => {
return [];
}
},
loading: {
type: Boolean,
default: true
},
showFullLoader: {
type: Boolean,
default: false
},
type: {
type: String, // striped | hover
default: "striped"
},
darkMode: {
type: Boolean,
default: false
},
csvFileName: {
type: String,
default: "CSV Exported at " + new Date().toLocaleTimeString()
}
},
data: () => ({
actionTypeText: "loading",
processingStuff: false,
currentSort: "default",
currentSortDir: "default",
showPerPage: 0,
availableTotalPage: 0,
search: "",
searchSelection: "",
currentPage: 1,
disable: "",
entriesPerPage: 0,
disableNext: "",
allPages: [
{ label: 6, value: 6 },
{ label: 12, value: 12 },
{ label: 18, value: 18 },
{ label: 24, value: 24 },
{ label: "All", value: 0 }
]
}),
methods: {
filterObject(haystack, needle = []) {
let data = [];
if (needle.length === 0 || haystack.length == 0) return [];
needle.forEach(key => {
if (haystack.hasOwnProperty(key)) data.push(haystack[key]);
});
return data;
},
handleCallback(callback, argument) {
if (!callback) return;
this.$emit(callback, ...argument);
},
sort: function(s, sortByDir = false) {
if (s === this.currentSort && !sortByDir) {
this.currentSortDir = this.currentSortDir === "asc" ? "desc" : "asc";
} else if (s === this.currentSort && sortByDir)
this.currentSortDir = sortByDir;
this.currentSort = s;
},
nextPage: function() {
if (this.currentPage * this.showPerPage < this.data.length)
this.currentPage++;
window.scroll(0, 0);
},
prevPage: function() {
if (this.currentPage > 1) this.currentPage--;
window.scroll(0, 0);
},
hasValue(item, column) {
return item[column.toLowerCase()] !== "undefined";
},
itemValue(item, column) {
return item[column];
},
startProcessingStuff: function(text) {
this.actionTypeText = text;
this.processingStuff = true;
},
doneProcessingStuff() {
this.processingStuff = false;
},
exportToCSV: function() {
this.startProcessingStuff("Exporting to CSV");
let headers = [];
let columns = this.columns.filter(column => {
return (
!column.hasOwnProperty("skipExport") && column.skipExport !== true
);
});
columns.map(column => headers.push(column.label));
const options = {
fieldSeparator: ",",
quoteStrings: '"',
decimalSeparator: ".",
showLabels: true,
showTitle: false,
title: this.csvFileName,
filename: this.csvFileName,
useTextFile: false,
useBom: true,
useKeysAsHeaders: false,
headers: headers
// headers: ['Column 1', 'Column 2', etc...] <-- Won't work with useKeysAsHeaders present!
};
const csvExporter = new ExportToCsv(options);
let data = [];
this.data.forEach(datum => {
let currentData = {};
// Loop through only the columns that needs to be exported
columns.forEach(column => {
// Add Data for only the column to be exported
currentData[[column.field]] = datum[column.field];
});
data.push(currentData);
});
csvExporter.generateCsv(data);
this.doneProcessingStuff();
this.$toastr.clear();
this.$toastr.success(
this.csvFileName + " was exported successfully!",
"Export successfully",
{ timeOut: 5000 }
);
}
},
computed: {
sortedActivity: function() {
return this.data
.sort((a, b) => {
// Set the sorting to the first column of header
// Order by desc
this.currentSort =
this.columns.length > 0 && this.currentSort === "default"
? this.columns[0]
: this.currentSort;
let modifier = 1;
if (this.currentSortDir === "desc") modifier = -1;
if (a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if (a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;
})
.filter((row, index) => {
let start = (this.currentPage - 1) * this.entriesPerPage;
let end = this.currentPage * this.entriesPerPage;
if (index >= start && index < end) return true;
});
},
filteredList: {
get: function() {
return this.data
.filter(val => {
if (this.search.length === 0) return true;
let match = false;
let keys = Object.keys(val);
for (let i = 0; i < keys.length; i++) {
let current = val[keys[i]] || "";
match = current
.toString()
.toUpperCase()
.match(this.search.toString().toUpperCase());
if (match) break;
}
return match;
})
.filter((row, index) => {
let start = (this.currentPage - 1) * this.entriesPerPage;
let end = this.currentPage * this.entriesPerPage;
if (index >= start && index < end) return true;
});
},
set: function(val) {
this.totalPages = val.length;
}
},
totalPages: {
get: function() {
return Math.ceil(this.data.length / this.entriesPerPage);
},
set: function() {
let dataLength =
this.search.length > 0 ? this.filteredList.length : this.data.length;
this.availableTotalPage = Math.ceil(dataLength / this.entriesPerPage);
}
},
totalData() {
return this.data.length;
}
},
created() {
// Init the Sort By
this.currentSort = this.sortBy;
this.currentSortDir = this.sortDir;
if (this.currentPage === 1) this.disable = "disabled";
if (this.currentPage * this.entriesPerPage < this.data.length)
this.disableNext = "disabled";
}
};
</script>
<style>
.gap {
margin-bottom: 20px;
}
.mdi-pointer {
cursor: pointer;
}
a:not([href]):not([tabindex]) {
cursor: pointer;
}
.table th img,
.jsgrid .jsgrid-table th img,
.table td img,
.jsgrid .jsgrid-table td img {
width: 80px !important;
height: 80px !important;
border-radius: 100% !important;
}
.dataTables_wrapper .dataTable .btn,
.dataTables_wrapper .dataTable .fc button,
.fc .dataTables_wrapper .dataTable button,
.dataTables_wrapper .dataTable .ajax-upload-dragdrop .ajax-file-upload,
.ajax-upload-dragdrop .dataTables_wrapper .dataTable .ajax-file-upload,
.dataTables_wrapper .dataTable .swal2-modal .swal2-buttonswrapper .swal2-styled,
.swal2-modal .swal2-buttonswrapper .dataTables_wrapper .dataTable .swal2-styled,
.dataTables_wrapper .dataTable .wizard > .actions a,
.wizard > .actions .dataTables_wrapper .dataTable a {
padding: 0.5rem 1rem;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment