Custom Template
Build your own UI
when using this advanced feature please take extra care not to break any existing functionality (e.g do not remove events or essential attributes like value
or ref
)
As of version 2 the package offers the ability to seamlessly swap any table component with your own UI implementation.
Usage
Swapped components are declared as the fifth argument you pass when registering the component:
Vue.use(ClientTable, [options = {}], [useVuex = false], [theme = 'bootstrap3'], {
[componentAKey]: ComponentA,
[componentBKey]: ComponentB,
//etc...
});
Each component has its current template stored in the Github repo under the templates folder. All components have a single props
prop, which is an object containing all contextual data needed to build the component. More on this in the examples section. The full structure of the components tree, along with the corresponding keys is listed below:
VtDataTable: dataTable
VtGenericFilter: genericFilter
VtPerPageSelector: perPageSelector
VtDropdownPagination: dropdownPagination
VtColumnsDropdown: columnsDropdown
VtTable: table
VtTableHead: tableHead
VtHeadingsRow: headingsRow
VtTableHeading: tableHeading
VtSortControl: sortControl
VtFiltersRow: filtersRow
VtTextFilter: textFilter
VtListFilter: listFilter
VtDateFilter: dateFilter
VtTableBody: tableBody
VtNoResultsRow: noResultsRow
VtTableRow: tableRow
VtTableCell: tableCell
VtChildRowToggler: childRowToggler
VtChildRow: childRow
VtGroupRow: groupRow
VtPagination: pagination
Users of premium version, use the template folder of the private repository instead(node_modules/vue-tables-2/templates),
as there might be some differences in some of the templates
Examples
Custom Pagination
Let's swap the default pagination, which uses vue-pagination-2, with some other 3rd party implementaion, say vue-plain-pagination .
Copy the existing pagination template:
<template>
<pagination
:options="props.optionsObj"
:for="props.name"
:vuex="props.vuex"
:records="props.records"
:per-page="props.perPage"
v-model="props.page"
@paginate="page => props.setPage(page)"/>
</template>
<script>
import Pagination from 'vue-pagination-2'
export default {
name: "VtPagination",
components: {
Pagination
},
props: ['props']
}
</script>
Modify it like so, and save to your project:
MyPagination.vue
<template>
<v-pagination v-model="props.page"
:page-count="props.totalPages"
@input="page => props.setPage(page)"/>
</template>
<script>
import vPagination from 'vue-plain-pagination'
export default {
name: "VtPagination",
components: {
vPagination
},
props: ['props']
}
</script>
Register it:
Vue.use(ClientTable, {}, false, 'bootstrap3', {
pagination: MyPagination
});
Et Voila!
Font Awesome Sort Icon
Let's use vue-fontawesome
Install the icons:
import { library } from '@fortawesome/fontawesome-svg-core'
import { faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faSort);
library.add(faSortUp);
library.add(faSortDown);
Copy the original template:
<template>
<span v-if="props.sortable" :class="props.class">
</span>
</template>
<script>
export default {
name: "VtSortControl",
props: ['props']
}
</script>
Modify and save to your project:
MySortControl.vue
<template>
<font-awesome-icon v-if="props.sortable" :icon="icon" class="fa-pull-right"/>
</template>
<script>
export default {
name: "VtSortControl",
props: ['props'],
computed: {
icon() {
// sortStatus = { sorted: Boolean, asc: Boolean }
// if not sorted return base icon
if (!this.props.sortStatus.sorted) return 'sort';
// return sort direction icon
return this.props.sortStatus.asc ? 'sort-up' : 'sort-down';
}
}
}
</script>
Register it:
Vue.use(ClientTable, {}, false, 'bootstrap3', {
sortControl: MySortControl
});
Footer Pagination
In this example we will reposition the pagination element inside the table by moving it from VtDataTable
to VtTable
:
Remove pagination from VtDataTable
:
MyDataTable.vue
<template>
<div :class="`VueTables VueTables--${props.source}`" slot-scope="props">
<div :class="props.theme.row">
<div :class="props.theme.column">
<div v-if="!props.opts.filterByColumn && props.opts.filterable"
:class="`${props.theme.field} ${props.theme.inline} ${props.theme.left} VueTables__search`">
<vnodes :vnodes="props.slots.beforeFilter"/>
<vt-generic-filter/>
<vnodes :vnodes="props.slots.afterFilter"/>
</div>
<vnodes :vnodes="props.slots.afterFilterWrapper"/>
<div v-if="props.perPageValues.length > 1"
:class="`${props.theme.field} ${props.theme.inline} ${props.theme.right} VueTables__limit`">
<vnodes :vnodes="props.slots.beforeLimit"/>
<vt-per-page-selector/>
<vnodes :vnodes="props.slots.afterLimit"/>
</div>
<div class="VueTables__pagination-wrapper" v-if="props.opts.pagination.dropdown && props.totalPages > 1">
<div :class="`${props.theme.field} ${props.theme.inline} ${props.theme.right} VueTables__dropdown-pagination`">
<vt-dropdown-pagination/>
</div>
</div>
<div v-if="props.opts.columnsDropdown"
:class="`VueTables__columns-dropdown-wrapper ${props.theme.right} ${props.theme.dropdown.container}`">
<vt-columns-dropdown/>
</div>
</div>
</div>
<vnodes :vnodes="props.slots.beforeTable"/>
<div class="table-responsive">
<vt-table ref="vt_table"/>
</div>
<vnodes :vnodes="props.slots.afterTable"/>
</div>
</template>
<script>
import VtColumnsDropdown from 'vue-tables-2/compiled/components/VtColumnsDropdown'
import VtDropdownPagination from 'vue-tables-2/compiled/components/VtDropdownPagination'
import VtGenericFilter from 'vue-tables-2/compiled/components/VtGenericFilter'
import VtPerPageSelector from 'vue-tables-2/compiled/components/VtPerPageSelector';
import VtTable from 'vue-tables-2/compiled/components/VtTable';
export default {
name: "VtDataTable",
props: ['props'],
components: {
VtGenericFilter,
VtPerPageSelector,
VtColumnsDropdown,
VtDropdownPagination,
VtTable,
vnodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes
}
}
}
</script>
Add pagination to VtTable
:
MyTable.vue
<template>
<table v-bind="props.tableAttrs">
<caption v-if="props.caption">{{props.caption}}</caption>
<vt-table-head/>
<vnodes :vnodes="props.slots.beforeBody"/>
<vt-table-body/>
<vnodes :vnodes="props.slots.afterBody"/>
<tfoot>
<tr>
<td :colspan="props.colspan">
<vt-pagination/>
</td>
</tr>
</tfoot>
</table>
</template>
<script>
import VtTableHead from 'vue-tables-2/compiled/components/VtTableHead'
import VtTableBody from 'vue-tables-2/compiled/components/VtTableBody'
import VtPagination from 'vue-tables-2/compiled/components/VtPagination'
export default {
name: "VtTable",
props: ['props'],
components: {
VtTableHead,
VtTableBody,
VtPagination,
vnodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes
}
}
}
</script>
Register Components:
Vue.use(ClientTable, {}, false,'bootstrap3', {
dataTable: MyDataTable,
table: MyTable
});
Footer Headings
Add vtHeadingsRow
to vtTable
:
MyTable.vue
<template>
<table v-bind="props.tableAttrs">
<caption v-if="props.caption">{{props.caption}}</caption>
<vt-table-head/>
<vnodes :vnodes="props.slots.beforeBody"/>
<vt-table-body/>
<vnodes :vnodes="props.slots.afterBody"/>
<tfoot>
<vt-headings-row/>
</tfoot>
</table>
</template>
<script>
import VtTableHead from 'vue-tables-2/compiled/components/VtTableHead'
import VtTableBody from 'vue-tables-2/compiled/components/VtTableBody'
import VtHeadingsRow from 'vue-tables-2/compiled/components/VtHeadingsRow'
export default {
name: "MyTable",
props: ['props'],
components: {
VtTableHead,
VtTableBody,
VtHeadingsRow,
vnodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes
}
}
}
</script>
Register the component:
Vue.use(ClientTable, {}, false,'bootstrap3', {
table: MyTable
});
Numeric Field Pagination
Say you have a huge number of pages in your server table, which prevents you from using dropdown pagination due to memory limitations. Let's replace dropdown pagination (VtDropdownPagination) with a numeric field.
NumericFieldPagination.vue
<template>
<input class="form-control"
type="number"
name="page"
ref="page"
:value="props.page"
@input="setPage"/>
</template>
<script>
import { debounce } from 'debounce'
export default {
name: "VtDropdownPagination",
props: ['props'],
methods: {
setPage: debounce(function (e) {
var page = e.target.value;
if (page >= 1 && page <= this.props.totalPages) {
this.props.setPage(page);
}
}, 250)
}
}
</script>
Register the component:
Vue.use(ServerTable, {}, false,'bootstrap3', {
dropdownPagination: NumericFieldPagination
});
Select2 List Filter
Replace VtListFilter with the following:
<template>
<div class="VueTables__list-filter"
:id="`VueTables__${props.column}-filter`">
<select :class="props.theme.select"
:name="props.name"
:value="props.value">
<option value="">
{{props.defaultOption}}
</option>
<option v-for="option in props.items" :value="option.id">
{{option.text}}
</option>
</select>
</div>
</template>
<script>
export default {
name: "MyListFilter",
props: ['props'],
mounted() {
$(this.$el).find('select').select2()
.on('select2:select', (e) => {
this.props.query[this.props.column] = e.target.value
this.props.search(false)
});
}
}
</script>
Register the component:
Vue.use(ServerTable, {}, false,'bootstrap3', {
listFilter: MyListFilter
});
Vuetify
This is NOT a complete example. It's what I used recently for a project I was working on, and can serve as a solid starting point for further modifications.
VtDataTable:
<template>
<v-container fluid :class="`VueTables VueTables--${props.source}`" slot-scope="props">
<v-row>
<v-col cols="12" :class="props.theme.column">
<div v-if="!props.opts.filterByColumn && props.opts.filterable"
:class="`${props.theme.field} ${props.theme.inline} ${props.theme.left} VueTables__search`">
<vnodes :vnodes="props.slots.beforeFilter"/>
<vt-generic-filter/>
<vnodes :vnodes="props.slots.afterFilter"/>
</div>
<vnodes :vnodes="props.slots.afterFilterWrapper"/>
<div v-if="props.perPageValues.length > 1"
:class="`${props.theme.field} ${props.theme.inline} ${props.theme.right} VueTables__limit`">
<vnodes :vnodes="props.slots.beforeLimit"/>
<vt-per-page-selector/>
<vnodes :vnodes="props.slots.afterLimit"/>
</div>
<div class="VueTables__pagination-wrapper" v-if="props.opts.pagination.dropdown && props.totalPages > 1">
<div :class="`${props.theme.field} ${props.theme.inline} ${props.theme.right} VueTables__dropdown-pagination`">
<vt-dropdown-pagination/>
</div>
</div>
<div v-if="props.opts.columnsDropdown"
:class="`VueTables__columns-dropdown-wrapper ${props.theme.right} ${props.theme.dropdown.container}`">
<vt-columns-dropdown/>
</div>
</v-col>
</v-row>
<vnodes :vnodes="props.slots.beforeTable"/>
<div class="table-responsive">
<vt-table ref="vt_table"/>
</div>
<vnodes :vnodes="props.slots.afterTable"/>
<vt-pagination/>
</v-container>
</template>
<script>
import VtColumnsDropdown from 'vue-tables-2/compiled/components/VtColumnsDropdown'
import VtDropdownPagination from 'vue-tables-2/compiled/components/VtDropdownPagination'
import VtGenericFilter from 'vue-tables-2/compiled/components/VtGenericFilter'
import VtPerPageSelector from 'vue-tables-2/compiled/components/VtPerPageSelector';
import VtPagination from 'vue-tables-2/compiled/components/VtPagination'
import VtTable from 'vue-tables-2/compiled/components/VtTable';
export default {
name: "VtDataTable",
props: ['props'],
components: {
VtGenericFilter,
VtPerPageSelector,
VtColumnsDropdown,
VtDropdownPagination,
VtTable,
VtPagination,
vnodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes
}
}
}
</script>
VtTable:
<template>
<v-simple-table v-bind="props.tableAttrs">
<caption v-if="props.caption">{{props.caption}}</caption>
<vt-table-head/>
<vnodes :vnodes="props.slots.beforeBody"/>
<vt-table-body ref="vt_table_body"/>
<vnodes :vnodes="props.slots.afterBody"/>
</v-simple-table>
</template>
<script>
import VtTableHead from 'vue-tables-2/compiled/components/VtTableHead'
import VtTableBody from 'vue-tables-2/compiled/components/VtTableBody'
export default {
name: "VtTable",
props: ['props'],
components: {
VtTableHead,
VtTableBody,
vnodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes
}
}
}
</script>
VtPagination:
<template>
<v-pagination
v-if="props.totalPages>1"
:length="props.totalPages"
v-model="props.page"
total-visible="10"
color="primary"
@input="page => props.setPage(page)"/>
</template>
<script>
export default {
name: "VtPagination",
props: ['props']
}
</script>
VtPerPageSelector:
<template>
<div class="VueTables__limit-field">
<v-select :id="props.selectAttrs.id"
:value="props.selectAttrs.value"
label="Per Page"
@change="props.selectEvents.change"
:items="props.perPageValues"
/>
</div>
</template>
<script>
export default {
name: "VtPerPageSelector",
props: ['props']
}
</script>
VtGenericFilter (remove label):
<template>
<div class="VueTables__search-field">
<input class="VueTables__search__input"
type="text"
placeholder="Search..."
@keyup="e=>props.search(props.debounce)(e)"
:id="`VueTables__search_${props.id}`"
autocomplete="off"
/>
</div>
</template>
<script>
export default {
name: "VtGenericFilter",
props: ['props']
}
</script>
VtSortControl:
<template>
<span class="VueTables__sort">
<v-icon v-if="props.sortable">
{{icon}}
</v-icon>
</span>
</template>
<script>
export default {
name: "VtSortControl",
props: ['props'],
computed:{
icon() {
// if not sorted return base icon
if (!this.props.sortStatus.sorted) return 'mdi-sort';
// return sort direction icon
return this.props.sortStatus.asc ? 'mdi-sort-ascending' : 'mdi-sort-descending';
}
}
}
</script>
Register the components:
Vue.use(ClientTable, {}, false, 'bootstrap4', {
table: VtTable,
genericFilter: VtGenericFilter,
dataTable: VtDataTable,
sortControl: VtSortControl,
pagination: VtPagination,
perPageSelector: VtPerPageSelector
})
Filter on button click
By default, the generic filter is triggered on key up. Here is how to trigger it on click instead:
VtGenericFilter.vue:
<template>
<div class="VueTables__search-field">
<label :for="`VueTables__search_${props.id}`" :class="props.theme.label">
{{props.display("filter")}}
</label>
<input :class="`VueTables__search__input ${props.theme.input} ${props.theme.small}`"
type="text"
v-model="query"
:placeholder="props.display('filterPlaceholder')"
:id="`VueTables__search_${props.id}`"
autocomplete="off"
/>
<button class="btn btn-primary" @click="e=>props.search(0)({
target:{
value:this.query
}
})">Search
</button>
</div>
</template>
<script>
export default {
name: "VtGenericFilter",
props: ['props'],
data() {
return {
query: ''
}
}
}
</script>
Register the component:
Vue.use(ClientTable, {}, false, 'bootstrap4', {
genericFilter: VtGenericFilter
})
Last updated
Was this helpful?