vue-tables-2
  • Dependencies & Compatability
  • Getting Started
    • Vue Version 3
  • Client Table
    • Asynchronous Loading
    • Grouping
    • Filtering Algorithm
    • Editable Cells
  • Server Table
    • Implementations
    • Custom Request Function
    • Setting Multiple Request Parameters
    • Error Message
    • Draw Counter
  • Virtual Pagination
  • Custom Template
  • Column Templates
  • Nested Data Structures
  • Selectable Rows
  • Date Columns
  • List Filters
  • Custom Filters
  • Custom Sorting
  • Multiple Sorting
  • Child Rows
  • Conditional Cell Styling
  • Columns Visibility
  • Methods
  • Properties
  • Events
  • Slots
  • Options API
Powered by GitBook
On this page
  • Usage
  • Examples
  • Custom Pagination
  • Font Awesome Sort Icon
  • Footer Pagination
  • Footer Headings
  • Numeric Field Pagination
  • Select2 List Filter
  • Vuetify
  • Filter on button click

Was this helpful?

Custom Template

Build your own UI

PreviousVirtual PaginationNextColumn Templates

Last updated 4 years ago

Was this helpful?

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 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:

  • : dataTable

    • : genericFilter

    • : perPageSelector

    • : dropdownPagination

    • : columnsDropdown

    • : table

      • : tableHead

        • : headingsRow

          • : tableHeading

            • : sortControl

          • : filtersRow

            • : textFilter

            • : listFilter

            • : dateFilter

      • : tableBody

        • : noResultsRow

        • : tableRow

          • : tableCell

          • : childRowToggler

        • : childRow

        • : groupRow

    • : pagination

All components provide an opts property, referencing the full options object (i.e the merged default, global and local options)

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

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!

In this example we used the totalPages prop, which wasn't used in the original component. If you encounter a use-case for other props which are not exposed through the props object, let me know and I'll consider adding it to the provided properties

Font Awesome Sort Icon

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>

As in the example above, note we are using the sortStatus property. which wasn't used in the original template

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

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

<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
})

Let's swap the default pagination, which uses , with some other 3rd party implementaion, say .

Let's use

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 () with a numeric field.

Replace with the following:

templates
VtDataTable
VtGenericFilter
VtPerPageSelector
VtDropdownPagination
VtColumnsDropdown
VtTable
VtTableHead
VtHeadingsRow
VtTableHeading
VtSortControl
VtFiltersRow
VtTextFilter
VtListFilter
VtDateFilter
VtTableBody
VtNoResultsRow
VtTableRow
VtTableCell
VtChildRowToggler
VtChildRow
VtGroupRow
VtPagination
vue-pagination-2
vue-plain-pagination
vue-fontawesome
VtDropdownPagination
VtListFilter