Dynamically compiling and mounting elements with VueJS

断了今生、忘了曾经 提交于 2020-03-02 06:27:07

问题


The Issue

I've created a light-weight wrapper around jQuery DataTables for VueJS like so:

<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'm utilizing the data table like so:

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

Note the "render" method for the Actions column. This function runs just fine and renders the button as expected, however the @click handler is not functional.

Looking around I've found two links which were not helpful:

Issue 254 on the VueJS GitHub repo provides a solution for VueJS 1.0 (using this.$compile) however this was removed in VueJS 2.0

A blog post by Will Vincent discusses how to make the DataTable re-render when local data changes dynamically, but doesn't provide a solution for attaching handlers to the rendered elements

Minimum Viable Solution

If the rendered element can't be compiled and mounted, that would alright so long as I could run methods of the DataTable component on-click. Perhaps something like:

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

Is there any such way to call methods from outside of the Vue context?


回答1:


This meets your minimum viable solution.

In your columns definition:

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

And in your 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're already using jQuery so it doesn't feel that terrible.

I played some games trying to get a component to mount in the render function of the data table with some success, but I'm not familiar enough with the DataTable API to make it work completely. The biggest issue was the DataTable API expects the render function to return a string, which is... limiting. The API also very irritatingly doesn't give you a reference to the cell you are currently in, which seems obvious. Otherwise you could do something like

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

Also, the render function is called with multiple modes. I was able to render a component into the cell, but had to play a lot of games with the mode, etc. This is an attempt, but it has several issues. I'm linking it to give you an idea what I was trying. Maybe you will have more success.

Finally, you can mount a component onto a placeholder rendered by DataTable. 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 could 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 a Vue on top of each placeholder, and re-render it when it is drawn. Here is an example of that.



来源:https://stackoverflow.com/questions/43635913/dynamically-compiling-and-mounting-elements-with-vuejs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!