How to programmatically launch a Vuetify Dialog and wait for the response

12,687

Solution 1

Ended up solving it this way:

I have a method called mbox (returns a promise) that creates a an instance of my component, adds it to the DOM, and then watches a property on that component to know which option the user chose. Resolves the promise once the user has chosen an option

My mbox method:

import MBOX from './components/mbox.vue';
import _Vue from 'vue';
export default {

mbox(mText, mTitle, mBtnCap1, mBtnCap2, mBtnCap3){
    return new Promise(async (resolve, reject) => {
        if (!mTitle){
            mTitle = 'My Title';
        }
        if (!mBtnCap1){
            mBtnCap1 = 'OK';
        }

        // I'm combining a bunch of stuff to make this work.
        // First, create the vue component
        var mboxInstance = _Vue.extend(MBOX); // mbox component, imported at top of Sublib
        var oComponent = new mboxInstance({ 
            propsData: { 
                msg: mText, 
                title: mTitle, 
                btnCap1: mBtnCap1, 
                btnCap2: mBtnCap2, 
                btnCap3: mBtnCap3,
                retval: 0
                }
        }).$mount();

        // now add it to the DOM
        var oEl = document.getElementById('app').appendChild(oComponent.$el);

        // NOTE: couldn't get it to work without adding a 'button' to activate it
        // progrmatically click it and make the button invisible
        // document.getElementById('mbox_btn_launch').click();
        var oLuanchBtn = document.getElementById('mbox_btn_launch');
        oLuanchBtn.style.visibility = 'hidden';
        oLuanchBtn.click();

        // Add a listener so we can get the value and return it
        oComponent.$watch('retval', function(newVal, oldVal){
            // this is triggered when they chose an option
            // Delete the component / element now that I'm done
            oEl.remove();
            resolve(Number(newVal));
        })
    }); // promise
}, // mbox
}

And My MBOX component:

<template>
<v-dialog max-width="290" persistent v-if="showMbox">
    <template v-slot:activator="{on}">
        <v-btn id="mbox_btn_launch" v-on="on">
            Test
        </v-btn>
    </template>
    <v-card>
        <v-card-title>{{title}}</v-card-title>
        <v-card-text>{{msg}}</v-card-text>
        <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn v-if="btnCap1" @click="btnClicked('1')">{{btnCap1}}</v-btn>
            <v-btn v-if="btnCap2" @click="btnClicked('2')">{{btnCap2}}</v-btn>
            <v-btn v-if="btnCap3" @click="btnClicked('3')">{{btnCap3}}</v-btn>
        </v-card-actions>
    </v-card>
</v-dialog>
</template>
<script>
export default {
    name: 'mbox',
    data: () => ({
        showMbox: true    
    }),
    props: [
        // these can be passed in, the 'data' stuff can't
        'msg',
        'title',
        'btnCap1',
        'btnCap2',
        'btnCap3',
        'retval' // watches this so we know when they clicked something
    ],
    created(){    
        this.showMbox = true;
    }, 
    methods: {
    btnClicked: function(mBtnClicked){
        // mBtnClicked = Char. Has to be a character in order for it to pass it in. Not sure why, numerics didn't work
        mBtnClicked = Number(mBtnClicked);
        this.retval = mBtnClicked; // watcher in Sublib will detect this value has changed
        this.showMbox = false;
    } // btnClicked
} // methods
} // export default
</script>
<style scoped>
</style>

I can then call it like so:

var mChoice = await mbox('What do you want to do?', 'Title', 'Option 1', 'Option 2');

or for a simple 'validation' prompt:

if (!userName){
    mbox('You must enter a username');
    return;
}

Solution 2

My solution.

Page.vue

<template>
  <v-container>
    <v-layout text-center wrap>
      <v-flex xs12>

        <v-btn v-on:click="open_dlg">Dlg Wrapper</v-btn>

        <dlg-wrapper ref="dlg">
          <dlg-frame title="Dialog" message="Message"></dlg-frame>
        </dlg-wrapper>

      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import DlgWrapper from "@/components/DlgWrapper";
import DlgFrame from "@/components/DlgFrame";

export default {
  data: () => {
    return {};
  },

  methods: {
    open_dlg: function(event) {
      this.$refs.dlg.open().then(result => {
        console.log("Result:", result);
      });
    }
  },

  components: {
    DlgWrapper,
    DlgFrame
  }
};

</script>

DlgWrapper.vue

<template>
  <div>
    <v-dialog
      v-model="dialog"
      persistent
      :width="options.width"
      v-bind:style="{ zIndex: options.zIndex }"
    >
      <slot></slot>
    </v-dialog>
  </div>
</template>

<script>
export default {
  name: "dlg-wrapper",

  data: () => ({
    dialog: false,
    options: {
      width: 400,
      zIndex: 200
    },
    resolve: null,
    reject: null
  }),

  methods: {
    open(options) {
      this.dialog = true;
      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;
    }
  },

  provide: function() {
    return { agree: this.agree, cancel: this.cancel };
  }
};
</script>

DlgFrame.vue

<template>
  <v-card dark>
    <v-card-title v-show="!!title">{{ title }}</v-card-title>
    <v-card-text v-show="!!message">{{ message }}</v-card-text>
    <v-card-actions>
      <v-btn @click="agree">OK</v-btn>
      <v-btn @click="cancel">NO</v-btn>
    </v-card-actions>
  </v-card>
</template>

<script>
export default {
  name: "dlg-frame",
  props: ["title", "message"],
  data: () => ({}),
  inject: ["agree", "cancel"],
  methods: {}
};
</script>

Good luck!

Share:
12,687
ScottR
Author by

ScottR

Updated on June 28, 2022

Comments

  • ScottR
    ScottR almost 2 years

    I'm fairly new to Vue.js and Vuetify (used AngularJS for several years but our company is switching to Vue.js). What I'm trying to accomplish is when the user clicks on a 'sign in' button it does some checks (i.e. username cannot be blank) and launches a Vuetify Dialog to alert the user. I know Vuetify has some built in validation but looking for something a little more robust where I can wait for a reponse (i.e. when I need to wait for something like can I use your history / location).

    Basically looking to do:

    if (!userName){
        userName = await mbox('You must Enter your Username');
        return
    }
    

    OR

    var mProceed = await mbox('Can we use your location for awesome stuff?');
    

    where mbox (a simple messagebox popup box) is a function that returns a promise, programmatically loads a vue component, adds it to the dom, and waits for the response.

    Exmple Dialog Screen Shot

    i.e.

    async function mbox (mText) {
        // load dialog component here and set message passed in
        // How Do I load the template / wait for it?
        return dialogResult
    
    }
    

    Vue Component would look something like (With button captions and text being variables I pass in to my mbox function):

    <template>
    <v-layout row justify-center>
    <v-dialog
      v-model="dialog"
      max-width="290"
    >
      <v-card>
        <v-card-title class="headline">Use Google's location service?</v-card-title>
    
        <v-card-text>
          Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.
        </v-card-text>
    
        <v-card-actions>
          <v-spacer></v-spacer>
    
          <v-btn
            color="green darken-1"
            flat="flat"
            @click="dialog = false"
          >
            Disagree
          </v-btn>
    
          <v-btn
            color="green darken-1"
            flat="flat"
            @click="dialog = false"
          >
            Agree
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    </v-layout>
    </template>
    

    I'm fine editing the template / adding the script for the vue component I'm just not sure how to have it called via a method that returns a promise and waits for the response?