willvincent
1761
21
Vue

Using DataTables with Vue

Posted 2 years ago by willvincent

I spent far too many hours fighting with DataTables the past couple of days, so I thought I would share how I worked out a reliable way to work with it alongside/within Vue.

Firstly, you obviously need to add all of the appropriate DataTables JS and CSS to the page, I'm going to assume you can manage that bit. (See the note about browserify issues at the bottom of this post)

Now, lets take a look at a pretty simple Vue component that will work with DataTables and allow you to dynamically update the data. For my purposes I wanted to have a fixed header and sidebar, which is really what drove me to datatables in the first place. The number of columns and rows being displayed in these tables on the site I'm working on is so vast that having fixed header and columns is essential to be able to navigate it.

<template>
  <table id="table-id" class="whatever-classes-you-need not-much-to-this-template"></table>
</template>

<script>
  export default {
    props: ['tableData'],
    data() {
      return {
        headers: [
          { title: 'Define' },
          { title: 'Your', class: 'some-special-class' },
          { title: 'Column Names' },
          { title: 'Here' }
        ],
        rows: [] ,
        dtHandle: null
      }
    },
    watch: {
      tableData(val, oldVal) {
        let vm = this;
        let rows = [];
        // You should _probably_ check that this is changed data... but we'll skip that for this example.
        val.forEach(function (item) {
          // Fish out the specific column data for each item in your data set and push it to the appropriate place.
          // Basically we're just building a multi-dimensional array here. If the data is _already_ in the right format you could
          // skip this loop...
          let row = [];

          row.push(item.some_thing);
          row.push(item.some_other_thing);
          row.push(item.another_thing);
          row.push(item.final_thing);

          vm.rows.push(row);
        });

        // Here's the magic to keeping the DataTable in sync.
        // It must be cleared, new rows added, then redrawn!
        vm.dtHandle.clear();
        vm.dtHandle.rows.add(vm.rows);
        vm.dtHandle.draw();
      }
    },
    ready() {
      // Instantiate the datatable and store the reference to the instance in our dtHandle element.
      this.dtHandle = $(this.$el).DataTable({
        // Specify whatever options you want, at a minimum these:
        columns: this.headers,
        data: this.rows
      });
    }
  }
</script>

So, DataTables is a little bit tricky to deal with, but if you want to be able to dynamically change it's data with Vue (without relying on DataTables' ajax functionality) you need to pass data to it via javascript, not try to latch onto a rendered table in the dom. The DataTables object can only be configured once, further updates have to reference the handle we define.. It takes a little doing, but in the end you can have a very dynamic table whose contents are controlled by one or several outside filters.

In my case I have 6 separate filters that could be applied at any given time to my dataset (one of which is a text search), and the data updates instantly.. the only requirement is that you pass in the filtered data to the component.

I do that by passing a computed value to it, and that computed value applies all of the applicable filters thusly:

computed: {
  filteredProjects() {
      let projects = this.projects;
      let program_id = this.currentProgram;
      let order = this['sortBy'];

      projects = this.$options.filters.orderBy(
        this.$options.filters.filterBy(
          this.$options.filters.projectFilters(
            this.$options.filters.filterBy(projects, program_id, 'program_id'
            ), this.filters
          ), this.filters.searchQuery
        ), order);

      return projects;
  }
}

Obviously the orderby isn't necessary since datatables is perfectly capable of handling sort order, but I have a different non-table view of the same data on the same page, so that I can toggle between a block and table view of the data. In the case of the block data it's ordered by a value selected in a dropdown, and optionally paginated, so I just v-for="project in filteredProjects | limitBy limit offset" for that. No sense in running the data through all the same filters in several places, and it looks a bit cleaner in the markup, to my eye, by not passing it through several filters..

You could copy/paste the above to a my-table.vue to use with vueify and it should work without any significant issues.

Of note: the datatables libraries do not seem to play very nicely with browserify, so you might want to just go ahead and skip that to avoid some headache... if you do want to try it that way, you'll need to do something like this:

window.DataTables = require('datatables.net')();

if you skip the () at the end of that line it won't work at all. I opted to manually concatenate a bunch of related libs outside of browserify because I could not for the life of me get the datatables buttons extension to work via browserify at all...

My datatables config (in the ready function) looks like this:

this.dtHandle = $(this.$el).DataTable({
        columns: this.headers,
        data: this.rows,
        searching: false,
        paging: false,
        fixedHeader: true,
        fixedColumns: true,
        scrollY: scrollY + 'px',
        scrollX: true,
        info: false,
        buttons: [
          {
            extend: 'colvis',
            collectionLayout: 'fixed two-column',
            text: 'Show/Hide Data'
          },
          {
            extend: 'csv',
            text: 'Export CSV'
          },
          {
            extend: 'excel',
            text: 'Export Excel'
          }
        ]
      });

      this.dtHandle.buttons().container().appendTo('#table-id_wrapper');

This fixes the header, left column, and includes buttons to toggle column visibilty and export to csv or excel... and then appends the buttons to the table wrapper (so they appear after the table), you could also prepend to put the buttons above the table. As for the scrollY: scrollY + 'px', line.. I calculate the height of the table, based on where the top of it lands on the page so that it fills the screen, or is at least 300px tall:

let scrollY = (Math.floor(window.innerHeight - this.$el.getBoundingClientRect().top) - 140);
if (scrollY < 300) {
  scrollY = 300;
}

That immediately preceeds the this.dtHandle line in my ready() function.

Anyway, hope this helps someone avoid the pain in the rear I dealt with getting this working!

Please sign in or create an account to participate in this conversation.

Reply to

Use Markdown with GitHub-flavored code blocks.