Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple modal components with Vue

I am having trouble implementing dynamic modal component in Vue.

A common approach I follow to display a set of data fetched from the db is I dump each of the rows in an HTML table element by iterating over each of the row of the db result. Something like this: enter image description here

As you can see in the screenshot, each of the rows has one or more buttons which are dynamically generated by the loop. In order to bind a modal component to each of the buttons (say the Remove buttons in this scenario) I do something like this.
HTML:

<div id="app">
    <?php foreach($result as $x): ?>
        <modal v-if="showModal">I am Modal $x</modal>
        <btn @trigger="onShowModal">Button $x</btn>
    <?php endforeach; ?>
</div>

So if I have three rows in my result, the aforementioned block of code will take the shape of something like this:

<div id="app">
        <modal v-if="showModal">I am Modal 1</modal>
        <btn @trigger="onShowModal">Button 1</btn>

        <modal v-if="showModal">I am Modal 2</modal>
        <btn @trigger="onShowModal">Button 2</btn>

        <modal v-if="showModal">I am Modal 3</modal>
        <btn @trigger="onShowModal">Button 3</btn>
</div>

And here is what I do in the JavaScript end:

JavaScript:

    Vue.component('btn',{
      template: `<button @click="$emit('trigger')"><slot></slot></button>`,
    });

    Vue.component('modal',{
      template: `<p><slot></slot></p>`,
    });

    new Vue({
      el: '#app',
      data: {
        showModal: false
      },
      methods: {
        onShowModal(){
          this.showModal = true;
        }
      }
    });

The problem with this approach is, when I click any one of the Remove buttons, I get a 'stack' of modals instead of the modal I wanted to view. And that's because I am setting showModal to true and if you see the populated HTML block you will see that each of the modals is set to v-if="showModal".

Click here to see a demo

And as I am beginning to understand frontend-backend relationship, I am learning that this pattern is appearing more frequently in an application. How do I fix this issue (with a very vue-beginner-friendly level)?

like image 328
Tanmay Avatar asked Oct 17 '17 13:10

Tanmay


2 Answers

The root issue is that you are referencing the same data property showModal on all of the modal components.

You could make another component to encapsulate the button and modal component pair. This way there is a separate showModal data property for each modal and btn pair:

Vue.component('btn',{
  template: `<button @click="$emit('trigger')"><slot></slot></button>`,
});

Vue.component('modal',{
  template: `<p><slot></slot></p>`,
});

Vue.component('modal-btn', {
  template: `
    <div>
      <modal v-if="showModal">
        <slot>I am a modal</slot>
      </modal>
      <btn @trigger="showModal = true">Show Modal</btn>
    </div>
  `,
  data() {
    return { showModal: false }
  }
});

new Vue({
  el: '#app',
  data: {
    showModal: false
  },
  methods: {
    onShowModal(){
      this.showModal = true;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
  <modal-btn></modal-btn>
  <modal-btn>I am another modal</modal-btn>
  <modal-btn>I'm a third modal</modal-btn>
</div>
like image 163
thanksd Avatar answered Sep 25 '22 14:09

thanksd


You might need a more data centric approach to what you are trying. Something like,

<!DOCTYPE html>
<html>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</head>
<body>
  <div id="app">
    <template v-for="item in items">
      <modal v-if="item.show">I am a dummy Modal for {{item.name}}</modal>
      <btn @trigger="item.show = !item.show">Button for {{item.id}}</btn>
    </template>
  </div>

  <script>
    Vue.component('btn',{
      template: `<button @click="$emit('trigger')"><slot></slot></button>`,
    });

    Vue.component('modal',{
      template: `<p><slot></slot></p>`,
    });

    new Vue({
      el: '#app',
      data: {
        items: [
          {id: 1, name: 'item 1', show: false},
          {id: 2, name: 'item 2', show: false},
          {id: 3, name: 'item 3', show: false}
        ],
        showModal: false
      },
      methods: {
        onShowModal(){
          this.showModal = true;
        }
      }
    });
  </script>
</body>
</html>

You could then also get away with a single modal component that receives the applicable data it should display via a prop

like image 24
Leon Vismer Avatar answered Sep 25 '22 14:09

Leon Vismer