Click event on label not firing (class doesn't change) - javascript

I have a checkbox inside a label. I added the event listener for the click event to the label, so as to trigger the class.
It does work when I click the checkbox.
However, if I click the label, nothing changes. Why is that, considering that the event is binded to the label and not the checkbox?
const formCheck = document.querySelector('.drinos-checker')
formCheck.addEventListener('click', function () {
formCheck.classList.toggle('checkerActive')
})
.checkerActive {
background-color: red;
}
<label class="drinos-checker"><input class="drinos-checkbox" type="checkbox" name="checkbox" value="value">Renovation</label>

Consider this simple example:
const formCheck = document.querySelector('.check')
formCheck.addEventListener('click', function () {
console.log("clicked!")
})
<input class="check" type="checkbox">
When you check the checkbox, you can see that the event is captured and then logged.
What happens if we then add a label?
Let's do that. If we then leave the code unchanged, appears that a click on a label does also trigger our click event, though it was binded to the checkbox.
const formCheck = document.querySelector('.check')
formCheck.addEventListener('click', function () {
console.log("clicked!")
})
<label><input class="check" type="checkbox">A label</label>
That's how it works. But we're not finished yet...
What happens if I bind the event on the label, not on the checkbox?
const formCheck = document.querySelector('label');
formCheck.addEventListener('click', function () {
console.log("clicked!")
})
<label><input class="check" type="checkbox">A label</label>
You have seen that correct: the click event triggers two times. But how is it even possible? Let's break down what happens here:
the label works this way that it triggers a click event in the checkbox
once you click the label, the event that you binded is triggered
but since the label itself "clicks" the checkbox, the event is triggered again, because the checkbox is in the label. This way, the event is called twice.
So, what does it mean?
You may have understood that already: the class doesn't change because it toggles two times on a single click. I.e. it toggles and then immediately toggles again, which results in you not noticing any changes.
How can one fix that?
There's a quick fix: you could replace your click event with change event. This way:
the label triggers checkbox
the change event on checkbox is called
the label itself doesn't have a change event, thus everything works as intended
"Working-as-intended" example:
const formCheck = document.querySelector('.drinos-checker')
formCheck.addEventListener('change', function () {
formCheck.classList.toggle('checkerActive')
})
.checkerActive {
background-color: red;
}
<label class="drinos-checker"><input class="drinos-checkbox" type="checkbox" name="checkbox" value="value">Renovation</label>

Instead of adding click event to the label, add it to the input element -
const formCheck = document.querySelector('.drinos-checker');
const inputCheck = document.querySelector('input');
inputCheck.addEventListener('click', function() {
formCheck.classList.toggle('checkerActive')
})
.checkerActive {
background-color: red;
}
<label class="drinos-checker"><input class="drinos-checkbox" type="checkbox" name="checkbox" value="value">Renovation</label>

Related

Why does preventDefault on parent element disable programmatically checking child checkbox on click?

A div element's click event has e.preventDefault() at the beginning. That makes manually clicking the checkbox input or it's associated label inside no longer works.
With the help of some more JavaScript code, manually clicking the label generates expected results because the checkbox is now programmatically checked/unchecked.
However, manually clicking the checkbox input still does not work despite the fact that similar JavaScript code for programmatically checking/unchecking has been implemented. Why?
document.querySelector('div').onclick = function (e)
{
e.preventDefault();
if (e.target.tagName.toUpperCase() == 'LABEL')
{
e.target.previousElementSibling.checked = !e.target.previousElementSibling.checked;
}
else if (e.target.tagName.toUpperCase() == 'INPUT')
{
e.target.checked = !e.target.checked;
}
}
<div>
<input id="check" type="checkbox">
<label for="check">Label Text</label>
</div>
Prevent default propagates often propagates down to the child. There is a way to stop that from happening by using using event.stopPropagation. Read here to learn more about other useful methods that might help your cause.
https://chunkybyte.medium.com/the-one-with-event-propagation-and-e-preventdefault-part-1-6d84f3c4220
Clicking the label sets the checked property as expected because there is no default action related to the label to be canceled. Clicking the input sets the property as expected, but due to the default action (toggling the checked property) being prevented it reverts to its previous state.
see: Why does preventDefault on checkbox click event returns true for the checked attribute? for more in depth discussion.
document.querySelector('div').onclick = function (e)
{
e.preventDefault();
if (e.target.tagName.toUpperCase() == 'LABEL')
{
e.target.previousElementSibling.checked = !e.target.previousElementSibling.checked;
}
else if (e.target.tagName.toUpperCase() == 'INPUT')
{
console.clear();
console.log(e.target.checked);
e.target.checked = !e.target.checked;
console.log(e.target.checked);
}
}
<div>
<input id="check" type="checkbox">
<label for="check">Label Text</label>
</div>
Edit
In response to your comment I think the cleanest solution would be to explicitly apply listeners to those elements that you want to control outside of the provided API methods calling stopPropagation() on them to avoid triggering the parent listeners.
You can then handle whatever logic you need without having to work around artifacts of the outside methods. If you need the parent to listener to run as well you can programmatically activate it after your control logic is finished.
// original API listener
document.querySelector('.container').addEventListener('click', function (e) {
e.preventDefault();
// add container specific code
console.log('clicked div');
});
// your isolated listener
document.querySelector('.checkbox-container').addEventListener('click', function (e) {
e.stopPropagation(); // stop the event from propagating to the parent listener
// add checkbox specific code
console.clear();
console.log('clicked checkbox container');
// even programmatically 'clicking' parent if you need to
e.currentTarget.parentElement.click();
});
.container {
width: 150px;
height: 60px;
background-color: lightgray;
}
.checkbox-container {
display: inline-block;
background-color: aquamarine;
}
<div class="container">
<div class="checkbox-container">
<input id="check" type="checkbox">
<label for="check" >Label Text</label>
</div>
</div>

How to keep track of focused elements in a form

I'm new to vuejs and I'm wondering what would be the best way to keep track of the latest focused input/textarea in a form in order to programatically modify their values from the parent component.
Example
Form
Input1
Input2 -> focused
Textarea
Button (onclick -> appends "hello" to the focused input)
Answer:
You can create a data property that tracks the currently focused/last focused input element. In the below example this is called current_focus.
To do this you could use the focus event - but because focus doesn't bubble you would have to apply it to each individual input element manually.
It is easier to provide a focusin event handler to the parent. This event, unlike focus, bubbles up the DOM from any child to its parent. This allows you to utilize an event delegation pattern.
Event Delegation means that you apply one handler to a parent for an event, then do something depending on the source of the event. This means when we receive a focusin event, we can simply check if the focused element is an input element, then update our data property ( current_focus )
Code Sandbox Example:
https://codesandbox.io/s/focus-handler-vzip0
Code Example:
FocusDemo.js
<template>
<div v-on:focusin="updateFocus">
<input name="one">
<br>
<input name="two">
<br>
<input name="three">
<br>
<button #click="handleClick">Add Text To Focused</button>
</div>
</template>
<script>
export default {
name: "FocusDemo",
data: function() {
return {
current_focus: undefined
};
},
methods: {
updateFocus: function(e) {
let element = e.target;
if (element.matches("input")) {
this.current_focus = element;
}
},
handleClick: function(e) {
if (this.current_focus) {
this.current_focus.value = "Button was clicked!";
}
}
}
};
</script>

Prevent function execution twice within two triggered events

I have the following code:
myInput.change(function (e) { // this triggers first
triggerProcess();
});
myButton.click(function (e) { // this triggers second
triggerProcess();
});
The problem with the above is when I click myButton both events are triggered and triggerProcess() is fired twice which is not desired.
I only need triggerProcess() to fire once. How can I do that?
Small demo
You can have a static flag that disables any more triggers once the first trigger has occurred. Might look something like this:
var hasTriggered = false;
myInput.change(function (e) { // this triggers first
triggerProcess();
});
myButton.click(function (e) { // this triggers second
triggerProcess();
});
function triggerProcess () {
// If this process has already been triggered,
// don't execute the function
if (hasTriggered) return;
// Set the flag to signal that we've already triggered
hasTriggered = true;
// ...
}
For resetting the hasTriggered flag, that's entirely up to you and how this program works. Maybe after a certain event occurring in the program you'd want to reenable the ability to trigger this event again — all you'd need to do it set the hasTriggered flag back to true.
You can use the mousedown event, which will fire before the input is blurred, and then check if the input has focus by checking if it's the activeElement, and if it does have focus, don't fire the mousedown event, as the change event will fire instead.
Additionally, if you want a mousedown event to occur when the value hasn't changed, and the change event doesn't fire, you'll need a check for that as well
var myInput = $('#test1'),
myButton = $('#test2'),
i = 0;
myInput.change(function(e) { // this triggers first
$(this).data('prev', this.value);
triggerProcess();
});
myButton.mousedown(function(e) { // this triggers second
var inp = myInput.get(0);
if (document.activeElement !== inp || inp.value === myInput.data('prev'))
triggerProcess();
});
function triggerProcess() {
console.log('triggered : ' + (++i))
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="test1">
<br />
<br />
<button id="test2">
Click
</button>
In a fairly typical scenario where you have an input with a button next to ie, eg quick search.
You want to fire when the input changes (ie onblur) but also if the user clicks the button.
In the case where the user changes the input then clicks the button without changing input focus (ie no blur), the change event fires because the text has changed and the click event fires because the button has been clicked.
One option is to debounce the desired event handler.
You can use a plugin or a simple setTimeout/clearTimeout, eg:
$('#inp').change(debounceProcess)
$('#btn').click(debounceProcess);
function debounceProcess() {
if (debounceProcess.timeout != null)
clearTimeout(debounceProcess.timeout);
debounceProcess.timeout = setTimeout(triggerProcess, 100)
}
function triggerProcess() {
console.log('process')
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input id="inp">
<button id="btn">Click</button>
Use a real <button>BUTTON</button>. If you click on input text, alert is triggered, then once you leave the input text to click anywhere else, that unfocuses the input text which triggers the change event, so now 2 events have been triggered from the text input.
This is an assumption since the code provided is far from sufficient to give a complete and accurate answer. The HTML is needed as well as more jQuery/JavaScript. What is myInput and myButton actually referring to, etc.?
So I bet if you change...
var myButton = $('{whatever this is}'); and <input type='button'>
...TO:
var myButton = $("button"); and <button></button>
...you should no longer have an event trigger twice for an element.
This is assuming that triggerProcess() is a function that does something that doesn't manipulate the event chain or anything else involving events. This is an entirely different ballgame if instead of click() and change() methods you are using .trigger() or triggerHandler(), but it isn't. I'm not certain why such complex answers are derived from a question with very little info...?
BTW, if myInput is a search box and myButton is the button for myInput, as freedomn-m has mentioned, simply remove:
myButton.click(...
Leave myButton as a dummy. The change event is sufficient in that circumstance.
SNIPPET
var xInput = $('input');
var xButton = $('button'); //«———Add
xInput.on('change', alarm);
xInput.on('click', alarm);
xButton.on('click', alarm);
function alarm() {
return alert('Activated')
}
/* For demo it's not required */
[type='text'] {
width: 5ex;
}
b {
font-size: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id='f1' name='f1'>
<input type='text'>
<input type='button' value='BUTTON TYPE'>
<label><b>⇦</b>Remove this button</label>
<button>BUTTON TAG</button>
<label><b>⇦</b>Replace it with this button</label>
</form>

How to toggle the check state of a radio input element on click?

How to (un)check a radio input element on click of the element or its container?
I have tried the code below, but it does not uncheck the radio.
HTML:
<div class="is">
<label><input type="radio" name="check" class="il-radio" /> Is </label>
<img src="picture" />
</div>
jQuery:
$(".is label, .is ").click(function () {
if (!$(this).find(".il-radio").attr("checked")) {
$(this).find(".il-radio").attr("checked", "checked");
} else if ($(this).find(".il-radio").attr("checked") == "checked") {
$(this).find(".il-radio").removeAttr("checked");
}
});
You have to prevent the default behaviour. Currently, on click, the following happens:
click event fires for the container (div.is).
click event fires for the label.
Since your function toggles a state, and the event listener is called twice, the outcome is that nothing seems to happen.
Corrected code (http://jsfiddle.net/nHvsf/3/):
$(".is").click(function(event) {
var radio_selector = 'input[type="radio"]',
$radio;
// Ignore the event when the radio input is clicked.
if (!$(event.target).is(radio_selector)) {
$radio = $(this).find(radio_selector);
// Prevent the event to be triggered
// on another element, for the same click
event.stopImmediatePropagation();
// We manually check the box, so prevent default
event.preventDefault();
$radio.prop('checked', !$radio.is(':checked'));
}
});
$(".il-radio").on('change click', function(event) {
// The change event only fires when the checkbox state changes
// The click event always fires
// When the radio is already checked, this event will fire only once,
// resulting in an unchecked checkbox.
// When the radio is not checked already, this event fires twice
// so that the state does not change
this.checked = !this.checked;
});
radio buttons don't uncheck, you need to use a checkbox (type = 'checkbox')
use only html , same functionality
<label for="rdio"><input type="radio" name="rdio" id="rdio" class="il-radio" /> Is </label>

check/uncheck radio input with javascript

I have a radio input group. If a radio is checked and I click again it becomes unchecked.
Is there a way to get the previous status of the radio onClick event?
<input name="options" type="radio" onClick="resetMeIfChecked()">
<input name="options" type="radio" onClick="resetMeIfChecked()">
<input name="options" type="radio" onClick="resetMeIfChecked()">
jQuery edition
// bind to retrieve old status
$('input[type="radio"]').mousedown(function() {
// if it was checked before
if(this.checked) {
// bind event to reset state after click is completed
$(this).mouseup(function() {
// bind param, because "this" will point somewhere else in setTimeout
var radio = this;
// apparently if you do it immediatelly, it will be overriden, hence wait a tiny bit
setTimeout(function() {
radio.checked = false;
}, 5);
// don't handle mouseup anymore unless bound again
$(this).unbind('mouseup');
});
}
});
But again, this is not how radio buttons are intended to be used. I think you'd be better of with a set checkbox'es where you could uncheck all other checkboxes than the current clicked (hence always max 1 selected)
A working example
I use this. You simply store the pre-click value and ! it into the value.
<input type=radio name="myoptions" value="1"
onmousedown="this.tag = this.checked;" onclick="this.checked = !this.tag;">
This behavior is not the expected one for radio buttons and I don't recommend it at all. Try to find another way of achieving this. Use another widget or another option to reset the field value:
http://jsfiddle.net/marcosfromero/rRTE8/
try this:
function resetMeIfChecked(radio){
if(radio.checked && radio.value == window.lastrv){
$(radio).removeAttr('checked');
window.lastrv = 0;
}
else
window.lastrv = radio.value;
}
<input value="1" name="options" checked="checked" type="radio" onClick="resetMeIfChecked(this)" />A
<input value="2" name="options" type="radio" onClick="resetMeIfChecked(this)" />B
<input value="3" name="options" type="radio" onClick="resetMeIfChecked(this)" />C
Its quite simple. Just follow the simple example and
var rdblength=document.formname.elementname.length;
alert('length='+rdblength);
for(i=0;i<rdblength;i++){
document.formname.elementname[i].checked=false;
}
Just find the length and make every index checked=true/false.
Ping me at:-
http://manojbardhan2009.blogspot.com
I had the same problem and figured it out. None of the answers above work exactly as I wanted - most of them require an additional button to reset the radio. The goal was to uncheck radio by clicking on the radio itself.
Here's the fiddle: http://jsfiddle.net/MEk5Q/1/
The problem was very complicated because the radio button value changes BEFORE the click event fires so when we're listening to the event we can't tell if the radio button was already checked or not. In both cases it is already checked.
Another approach was to listen to mousedown event. Unlike click, it fires before changing radio checked attribute but unchecking it inside event handler gives us nothing since it is checked back again during mouseup event.
My answer is a little ugly workaround so I generally don't suggest it to others and I'll probably abandon it myself. It works but it involves 20ms timeout function which I'm not fond of in cases like this.
Here is the code explanation:
$('input[type="radio"]').on('mousedown', function() {
if (this.checked) { //on mousedown we can easily determine if the radio is already checked
this.dataset.check = '1'; //if so, we set a custom attribute (in DOM it would be data-check="1")
}
}).on('mouseup', function() {
if (this.dataset.check) { //then on mouseup we determine if the radio was just meant to be unchecked
var radio = this;
setTimeout(function() {radio.checked = false;}, 20); //we give it a 20ms delay because radio checking fires right after mouseup event
delete this.dataset.check; //get rid of our custom attribute
}
});
As a timeout function I could use a string (less writing) but as far as I know it would be eval'ed. Though I don't trust eval function, I prefered anonymous function.
One more thing - one could ask why spreading the code into two separate event handlers while we can fire the timeout function on mousedown? Well, what if someone press the mouse on a radio and holds it for a few secs or even someone is simply a very slow person ;). Generally, with this solution we omit the problem of lag between mousedown and mouseup.
If you need some more info about dataset, here's the MDN reference:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.dataset
This property came with HTML5 and might be not cross-browser, I guess, so if you want 100% compatibility, replace it with any other solution that'll contain the data, you name it.
Sorry about jQuery here and there but I hope you're fine with it - it was much easier that way.
Hope you'll enjoy it.
$('input[type="radio"]').on("mousedown", function () {
if (this.checked) {
$(this).one("click", function () {
this.checked = false;
});
}
});
I was never too happy about being forced to aim at that tiny radio button, so I came up with a larger target AND a way to turn a radio group off without resorting to anything that would upset the HTML / JavaScript purists.
The technique relies on not molesting the radio buttons at all via event handlers, but checking for a readonly proxy for each one instead. Everything is contained in what's below in pure JavaScript using a radio group to select a type of cheese, or no cheese at all.
I purposely used no styling in this example to avoid that added layer. The dump button will tell you what the three checked states are, so use it to interrogate what happened after hitting the radio or text input elements. For example simplicity I used a global to remember the former state, but a more elegant method is to use a dataset, which I what I use in the real code of my application.
<!DOCTYPE html>
<html>
<head>
<title>Uncheck a radio button</title>
<script>
function attachEventListener(target, eventType, functionRef, capture) {
"use strict";
if (typeof target.addEventListener !== 'undefined') {
// Most modern browsers
target.addEventListener(eventType, functionRef, capture);
} else if (typeof target.attachEvent !== 'undefined') {
// IE
target.attachEvent('on' + eventType, functionRef);
} else {
eventType = 'on' + eventType;
if (typeof target[eventType] === 'function') {
var oldListener = target[eventType];
target[eventType] = function() {
oldListener();
return functionRef();
};
} else {
target[eventType] = functionRef;
}
}
}
</script>
</head>
<body>
<form>
<input id="Cheddar-radio" class="radio" type="radio" name="Cheeses-0" value="Cheddar Cheese" tabindex="-1"></input>
<input id="Cheddar-text" type="text" readonly value="Cheddar Cheese" tabindex="-1"></input><br>
<input id="Swiss-radio" class="radio" type="radio" name="Cheeses-0" value="Swiss Cheese" tabindex="-1"></input>
<input id="Swiss-text" type="text" readonly value="Swiss Cheese" tabindex="-1"></input><br>
<input id="American-radio" class="radio" type="radio" name="Cheeses-0" value="American Cheese" tabindex="-1"></input>
<input id="American-text" type="text" readonly value="American Cheese" tabindex="-1"></input><br><br>
<input onclick="dumpStates()" type="button" name="button" value="dump" tabindex="-1"></input>
</form>
<script>
window.onload = addRadioListeners;
function addRadioListeners() { // But do it on the -text elements.
attachEventListener(document.getElementById('Cheddar-text') , 'mousedown', rememberCurrentState, false);
attachEventListener(document.getElementById('Swiss-text') , 'mousedown', rememberCurrentState, false);
attachEventListener(document.getElementById('American-text'), 'mousedown', rememberCurrentState, false);
attachEventListener(document.getElementById('Cheddar-text') , 'mouseup', checkNewState, false);
attachEventListener(document.getElementById('Swiss-text') , 'mouseup', checkNewState, false);
attachEventListener(document.getElementById('American-text'), 'mouseup', checkNewState, false);
}
function dumpStates() {
console.log(document.getElementById('Cheddar-radio').checked +
' ' + document.getElementById('Swiss-radio').checked +
' ' + document.getElementById('American-radio').checked);
}
var elementWasChecked; // Global - Could just as well use a dataset attribute
// on either the -radio or -text element and check it instead.
function rememberCurrentState(event) {
var element;
var radioElement;
element = event.target;
radioElement = document.getElementById(element.id.replace(/text/,'radio'));
elementWasChecked = radioElement.checked;
radioElement.checked = true;
}
function checkNewState(event) {
var element;
var radioElement;
element = event.target;
radioElement = document.getElementById(element.id.replace(/text/,'radio'));
var currentState = radioElement.checked;
if (elementWasChecked === true && currentState === true) {
console.log('Changing ' + radioElement.id + ' to false.');
radioElement.checked = false;
}
};
</script>
</body>
</html>
If you click on the radio buttons they work as expected. If you click on the text items next to each, they are a proxy for the radio buttons with one exception. If you click on a text item that has an associated radio button that's already checked, it will uncheck it. Therefore, the text proxy's are event triggered, and not the radio buttons.
The added benefit is that you can now hit the larger text target too.
If you want to make it simple and wouldn't mind using a double-click event try something like this:
<input name="options" type="radio" ondblclick="this.checked=false;">
#jerjer's answer is almost perfect, but radios can be switched also by arrows if the radio group has the focus (so mousedown event is not enough). Alas, the radio group also gets checked when activated by focus shift (Tab), which can undesirably check one option. Therefore space should uncheck the focused radio, just like the checkbox behavior.
This code fixes that for all radios (Most credit still goes to jerjer):
window.addEventListener("DOMContentLoaded", function() {
var radios = document.querySelectorAll("input[type=radio]");
for(var i=0; i<radios.length; ++i) {
radios[i].addEventListener("click", function(e) {
if(e.target.checked && e.target.value == window.lastrv){
e.target.checked = false;
window.lastrv = 0;
}
else
window.lastrv = e.target.value;
});
radios[i].addEventListener("keypress", function(e) {
if(e.keyCode==32) e.target.click();
});
}
});

Categories