Vuejs click event from checkbox? - javascript

I have a v-model on checkbox which values are assigned from a loop.
I want the click event to invoke a funtion where I need to access the data of the checked ones. When a click is trigerred, If I log the state it doesnot print the current clicked data of the checkbox. It prints previous clicked checkbox data. Should a event must be passed and accessed data in the function?
<div id="demo">
<ul>
<li v-for="mainCat in mainCategories">
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#click="check(checkedCategories)"
>
{{mainCat.merchantId}}
</li>
</ul>
{{ checkedCategories }}
</div>
script:
var demo = new Vue({
el: '#demo',
data: {
checkedCategories: [],
mainCategories: [{
merchantId: '1'
}, {
merchantId: '2'
}] //testing with data use: [{done:false,content:'testing'}]
},
methods: {
check: function(e) {
console.log(this.checkedCategories,e)
}
}
})
Js fiddle:http://jsfiddle.net/bmpfs2w2/

Use #change instead of #click. Click event is triggered before value is really changed.
<input type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
>
http://jsfiddle.net/eLzavj5f/

If you'd like the function to be called after the value has been changed, you can wrap your handler functionality inside of this.$nextTick(). You can read about $nextTick, but the gist is
Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.
So your handler will get called after the DOM gets updated, aka after your state has changed and the effects have been reflected in the DOM.
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
>
// ...
check (e) {
this.$nextTick(() => {
console.log(checkedCategories, e)
})
}

Altered Grant's solution
<input
type="checkbox"
:value="mainCat.merchantId"
id="mainCat.merchantId"
v-model="checkedCategories"
#change="check($event)"
/>
// ...
check (e) {
this.$nextTick(() => {
console.log(e.target.id)
})
}
Thanks Grant

Related

vue.js element selected by focus is not reactive

I have a listener to check what input was selected at last to add some kind of string/variable later into it.
created: function () {
document.addEventListener('focusin', this.focusChanged);
}
focusChanged(event) {
if (event.target.id !== 'variable-search') {
this.lastElement = event.target;
}
}
This seems to work fine, and when I click on an input field this.lastElement gets updated with the focused element. All these inputs have a v-model which can be a string in an object or just a plain string.
Now the thing is when I try to update the value by:
this.lastElement.value += variable;
Vue won't detect its changes, also in the Vue Developer tools the string won't get updated. But in the input field it does get updated. So this should be a reactivity thing.
When I add a new character into the input field (v-model) it does update again. So it's just when I update the string by this.lastElement it won't register its changes.
The thing is that the input fields are dynamic, so I don't know how many input fields are here and how many lists etc. So I need Vue to re-render the variable after the value of lastElement is updated.
Edit
I just tried it with an #focus here an example
<input v-model="testVar" #focus="lastElement = testVar">
If I update lastElement later on it doesn't update it for testVar but just for lastElement.
Changing values in DOM elements programmatically does not cause DOM events to fire. v-model relies on input (or change when using .lazy) events to update its bound variable. If you dispatch those events when you update the value in an input, the variable will react.
new Vue({
el: '#app',
data: {
items: ['one','two','three']
},
methods: {
addAddress() {
this.lastElement.value += 'address';
this.lastElement.dispatchEvent(new Event('input'));
this.lastElement.dispatchEvent(new Event('change'));
},
focusChanged(event) {
this.lastElement = event.target;
}
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div v-for="item, index in items">
<input v-model="items[index]" #focus="focusChanged">
{{item}}
</div>
<button type="button" #click="addAddress">+address</button>
</div>
You could add a ref attribute to each of the inputs and use the ref to update their values. For example, an input element could be:
<input v-model="testVar" ref="input1" id="input1" #focus="focusChanged">
In your methods:
methods: {
focusChanged(event) {
if (event.target.id !== 'variable-search') {
this.lastElement = event.target.id;
}
},
}
And where you want to update the value: this.$refs[this.lastElement].value += variable;

Vue.js blur with enter event - triggers both of them

I am trying to make it so that when I press enter OR if I lose focus from an element it triggers a function but when I press enter it then triggers the blur event also. So the function is called twice. It should only be called once.
<input v-on:blur="saveField('name')" keyup.enter="saveField('name')">
The problem is that my saveField() function hides the element, triggering also the blur event.
I guess the other issue is how not to have to write the same function call twice. (DRY).
You can use some kind of condition to check if the value needs to be updated. It seems complicated to avoid the two events from being triggered:
<div id="app">
<input v-model="inputValue" type="text" #blur="save()" #keyup.enter="save()">
<div v-for="saving in savings">
{{ saving }}
</div>
</div>
new Vue({
el: '#app',
data: {
inputValue: '',
savedValue: '',
savings: []
},
methods: {
save () {
if (this.inputValue !== this.savedValue) {
this.savings.push('Saving value ' + this.inputValue)
this.savedValue = this.inputValue
}
}
}
})
Here is a working JsFiddle: https://jsfiddle.net/L6kfz48m/

Firing change() event for input checkboxes doesn't trigger all event handlers

We have around twenty input checkboxes, and they're divided into six groups using CSS classes. We added an event handler for the change event on each of the six classes, and then we added a final event handler for the change event on the input element itself. It looks like this:
(HTML)
<input type="checkbox" name="cbox" class="A"> 1
<input type="checkbox" name="cbox" class="A"> 2
<input type="checkbox" name="cbox" class="B"> 3
<input type="checkbox" name="cbox" class="B"> 4
// ...
// checkboxes for classes C, D, E
// ...
<input type="checkbox" name="cbox" class="F"> 20
(jQuery)
$('.A').change(function() {
// doesn't get called
});
$('.B').change(function() {
// doesn't get called
});
$('.C').change(function() {
// doesn't get called
});
$('.D').change(function() {
// doesn't get called
});
$('.E').change(function() {
// doesn't get called
});
$('.F').change(function() {
// doesn't get called
});
$('input[name="cbox"]').change(function() {
// this one gets called
});
$('input[name="cbox"]').change();
When I finally call $('input[name="cbox"]').change(), the only event handler that gets called is the last one, i.e.
$('input[name="cbox"]').change(function() {
// this one gets called
});
All the other event handlers that I added (for classes A, B, C, D, E and F) don't get called. Why is this? Shouldn't $('input[name="cbox"]').change() trigger all of the event handlers, and not just the one?
Try this,
$('input.A').change(function(event) {
alert($(this).val());
});
jsfiddle
I dont know why its not working, Working to me i posted fiddle for sandbox
let classnamelist = ['a','b','c','d','e','f','g','l','m','n','o','p','q','r','s','y',]
for(let i = 0; i<classnamelist.length; i++){
let el = $('<input>').attr({
'type':'checkbox',
'name':'cbox'
}).addClass(classnamelist[i]);
$('body').append(el);
$('.'+classnamelist[i]).change(function(){
alert($(this).is(':checked'))
})
}
$('input[name=cbox]').change();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
If you're adding the checkboxes dynamically you should try :
$(document).on('change', '.A', function() {
// do stuff
});

Check if a component has an event listener attached to it

Assuming there's some <Form> component. It can be called with a #cancel event listener attached to it and if it's the case, I want to show the cancel button that triggers this event. If there's no #cancel event, the cancel button should not be visible.
Is there a way to check if a component has event listener attached to it?
Currently I do:
<template>
<form>
<button v-if="cancelEventPassed" #click="$emit('cancel')">Cancel</button>
</form>
</template>
And call it like this:
<Form :cancelEventPassed="true" #cancel="handle_cancel" />
either
<Form/>
Is it possible to achieve this without using any additional property like cancelEventPassed?
When there are listeners attached to a component they are available in the $listeners property of the component.
You can use that property to determine if a specific listener is available. For example, here is a computed property that checks for the existence of a cancel listener.
computed:{
hasCancelListener(){
return this.$listeners && this.$listeners.cancel
}
}
And here is an example of that used in a component.
console.clear()
Vue.component("CustomForm", {
template:`
<div>
<h1>Custom Form</h1>
<button v-if="hasCancelListener" #click="$emit('cancel')">I have a listener!</button>
</div>
`,
computed:{
hasCancelListener(){
return this.$listeners && this.$listeners.cancel
}
},
})
new Vue({
el: "#app",
methods:{
onCancel(){
alert('canceled')
}
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<custom-form #cancel="onCancel"></custom-form>
<hr>
<custom-form></custom-form>
</div>
In Vue 3, the $listeners object has been removed. The listeners are now part of the $attrs object and are prefixed with on.
In order to check if a particular listener is present or not in a child component, you can do:
computed: {
hasCancelListener() : boolean {
return (this.$attrs && this.$attrs.onCancel) as boolean
}
}
The child component is called as:
<custom-form #cancel="onCancel"></custom-form>
This example for Vue 3
this.$attrs.onCancel
Explanation
Vue 3 has removed the $listeners object instead listener will be parted from the $attrs object with the on.. prefix.
Reference
https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html#overview
You can check if listener exists like that:
this._events['listener-name']
If you're here looking for Vue 3 script setup or setup function solution, you can check the attrs key in getCurrentInstance function
<template>
<form>
<button #click="$emit('cancel')">Cancel</button>
</form>
</template>
<custom-form #cancel="onCancel"></custom-form>
onMounted(() => {
const instance = getCurrentInstance() // only available inside lifecycle hooks
console.log(instance?.attrs?.onCancel)
})

Backbone view event handler for checkbox and labels

I'm trying to find out whether a checkbox ids cheked o not after a user has clicked it. The checkbox is wrapped in a label which must be clickable too. My HTML code is:
<div id="view_container">a</div>
<script type="text/template" id="view_template">
<ul>
<li>
<label>
<input type="checkbox" checked="" value="a">A option
</label>
</li>
<li>
<label>
<input type="checkbox" value="b">B option
</label>
</li>
<li>
<label>
<input type="checkbox" value="c">C option
</label>
</li>
</ul>
</script>
Ans the JavaScript:
View = Backbone.View.extend({
initialize: function() {
console.log('in the init');
this.render();
},
render: function() {
var template = _.template($("#view_template").html(), {});
this.el.html(template);
},
events: {
'click ul li label': 'clicked',
},
clicked: function(e) {
e.stopPropagation();
console.log('e.target is ' + e.target);
console.log('e.currentTarget is ' + e.currentTarget);
var isChecked = $(e.currentTarget).find('input:first').is(':checked');
var selected = $(e.currentTarget).find('input:first').val().trim();
console.log('isChecked is ' + isChecked);
},
});
var view = new View({
el: $("#view_container")
});
The jsfiddle is here.
There are a few problems. If i click the input, it works fine and the value of isChecked is true. However, if i clicked the label, the function clicked is executed twice with console.log output of:
e.target is [object HTMLLabelElement]
e.currentTarget is [object HTMLLabelElement]
isChecked is false
e.target is [object HTMLInputElement]
e.currentTarget is [object HTMLLabelElement]
isChecked is true
So isChecked is false the first time.
So, how do I know if the checkbox is being checked or not, regardless of if user clicks on label or directly on the input? And how do I stop handler from executing twice when user clicks on label?
The issue is that, by nature, the labels containing <input>s or connected to them via for attribute triggers a click event on the associated input. So there are 2 click events:
Actual click on <label>
Click triggered on <input> by the label.
So if we listen to click events on label, we will catch both of them in this case - because the click triggered on <input> will also bubble up to <label> since it's a parent.
We can solve this by listening to clicks on the checkbox itself, rather than the label.
You can do the following using jQuery is method with :checkbox and :checked selectors as shown below.
View = Backbone.View.extend({
initialize: function () {
this.render();
},
template: _.template($("#view_template").html()),
events: {
'click ul li :checkbox': 'clicked',
},
render: function () {
this.$el.append(this.template({}));
},
clicked: function (e) {
var $target = $(e.target);
var selected = $target .is(':checked');
console.log('selected: ', selected, 'value: ', $target.val());
}
});
var view = new View({
el: $("#view_container")
});
Updated fiddle
Note that it's a good practice to cache the template function rather than calling _template each time render is called.
Please see the best answer here
Why Does a Label Inside an Input Trigger a Click Event .
In short, it's a specification of label element.

Categories

Resources