I'm new to vue.js and am trying to understand how $emit can be used. In the following code, every input element except for the last one works as expected.
My assumption in the last input element is that calling $emit with an event name would be the same as calling the function which has that event name, but it doesn't call that function. What is occurring with this $emit?
I've read through Binding Native Events to Components and that shows that it is to be used differently as I'm doing it. All this approach started from me watching a YouTube video (7 Secret Patterns...), specifically at this time https://youtu.be/7lpemgMhi0k?t=21m57s where you can see that usage on the slide.
Here is the code in JSFiddle https://jsfiddle.net/sbtmfweq/
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="test-app">
<input v-model="text1" placeholder="edit me" #keyup.enter="submit"> {{ text1 }}
<br><br>
<input :value="text2" #input="inputEvent" #keyup.enter="submit"> {{ text2 }}
<br><br>
<input :value="text2" #input="inputEvent($event)" #keyup.enter="submit"> {{ text2 }}
<br><br>
<input :value="text2" #input="$emit('inputEvent', $event.target.value)"> {{ text2 }} | {{reversedText2}}
</div>
<script>
new Vue({
el: '#test-app',
data: {
text1: 'text1',
text2: 'text2',
},
methods: {
log: console.log,
submit: function(event) {
console.log("submit -->", event, event.target.value, '<--')
},
inputEvent: function(event) {
console.log("input 2 -->", event.target.value, '<--')
this.text2 = event.target.value
},
},
watch: {
text1: {
handler: function(newValue, oldValue) {
console.log("input 1 -->", newValue, '<-->', oldValue, '<--')
}
}
},
computed: {
reversedText2: function () {
return this.text2.toUpperCase()
}
}
})
</script>
It's just a simple issue. Whenever you are dispatching an event, you have to add listener to it. In $emit, the first param is actually the name of the event.
As written in docs for $emit.
vm.$emit( eventName, […args] )
In the below code, I have added an event listener, using $on. Also I have changed the arg for $emit.
<input :value="text2" #input="$emit('inputEvent', $event)"> {{ text2 }} | {{reversedText2}}
<script>
new Vue({
...
created(){
this.$on('inputEvent', this.inputEvent);
}
...
})
</script>
I hope it helps.
$emit works like this.
Say you have 2 components, <parent> and <child>, where <child> is inside <parent>'s template.
When <child> emits an event, it can be handled by the <parent> component only (unless you register a listener via $on, but that's unrelated to your scenario). In your code, the $emit call is executed within the scope of the parent component (which is the root component instantiated with new Vue()); that is, the parent component is the one that's emitting the event. The root component has no parent so the emit call is pointless.
My assumption in the last input element is that calling $emit with an event name would be the same as calling the function which has that event name, but it doesn't call that function.
This isn't true; the name of the event has no relation to the method registered as a listener for that event in the parent.
it's normal that $emit doesn't work in your case.
In fact, $emit is used for communication between parent and child components. Here, you are using $emit but you haven't defined any parent component.
Here is the docs for the custom events.
Hope it helps!
Related
Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.
I am emitting an event in a child component so I can, if I need to, prevent it from happening in the parent.
Is there a way to retrieve event object so I can prevent it in the parent component like this ?
// child component event
this.$emit('navigationTab');
// parent template event bind
#onTab="on_tab($event)"
// parent handler method
on_tab(event) {
event.preventDefault
// ...logic etc
}
It only works if y pass the event object like this I wish to catch $event directly without sending the object inside the event, which is probably already sent in the emit
// child component event
this.$emit('navigationTab', event);
// parent template bind
#navigationTab="on_tab"
// parent handler method
on_tab(event) {
event.preventDefault
// ...logic etc
}
Event argument is automatically passed as an parameter into handler method (see example bellow)
You can't use preventDefault() on custom events, only on native browser events. And of course you don't need to because there is not any "default" behavior for custom events (emitted with $emit)
const myComponent = Vue.component('myComponent', {
template: `
<div>
<button #click="$emit('myEvent', 'event payload')">Click me!</button>
</div>
`
})
const app = new Vue({
components: {myComponent} ,
template: `
<div>
<myComponent #myEvent="handle" />
</div>
`,
methods: {
handle(event) {
console.log("Event received:", event)
}
}
})
app.$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
I have read this post which goes in depth about renderless components:
https://adamwathan.me/renderless-components-in-vuejs/
A renderless component would pretty much look like this:
export default {
render() {
return this.$scopedSlots.default({})
},
}
Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.
In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.
How would I go about adding a click listener to the element that is being passed into the slot?
Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.
See createElements Arguments, the second arg is the object to enhance
A data object corresponding to the attributes you would use in a template. Optional.
console.clear()
Vue.component('renderless', {
render(createElement) {
var vNode = this.$scopedSlots.default()[0]
var children = vNode.children || vNode.text
const clone = createElement(
vNode.tag,
{
...vNode.data,
on: { click: () => alert('clicked') }
},
children
)
return clone
},
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<renderless>
<button type="button" slot-scope="{props}">Click me</button>
</renderless>
</div>
Here's one way to go about this.
Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.
Vue.component('renderless-action-wrapper', {
props: ['action'],
render() {
return this.$scopedSlots.default({
action: this.action,
});
},
});
Then another component which uses the aforementioned wrapper would enclose a customisable slot with a #click handler, which invokes the action that is passed in when triggered.
Vue.component('clickable', {
props: ['action'],
template: `
<renderless-action-wrapper :action="action">
<span slot-scope="{ url, action }">
<span #click="action()">
<slot name="action"></slot>
</span>
</span>
</renderless-action-wrapper>
`,
});
Finally, wire up the specialised version of the wrapper.
<clickable :action="doAjaxRequest">
<button type="button" slot="action">Button</button>
</clickable>
Here's a live example of the above suggestion you can play around with.
So I have the following element:
<input v-on:click="$emit('addPartId', $event)" v-bind:value="13209" name="add_13209" type="checkbox">
Which then calls the following method:
methods: {
....
addPartId(evnt) {
console.log(evnt);
}
},
In the parent container and is passed to the child:
<table-body
v-bind:items="items"
v-bind:columns="columns"
v-bind:sort-column="sortColumn"
v-bind:direction="direction"
#sort="sort"
#addPartId="addPartId"
>
</table-body>
The question I have, that I can't find on stack, is how do I register a click event so that when the checkbox is clicked I get the event object (I want the value, from v-bind:value, of the checkbox.
You should use event name which is the kebab-cased version, check Vue Guide: Custom Event,
As the guide says:
Unlike components and props, event names will never be used as
variable or property names in JavaScript, so there’s no reason to use
camelCase or PascalCase. Additionally, v-on event listeners inside DOM
templates will be automatically transformed to lowercase (due to
HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent
– making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event
names.
Vue.component('my-checkbox', {
template: `
<input v-on:click="$emit('add-part-id', {'whole': $event, 'value':13209})" v-bind:value="13209" name="add_13209" type="checkbox">
`
})
Vue.component('my-another-checkbox', {
template: `
<input v-on:click="$emit('add-part-id', $event)" v-bind:value="13209" name="add_13209" type="checkbox">
`
})
new Vue({
el: '#emit-example-simple',
methods: {
getChecked1: function (ev) {
console.log('checkbox1', ev.value)
console.log('checkbox1', ev.whole.target.value)
},
getChecked2: function (ev) {
console.log('checkbox2', ev.target.value)
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="emit-example-simple">
First Example: <my-checkbox #add-part-id="getChecked1"></my-checkbox>
Second Example: <my-another-checkbox #add-part-id="getChecked2"></my-another-checkbox>
</div>
I have three nested components
<parent #event="handle">
<inbetween #event="$emit('event')">
<child> // emits event
</child>
</inbetween>
</parent>
so, when a child emits the event, I have to add that annoying part #event="$emit('event')" to the component inbetween, otherwise parent does not receive any event.
I hope it is not supposed to work that way. I wonder what could be wrong with it?
Yes thats how its supposed to work. Events go only from child to parent, it does not go from child up to grand parent. Therefore you have to emit the event from both child and inbetween component.
If you want to avoid this method you can use Event Bus:
https://medium.com/#andrejsabrickis/https-medium-com-andrejsabrickis-create-simple-eventbus-to-communicate-between-vue-js-components-cdc11cd59860
alligator.io/vuejs/global-event-bus
This is actually intentional. The reasoning is, when looking at the code for one component and you see that it's listening to an event, you can then look at the template to see where that event is coming from. If events could reach a component arbitrarily deep, it would be harder to figure out how and from where that event is being triggered.
However, Vue used to have a way of doing what you want to do, through the methods $broadcast and $dispatch, and they were eventually removed for the reasons talked about here. Here's a page from the docs which explains why, along with possible solutions, such as using a global event bus, or a centralized state management solution such as Vuex.
Vue custom events don't bubble.
The recommended way to handle ancestor/sibling communication in complex cases is using Vuex. If you have simple needs you can create Vue instance to use as event hub.
You would create a global variable:
var eventHub = new Vue(); // use a Vue instance as event hub
To emit events you would use in any component:
eventHub.$emit('myevent', 'some value');
And, to listen to that event, again, in any component, do:
eventHub.$on('myevent', (e) => {
console.log('myevent received', e)
});
Demo:
var eventHub = new Vue(); // use a Vue instance as event hub
Vue.component('parent', {
template: "#parent-tpl",
created() {
eventHub.$on('event', (e) => {
console.log('event received at parent! value:', e);
});
}
});
Vue.component('inbetween', {
template: "#inbetween-tpl"
});
Vue.component('child', {
template: "#child-tpl",
methods: {
emitEvent() {
eventHub.$emit('event', 123);
}
}
});
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="parent-tpl">
<div>
<inbetween></inbetween>
</div>
</template>
<template id="inbetween-tpl">
<div>
<child></child>
</div>
</template>
<template id="child-tpl">
<div>
<h1>I'm the child</h1>
<button #click="emitEvent">Trigger EVENT</button>
</div>
</template>
<div id="app">
<p>{{ message }}</p>
<parent></parent>
</div>
Note: If creating a dedicated instance as event hub is something complicated in your environment, you can replace eventHub with this.$root (inside your components) and use your own Vue instance as hub.