Search code examples
arraysvue.jsevent-handlingonchangev-for

Vue v-for: Dynamicly fill HTML with event handler (@change) from an array into a component template


This is my current setup:

HTML

<question
   v-for="question in questions"
   v-bind:id="question.id"
   v-bind:title="question.title"
   v-bind:statement="question.statement"
   v-bind:interaction="question.interaction"
   @onchange-vg="onChangeVg"/>
</question>

Definition of <question>

var questionComponent = Vue.component('question', {
   props: ['id', 'title', 'statement', 'interaction', 'currentQuestion'],
   template: `
      <div :id="'question-' + id">
         <div class="flex my-3">
            <div class="py-0"><span>{{ id }}</span></div>
            <div>
               <p>{{ title }}</p>
               <div :class="{'hidden':(id !== this.$parent.currentQuestion), 'block':(id === this.$parent.currentQuestion)}">
                  <p>{{ statement }}</p>
                  <span v-html="interaction"></span>
               </div>
            </div>
         </div>
      </div>
     `
});

Vue

var app = new Vue({
   el: '#app',
   data()  {
      return {
         vg: null,
         currentQuestion: 1,

         questions: [
            { 
               id: 1, 
               title: 'Question 1',
               statement: 'Details for question 1',
               interaction: `
                  <select ref="vb-art" @change="$emit('onchange-vg')">
                     <option disabled value="" selected>Make a choice</option>
                     <option value="red">Red</option>
                     <option value="blue">Blue</option>
                  </select>
               `
            },
            {
               title: 'Question 2',
               statement: 'Details for question 2',
               interaction: `
                  <select ref="vb-art" @change="$emit('onchange-vg')">
                     <option disabled value="" selected>Make a choice</option>
                     <option value="black">Black</option>
                     <option value="white">White</option>
                  </select>
               `
            },
         ], 

      }
   },

   methods: {
      onChangeVerguetungsart() {
         console.log("Value: " + event.target.value);
         currentQuestion++;
      },
   },
});

Everything is rendered fine. My problem is, that the event handler @change in <select ref="vb-art" @change="$emit('onchange-vg')"> does not fire.

Whe I replace <span v-html="interaction"></span> whit the whole <select ref="vb-art" @change="$emit('onchange-vg')">...</select> code from interaction every thing works fine.

The interactions may be different from question to question. It could be an , a or just a simple link. That's why i try to put the code into the array and not to the component definition.

How can I solve this issue, so that <span v-html="interaction"></span> accepts the event handler from a code snippet that is delivered by an array value?


Solution

  • You should avoid v-html. v-html bind just plain HTML, not Vue directives.

    For you case v-html is not necessary. Make array of options from interaction property and generate your markup by vue template.

    interaction: [
      {value: 'red', label: 'Red'},
      {value: 'blue', label: 'Blue'}
    ]
    

    and in your component

    <select ref="vb-art" @change="$emit('onchange-vg')">
      <option disabled value="" selected>Make a choice</option>
      <option v-for="{value, label} in interaction" :key="value" :value="value">{{ label }}</option>
    </select>
    

    update: IF you have dynamic content, then slot is your friend. (Refer docs https://v2.vuejs.org/v2/guide/components-slots.html for more examples)

    Your component:

    <div>
       <p>{{ title }}</p>
       <div>
          <p>{{ statement }}</p>
          <slot />
       </div>
    </div>
    

    And usage

    <question
       v-for="question in questions"
       v-bind:id="question.id"
       v-bind:title="question.title"
       v-bind:statement="question.statement"
       v-bind:interaction="question.interaction"
     >
      <select ref="vb-art" @change="onChangeVg">
         <option disabled value="" selected>Make a choice</option>
         <option value="red">Red</option>
         <option value="blue">Blue</option>
       </select>
    </question>
    

    Bonus is, that you don't need to emit anything.

    Btw using this.$parent is also not qood idea. Your component is coupled with parent. Use props instead.