Step-by-step guide on creating a plugin that displays a modal popup with Vue. The function call should be executed as a function()

I've been working on creating a VueJS plugin that will allow me to call a global method, triggering a popup message with an input text field. My goal is to be able to use the following code snippet in any Vue component:

this.$customPopup("Title","Body","Value");

Upon calling this method, a popup should appear on the screen.

I attempted to build it, but encountered an issue where this.$refs was not recognized when called within the install() function:

DeleteConfirmation.vue

<template>
    <b-modal size="lg" ref="deleteConfirmationModal" :title="this.title" header-bg-variant="danger" @ok="confirmDelete" @cancel="confirmCancel">
        <p>
            {{this.body}}
        </p>
    </b-modal>
</template>
<script>
    export default {
        data()
        {
            return {
                title: null,
                body: null,
                valueCheck: null,
                value: null
            };
        },
        install(vue, options)
        {
            Vue.prototype.$deleteConfirmation = function(title, body, expectedValue)
            {
                this.title = title;
                this.body = body;
                this.valueCheck = expectedValue;
                this.$refs.$deleteConfirmation.show()
            }
        },
    }
</script>

app.js

import DeleteConfirmation from './components/global/DeleteConfirmation/DeleteConfirmation';
Vue.use(DeleteConfirmation);

The call I am attempting to make is:

$vm0.$deleteConfirmation("title","body","val");

During runtime, I encounter the following error:

app.js?id=c27b2799e01554aae7e1:33 Uncaught TypeError: Cannot read property 'show' of undefined
    at Vue.$deleteConfirmation (app.js?id=c27b2799e01554aae7e1:33)
    at <anonymous>:1:6
Vue.$deleteConfirmation @ app.js?id=c27b2799e01554aae7e1:33
(anonymous) @ VM1481:1

It seems that this.$refs in DeleteConfirmation.vue is undefined.

Answer №1

Consider avoiding the use of $ref in Vue (it is intended for third-party and special cases).
$ref is not reactive and is populated after rendering...

My preferred solution is to utilize an event bus like so:

const EventBus = new Vue({
      name: 'EventBus',
});
Vue.set(Vue.prototype, '$bus', EventBus);

Then, use the event bus to call functions on your modal...

(

this.$bus.on('event-name', callback)
/ this.$bus.off('event-name');
this.$bus.$emit('event-name', payload);
)


You can create a wrapper around the bootstrap modal similar to mine (except I use sweet-modal)

<template>
  <div>
    <sweet-modal
      :ref="modalUid"
      :title="title"
      :width="width"
      :class="klass"
      class="modal-form"
      @open="onModalOpen"
      @close="onModalClose"
    >
      <slot />
    </sweet-modal>
  </div>
</template>

<script>
  export default {
    name: 'TModal',
    props: {
      eventId: {
        type: String,
        default: null,
      },
      title: {
        type: String,
        default: null,
      },
      width: {
        type: String,
        default: null,
      },
      klass: {
        type: String,
        default: '',
      },
    },
    computed: {
      modalUid() {
        return `${this._uid}_modal`; // eslint-disable-line no-underscore-dangle
      },
      modalRef() {
        return this.$refs[this.modalUid];
      },
    },
    mounted() {
      if (this.eventId !== null) {
        this.$bus.$on([this.eventName('open'), this.eventName('close')], this.catchModalArguments);
        this.$bus.$on(this.eventName('open'), this.modalRef ? this.modalRef.open : this._.noop);
        this.$bus.$on(this.eventName('close'), this.modalRef ? this.modalRef.close : this._.noop);
      }
    },
    beforeDestroy() {
      if (this.eventId !== null) {
        this.$off([this.eventName('open'), this.eventName('close')]);
      }
    },
    methods: {
      onModalOpen() {
        this.$bus.$emit(this.eventName('opened'), ...this.modalRef.args);
      },
      onModalClose() {
        if (this.modalRef.is_open) {
          this.$bus.$emit(this.eventName('closed'), ...this.modalRef.args);
        }
      },
      eventName(action) {
        return `t-event.t-modal.${this.eventId}.${action}`;
      },
      catchModalArguments(...args) {
        if (this.modalRef) {
          this.modalRef.args = args || [];
        }
      },
    },
  };
</script>

<style lang="scss" scoped>
  /deep/ .sweet-modal {
    .sweet-title > h2 {
      line-height: 64px !important;
      margin: 0 !important;
    }
  }
</style>

Answer №2

CustomModal.vue

<template>
  <div class="custom-modal-wrapper" v-if="isVisible">
    <h2>{{ modalTitle }}</h2>
    <p>{{ modalText }}</p>
    <div class="custom-modal-buttons">
      <button class="modal-button" @click="hideModal"&ellt;Close</button>
      <button class="modal-button" @click="confirmAction"&egt;Confirm</button>
    </div>
  </div>
</template>


<script>
export default {
  data() {
    return {
      isVisible: false, 
      modalTitle: '',
      modalText: ''
    }
  },
  methods: {
    hideModal() {
      this.isVisible = false;
    },
  }
}
</script>

ModalPlugin.js (plugin)

import CustomModal from 'CustomModal.vue'

const ModalPlugin = {
  install(Vue, options) {
    this.EventBus = new Vue()
    Vue.component('custom-modal', CustomModal)
    Vue.prototype.$customModal = {
      showModal(modalParams) {
        ModalPlugin.EventBus.$emit('showModal', modalParams)
      }
    }
  }
}

export default ModalPlugin

app.js

import ModalPlugin from 'ModalPlugin.js'
// ...
Vue.use(ModalPlugin)

MainApp.vue

<template>
  <div id="main-app">
    // ... 
    <custom-modal/>
  </div>
</template>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Operating PhantomJS in server mode

I am considering using PhantomJS to convert a dynamic AngularJS application into static HTML that can be searched by Google. My plan is to set up a PhantomJS server behind a proxy to handle the ?escaped_fragment requests. While I know that PhantomJS is pri ...

The power of VueRouter lies in its ability to leverage metadata

According to the VueRouter documentation, you can include meta fields and globally restrict routes based on their specified values. However, upon trying to implement this feature based on the guidelines provided, an error occurred: ReferenceError: record ...

Difficulty arising from the final column of average sums - Information extracted from JSON using Angular JS

I am facing a problem with calculating the average total. I need to determine the average sum using a specific formula, but the code provided does not calculate the last column of Average. I am looking for a desired result and would appreciate any assistan ...

"Dropping a file into the Dropzone module will always result in the return of 'empty($_FILES)' being true

I'm currently working on a project that requires drag and drop functionality on my web page. My ultimate goal is to retrieve dropped images and upload them to my database. HTML <form action="parser.php" id="file-up" class="dropzone"> <in ...

When using Typescript in conjunction with Redux Toolkit, you may encounter an issue where the argument specified as type 'T' cannot be assigned to a parameter of type 'WritableDraft<T>'

I am currently learning Typescript and experimenting with Redux-Toolkit in my React project. My goal is to develop a To Do application with a nested state structure where each ToDo item includes an array of Comment. Below are the interfaces I have defined: ...

The D3 path is generating an unexpected output of 0px by 0px, causing the map not to display properly. I am currently unsure how to adjust the projection to

I am currently working on creating a world map using D3. I obtained the JSON file from the following link: https://raw.githubusercontent.com/johan/world.geo.json/master/countries.geo.json Below is the code snippet I'm using: // Defining SVG dimensio ...

Guide on incorporating vanilla JavaScript into a personalized Vue component

I'm currently working on incorporating a basic date-picker into a custom Vue component. Since I am not utilizing webpack, I want to avoid using pre-made .vue components and instead focus on understanding how to incorporate simple JavaScript into Vue. ...

Angular Component Caching

I am currently working with two Angular components. The first component displays a list of picture items that are fetched from a server. When the user selects a picture, they are taken to the second page where additional information is shown in a detailed ...

Steps for resending an Ajax request once the previous one has been completed

Currently, I am experimenting with a basic Ajax request to a webpage by triggering it through an onclick event on a button: // 1. Create an XMLHttpRequest object var myRequest = new XMLHttpRequest(); // 2. Use the open method to request a resource from th ...

Tips for displaying just one dropdown when selecting an option in a jQuery menu

My menu is almost perfect, but I am facing one issue. When I click on .dropdown-toggle, only the closest ul should show, but in my code, all of them are shown and hidden simultaneously. What I want is that when I click on .dropdown-toggle, only the next ul ...

What is the significance of having nodejs installed in order to run typescript?

What is the reason behind needing Node.js installed before installing TypeScript if we transpile typescript into JavaScript using tsc and run our code in the browser, not locally? ...

extracting a JSON array from an API

I'm trying to extract the title from my API using JavaScript and then display it in HTML. api:(https://k0wa1un85b.execute-api.us-east-1.amazonaws.com/production/books) The JSON response is as follows: {"statusCode":200,"body":[{"id":"4f160b1f8693cd ...

Designing an accordion with the main content peeking out slightly before collapsing into place

Seeking assistance in creating an accordion that displays a snippet of content before collapsing. I'm facing difficulty starting this project. While a basic accordion is simple to implement, I am unsure how to show a portion of the content (such as t ...

Creating a button within an HTML gridview to initiate printing

Here is the code snippet I am currently using: <asp:GridView ID="GridViewProducts" runat="server" AutoGenerateColumns="false" OnSelectedIndexChanged="GridViewProducts_SelectedIndexChanged" OnRowDataBound="GridViewProducts_Bound" CssClass="gridviewprodu ...

Adjusting the image placement within a modal window (using bootstrap 3)

Behold, an example modal: <!-- Large Modal --> <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel"> <div class="modal-dialog modal-lg"> <div class="modal-content"> ...

Address the scenario where only one of the two AJAX requests was able to successfully complete

I have a scenario where I am making two ajax requests using jQuery deferred objects. I have successfully handled the case when both requests are executed successfully. Now, I need to handle the situation when one request is successful and the other fails. ...

Webpack is ejected from watch mode

Currently, I am in the process of learning React and have decided to use webpack alongside it. However, I seem to be facing an issue with webpack when it comes to the watch mode. It builds the files once but then halts. The console output reflects the foll ...

Working with multiple checkboxes in Vue.js - A detailed guide on filtering data

My code for combining filter, sort, and search is functioning well. <template> <h5>List of Products</h5> <h3>Filter</h3> <button v-on:click="resetOptions">Reset</button> <bu ...

Is it possible to generate a dynamic multiplication function by utilizing nested attributes together with the selected option in a combobox

I have a unique question that may seem strange or confusing, but I have not been able to find an example similar to my situation. I created a nested form where each new row has a long name structure like this: <input id="shopping_document_shopping_prod ...

Exposing external variables within the setInterval function

Here is the snippet of code I am working with: update: function(e,d) { var element = $(this); var data = element.data(); var actor = element.find('.actor'); var value = basicdesign.defaultUpdate( e, d, element, true ); var on = templateEn ...