Dynamic, reusable modal in vue

2019-08-17 03:19发布

问题:

I have a parent application with two child components.

  • Child component A (manager-tab) toggles a data property in the parent app by emitting an event.
  • The data property is bound to a prop in child component B (manager-modal).
  • When that data property is toggled, manager-modal opens.
  • Clicking "Send" on manager-modal then is supposed to close the modal.

The problem is, the only way I've been able to have this behavior is by directly mutating the prop which is toggled by manager-tab's emitted event. This causes a console warning, and that's what I would like to have resolved. The warning is:

view.app.php

<div id="app">
  <div class="app">
    <div class="app__container">
      <div class="app__row">
        <?php foreach ($member->rms as $rm): ?>
          <manager-tab
            :name="'<?= $rm->name; ?>'"
            :position="'RM'"
            :thumbnail="'<?= $rm->headshot ?>'"
            v-on:activate-rm-modal="activateRmModal"
          ></manager-tab>
        <?php endforeach; ?>
        <?php foreach ($member->ams as $am): ?>
          <manager-tab
            :name="'<?= $am->name; ?>'"
            :position="'AM'"
            :thumbnail="'<?= $am->headshot ?>'"
            v-on:activate-am-modal="activateAmModal"
          ></manager-tab>
        <?php endforeach; ?>
      </div>
    </div>
  </div>

  <manager-modal 
    :is-active="showRmModal"
    :phone="'<?= $rm->phone ?>'"
    :full-name="'<?= $rm->fullName; ?>'"
    :email="'<?= $rm->email; ?>'"
    :position="'RM'"
    :thumbnail="'<?= $rm->headshot ?>'"
  ></manager-modal>
  <manager-modal 
    :is-active="showAmModal"
    :phone="'<?= $am->phone ?>'"
    :full-name="'<?= $am->fullName; ?>'"
    :email="'<?= $am->email; ?>'"
    :position="'AM'"
    :thumbnail="'<?= $am->headshot ?>'"
  ></manager-modal>
</div>

view.app.js:

import Vue from 'vue';
import ManagerTab from '../vue-components/app/Manager_Tab.vue';
import ManagerModal from '../vue-components/app/Manager_Modal.vue';

window.App = new Vue({
  el: '#app',
  components: {
    ManagerTab,
    ManagerModal
  },
  data: {
    showRmModal: false,
    showAmModal: false,
  },
  methods: {
    activateRmModal: function() {
      this.showRmModal = true;
      this.showAmModal = false;
    },
    activateAmModal: function() {
      this.showAmModal = true;
      this.showRmModal = false;
    },
  },
});

Manager_Tab.vue:

<template>
  <div class="manager-tab" @click="emit">
    <img :src="this.thumbnail" class="manager-tab__thumbnail" />
    <div class="manager-tab__identity">
      <h5 class="identity__position">
        {{ this.position }}
      </h5>
      <h5 class="identity__name">
        {{ this.name }}
      </h5>
    </div>
    <div class="manager-tab__icon">
      <i class="fa fa-chevron-right"></i>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'manager-tab',
    data() {
      return {
      }
    },
    props: {
      name      : '',
      phone     : '',
      fullName  : '',
      email     : '',
      position  : '',
      thumbnail : ''
    },
    methods: {
      emit: function() {
        if (this.position == "RM") {
          this.$emit('activate-rm-modal');
        } else {
          this.$emit('activate-am-modal');
        }
      }
    },
  }
</script>

Manager_Modal.vue:

<template>
    <div :class="{ 'modal-open' : isActive }" class="modal in" v-show="isActive">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal__header">
                    <img class="modal__profile-pic" :src="this.thumbnail" />
                    <div class="modal__contact-name">{{ this.name }}</div>
                    <div class="modal__contact-position">{{ this.position }}</div>

                    <div class="modal__contact-row">
                        <div class="modal__contact-phone">{{ this.phone }}</div>
                        <div class="modal__contact-separator">|</div>
                        <div class="modal__contact-email">{{ this.email }}</div>
                    </div>
                </div>
                <div class="modal-body">
                    <textarea class="modal__comment-block" placeholder="Send a message..." v-model="text"></textarea>
                </div>
                <div class="modal__footer">
                    <div class="modal__send-btn" @click="sendMessage()">Send</div>
                </div>
            </div>
        </div>
    </div>
</template>



<script>  
  import Axios from 'axios';

  export default {
    name: 'manager-modal',
    data() {
      return {
        isModalOpen: this.isActive,
        msg: '',
        personName: '',
        personPhone: '',
        personMessage: '',
      }
    },
    props: {
        phone       : '',
        fullName    : '',
        email       : '',
        position    : '',
        thumbnail   : '',
        isActive    : false
    },
    methods: {
        sendMessage: function() {
            if (this.text) {
                this.personMessage = this.text;
                this.close();
            } else {
                console.error('No message text');
            }
        },
        close: function() {
          this.isActive = !this.isActive;
        },
    },
  }
</script>


<style lang="scss" scoped>
    @import '../../../../scss/utilities/_index.scss';
    .modal-open {
        display: block;
    }
</style>

I've tried to assign the value of isActive in Manager_Modal.vue to a data property, and use that for my mutations.

That didn't work and I think the reason that did't work is because of how isActive is initially passed in from the parent app.

I've tried to do essentially the same with a computed property, but that also does not work. I did this by having a snippet like the following in Manager_Modal.vue:

computed: {
    isModalActive: function() {
        this.isActive = !this.isActive;
    }
}

My final attempted solution before coming here to SO was to try emitting another event back to the parent to toggle the respective flag (showRmModal or showAmModal) but the parent won't catch the event for some reason. In vue devtools the event fired, but it would never be caught by the parent.

I know the solution to this is right in front of me but I'm just not seeing it. Any help would be greatly appreciated.

JSFiddle: http://jsfiddle.net/eywraw8t/380816/

Here are some links I went through before writing a new question:

How to add vue js functions to jquery dynamically

Web Components Design Pattern

Vue 2 - Mutating props vue-warn

Reuse Modal with VueJS 2 <-- this is a year old question identical to mine, but it has no answer and I don't like digging up old threads, especially when there's no solution on them.

https://vuejs.org/v2/guide/components.html

https://laracasts.com/discuss/channels/vue/avoid-mutating-a-prop-directly-since-the-value-will-be-overwritten-whenever-the-parent-component-re-renders