Skip to content

Instantly share code, notes, and snippets.

@itzikbenh
Last active January 13, 2018 19:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itzikbenh/5e7dea5ea78ee5587f544a4f40f7e6a2 to your computer and use it in GitHub Desktop.
Save itzikbenh/5e7dea5ea78ee5587f544a4f40f7e6a2 to your computer and use it in GitHub Desktop.
Vue datatables
<style>
thead {
.sorting {
background-image: url('DataTables-1.10.15/images/sort_both.png');
background-repeat: no-repeat;
background-position: center right;
}
.sorting_asc {
background-image: url('DataTables-1.10.15/images/sort_asc.png');
background-repeat: no-repeat;
background-position: center right;
}
.sorting_desc {
background-image: url('DataTables-1.10.15/images/sort_desc.png');
background-repeat: no-repeat;
background-position: center right;
}
}
</style>
<template>
<table class="table is-bordered data-table"">
<thead>
<tr>
<th v-for="column in columns" v-if="!(column.hasOwnProperty('orderable') && !column.orderable)" :key="column.name" @click="$emit('sort', column.name)"
v-show="!(column.hasOwnProperty('visible') && !column.visible)"
:class="sortKey === column.name ? (sortOrders[column.name] > 0 ? 'sorting_asc' : 'sorting_desc') : 'sorting'"
:style="'width:'+column.width+';'+'cursor:pointer;'">
{{column.label}}
</th>
<th v-else :style="'width:'+column.width" :key="column.name"
v-show="!(column.hasOwnProperty('visible') && !column.visible)">
{{column.label}}
</th>
</tr>
</thead>
<slot></slot>
</table>
</template>
<script>
export default {
props: ['columns', 'sortOrders', 'sortKey'],
}
</script>
<template>
<div>
<div class="table-filters">
<div class="textSearch">
<input type="text" class="input" v-model="search" @input="resetPagination();" placeholder="Search Table">
</div>
<div class="flex-right">
<excel-export :data="filteredDff" :columns="columns" filename="data"></excel-export>
<select v-model="length" @change="resetPagination()">
<option value="20">20</option>
<option value="150">150</option>
<option value="300">300</option>
<option value="450">450</option>
<option value="2000">2000</option>
<option value="6000">6000</option>
<option value="10000">10000</option>
</select>
</div>
</div>
<datatable :columns="columns" :sortKey="sortKey" :sortOrders="sortOrders" @sort="sortBy">
<tbody>
<tr v-for="d in paginated" :key="d.user_id">
<td>{{ d.first_name }}</td>
<td>{{ d.last_name }}</td>
<td>{{ d.email }}</td>
<td>{{ d.company }}</td>
<td>{{ d.job_title }}</td>
<td>{{ d.file_description }}</td>
<td>{{ d.created_at | niceDate }}</td>
</tr>
<tr v-if="!filteredDff.length">
<td class="text-center" colspan="100%">No results were found.</td>
</tr>
</tbody>
</datatable>
<pagination :pagination="pagination" :client="true" :filtered="filteredDff"
@paginate:prev="--pagination.currentPage"
@paginate:next="pagination.currentPage++">
</pagination>
</div>
</template>
<script>
import {helpers} from '../mixins/helpers';
export default {
mixins: [helpers],
props: ['dff'],
data() {
let sortOrders = {};
let columns = [
{ width: '12%', label: 'First Name', orderable: true, name: 'first_name' },
{ width: '12%', label: 'Last Name', orderable: true, name: 'last_name' },
{ width: '20%', label: 'Email', orderable: true, name: 'email'},
{ width: '10%', label: 'Company', orderable: true, name: 'company'},
{ width: '8%', label: 'Job Title', orderable: true, name: 'job_title'},
{ width: '12%', label: 'File Description', orderable: true, name: 'file_description'},
{ width: '8%', label: 'Date', orderable: true, name: 'created_at', type: 'date' },
];
columns.forEach((column) => {
if (column.orderable) {
sortOrders[column.name] = -1;
}
});
return {
columns: columns,
sortKey: 'created_at',
sortOrders: sortOrders,
search: '',
length: 20,
pagination: {
prevPage: '',
currentPage: 1,
nextPage: '',
from: '',
to: '',
total: this.dff.length,
},
}
},
methods: {
paginate(array, length, pageNumber) {
this.pagination.from = array.length ? ((pageNumber - 1) * length) + 1 : ' ';
this.pagination.to = pageNumber * length > array.length ? array.length : pageNumber * length;
this.pagination.prevPage = pageNumber > 1 ? pageNumber : '';
this.pagination.nextPage = array.length > this.pagination.to ? pageNumber + 1 : '';
return array.slice((pageNumber - 1) * length, pageNumber * length);
},
resetPagination() {
this.pagination.currentPage = 1;
this.pagination.prevPage = '';
this.pagination.nextPage = '';
},
sortBy(key) {
this.resetPagination();
this.sortKey = key;
this.sortOrders[key] = this.sortOrders[key] * -1;
},
},
computed: {
filteredDff() {
let dff = this.dff;
if (this.search) {
dff = this.searchData(dff, this.columns, this.search);
}
let sortKey = this.sortKey;
let order = this.sortOrders[sortKey] || 1;
if (sortKey) {
dff = this.sortData(dff, this.columns, sortKey, order);
}
return dff;
},
paginated() {
return this.paginate(this.filteredDff, this.length, this.pagination.currentPage);
},
}
}
</script>
export const helpers = {
methods: {
getIndex(array, key, value) {
return array.findIndex(i => i[key] == value)
},
leaf(obj, path) {
path = path.split('.');
let res = obj;
for (let i = 0; i < path.length; i++) {
if (res[path[i]]) {
res = res[path[i]]
} else {
return '';
}
}
return res;
},
sortData(data, columns, sortKey, order) {
return data.slice().sort((a, b) => {
let index = this.getIndex(columns, 'name', sortKey);
a = String(this.leaf(a, sortKey)).toLowerCase();
b = String(this.leaf(b, sortKey)).toLowerCase();
if (columns[index].type && columns[index].type === 'date') {
return (a === b ? 0 : new Date(a).getTime() > new Date(b).getTime() ? 1 : -1) * order;
} else if (columns[index].type && columns[index].type === 'number') {
return (+a === +b ? 0 : +a > +b ? 1 : -1) * order;
} else {
return (a === b ? 0 : a > b ? 1 : -1) * order;
}
})
},
searchData(data, columns, search) {
return data.filter((row) => {
return Object.keys(columns).some((key) => {
if (!(key > -1) || (columns[key].hasOwnProperty('visible') && !columns[key].visible) || (columns[key].hasOwnProperty('searchable') && !columns[key].searchable)) {
return false;
}
return String(this.leaf(row, columns[key].name)).toLowerCase().indexOf(search.toLowerCase()) > -1;
});
});
},
}
};
<template>
<nav class="pagination" v-if="!client">
<span class="page-stats">{{pagination.from}} - {{pagination.to}} of {{pagination.total}}</span>
<a v-if="pagination.prevPage" class="prev-page" @click="$emit('paginate:prev');">
<span class="dashicons dashicons-arrow-left"></span>
</a>
<a class="prev-page" v-else :disabled="true">
<span class="dashicons dashicons-arrow-left"></span>
</a>
<a v-if="pagination.nextPage" class="next-page" @click="$emit('paginate:next');">
<span class="dashicons dashicons-arrow-right"></span>
</a>
<a class="next-page" v-else :disabled="true">
<span class="dashicons dashicons-arrow-right"></span>
</a>
</nav>
<nav class="pagination" v-else>
<span class="page-stats">
{{pagination.from}} - {{pagination.to}} of {{filtered.length}}
<span v-if="filtered.length < pagination.total">(filtered from {{pagination.total}} total entries)</span>
</span>
<a v-if="pagination.prevPage" class="prev-page" @click="$emit('paginate:prev');">
<span class="dashicons dashicons-arrow-left"></span>
</a>
<a class="prev-page" v-else :disabled="true">
<span class="dashicons dashicons-arrow-left"></span>
</a>
<a v-if="pagination.nextPage" class="next-page" @click="$emit('paginate:next');">
<span class="dashicons dashicons-arrow-right"></span>
</a>
<a class="next-page" v-else :disabled="true">
<span class="dashicons dashicons-arrow-right"></span>
</a>
</nav>
</template>
<script>
export default {
props: ['pagination', 'client', 'filtered'],
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment