how to call a method on the component by clicking Vue.js? - 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

Related

How to dynamically switch a component based on the existence of window in Nuxt.js?

I have a dynamic component that looks different at different screen resolutions.
<template>
<div>
<headerComponent></headerComponent>
<div v-if="!large" class="placeholder"></div>
<component
v-else
:is="tariffBlock"
>
</component>
</div>
</template>
<script>
import smallComponent from '#/components/small-component'
import largeComponent from '#/components/large-component'
import headerComponent from '#/components/header-component'
const components = {
smallComponent,
largeComponent
}
export default {
components: {
headerComponent
},
data () {
return {
large: false
}
},
computed: {
getComponent () {
if (!this.large) return components.smallComponent
return components.largeComponent
}
},
created () {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
}
}
</script>
By default, a smallComponent is shown, and then a largeComponent. To avoid "jumping" I decided to show the placeholder while large === false.
To avoid the error window in not defined I use the check for process.browser.
PROBLEM: placeholder is only shown in dev mode, but when I start generate the placeholder is not displayed.
The following solutions DIDN'T help:
1.
created () {
this.$nextTick(() => {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
})
}
created () {
this.$nextTick(() => {
this.large = window.matchMedia('(min-width: 1200px)').matches
})
}
mounted () {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
and with the addition process.browser and nextTick()
Creating a mixin with ssr: false, mode: client
Thanks in advance!
This is how you toggle between components in Nuxt.js
<template>
<div>
<div #click="toggleComponents">toggle components</div>
<hr />
<first-component></first-component>
<second-component></second-component>
<hr />
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
methods: {
toggleComponents() {
if (this.firstOrSecond === 'first-component') {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
You don't need to import them, it's done automatically if you have the right configuration, as explained here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components
In this snippet of code, first-component and second-component are shown initially (between the two hr) just to be sure that you have them properly loaded already. You can of course remove them afterwards.
Not recommended
This is what you're looking for. Again, this is probably not how you should handle some visual changes. Prefer CSS for this use-case.
<template>
<div>
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
mounted() {
window.addEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
beforeDestroy() {
// important, otherwise you'll have the eventListener all over your SPA
window.removeEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
methods: {
toggleComponentDependingOfWindowWidth() {
console.log('current size of the window', window.innerWidth)
if (window.innerWidth > 1200) {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
PS: if you really wish to use this solution, at least use a throttle because the window event will trigger a lot and it can cause your UI to be super sluggish pretty quickly.

VueJS 2.x Child-Component doesn't react to changed parent-property

I have the problem, that a component doesn't recognize the change of a property.
The component is nested about 5 levels deep. Every component above the faulty one does update with the same mechanics and flawlessly.
I invested some time to get to the problem, but I can't find it.
The flow is:
Dashboard (change value and pass as prop)
TicketPreview (Usage and
pass prop)
CommentSection (Pass prop)
CommentList (FAULTY / Usage of prop)
Everything down to the commentSection is being updated as expected, but the commentList doesn't get the update notification (beforeUpdate doesn't get triggered).
Since I tested quite a few things I will only post the essential code from commentSection (parent) and commenList (child)
DISCLAIMER: This is a prototype code without backend, therefore typical API-Requests are solved with the localStorage of the users browser.
commentSection
<template>
<div id="comment-section">
<p>{{selectedTicket.title}}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList,
},
props: {
selectedTicket: Object,
},
beforeUpdate() {
console.log("Comment Section");
console.log(this.selectedTicket);
},
updated() {
console.log("Comment Section is updated");
}
}
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem,
},
data() {
return {
comments: Array,
}
},
props: {
selectedTicket: Object,
},
methods: {
getComments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for(let i = 0; i < comments.length; i++){
if (comments[i].ticketId === this.selectedTicket.id){
filteredComments.push(comments[i]);
}
}
this.comments = filteredComments;
}
},
beforeUpdate() {
console.log("CommentList");
console.log(this.selectedTicket);
this.getComments();
},
mounted() {
this.$root.$on("updateComments", () => {
this.getComments();
});
console.log("CL Mounted");
},
}
</script>
The beforeUpdate() and updated() hooks from the commentList component are not being fired.
I guess I could work around it with an event passing the data, but for the sake of understanding, let's pretend it's not a viable option right now.
It would be better to use a watcher, this will be more simple.
Instead of method to set comments by filtering you can use computed property which is reactive and no need to watch for props updates.
CommentSection
<template>
<div id="comment-section">
<p>{{ selectedTicket.title }}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList
},
props: {
selectedTicket: Object
},
methods: {
updateTicket() {
console.log("Comment section is updated");
console.log(this.selectedTicket);
}
},
watch: {
selectedTicket: {
immediate: true,
handler: "updateTicket"
}
}
};
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem
},
props: {
selectedTicket: Object
},
computed: {
comments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for (let comment of comments) {
if (comment.ticketId == this.selectedTicket.id) {
filteredComments.push(comment);
}
}
// // using es6 Array.filter()
// let filteredComments = comments.filter(
// (comment) => comment.ticketId == this.selectedTicket.id
// );
return filteredComments;
}
}
};
</script>
I found the problem: Since commentList is only a wrapper that doesn't use any of the values from the prop, the hooks for beforeUpdate and updated are never triggered. The Vue Instance Chart is misleading in that regard. The diagram shows it like beforeUpdate would ALWAYS fire, when the data changed (then re-render, then updated), but beforeUpdate only fires if the Component and Parent has to be re-rendered.
The Object updates as expected, it just never triggered a re-render on the child component because the wrapper has not been re-rendered.

Reuse Quasar Dialog plugin with custom component on another component

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>

Call method from another component in Vue.js

How do I call the method from another component in this scenario? I would like to load additional pice of data from the API once the button is clicked in the component 1 to the component 2.
Thanks
Here are my two components in the seperate files:
compbutton.vue
<template>
<div>
<a href v-on:click="buttonClicked">Change Name</a>
</div>
</template>
<script>
export default {
name: 'compbutton',
methods: {
buttonClicked: function () {
//call changeName here
}
}
}
</script>
compname.vue
<template>
<div>{{name}}</div>
</template>
<script>
export default {
name: 'compname',
data: function () {
return {
name: 'John'
}
},
methods: {
changeName: function () {
this.name = 'Ben'
}
}
}
</script>
You can name the component and then $ref to the method from another componenent.
compbutton.vue
<template>
<div>
<a href v-on:click="buttonClicked">Change Name</a>
</div>
</template>
<script>
export default {
name: "compbutton",
methods: {
buttonClicked: function() {
//call changeName here
this.$root.$refs.compname_component.changeName();
}
}
};
</script>
compname.vue
<template>
<div>{{name}}</div>
</template>
<script>
export default {
name: "compname",
data: function() {
return {
name: "John"
};
},
methods: {
changeName: function() {
this.name = "Ben";
}
},
created() {
// set componenent name
this.$root.$refs.compname_component = this;
}
};
</script>
Alternative answer: you can pass the function you want the child to invoke as a prop from the parent component. Using your example:
compbutton.vue
<template>
<div>
<a href v-on:click="buttonClicked">Change Name</a>
</div>
</template>
<script>
export default {
name: 'compbutton',
props: {
clickHandler: {
type: Function,
default() {
return function () {};
}
}
},
methods: {
buttonClicked: function () {
this.clickHandler(); // invoke func passed via prop
}
}
}
</script>
compname.vue
<template>
<div>{{name}}</div>
<compbutton :click-handler="changeName"></compbutton>
</template>
<script>
export default {
name: 'compname',
data: function () {
return {
name: 'John'
}
},
methods: {
changeName: function () {
this.name = 'Ben'
}
}
}
</script>
Note, in your example, it doesn't appear where you want the 'compbutton' component to be rendered, so in the template for compname.vue, thats been added as well.
You can use a service as a go-between. Usually, services are used to share data but in javascript functions can be treated like data also.
The service code is trivial, just add a stub for the function changeName
changeName.service.js
export default {
changeName: function () {}
}
To have services injected into the components, you need to include vue-injector in the project.
npm install --save vue-inject
or
yarn add vue-inject
and have a register of services,
injector-register.js
import injector from 'vue-inject';
import ChangeNameService from '#/services/changeName.service'
injector.service('changeNameService', function () {
return ChangeNameService
});
then in main.js (or main file may be called index.js), a section to initialize the injector.
import injector from 'vue-inject';
require('#/services/injector-register');
Vue.use(injector);
Finally, add the service to the component dependencies array, and use the service
compname.vue
<script>
export default {
dependencies : ['changeNameService'],
created() {
// Set the service stub function to point to this one
this.changeNameService.changeName = this.changeName;
},
...
compbutton.vue
<script>
export default {
dependencies : ['changeNameService'],
name: 'compbutton',
methods: {
buttonClicked: function () {
this.changeNameService.changeName();
}
}
...
Add a # to the button href to stop page reloads
Change Name
See the whole thing in CodeSandbox

How to share data between components Vue js

Hi there I saw posts talking about this, but is not easy for me understand what I have to do to share data between components, I don't want to use event bus so can you tell me how to use props??
Component A:
<template>
<div>
<div class="container">
<fileForm></fileForm> //<--- THE COMPONENT B
</div>
</div>
</div>
</template>
<script>
export default {
name: "DashBoard",
data() {
return {
user: {},
};
},
methods: {
checkIfImLoggedIn() {
}
},
onComplete() {
},
},
mounted() {
this.checkIfImLoggedIn();
}
};
</script>
Component B:
<template>
//...
</template>
<script>
export default {
name: "FileForm",
data() {
return {
fileExtensions: ["CSV", "EXCEL"],
sharedData : {}, //<--- for example share this
};
},
methods: {}
};
</script>
If you want to share data from the parent to the child you can do:
on component A
<fileForm :something="user"></fileForm>
In component B
props: {
something: Object
}
If you want to share data from the child to the parent you have to use an event bus or vuex: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
https://alligator.io/vuejs/component-communication/
You need to do some research about communication among components.
There are a lot of ways to do it ;)