简体   繁体   中英

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: 在此输入图像描述

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

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> 

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

The issue you are having is that all modals are bound to one data attribute ( showModal ) so all modals will show when you set this to true. The solution is to isolate each button within it's own component which has it's own showModal attribute rather than emitting it to the parent:

Vue.component('btn',{
  template: `<div><button @click="showModal = true"><slot></slot></button>   
    <modal v-if="showModal">I am a modal for {{button}}</modal></div>`,
  props: ['button'],
  data(){
    return {
      showModal: false
    }
  }
});

And here's the updated JSBin

If you need different modals for each button type, then simply create two button components edit-button , delete-button etc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM