Programmatically bind custom events for dynamic components in VueJS - vue.js

In my vuejs app I use dynamic component in the following way:
<mycomponent>
<component ref="compRef" :is="myComponent" v-bind="myComponentProps"></component>
<div class="my-buttons">
<my-button label="Reset" #click="reset()"/>
</div>
</mycomponent >
myComponent is a prop on the parent component which hold the actual component to inject.
myComponentProps are also prop which holds the porps for the injected instance.
I would like to know how can I also dynamically bind listeners to the component - I have understand that I cannot send an object to v-on with multiple events.
I was thinking about adding it programatically however haven't found any info about how it can be done for Vue custom events (kind for addEventListener equivalent for custom events)
Any tip would be much appreciated!

With Vue 2.2+, you can programmatically add an event listener with $on(eventName, callback):
new Vue({
el: '#app',
created() {
const EVENTS = [
{name: 'my-event1', callback: () => console.log('event1')},
{name: 'my-event2', callback: () => console.log('event2')},
{name: 'my-event3', callback: () => console.log('event3')}
]
for (let e of EVENTS) {
this.$on(e.name, e.callback); // Add event listeners
}
// You can also bind multiple events to one callback
this.$on(['click', 'keyup'], e => { console.log('event', e) })
}
})
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<div>
<!-- v-on:EVENTNAME adds a listener for the event -->
<button v-on:click="$emit('my-event1')">Raise event1</button>
<button v-on:click="$emit('my-event2')">Raise event2</button>
<button v-on:click="$emit('my-event3')">Raise event3</button>
</div>
<div>
<!-- v-on shorthand: #EVENTNAME -->
<button #click="$emit('my-event1')">Raise event1</button>
<button #click="$emit('my-event2')">Raise event2</button>
<button #click="$emit('my-event3')">Raise event3</button>
</div>
</div>
With Vue 2.6+, you can add an event listener dynamically in the template:
new Vue({
el: '#app',
data: {
eventname: 'click',
},
methods: {
handler(e) {
console.log('click', e.target.innerText)
}
}
})
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<button #[eventname]="handler">Raise dynamic event</button>
<!-- Set dynamic key to null to remove event listener -->
<button #click="eventname = null">Unbind event</button>
</div>
You can also declaratively bind multiple event listeners with v-on="{event1: callback, event2: callback, ...}":
new Vue({
el: '#app',
methods: {
onClick() { console.log('click') },
onKeyUp(e) { console.log('keyup', e.keyCode) }
}
})
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<input type="text" placeholder="type here" v-on="{click: onClick, keyup: onKeyUp}">
</div>

Related

Calling function from outside of component but rendered in v-for loop

I have some component which is rendered 6 times in v-for loop. I'm trying to do onclick function which will call method inside of specified chart component. But now i'm getting errors like this.$refs.chart1.pauseChart() is not an function. This is how i'm trying to achieve it:
<BaseChart ref="`chart$[index]`" #click="pauseChart(index)"/>
pauseChart(index) {
this.$refs.[`chart${index}`].pauseChart()
}
refs inside v-for are arrays rather than singulars. Therefore, if you have
<template v-for="(something, index) in list">
<BaseChart ref="chart" :key="index" #click="pauseChart(index)" />
</template>
you should use
methods:
{
pauseChart(idx)
{
this.$refs.chart[idx].pauseChart();
}
}
For more information - refer to Vue documentation
ref will be having an array. hence, you have to access that with 0 index.
Live Demo :
Vue.component('child', {
methods: {
pauseChart() {
console.log('child method call');
}
},
props: ['childmsg'],
template: '<p v-on="$listeners">{{ childmsg }}</p>',
});
var app = new Vue({
el: '#app',
methods: {
pauseChart(chartIndex) {
this.$refs[chartIndex][0].pauseChart();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="index in 6" :key="index">
<child childmsg="Hello Vue!" :ref="`chart${index}`" #click="pauseChart(`chart${index}`)">
</child>
</div>
</div>

Mass chain handler in Vue2

I have a component (Vue2) with several buttons. After processing every button's #click, I need to call a method.
How can I do it without adding a call into each #click? Of course, all #clicks are different.
The only idea I see is to create component for the button, maybe there are better solutions?
Just use single handler for all #click events and pass the handler function for the specific button as an argument....
new Vue({
el: "#app",
methods: {
handleClick: function(handler) {
handler()
console.log("Common code executed")
},
button1: function() {
console.log("Button 1 clicked")
},
button2: function() {
console.log("Button 2 clicked")
},
button3: function(a) {
console.log(`Button 3 clicked (with additional argument: "${a}")`)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<button type="button" #click="handleClick(button1)">Button 1</button>
<button type="button" #click="handleClick(button2)">Button 2</button>
<button type="button" #click="handleClick(() => button3('hi!'))">Button 3 (with additional argument)</button>
</div>

How to use keyboard buttons to manipulate numbers

How to click a button not only directly clicking mouse, but pressing a button on a keyboard (in this case, its a keyboard button with a value "1" that hasevent.key` = 1)???
new Vue({
el: "#app",
data: {
one: 1
},
methods: {
add(){
this.one++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button v-on:1 #click="add">One</button>
<span> {{ one }}</span>
</div>
If you want to listen for global keyboard events you'll need to add the listener to the window, otherwise you need focus on element that the event is dispatched from.
It's just plain vanila js from there:
new Vue({
el: "#app",
data: {
one: 1
},
created() {
const component = this;
this.handler = function (e) {
e.keyCode == 38 && component.add()
e.keyCode == 40 && component.remove()
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
},
methods: {
remove() {
this.one--;
},
add(){
this.one++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="add">One</button>
<span> {{ one }}</span>
</div>
This is covered in the VueJS documentation.
From the documentation:
Key Modifiers
When listening for keyboard events, we often need to check for
specific keys. Vue allows adding key modifiers for v-on when listening
for key events:
<!-- only call `vm.submit()` when the `key` is `Enter` --> <input
v-on:keyup.enter="submit">
You can directly use any valid key names exposed via KeyboardEvent.key
as modifiers by converting them to kebab-case.
<input v-on:keyup.page-down="onPageDown">
In the above example, the handler will only be called if $event.key is
equal to 'PageDown'.

How to append a component on button click

I have a component that I have loading in app.vue. I have a button that I'm using to call a method to try and add another instance of that component so I can have however many instances of that component on the page
When I call div.append(EquipmentInput);, it just appends [object Object] to the DOM.
html:
<div id="eDiv">
<EquipmentInput></EquipmentInput>
<button class="block mx-auto px-4 py-2 rounded-full bg-gray-200 hover:bg-blue-300 hover:font-bold" v-on:click.prevent="addEquipmentLine">+</button>
</div>
<div class="text
method:
addEquipmentLine(){
let eDiv = document.getElementById('eDiv');
eDiv.append(EquipmentInput);
},
I think it should be appending another instance of the component, but it's just appending that object text.
I believe you do something like this: Inserting in DOM.
Use a $ref instead of an ID
Create a new element and $mount it
Get the $ref of the parent and append the DOM element that way
const EquipmentInput = Vue.extend({
template: "<div>Hello World</div>"
});
const app = new Vue({
el: "#app",
components: {
EquipmentInput
},
methods: {
add() {
const instance = new EquipmentInput();
instance.$mount();
this.$refs.ediv.appendChild(instance.$el);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div ref="ediv"></div>
<button #click="add">Click Me</button>
</div>

VueJS - Ajax communication between templates

I'm very new to VueJS and i'm having a difficult to share a result from Two template, that come from AJAX Request.
This is the home page:
<div>
<search-bar></search-bar>
<tracking-results></tracking-results>
</div>
This is the search-bar component, where i have a text input field and after press the button, it will do an Ajax Request:
<template>
<div class="row">
<div class="col-lg-8 col-lg-offset-3">
<div class="col-lg-5">
<div class="input-group">
<input type="text" class="form-control" placeholder="Numero Spedizione" v-model="trackingNumber">
<span class="input-group-btn">
<button class="btn btn-default"
type="button"
#click.prevent="search">Ricerca</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-3 -->
</div>
</div><!-- /.row -->
</template>
<script>
export default {
data() {
return {
trackingNumber: '',
}
},
methods: {
search() {
Vue.http.options.emulateJSON = true;
this.$http.post('/endpoint').then(function (response) {
var parsedResponse = JSON.parse(response.data) || undefined;
/* HERE I WANT TO SEND THE RESPONSE TO ANOTHER COMPONENT */
}, function (err) {
console.log('ERROR', err);
});
}
}
}
</script>
I did tried with $broadcast, but my components arent child, are sibling.
I did see a way can be Vuex, but my application will not be written entirely with Vue. I will use this framework just to "simplify some Javascript process".
The only alternative i did find is to "merge" the search-bar and tracking-result in a single component. In this way the data will be "shared", and i can communicate with the state.
[Update: sync is removed in Vue 2, so you would need to follow the standard props-down, events-up design pattern]
You can have the parent viewmodel pass a prop to each of the components, using sync for the search bar. The search bar would populate the value in the ajax call, it would sync up to the parent and down to the tracking-results.
Some example code:
Vue.component('child1', {
props: ['ajaxData'],
methods: {
loadData: function () {
this.ajaxData = 'Some data is loaded';
}
},
template: '<div>Child1: {{ajaxData}} <button v-on:click="loadData">Load data</button></div>'
});
Vue.component('child2', {
props: ['ajaxData'],
template: '<div>Child2: {{ajaxData}}</div>'
});
new Vue({
el: 'body',
data: {
hi: 'Hello Vue.js!'
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<child1 :ajax-data.sync='hi'></child1>
<child2 :ajax-data='hi'></child2>
Ideally, you can send data to the parent, then the parent send data to the component via props. The parent handles the communication between the siblings.
Another way of doing it is using state management or vuex. But that depends on the complexity of your project. If it's a simple thing, I suggest to let the parent handle the communication.