Search code examples
javascriptvue.jsdatatablesvuejs3vue-router

Using Vue Router within DataTables


I'm trying to add links/buttons to rows in my DataTable, which take you to a vue route once clicked. Of course, trivially I could simply add a normal <a> link with href="/item/${id}". But that bypasses the router and causes the whole page to reload

I came up with two solutions, both of which rely on setting onclick to execute some code:

  1. In route1, I attach a function called vueRoute to the window object, which is just a reference to the vue method route1. This function can now be called from anywhere

  2. In route2, I have the onclick fire a CustomEvent, and I have an event listener set to have it handled by the route2 vue method

I am not comfortable with either solution. Even though they work, they seem very "hacky". What should I be doing instead?

// datatables column definitions
columns: [
  // route1
  {
      data: 'id',
      render: function (dt) {
          return `<a href="#" onclick="(function(){vueRoute('${dt}');})()">route1</a>`;
      },
  },
  // route2
  {
    data: 'id',
    render: function (dt) {
        return `<a href="#" onclick="(function(){document.body.dispatchEvent(
          new CustomEvent('clickedEdit', {detail : ${dt} }));})() ">route2</a>`;
    },
  },
],

// vue methods
methods: {
  route1(id) {
    this.router.push('/item/' + id);
  },

  route2(evt) {
    this.router.push('/item/' + evt.detail);
  },
},

// on mount, set up route1 and route2
mounted() {
  window.vueRoute = this.route1;

  document.body.addEventListener('clickedEdit', this.route2);
},

A working example can be found here: https://stackblitz.com/edit/datatables-net-vue3-simple-mgdhf1?file=src/components/ListView.vue

EDIT: What I actually ended up doing was to switch to a headless table component (TanStack), which plays better with Vue than DataTables. But the solutions posted below by @Damzaky and @Moritz Ringler work fine if you need to use DataTables


Solution

  • You can set @click handler on the DataTable object:

    <DataTable
      ...
      @click="resolveRouteFromClick($event)"
    >
    

    The event will be a regular click event, so we need a recognizable dataset property for the id:

    {
      data: 'id',
      render: function (idValue) {
        return `<a href="#" data-item-id="${idValue}">route3</a>`;
      },
    },
    

    The click handler just has to make sure that the id can be retrieved, ensuring that the click came from a link:

    resolveRouteFromClick(e){
      const itemId = e.target.dataset.itemId
      if (!itemId) {
        return
      }
      e.preventDefault()
      this.router.push('/item/' + itemId);
    }
    

    Here it is in the updated stackblitz


    Interestingly enough, just as background, the problem isn't even the connection between Vue and jQuery, but the way DataTables work, particularly that there is no event for a click on a cell or a click on a row, and that the renderer can only return HTML strings.

    That's no surprise though, just how jQuery works, where data, view and behavior is not inherently linked, but you connect them as you see fit. So there is no cell-click event we could piggy-back on, because in jQuery, you write it yourself or it is not there.

    The most efficient way to set the handler in jQuery would be to set the event on the table with a target filter:

    $('.datatable').on('click', 'a[data-item-id]', function(){...})
    

    The advantage is:

    • only one event for all rows
    • no need to add events when changing page or re-render
    • code in render function stays minimal

    And the above solution in Vue does the exact same thing, just without jQuery.

    So I think this is the most efficient way to make it work in Vue, both in amount of code (maintainability) and performance.

    Note that since DataTable requires jQuery to be globally accessible, it is also available in your Vue component, and you could put the given jQuery statement into a mounted hook, where you can access the Vue router in the callback. So instead of putting the router in global scope to use it on jQuery side, you would use an object already in there on Vue side.
    Still, as it is not necessary to mix jQuery into your Vue code, I would avoid it and go with the above Vue-only solution.