Getting access to the parent's ref from child components in Vue/Nuxt can be achieved by using

Incorporating a global confirm modal component into my default layout file has been a challenge. Attempting to access this component from my pages/index.vue has proven to be unsuccessful, as calling this.$refs returns an empty object. While placing the modal component directly in pages/index.vue technically resolves the issue, it defeats the purpose of having a global confirm modal in the first place.

layouts/default.vue

<template lang="pug">
v-app(v-if="show")
  v-main
    transition
      nuxt
  confirm(ref='confirm')
</template>
<script>
import confirm from '~/components/confirm.vue'
export default {
  components: { confirm },
  data: () => ({
    show: false
  }),
  async created() {
    const isAuth = await this.$store.dispatch("checkAuth")
    if (!isAuth) return this.$router.push("/login")
    this.show = true
  }
}
</script>

components/confirm.vue

<template>
  <v-dialog v-model="dialog" :max-width="options.width" @keydown.esc="cancel">
    <v-card>
      <v-toolbar dark :color="options.color" dense flat>
        <v-toolbar-title class="white--text">{{ title }}</v-toolbar-title>
      </v-toolbar>
      <v-card-text v-show="!!message">{{ message }}</v-card-text>
      <v-card-actions class="pt-0">
        <v-spacer></v-spacer>
        <v-btn color="primary darken-1" @click.native="agree">Yes</v-btn>
        <v-btn color="grey" @click.native="cancel">Cancel</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>
<script>
  export default {
    data: () => ({
      dialog: false,
      resolve: null,
      reject: null,
      message: null,
      title: null,
      options: {
        color: 'primary',
        width: 290
      }
    }),
    methods: {
      open(title, message, options) {
        this.dialog = true
        this.title = title
        this.message = message
        this.options = Object.assign(this.options, options)
        return new Promise((resolve, reject) => {
          this.resolve = resolve
          this.reject = reject
        })
      },
      agree() {
        this.resolve(true)
        this.dialog = false
      },
      cancel() {
        this.resolve(false)
        this.dialog = false
      }
    }
  }
</script>

My goal is to invoke this modal from pages/index.vue as follows (the ref placement here works, but I aim to have a globally accessible confirm modal):

methods: {
    async openConfirm() {
      console.log("openConfirm")
       if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
         console.log('--yes')
       }else{
         console.log('--no')
       }
    },

Answer №1

To avoid misusing $ref in your code, it's important to steer clear of creating patterns within other patterns.

For a more specific answer tailored to your current task: Having dealt with similar scenarios involving global, promise-based confirmation dialogs in several Vue projects, the following approach has proven to be effective:

  1. Set up the confirm dialog as a standalone 'module' that can be easily added to main.js with just a couple of lines:
import ConfirmModule from './modules/confirm';
Vue.use(ConfirmModule);

(on a side note: there are a few other 'global modular components' like alerts, etc. that can be utilized in a similar fashion)

  1. Utilize a JavaScript file for managing the setup process, promise handling, and component instantiation. Here's an example:
import vuetify from '@/plugins/vuetify';
import confirmDialog from './confirm-dialog.vue';

export default {
  install(Vue) {
    const $confirm = (title, text, options) => {
      const promise = new Promise((resolve, reject) => {
        try {
          let dlg = true;
          const props = {
            title, text, options, dlg,
          };
          const on = { };
          const comp = new Vue({
            vuetify,
            render: (h) => h(confirmDialog, { props, on }),
          });
          on.confirmed = (val) => {
            dlg = false;
            resolve(val);
            window.setTimeout(() => comp.$destroy(), 100);
          };

          comp.$mount();
          document.getElementById('app').appendChild(comp.$el);
        } catch (err) {
          reject(err);
        }
      });
      return promise;
    };

    Vue.prototype.$confirm = $confirm;
  },
};
  1. Add it to the Vue.prototype to enable usage from any component within your application simply by calling: this.$confirm(...)

  2. When constructing your Vue component (confirm-dialog.vue), ensure to bind your props for title, text, and options in a one-way manner, bind the dlg prop to the dialog, or establish a two-way binding via a computed property with a getter and setter...either approach works.

  3. Emit a "confirmed" event with true upon user confirmation. Hence, in the confirm-dialog.vue component: this.$emit('confirmed', true);

  4. If the user dismisses the dialog or selects 'no', emit false to prevent the promise from lingering: this.$emit('confirmed', false);

  5. Now, in any component, you can integrate it as follows:

methods: {
  confirmTheThing() {
    this.$confirm('Do the thing', 'Are you really sure?', { color: 'red' })
      .then(confirmed => {
        if (confirmed) {
          console.log('Well OK then!');
        } else {
          console.log('Eh, maybe next time...');
        }
      });
  }
}

Answer №2

When working on a nuxt project with the default layout, you can include the component Confirm like this:

    <v-main>
      <v-container fluid>
        <nuxt />
        <Confirm ref="confirm" />
      </v-container>
    </v-main>

Once the component is included, you can use its open method as shown below:

const confirmed = await this.$root.$children[2].$refs.confirm.open(...)
if (!confirmed) {
  return // user cancelled, stop here
}
// user confirmed, proceed

Originally, the tricky part was accessing the component within the layout. While $root.$children[2] worked during development, it had to be changed to $root.$children[1] once deployed.

To address this, I implemented the following solution:

  // assume default.vue has data called 'COPYRIGHT_TEXT'
  const child = this.$root.$children.find(x => x.COPYRIGHT_TEXT)
  if (child) {
    const confirm = child.$refs.confirm
    if (confirm) {
      return await confirm.open(...)
    }
  }
  return false

Initially, when a new requirement arose for confirmation before saving data in a specific mode, I considered using an event bus. However, this would require significant refactoring of existing code. Eventually, the project migrated to the composition API, allowing me to use the store's getter/setter to share the Confirm component's reference. This eliminated the need for the hacky method of finding the reference as described above:

// default.vue stores a ref
store.setRefGetConfirm(confirm)

// anywhere it needs to confirm
const confirmed = await store.refGetConfirm.open()

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

When working with NodeJS, Express, and MySQL, it is important to avoid setting headers after they have already been sent

I am working on a NodeJS application using Express and here is the relevant code snippet. app.post('/open', checkStatus, function(req, res) { if (req.error) { console.log(req.log); return res.json(req.error); } console.log(current ...

What exactly comprises an HTTP Parameter Pollution attack within the context of Node.js and Express.js?

I came across this information on https://www.npmjs.com/package/hpp According to the source, "Express populates http request parameters with the same name in an array. An attacker can manipulate request parameters to exploit this vulnerability." I am cur ...

What is the best way to display all of these JSON objects on a webpage given the current AJAX setup?

I need help figuring out how to display all the objects and their contents from a JSON file on a webpage when a button is clicked. I don't want to use console.log, I actually want it to show on the webpage but I'm unsure of how to do that. Please ...

NgModel in Angular Datapicker does not successfully transmit value

My page features a form that functions as a filter search, with one of the fields being a date field. I have configured the Angular UI datepicker plugin (https://github.com/angular-ui/ui-date) and the calendar pops up when I focus on the date field. Howe ...

In React, firebase.firestore() is operational, but firebase.functions() remains undefined

Currently, I am engaged in a React project that heavily relies on Firebase for various features. In order to incorporate HTTPS callable functions into the project, I encountered an issue. The problem lies in the incorrect importation of the 'firebase ...

Unexpected behavior: custom event firing multiple times despite being emitted only once

I am utilizing the ws module for incorporating web sockets functionality. An event named newmessage seems to be triggering multiple times in correlation with the number of active sockets connected to the web-socket-server. The scenario puzzled me initiall ...

Including onMouseUp and onMouseDown events within a JavaScript function

I am experiencing an issue with a div that contains an input image with the ID of "Area-Light". I am attempting to pass the ID of the input image to a function. Although event handlers can be directly added inside the input tag, I prefer to do it within ...

Creating a banner image that scrolls while maintaining a fixed size

I'm looking to add a banner image with a scrolling effect as users navigate down the page, but I'm not sure what this technique is called. An example of what I'm trying to achieve can be seen on this site. As the user scrolls down, the res ...

Is there a way to create optional sections within a reusable component's template in a Vue 3 application?

Recently, I've been immersed in developing a Single Page Application (SPA) using the powerful combination of Vue 3, TypeScript, and integrating The Movie Database (TMDB) API. One interesting challenge I encountered was with my versatile movie card co ...

What steps do I need to follow to utilize LiveReload with an AngularJS templateURL?

Is there a way to trigger the reload of templateURL when using LiveReload and Grunt? angular.module('meshApp', [ 'ngSanitize', 'ngRoute' ]) .config(function ($routeProvider) { $routeProvider .when('/&apos ...

Issue: req.flash() not functioning correctly following the execution of req.session.destroy()

In order to log the user out and redirect them to a login page with a message under specific conditions, it is necessary to destroy the user's current session. I usually use the flash feature to display a one-time message in my application, which work ...

ng-if not functioning properly following the activation of the "Save Changes" button

When I click the edit change button, I am updating information and then hiding the form to show the updated details by clicking on the Save Changes button. My API successfully updates the information, but for some reason, ng-if does not seem to work afte ...

What sets apart optionalDependencies from peerDependencies in the Meta optional?

Why are both marking dependency as optional and what is the specific use-case of each? Is peerDependenciesMeta intended for npm packages while optionalDependencies is meant for projects? For example, in my npm package, certain modules require dependency ...

Is it possible to iterate through an object with multiple parameters in Op.op sequelize?

Currently, I am in the process of setting up a search API that will be able to query for specific parameters such as id, type, originCity, destinationCity, departureDate, reason, accommodation, approvalStatus, and potentially more in the future. const opt ...

How do I dynamically incorporate Tinymce 4.x into a textarea?

I am encountering a small issue when trying to dynamically add tinymce to a textarea after initialization. tinymce.init({ selector: "textarea", theme: "modern", height: 100, plugins: [ "advlist autolink image lists charmap print p ...

A guide on implementing Angular ngbPopover within a CellRenderer for displaying in an ag-grid cell

I successfully set up an Angular Application and decided to utilize ag-grid community as a key component for displaying data from a backend API in tables, using fontawesome icons to enhance readability. While everything looks fine and my application is fu ...

Guide to altering the characteristics of a button

Here is the code for a button within My Template: <div *ngFor="let detail of details" class = "col-sm-12"> <div class="pic col-sm-1"> <img height="60" width="60" [src]='detail.image'> </div> <div ...

Exploring Firebase database with AngularJS to iterate through an array of objects

I'm working on an app that pulls data from a Firebase database, but I'm running into an issue. When I try to loop through the data and display it on the page, nothing shows up. However, if I use console.log to output the data, it's all there ...

Type of Angular Service Issue: string or null

I'm encountering a persistent issue with my Angular code, specifically while calling services in my application built on Angular 13. The problem arises when trying to access the user API from the backend, leading to recurrent errors. Despite extensive ...

Creating a topographical map from a 3D model

<!--B"H--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Custom Terrain Heightmap Generator</title> ...