vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.97k stars 33.68k forks source link

Compile dynamic template and bind vue event listeners #3863

Closed envomer closed 8 years ago

envomer commented 8 years ago

I have a datatable component and i am using it on many different components/pages with different data. i just pass an object with the columns and it renders it. and some datatables have action buttons (edit, delete), some don't. and each of the action button should trigger a function on the component it was called on.

Here's the code. https://jsfiddle.net/hwk7bLhf/4/

var datatableComponent = {
  template: '#datatable-template',

  props: {
    columns: { default: function(){ return [] }, type: Array },
    url: {default: null, type: String },
  },

  data: function() {
    return {
        data: {}
    }
    },

  mounted: function() {
    this.getData();
  },

  methods: {
    getData: function() {
      // fetch data from api using defined url
      this.data = {
        items: [
          { id: 1, title: 'Test 1' },
          { id: 2, title: 'Test 2' },
          { id: 3, title: 'Test 3' },
        ]
      }
    }
  }
}

Vue.component('datatable', datatableComponent);

new Vue({
    el: '#app',
  data: function() {
    return {
      columns: [
        { key: 'id', title: 'ID' },
        { key: 'title', title: 'Title' },
        { key: 'id', title: 'Options', html: function(item) {
            return '<button @click="editItem('+item.id+')">Edit</button>';
        }}
      ]
    }
  },

  methods: {
    editItem: function(item) {
        alert('editing item')
    }
  }
})
<div id="app">
  <datatable :columns="columns"></datatable>
</div>

<script type="text/x-template" id="datatable-template">
    <div>
  Datatable title
        <table>
            <thead>
                <tr>
                    <th v-for="column in columns" v-text="column.title"></th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="item in data.items">
                    <td v-for="column in columns">
            <div v-if="typeof column.html != 'undefined'" v-html="column.html(item)"></div>
            <div v-else v-text="item[column.key]"></div>
          </td>
                </tr>
            </tbody>
        </table>
    </div>
</script>

It would be cool to be if i was able to somehow compile the 'html' content given in the columns object and use the vue provided elements and directives/plugins/mixins, etc....

I tried turning the column definition into components but i am not sure if it's a good idea to define global components for every column that has a html field with vue logic in it.

In vue 1 I used partials that handled this problem very well, but now with vue2 it's gotten a little harder :P

Do any of you guys have idea or can think of a way to solve this problem?

There are also other people wondering if it's possible: http://forum.vuejs.org/t/on-vue2-0-i-should-be-how-to-dynamic-compile-the-template-and-binding-template-events/117 http://forum.vuejs.org/t/vue-js-1-x-this-compile-equivalen-vue-js-2-x-please/515 http://forum.vuejs.org/t/vue-2-0-dynamic-event-handling/916/2

yyx990803 commented 8 years ago

This should be addressed in 2.1 via "template slots" - basically, a slot element that serves as a template that will be given data from the child component. Similar to passing a render function down instead of passing down already-rendered elements.

I'll open an issue discussing this feature in details when we start working on it.

kenberkeley commented 8 years ago

@envomer

// inside v-for="column in columns"
<partial v-if="column.html" :name="column.html"></partial>
<span v-else>...</span>

new Vue({
  ...
  data () {
    return {
      columns: [
       ...
       { key: 'opt', title: 'Options', html: 'my-html' }
      ]
    }
  },
  partials: {
    'my-html': '<button @click="editItem(XXX)">Edit</button>'
  },
  methods: {
    editItem(xxx) { ... }
  }
})
envomer commented 8 years ago

@kenberkeley partials have been deprecated in vue2, haven't they?

https://vuejs.org/v2/guide/migration.html#Vue-partial-removed

kenberkeley commented 8 years ago

@envomer so just use slot or dynamic component instead, for vue 1

chrislandeza commented 7 years ago

Any updates to this issue? @envomer - Have you solved this problem? I'm also trying to implement Datatables on vue 2.

In Vue 1.x all I had to do is recompile the Datatable DOM inside the Datatable's drawCallback() function to make my action buttons work:

...
$('#datatable').dataTable({
   ...
    drawCallback: function (settings) {
        var $element = $('#app-datatable');
        vm.$compile($element.get(0));
    },
 ...
});

But it does not work any more because the "$compile" has been removed.

kenberkeley commented 7 years ago

@chrislandeza try dynamic component instead, demo' s here

Ucdit commented 7 years ago

what is the solution?

Micaso commented 7 years ago

@Ucdit the answer is called 'scoped slots' see: https://vuejs.org/v2/guide/components.html#Scoped-Slots

e.g Child component:

<div class="child">
  <slot text="hello from child"></slot>
</div>

Parent component:

<div class="parent">
  <child>
    <template scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
    </template>
  </child>
</div>

Result:

<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>
joseocabarcas commented 7 years ago

@chrislandeza Have you solved this problem?