Dynamically compiling and mounting elements with VueJS

Problem

I created a lightweight wrapper around jQuery DataTables for VueJS like:

<template>
    <table ref="table" class="display table table-striped" cellspacing="0" width="100%">
        <thead>
            <tr>
                <th v-for="(column, index) in columns">
                    {{ column.name }}
                </th>
            </tr>
        </thead>
    </table>
</template>

<script>
    export default {
        props: ['columns', 'url'],
        mounted: function () {
            $(this.$refs.table).dataTable({
                ajax: this.url,
                columns: this.columns
            });
            // Add any elements created by DataTable
            this.$compile(this.$refs.table);
        }
    }
</script>

      

I am using datasheet like this:

<data-table
    :columns="
        [
            {
                name: 'County',
                data: 'location.county',
            },
            {
                name: 'Acres',
                data: 'details.lot_size',
            },
            {
                name: 'Price',
                data: 'details.price',
                className: 'text-xs-right',
            },
            {
                name: 'Actions',
                data: null,
                render: (row) => {
                    return &quot;\
                        <a @click='editProperty' class='btn btn-warning'><i class='fa fa-pencil'></i> Edit</a>\
                    &quot;;
                }
            },
        ]
    "
    url="/api/properties"
></data-table>

      

Notice the "render" method for the "Actions" column. This function works very well and renders the button as expected, however the handler @click

does not work.

Looking around I found two links that did not help:

Issue 254 in the VueJS GitHub repository provides a solution for VueJS 1.0 (using this.$compile

), however this has been removed in VueJS 2.0

Blog post Will Vincent discusses how to re-render the DataTable when local data changes dynamically, but does not provide a solution for attaching handlers to rendered items

Minimum viable solution

If the processed item cannot be compiled and mounted, it will be fine if I can run the component's methods DataTable

. Perhaps something like:

render: (row) => {
    return &quot;\
        <a onclick='Vue.$refs.MyComponent.methods.whatever();' />\
    &quot;;
}

      

Is there a way to call methods from outside the Vue context?

+3


source to share


1 answer


This is your minimum viable solution.

In the column definition:

render: function(data, type, row, meta) {
   return `<span class="edit-placeholder">Edit</span>`  
}

      

And in the DataTable component:

methods:{
  editProperty(data){
    console.log(data)
  }
},
mounted: function() {
  const table = $(this.$refs.table).dataTable({
    ajax: this.url,
    columns: this.columns
  });

  const self = this
  $('tbody', this.$refs.table).on( 'click', '.edit-placeholder', function(){
      const cell = table.api().cell( $(this).closest("td") );
      self.editProperty(cell.data())
  });
}

      

Example (uses a different API, but the same idea).

This is using jQuery, but you are already using jQuery so it doesn't feel terrible.

I've played some games trying to get the component to mount in the render

data table function with some success, but I'm not familiar enough with the DataTable API to make it fully work. The biggest problem was that the DataTable API expects the render function to return a string, which ... limits. The API is also very annoying, doesn't give you a reference to the cell you are currently in, which seems obvious. Otherwise, you can do something like



render(columnData){
   const container = document.createElement("div")
   new EditComponent({data: {columnData}).$mount(container)
   return container
}

      

In addition, the render function is called with multiple modes. I was able to render a component in a cell, but I had to play a lot of games with mode etc. This is an attempt , but he has a few questions. I link it to give you an idea of ​​what I was trying. Perhaps you will have more success.

Finally, you can wire the component to the placeholder provided by the DataTable. Let's consider this component.

const Edit = Vue.extend({
  template: `<a @click='editProperty' class='btn btn-warning'><i class='fa fa-pencil'></i> Edit</a>`,
  methods:{
    editProperty(){
      console.log(this.data.name)
      this.$emit("edit-property")
    }
  }
});

      

In your mounted method, you can do this:

mounted: function() {
  const table = $(this.$refs.table).dataTable({
    ajax: this.url,
    columns: this.columns
  });

  table.on("draw.dt", function(){
    $(".edit-placeholder").each(function(i, el){
        const data = table.api().cell( $(this).closest("td") ).data();
        new Edit({data:{data}}).$mount(el)
    })
  })

      

}

This will render Vue on top of each placeholder and re-render as it is drawn. Here's an example of this.

+3


source







All Articles