I have a vue instance set as a global variable called todoEventBus.
When my component is created I set $on to trigger an alert when I emit an event called "pluralise".
My component has a button which triggers this pluralise event.
Here is the code:
<template>
<div class="todo-item">
<!--// -->
<button #click="pluralise">Plural</button>
</div>
</template>
<script>
export default {
name: "todo-item",
props: {
// props
todo: {
type: Object,
required: true,
},
},
data() {
// data
'title': this.todo.title,
},
created() {
todoEventBus.$on('pluralise', alert());
},
methods: {
pluralise() {
todoEventBus.$emit('pluralise.')
},
handlePluralise() {
this.title = this.title + 's';
}
},
}
</script>
When I click the pluralise button I see the following event get emitted:
name:"pluralise."
type:"$emit"
source:"<Root>"
payload:Array[0]
But no alert gets triggered so this todoEventBus.$on is not picking up the event.
Related
I have a child component which includes form:
<el-form :model="abc" ref="ruleForm" :rules="rules">
<el-form-item prop="files">
<abc-card :title="getTranslation('abc.files')">
<file-selector v-model="abc.files" />
</abc-card>
</el-form-item>
</el-form>
And I want to add simple validations to this form:
rules: function () {
return {
files: [
{
type: 'object',
required: true,
trigger: 'change',
message: 'Field required',
},
],
};
},
But my click button is in the parent component:
<files v-model="editableAbc" ref="editableTab" />
<el-button type="primary" #click="submitForm()">Create</el-button>
methods: {
submitForm() {
this.$refs.form.validate((isValid) => {
if (!isValid) {
return;
}
////API CALLS////
});
},
}
So I am trying to achieve that when the button is clicked the navigation should be rendered. How can I do that?
As per your requirement, My suggestion would be to use a ref on child component to access its methods and then on submit click in parent component, trigger the child component method.
In parent component template :
<parent-component>
<child-component ref="childComponentRef" />
<button #click="submitFromParent">Submit</button>
</parent-component>
In parent component script :
methods: {
submitFromParent() {
this.$refs.childComponentRef.submitForm();
}
}
In child component script :
methods: {
submitForm() {
// Perform validations and do make API calls based on validation passed.
// If you want to pass success or failure in parent then you can do that by using $emit from here.
}
}
The "files" component is the form you're talking about?
If so, then ref should be placed exactly when calling the 'files' component, and not inside it. This will allow you to access the component in your parent element.
<files v-model="editableAbc" ref="ruleForm" />
There is a method with the props, which was mentioned in the comments above. I really don't like it, but I can tell you about it.
You need to set a value in the data of the parent component. Next you have to pass it as props to the child component. When you click the button, you must change the value of this key (for example +1). In the child component, you need to monitor the change in the props value via watch and call your validation function.
// Parent
<template>
<div class="test">
<ChildComponent />
</div>
</template>
<script>
export default {
data() {
return {
updateCount: 0,
};
},
methods: {
submitForm() {
// yout submit method
this.updateCount += 1;
},
},
};
</script>
// Child
<script>
export default {
props: {
updateCount: {
type: Number,
default: 0,
},
},
watch: {
updateCount: {
handler() {
this.validate();
},
},
},
methods: {
validate() {
// yout validation method
},
},
};
</script>
And one more solution. It is suitable if you cannot place the button in the child component, but you can pass it through the slot.
You need to pass the validate function in the child component through the prop inside the slot. In this case, in the parent component, you will be able to get this function through the v-slot and bind it to your button.
// Parent
<template>
<div class="test">
<ChildComponent>
<template #button="{ validate }">
<button #click="submitForm(validate)">My button</button>
</template>
</ChildComponent>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
components: {
ChildComponent,
},
methods: {
submitForm(cb) {
const isValid = cb();
// your submit code
},
},
};
</script>
// Child
<template>
<div class="child-component">
<!-- your form -->
<slot name="button" :validate="validate" />
</div>
</template>
<script>
export default {
methods: {
validate() {
// yout validation method
console.log("validate");
},
},
};
</script>
i want to execute my function depends of the type, this function is in the child component,
childComponent:
methods: {
searchButton (type) {
console.log(type)
if(type==='1'){}
if(type==='2'){}
if(type==='3'){}
},
},
i would like to this component like this
<Child #emit="seachButton('1')"
its possible?
Edit1: why the follow example does not work?
i am sending from the parent
Parent.vue
<Child #onClick="onClick1"/>
<Child #onClick="onClick2"/>
<script>
import Child from './Child.vue'
export default {
name: 'Tabs',
props: {},
components: {
Child,
},
methods: {
onClick1() {
console.log('onClick1')
},
onClick2() {
console.log('onClick2')
},
and on my child component:
<template>
<el-button #click="onClick" type="primary">{{ buttonText }}</el-button></template>
<script>
export default {
name: 'Child',
props: {
},
methods: {
onClick () {
this.$emit('click')
},
},
}
</script>
Just do want you want to do inside of your child.vue methods and use emit there too.
First: Do something in your if-statement and emit the value to your parent.vue:
methods: {
searchButton (type) {
console.log(type)
if(type==='1'){
//just a e.g.
this.value = type;
this.$emit('myValue', this.value);
}
if(type==='2'){}
if(type==='3'){}
},
},
Second: Define the emitted in your parent.vue
<Child #myValue="myValue"/>
Third: Reference second step and set a new variable in your parent.vue methods like this:
methods: {
newValue(val) { //val = your emitted value
this.valueFromChild = val //define your val in your parent.vue
}
}
Hopefully this helps you out!
Just for additional info:
EMIT is to pass data from your child to your parent and will be declared like my e.g in step 2 (#example="example)
PROPS is to pass data from parent to child, these will declared in the component-tag like :example="example"
UPDATE TO NEW EDIT:
In your child.vue (!) template you have to define a click-event first when you want to do it after clicking on a button like in my code. Than go to your methods and define a function for each and emit this with a name and a value !
<template>
<el-button type="primary" #click="triggerClick1()">{{ buttonText }}</el-button>
<el-button type="primary" #click="triggerClick2()">{{ buttonText }}</el-button>
</template>
<script>
export default {
name: 'Child',
methods: {
triggerClick1() {
this.$emit('onClick1', "First button was triggerd")
}
triggerClick2() {
this.$emit('onClick2', "Second button was triggerd")
}
},
}
</script>
In you parent.vue (!) you have to declare but where you add your component than you go to your methods as well like in my code and declare your variable, than you can use it in your parent! Like this
<template>
<Child #onClick1="onClick1" #onClick1="onClick2"/>
</template>
<script>
import Child from './Child.vue'
export default {
name: 'Tabs',
props: {},
components: {
Child,
},
methods: {
onClick1(val) {
this.valClick1 = val;
},
onClick2(val) {
this.valClick2 = val;
}
}
</script>
An easier way would be using a ref like
<template>
<Child #child-click="searchButton" ref="child_ref"/>
</template>
<script>
export default {
name: 'Parent',
methods: {
searchButton (type) {
console.log(type)
if(type==='1'){
this.$refs.child_ref.handler1(); // can pass args if required
}
if(type==='2'){
this.$refs.child_ref.handler1(); // can pass args if required
}
},
}
}
</script>
and your child component be like
<template>
<el-button #click="onClick" type="primary">{{ buttonText }}</el-button></template>
<script>
export default {
name: 'Child',
methods: {
onClick () {
let someVal = 1 // can you any value that is used in if-else inside parent
this.$emit('child-click', someVal) // changed the name becoz there is already a default handler called 'click`
},
handler1() {
},
handler2() {
}
},
}
</script>
Context
In Vue 2.0 the documentation and others clearly indicate that communication from parent to child happens via props.
Question
How does a parent tell its child an event has happened via props?
Should I just watch a prop called event? That doesn't feel right, nor do alternatives ($emit/$on is for child to parent, and a hub model is for distant elements).
Example
I have a parent container and it needs to tell its child container that it's okay to engage certain actions on an API. I need to be able to trigger functions.
Vue 3 Composition API
Create a ref for the child component, assign it in the template, and use the <ref>.value to call the child component directly.
<script setup>
import {ref} from 'vue';
const childComponentRef = ref(null);
function click() {
// `childComponentRef.value` accesses the component instance
childComponentRef.value.doSomething(2.0);
}
</script>
<template>
<div>
<child-component ref="childComponentRef" />
<button #click="click">Click me</button>
</div>
</template>
Couple things to note-
If your child component is using <script setup>, you'll need to declare public methods (e.g. doSomething above) using defineExpose.
If you're using Typescript, details of how to type annotate this are here.
Vue 3 Options API / Vue 2
Give the child component a ref and use $refs to call a method on the child component directly.
html:
<div id="app">
<child-component ref="childComponent"></child-component>
<button #click="click">Click</button>
</div>
javascript:
var ChildComponent = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
}
}
new Vue({
el: '#app',
components: {
'child-component': ChildComponent
},
methods: {
click: function() {
this.$refs.childComponent.setValue(2.0);
}
}
})
For more info, see Vue 3 docs on component refs or Vue 2 documentation on refs.
What you are describing is a change of state in the parent. You pass that to the child via a prop. As you suggested, you would watch that prop. When the child takes action, it notifies the parent via an emit, and the parent might then change the state again.
var Child = {
template: '<div>{{counter}}</div>',
props: ['canI'],
data: function () {
return {
counter: 0
};
},
watch: {
canI: function () {
if (this.canI) {
++this.counter;
this.$emit('increment');
}
}
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
data: {
childState: false
},
methods: {
permitChild: function () {
this.childState = true;
},
lockChild: function () {
this.childState = false;
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<my-component :can-I="childState" v-on:increment="lockChild"></my-component>
<button #click="permitChild">Go</button>
</div>
If you truly want to pass events to a child, you can do that by creating a bus (which is just a Vue instance) and passing it to the child as a prop.
You can use $emit and $on. Using #RoyJ code:
html:
<div id="app">
<my-component></my-component>
<button #click="click">Click</button>
</div>
javascript:
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
},
created: function() {
this.$parent.$on('update', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
click: function() {
this.$emit('update', 7);
}
}
})
Running example: https://jsfiddle.net/rjurado/m2spy60r/1/
A simple decoupled way to call methods on child components is by emitting a handler from the child and then invoking it from parent.
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue(value) {
this.value = value;
}
},
created() {
this.$emit('handler', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
setValueHandler(fn) {
this.setter = fn
},
click() {
this.setter(70)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<my-component #handler="setValueHandler"></my-component>
<button #click="click">Click</button>
</div>
The parent keeps track of the child handler functions and calls whenever necessary.
Did not like the event-bus approach using $on bindings in the child during create. Why? Subsequent create calls (I'm using vue-router) bind the message handler more than once--leading to multiple responses per message.
The orthodox solution of passing props down from parent to child and putting a property watcher in the child worked a little better. Only problem being that the child can only act on a value transition. Passing the same message multiple times needs some kind of bookkeeping to force a transition so the child can pick up the change.
I've found that if I wrap the message in an array, it will always trigger the child watcher--even if the value remains the same.
Parent:
{
data: function() {
msgChild: null,
},
methods: {
mMessageDoIt: function() {
this.msgChild = ['doIt'];
}
}
...
}
Child:
{
props: ['msgChild'],
watch: {
'msgChild': function(arMsg) {
console.log(arMsg[0]);
}
}
}
HTML:
<parent>
<child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
The below example is self explainatory. where refs and events can be used to call function from and to parent and child.
// PARENT
<template>
<parent>
<child
#onChange="childCallBack"
ref="childRef"
:data="moduleData"
/>
<button #click="callChild">Call Method in child</button>
</parent>
</template>
<script>
export default {
methods: {
callChild() {
this.$refs.childRef.childMethod('Hi from parent');
},
childCallBack(message) {
console.log('message from child', message);
}
}
};
</script>
// CHILD
<template>
<child>
<button #click="callParent">Call Parent</button>
</child>
</template>
<script>
export default {
methods: {
callParent() {
this.$emit('onChange', 'hi from child');
},
childMethod(message) {
console.log('message from parent', message);
}
}
}
</script>
If you have time, use Vuex store for watching variables (aka state) or trigger (aka dispatch) an action directly.
Calling child component in parent
<component :is="my_component" ref="my_comp"></component>
<v-btn #click="$refs.my_comp.alertme"></v-btn>
in Child component
mycomp.vue
methods:{
alertme(){
alert("alert")
}
}
I think we should to have a consideration about the necessity of parent to use the child’s methods.In fact,parents needn’t to concern the method of child,but can treat the child component as a FSA(finite state machine).Parents component to control the state of child component.So the solution to watch the status change or just use the compute function is enough
you can use key to reload child component using key
<component :is="child1" :filter="filter" :key="componentKey"></component>
If you want to reload component with new filter, if button click filter the child component
reloadData() {
this.filter = ['filter1','filter2']
this.componentKey += 1;
},
and use the filter to trigger the function
You can simulate sending event to child by toggling a boolean prop in parent.
Parent code :
...
<child :event="event">
...
export default {
data() {
event: false
},
methods: {
simulateEmitEventToChild() {
this.event = !this.event;
},
handleExample() {
this.simulateEmitEventToChild();
}
}
}
Child code :
export default {
props: {
event: {
type: Boolean
}
},
watch: {
event: function(value) {
console.log("parent event");
}
}
}
I will be happy if i can either trigger and event or call a method within the vue-button component when the user object gets updated. I need to make sure that this only happens on this specific vue-button and not on any other ones on the page.
The button controls a state object that styles the button depending on response from the server. Will display red if an error response is returned or green if a successful response is returned. In ether case the button then is also disabled so users can't spam click it. I need to have the ability to reset the button from the parent when the user is updated.
I do not believe that a global event bus is the solution because i need granular control over which components respond to the event and this button is used in a lot of places.
<template>
<div class="row">
<div class="col-12">
<basic-input-field :resource="user" :set-resource="setUserProperty" property="name" label="Name"></basic-input-field>
<basic-input-field :resource="user" :set-resource="setUserProperty" property="email" label="Email"></basic-input-field>
<basic-input-field :resource="user" :set-resource="setUserProperty" property="password" label="Password"></basic-input-field>
<vue-button :on-click="updateUser" label="Save"></vue-button>
</div>
</div>
</template>
<script>
import axios from 'axios';
import basicInputField from '../general/forms/basic-input-field.vue';
import vueButton from '../general/buttons/vue-button.vue';
export default {
name: 'user',
data() {
return {
};
},
mixins: [],
components: {
basicInputField,
vueButton
},
computed: {
user() {
return this.$store.state.user;
},
},
mounted() {
this.$httpGet('user', {id: 5});
},
watch: {
'user': function (newUser) {
// I want to trigger an event inside the component vue-button
// I do not want to trigger then event in every vue-button component on the page just this vue-button
// I need to call a resetStatus method within the vue-button component when the user changes
// This would have worked in vue 1 with an event 'resetStatus' that would call the method in the vue-button component
this.$broadcast('resetStatus');
}
},
methods: {
setUserProperty(property, value) {
this.$store.commit('UPDATE_MODULE_RESOURCE', {module: 'user', resource: property, value: value});
},
updateUser() {
return this.$httpPut('user', {id: this.user.id}, this.user);
}
},
};
</script>
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