Reuse Quasar Dialog plugin with custom component on another component - vue.js

I'm taking the first steps with Quasar.
I want to build a modal window to be reused in forms.
I am using Dialog plugin and q-layout in a custom component.
However, when I use this custom component in another component the dialog plugin methods do not work.
Is there any way to solve this?
util/modal.js
import { Dialog } from "quasar";
export function ModalWindow(CustomComponent) {
Dialog.create({
component:CustomComponent,
ok: {
push: true
},
cancel: {
color: 'negative'
},
persistent: true
})
}
modal/ModalWindow.vue (custom component):
<template>
<q-dialog persistent ref="dialog" #hide="onDialogHide">
<q-layout view="lhh LpR lff" container style="height: 400px" class="bg-grey-3">
<q-toolbar class="bg-primary text-white q-mb-sm">
<q-toolbar-title>
<slot name="titelWindow"></slot>
</q-toolbar-title>
<q-btn v-close-popup flat round dense icon="close" />
</q-toolbar>
<q-form #submit.prevent="submitForm">
<q-card-section>
<slot></slot>
</q-card-section>
<q-card-actions align="right">
<slot name="toolbarBottom"></slot>
</q-card-actions>
</q-form>
</q-layout>
</q-dialog>
</template>
<script>
export default {
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
}
}
}
</script>
Call method ModalWindow on a page:
<script>
import { ModalWindow } from 'src/util/modal'
import CustomComponent from "components/modal/MyModalWindow.vue"
export default {
methods: {
showUWin: function(id) {
ModalWindow(CustomComponent)
}
}
}
</script>
So far it works well.
However, as I said,when I use this custom component in another component the dialog plugin methods do not work.
render custom component in another component: MyModalForm.vue
<template>
<MyModalWindow>
<!--Dialog's show method doesn't work-->
</MyModalWindow>
</template>
<script>
export default {
name: 'MyModalForm',
components: {
'MyModalWindow': require('components/modal/MyModalWindow.vue').default,
}
}
</script>
Call method ModalWindow on a page:
<script>
import { ModalWindow } from 'src/util/modal'
import CustomComponent from "components/modal/MyModalForm.vue"
export default {
methods: {
showWin: function(id) {
ModalWindow(CustomComponent)
}
}
}
</script>
I get on de console:
[Vue warn]: Error in mounted hook: "TypeError: this.$refs.dialog.show
is not a function"

I recently got into the same error.
My understanding is that, when you use something like:
Dialog.create({
component: CustomComponent,
...
})
// or
this.$q.dialog({
component: CustomComponent
})
the CustomComponent must directly implement the required show/hide/... methods, as per documentation.
So you have to repeat in each custom component this code (adapting it to the right "ref", specific to your component):
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
}
}
and propagate onOk and onCancel events appropriately.
For instance, summarizing everything:
<template>
<MyModalWindow ref="myModalForm" #ok="onOk" />
</template>
<script>
export default {
name: 'MyModalForm',
components: {
'MyModalWindow'
},
methods: {
show() {
this.$refs.myModalForm.show();
},
hide() {
this.$refs.myModalForm.hide();
},
onHide() {
this.$emit('hide');
},
onOk() {
this.$emit('ok');
this.hide();
}
}
}
</script>

Related

Import component and its properties/methods inside other component

if i have component LogoutButton.vue
export default {
data()
{
return {
}
},
methods:
{
Logout()
{
console.log("something")
}
}
}
</script>
<template>
<button #click="Logout">Logout</button>
</template>
How can i use the LogoutButton component inside other component?
I mean how can i import the component and its click methods inside other component?
<script>
import LogoutButton from './LogoutButton.vue'
export default {
components: {
LogoutButton
},
data()
{
return {
}
},
methods:
{
// Your different methods
}
}
</script>
<template>
<div>
<!-- The template from LogoutButton component will display here -->
<LogoutButton />
</div>
</template>

How to use lottie with NuxtJS to have hover, click and all functions working?

I'm using the 'vue-lottie' package and there's not much information about how to use it.
I got the JSON animations from Lordicons and it shows correctly but I can't make the animation work on hover or click, only loop or static (no animation).
My Component:
<template>
<div>
<lottie
:options="lottieOptions"
:width="50"
/>
</div>
</template>
<script>
import lottie from "vue-lottie/src/lottie.vue";
import * as animationData from "~/assets/about.json";
export default {
components: {
lottie
},
data() {
return {
anim: null, // for saving the reference to the animation
lottieOptions: {
animationData: animationData.default,
loop: false,
autoplay: false,
}
};
},
methods: {
handleAnimation(anim) {
this.anim = anim;
},
stop() {
this.anim.stop();
},
play() {
this.anim.play();
},
pause() {
this.anim.pause();
},
}
};
</script>
And I'm using on the page importing the component only:
...
<AboutIcon />
...
<script>
import AboutIcon from "~/components/AboutIcon.vue";
export default {
components: {
AboutIcon,
},
data() {
return {};
}
};
</script>
From the code sample you provided, you forgot to attach the #animCreated="handleAnimation" event.
So this.anim is actually always null.
<template>
<div>
<lottie
:options="lottieOptions"
:width="50"
#animCreated="handleAnimation"
/>
</div>
</template>
Then you just have to set a #mouseover="play" to start the animation on hover.

Emit event from App.vue and catch in component

I am trying to implement sign out handling in Vue. I redirect to Home which works fine on all pages except Home which is not refreshed. So I decided to emit a signal and refresh data once I catch it.
App.vue
<b-dropdown-item href="#0" v-on:click="signMeOut()">Sign out</b-dropdown-item>
methods: {
signMeOut() {
this.$store.dispatch('SIGN_USER_OUT');
if (this.$route.path === '/') {
this.$emit('sign-out');
} else {
this.$router.push({ name: 'home' });
}
},
Home.vue
<b-container fluid="true" class="pt-3 w-75 m-auto" v-on:sign-out="reload">
created() {
this.$store.dispatch('INIT_STREAM');
},
methods: {
reload() {
console.log('reload');
this.$store.dispatch('INIT_STREAM');
},
},
but the signal does not reaches the Home.vue or is ignored. How can I fix it? Or do you have a better solution of this sign out procedure?
When you use the hook $emit.
You should listen to this event in $root instance from your vuejs application, $root.
So for achieve the desired result you just have to change your code to:
In your component home (I'm putting only the session script from a .vue file)
<script>
export default {
name: 'Home',
components: {
HelloWorld
},
created(){
this.$root.$once('mylogouthandler', this.logoutEventHandler)
},
methods: {
logoutEventHandler() {
console.log('exit')
//do your stuff here.
}
}
}
</script>
your component with action logout.
<template>
<div class="about">
<button #click="handleButtonClick()">logout</button>
</div>
</template>
<script>
export default {
name: 'About',
methods: {
handleButtonClick(){
console.log('clicked')
this.$root.$emit('mylogouthandler')
}
}
}
</script>
If you would like to know more, here is the documentation for handling events in vuejs.

Element UI dialog component can open for the first time, but it can't open for the second time

I'm building web app with Vue, Nuxt, and Element UI.
I have a problem with the Element dialog component.
It can open for the first time, but it can't open for the second time.
This is the GIF about my problem.
https://gyazo.com/dfca3db76c75dceddccade632feb808f
This is my code.
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first :visible=visible></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
ModalFirst.vue
<template>
<el-dialog
title="Tips"
:visible.sync="visible"
width="30%"
>
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
<a>Hello</a>
</span>
</el-dialog>
</template>
<script>
export default {
props: [ 'visible' ]
}
</script>
And I can see a warning message on google chrome console after closing the dialog.
The warning message is below.
webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:620 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "visible"
found in
---> <ModalFirst> at components/ModalFirst.vue
<Pages/index.vue> at pages/index.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
This is the screenshot of the warning message.
https://gyazo.com/83c5f7c5a8e4d6816c35b3116c80db0d
In vue , using directly to prop value is not allowed . Especially when your child component will update that prop value , in my option if prop will be use
for display only using directly is not a problem .
In your code , .sync will update syncronously update data so I recommend to create local data.
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'visible' ],
data: function () {
return {
localVisible: this.visible // create local data using prop value
}
}
}
</script>
If you need the parent visible property to be updated, you can create your component to leverage v-model:
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'value' ],
data() {
return {
localVisible: null
}
},
created() {
this.localVisible = this.value;
this.$watch('localVisible', (value, oldValue) => {
if(value !== oldValue) { // Optional
this.$emit('input', value); // Required
}
});
}
}
</script>
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first v-model="visible"></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
v-model is basically a shorthand for :value and #input
https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage
Side-note:
You can also import your component like so:
components: { ModalFirst },
as ModalFirst will be interpreted as modal-first as well by Vue.js

how to call a method on the component by clicking Vue.js?

I am use component of the dialog window dialog.vue from vue-mdl package
<template>
<div class="mdl-dialog-container" v-show="show">
<div class="mdl-dialog">
<div class="mdl-dialog__title">{{title}}</div>
<div class="mdl-dialog__content">
<slot></slot>
</div>
<div class="mdl-dialog__actions" :class="actionsClasses">
<slot name="actions">
<mdl-button class="mdl-js-ripple-effect" #click.native.stop="close">Close</mdl-button>
</slot>
</div>
</div>
</div>
</template>
<script>
import mdlButton from './button.vue'
import createFocusTrap from 'focus-trap'
export default {
components: {
mdlButton
},
computed: {
actionsClasses () {
return {
'mdl-dialog__actions--full-width': this.fullWidth
}
}
},
data () {
return {
show: false
}
},
props: {
title: {
type: String
},
fullWidth: Boolean
},
mounted () {
this._focusTrap = createFocusTrap(this.$el)
},
methods: {
open () {
this.show = true
this.$nextTick(() => this._focusTrap.activate())
this.$emit('open')
},
close () {
this.show = false
this._focusTrap.deactivate()
this.$emit('close')
}
}
}
</script>
I want to bring a dialog window to the other component
<mdl-dialog></mdl-dialog>
<button class="mdl-button mdl-js-button mdl-button--raised">Click me</button>
I found no information on how to call a method of one component within the other. All examples are mainly used props. Tell me how to do it?
How can I call a method open() in <mdl-dialog></mdl-dialog>?
Since they're not parent child you'd want to use an event bus. Since you're using .vue files you can create a file called bus.js like
import Vue from 'vue'
export default new Vue()
Then, import that wherever you need to emit and listen for centralized events. Here's a quick example:
// SomeComponent.vue
import bus from './bus.js'
export default {
methods: {
log (msg) {
console.log(msg)
}
},
created () {
bus.$on('someEvent', this.log)
}
}
Then in another component you can do like...
// AnotherComponent.vue
import bus from './bus.js'
export default {
methods: {
emitClick (msg) {
bus.$emit('Hello from AnotherComponent.vue')
},
},
}
You can read up a bit more about it here: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
You can create below helper method in methods in your parent component:
getChild(name) {
for(let child of this.$children) if (child.$options.name==name) return child;
},
And call child component method in this way:
this.getChild('mdl-dialog').open();
I don't test it for Vue>=2.0