Vue-js-modal: Listening on events emitted by dynamic modal

Created on 6 Mar 2018  路  17Comments  路  Source: euvl/vue-js-modal

A dynamic modal that we use emits a 'customEvent' on certain action. Can we listen to this event while initializing the modal ?

Example:

 this.$modal.show(CustomComponent, {message: 'Hello'}, {height: 'auto'});

The CustomComponent emits an event that we want to bind to. What is the best way to achieve it ?

enhancement help wanted todo

Most helpful comment

I think I might have a solution for this - it sounds similar to an issue I have been having trying to pass data from modal to parent. I just passed a function from the parent component as a prop then called the function with the value I needed passing upstream, so instead of an event its a callback really but the same end product.

this.$modal.show(MyModal, {
           initialValue: _this.value,
           valueUpdated:(newValue) => {
              console.log('newValue : '+newValue);
              _this.value = newValue;
          }
        }, { width:  900 });

then just call the following in the actual modal when you want to pass data of trigger an event etc...

this.valueUpdated(value);

All 17 comments

If you are using component for your dynamic modal, why not to make just an ordinary modal and use it as... component?

We were trying to re-use the component in different places without having to render it in the template in other components but I suppose that an ordinary modal used as a component would do the job.
Thanks for the suggestion.

No problem at all 馃憤 Thanks for using the plugin 馃槃

Might be good though to think how this can be done.

Will keep an issue open in case other people will have ideas.

I think I might have a solution for this - it sounds similar to an issue I have been having trying to pass data from modal to parent. I just passed a function from the parent component as a prop then called the function with the value I needed passing upstream, so instead of an event its a callback really but the same end product.

this.$modal.show(MyModal, {
           initialValue: _this.value,
           valueUpdated:(newValue) => {
              console.log('newValue : '+newValue);
              _this.value = newValue;
          }
        }, { width:  900 });

then just call the following in the actual modal when you want to pass data of trigger an event etc...

this.valueUpdated(value);

Been wanting to do something similar to this. Would like to have your opinion on this suggestion.

The actions (method) that you want executed when an event emits could be passed as params to the modal show.

Example:

this.$modal.show(CustomComponent, {
    message: 'Hello'
}, {
    height: 'auto',
    beforeOpenAction: methodToExecute,
    beforeCloseAction: otherMethodToExecute
})

Then, the only changes necessary to the modals code would be :

  • Whenever an event gets emitted (emit normally), check if there's a method associated to that event's Action param.
  • If there's an action param for that event, trigger the passed method.

Example:

const beforeEvent = this.genEventObject({ stop, state, params })
this.$emit(beforeEventName, beforeEvent)

if (typeof this[beforeEventName + 'Action'] === 'function') {
    this[beforeEventName + 'Action']()
}

Does this make sense to you?

BTW, great work @euvl and @NoelDeMartin

When I worked with dynamic modals in my apps, I used one of the following for communication with modals:

  • Vuex (modals and callers don't even know each other, modals just modify whatever they need and since vuex is reactive, anything that needs to be updated does so).
  • Global Event Bus (I normally create a Vue instance that I use only as a global variable to emit / listen events, but I've found myself doing this less every time so I may stop doing it).
  • Promises (once the modal is closed, the promise is resolved with some result if necessary).

The first 2 options are already possible since they are not related with modals implementation. The 3rd is not possible here, but I think it could be implemented because the show() method doesn't return anything now, so it could return a promise. An example of how using it would look like:

this.$modal.show(LoginForm).then(function(user) {
    if (user) {
        // User logged in successfully, do something with user
    } else {
        // User didn't log in
    }
});

Anything more than this I didn't find the need to implement, so I don't know, tell me a scenario were you think that would be needed and we can work it out.

@pdesmarais what you mention I think it's already possible. Not in a generic way, but if you need that for a modal you can just give callbacks as parameters (like @elliot-a's example) and call them inside vue hooks, like mounted or destroyed.

Btw, it is already possible to do this but it's a bit more involved and not so clean:

this.$modal.show({
    render(h) {
        return h(MyModal, {
            on: {
                myEvent() {
                    // do something
                }
            }
        });
    }
});

(I guess if you wanted you could extract this to a function to make it clean).

The only use case for which I was wanting this, was for being able to focus on an input field when a dynamic modal gets opened... but.

Since all my modals are components in standalone .vue files, I can get around this easily by focussing on the field in the mounted hook of the component...

So... I guess, still no real use case for this. ;)

Thanks for the reply! :)

Hi, everybody, what do you think about:

//call modal
this.$modal.show(MyModal, {
  handlers: {
    doSomethingInComponent: (...args) => {
      //do something
      //this - is your component where was called modal
      //args - corrected arguments from modal
      console.log(args, this);
    }
  }
}, {
  width: "100%",
  height: "auto"
});

//modal
export default {
  name: "MyModal",

  props: ["handlers"],

  methods: {
    doSomething () {
      this.handlers.doSomethingInComponent(123, 432, "opa");  
      this.$emit("close");
    }
  }
};

is it good idea set handler as props in vue?

@SanychSan I guess you can technically do it, there is nothing "wrong" with it. But in Vue, that kind of pattern is implemented using events (using props as callbacks is more common in React, that I know of). You can see an example of what I mean in this comment: https://github.com/euvl/vue-js-modal/issues/192#issuecomment-373908934

@NoelDeMartin
Hi, thanks for you comments, well, now I know two ways :)
I tried do that, but there are other problems:

methods: {
  confirmEmail () {
    const self = this; // unfortunately I was needed save context in const for props
    this.$modal.show({
      render (h) {
        return h(
          ConfirmEmail,
          {
            props: {
              email: self.email /* yes, there "this" is not that i need,
                                    and i need use self
                                */
            },
            on: {
              myEvent (...args) {
                // do something
                console.log('myEvent', args);
              }
            }
          }
        );
      }
    },
    {}, // also we need empty object
    {
      name: 'ConfirmEmail',
      width: '100%',
      height: 'auto'
    });
  }
}

And one more problem with this way: this seems not possible to close modal from modals template use @click="$emit('close')"

@SanychSan I've implemented a working example with your code here: https://codesandbox.io/s/5z37xx1zwn?fontsize=14

You have to "echo" the close event, because now the modal is actually your component created inline, not the original MyModal component.

Anyways, as I said in https://github.com/euvl/vue-js-modal/issues/192#issuecomment-373908777 I don't think it's necessary to go this route, if you are reaching this levels of complexity you can probably solve your issue by using vuex or handling events differently.

Hi guys,

I just stumbled upon this topic while I was trying to do exactly this... But since this issue has been closed, I'm not sure whether this will ever be supported.

I've read the other options you mentionned, @NoelDeMartin, but it just seems to be adding much complexity in my opinion.

Let's consider my use case. I have a form that represents a model, and that model has a foreign key to another model. Let's make it more concrete: I have a form that represents an invoice, and it holds a reference to a customer. What I want on that form, is a button that says 'select customer' which shows the modal, and then allows for the user to select, or create a customer there. That modals works really nice, and I can select a customer in a list by searching for it, or creating a new customer ad hoc.

At the end of that process, the user will either select or create a customer, at which time I want the modal to CLOSE, and to EMIT an event 'customer-selected', that in my parant, I can listen for to set the customer on the invoice form.

This seems like a great use case for me.

1/ Vuex - I don't see how this can help in this particular use case. It probably can in others, but this usecase I don't see.
2/ Global event bus. I could easily have the modal emit an event on my global event bus (yes, I have one in place), but what if we have two buttons on the page that could theoretically open the same modal (like an invoice and a shipping address that might popup the same modal to select the address); the modal will emit the event "address-selected", and when I'm listening to that global event bus, I have no way of knowing whether that event was meant for me unless we pass along context to the modal that is also sent to the event bus, and that we also validate when listening to it. this seems a bit overkill, and again raises the level of couping between child and parent (child should not know the context at all, let alone send it along in the event it emits...)
3/ Promises. Do you have an example on how you see this, @NoelDeMartin? maybe I'm missing something, and this may very well solve my issue...

I still feel that something like this would be the easiest to read and maintain:

this.$modal.show(ContactSelectComponent, {}, {}, {'contact-selected': this.listener}});

Again - thanks for the great package. I like it, and it has made life a bit easier when it comes to managing the modals in the app I maintain. Keep up the good work!

@denjaland Yeah I think for that scenario the cleanest option would be promises, but it's not implemented in the package at the moment. In an ideal scenario where this is implemented, you'd do something like this:

const customer = await this.$modal.show(ContactSelectComponent);

But given that it isn't implemented, I guess you can do this:

methods: {
    async selectCustomer() {
        const customer = await this.getCustomer();

        // ...
    },
    getCustomer() {
        return new Promise(resolve => {
            this.$modal.show(ContactSelectComponent, {}, {}, {'contact-selected': resolve});
        });
    }
}

I agree with you on Vuex and the global event bus not being appropriate for this, given that the scope of those is global and this should be specific to this action.

Hi Noel,

But isn鈥檛 the issue exactly that you currently can not pass along a custom event in that fourth parameter, as you just did now in your example?

@denjaland Yes, I guess you're right, I thought you could listen to any events but it's only the modal container events.

Well in that case I guess the only option is to pass a callback property, in the style of React :/. You could also wrap the component like I did in this comment, but it may be more trouble than it's worth. Having a promise response would be the cleanest thing but it isn't implemented in the package.

Check out this to see what I mean with a callback prop: https://codesandbox.io/s/vuejs-modal-getting-results-e66ek?fontsize=14

Was this page helpful?
0 / 5 - 0 ratings

Related issues

smholsen picture smholsen  路  4Comments

ar53n picture ar53n  路  4Comments

a3020 picture a3020  路  5Comments

outOFFspace picture outOFFspace  路  4Comments

yyh1102 picture yyh1102  路  3Comments