Can't check radio button programmatically with preventDefault() - javascript

I have some elements (for example divs with class .label) with radio buttons inside of each. When user clicks this "labels" I programmatically set radio in it as selected. But if I use preventDefault() for click event, the radio didn't selected if user clicked exactly on radio.
Please help me to understand this strange behaviour. I know the solution, I know why preventDefault() on parent element disallows to check radio, but I want to understand, why click event on radio can disallow to set its state programmatically. You will see that click on radio button will say that radio is checked, but it's not.
$(function () {
$('.label').on('click', function(e) {
var $radio = $(this).find(':radio')
console.log('before prevent', `checked=${$radio.prop('checked')}`, `prevented=${e.isDefaultPrevented()}`);
e.preventDefault();
if (!$(this).hasClass('checked')) {
$('.checked').removeClass('checked');
$(this).addClass('checked');
}
$radio.prop('checked', true);
console.log('after prevent', `checked=${$radio.prop('checked')}`, `prevented=${e.isDefaultPrevented()}`);
setTimeout(function () {
console.log('after timeout', `checked=${$radio.prop('checked')}`);
}, 500);
});
$(':radio').on('click', function (e) {
console.log('click', `prevented=${e.isDefaultPrevented()}`);
});
});
.label {
padding: 10px;
margin-bottom: 10px;
border: 1px solid #000;
}
.label.checked {
background-color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="label">
Label 1 <input type="radio" name="radio" value="1">
</div>
<div class="label">
Label 2 <input type="radio" name="radio" value="2">
</div>
UPDATE. How do I see this situation:
User clicks on radio
Firstly event triggered on radio and input setted as checked.
Then event is bubbling up and triggered on .label
Calling preventDefault() sets up an internal cancelled flag.
div getting class '.checked' and radio setted as checked again, now programmatically.
Event bubbles on, but nothing happens any more.
Since the event was cancelled, the default action should not occur and the checkbox is reset to its previous state.
Am I right?

How do I see this situation (inspired by https://stackoverflow.com/a/15767580/11357125):
You click on the radio
It gets checked
The event is dispatched on the document root
Capture phase, nothing happens with your handlers
The event arrives at the <input>
…and begins to bubble
On the <div>, it is handled. Event listener calls the preventDefault method, setting an internal cancelled flag. <div> getting class '.checked' and radio setted as checked again, now programmatically.
Event bubbles on, but nothing happens any more.
Since the event was cancelled, the default action should not occur and the checkbox is reset to its previous state even after it was checked programmatically.

Using preventDefault() on parent elements prevent the original event to be fired, but does not stop propagation. Understanding hierarchy and event propagation is crucial.
You have part of the solution in your code snippet. If you comment out that particular line, the code works properly, like you would expect.
But if you use
e.stopPropagation();
it also works.
In order not to repeat information already on the web, I found a very similar case here (Why does preventDefault() on a parent element's click 'disable' a checkbox?) which may help you understand better event propagation and bubbling.
You can also find an even better explanation here (https://medium.freecodecamp.org/a-simplified-explanation-of-event-propagation-in-javascript-f9de7961a06e).
MDN documentation also rarely fails to impress (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault).

You just need to add e.stopPropagation() to return the default functionality for radio button again which is checked
The behaviour is that You have radio is a child to the div and you click listener is based on the parent div and when you preventDefault then the children inherit the prevention as well.
check that
If the button is pressed on one element and released on a different one, the event is fired on the most specific ancestor element that contained both.

Related

Why is trigger() triggering the select() handler 3 times?

$("#select-test").select(function(e){
//console.log(e)
console.log("selected")
})
$("#newTest").click(()=> {
$("#select-test").trigger("select")
})
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<button id="newTest">TEST ME</button>
<input type="text", id="select-test" value="some">
This is in Chrome, in Firefox, it's triggered twice.
I read this JQuery - Why does Trigger method call it three times? thread which talked about this issue, but honestly I couldn't understand it.
Somebody said:
we have 1 isTrigger and 2 simple select events.
What is that mean? 2 simple select events? Where? We only have 1 select event, where's the second one?
The best answer says that this happens because of bubbling, but... how? I mean, where's the bubbling? I don't see how this explain the event handler being triggered 3 times. Bubbling is when you target a child element, and the parent with the same handler is triggered too, but that's not what we have here. We only have ONE select handler, so .. where's the bubbling? And why is it triggered twice in Firefox?
A select needs a prevent default if you wish to stop it at some point.
the select event isn't the focus event, when a field becomes in focus. The select event gets fired when something is selected.
When you trigger the select event with the below code and have the browser tools open, you will see that the debugger shows the below code. When you select call stack -> dispatch, you can see what triggered the event and see that the third event is triggered by a native event.
var count = 0;
$("#select-test").select(function(e){
if(count == 2) {
debugger;
}
count++;
console.log("selected")
})
$("#newTest").click(()=> {
count = 0
$("#select-test").trigger("select")
})
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<button id="newTest">TEST ME</button>
<input type="text", id="select-test" value="some">
If we look at the documentation for select we see this:
In addition, the default select action on the field will be fired, so the entire text field will be selected.
So what happens is:
You trigger select -> jquery fires select to all event handlers
jQuery focusses the field, which triggers an automatic browser select all text, which triggers the select event.
Jquery selects all text in the field, native select is triggered, which is wrapped by jquery and then passed on to the handlers.
To stop any of these steps from happening you can use event.preventDefault() on the first time select is triggered, this will have the side event of not selecting the text.
Rather than using select I suggest you use focus() unless you really need to know what text is selected every time a select is triggered.
I have no idea why .select() (and also .on("select")) trigger three times, but .one("select") triggers once :
const $selectTest = $("#select-test");
$selectTest.one("select", function(e){
console.log(e.target)
console.log("selected")
});
$("#newTest").click(()=> {
console.log("Clicked");
$selectTest.trigger("select")
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="newTest">TEST ME</button>
<input type="text", id="select-test" value="some">

Canceling checkbox's click event prevents checking it?

I need to have a list of checkboxes with the condition that at least one of them must be checked.
The following code produces that effect.
document.querySelector('div').addEventListener('click', function(evt){
if( this.querySelectorAll('input:checked').length == 0 )
evt.preventDefault() ;
}) ;
<div>
<input type=checkbox checked>
<input type=checkbox>
<input type=checkbox>
</div>
That's fine, however, I don't understand why this code even works.
Firstly, I'm doing a .preventDefault() on the click event only after I've checked the condition that says there are no checkboxes checked. So canceling the click event at this point should make no difference.
Secondly, the code works even if you try to check the checkboxes using the keyboard, which is totally weird because I'm only canceling the click event.
Please explain why the code works the way it does.
While an input event listener is running, its effect on the element's state is actually already performed. If event.preventDefault() is called, this change is undone when the listener returns. This allows a checkbox event handler to test the new state of the checkbox, and allows the handler for a keyboard event on a text input to test the value that includes the new input.
The reason it works when you use the keyboard is that the click event is a high-level event that encompasses all the different ways to click on a checkbox: you can do it with the mouse, with they keyboard, with a touchscreen, etc. If you want to listen to a specific mode of clicking, you would have to use mousedown, keypress, etc.

How to select check box without selecting the whole row (see body)?

Say, I have an html:
<div class="row">
<input type="checkbox">
</div>
When user clicks on the whole row, it becomes highlighted (selected class added by an onClick event). I attach onClick event to elements with class .row.
When user clicks on checkbox (which is inside .row), this checkbox becomes selected. But row should not be highlighted.
Is it possible to exclude the area of the checkbox from the area of the .row for an onClick event?
UPDATE
Here is what I have now: http://jsfiddle.net/saAGU/
I don't want class to be toggled when I click exactly on checkbox.
UPDATE 2
Here is the working solution with jQuery for future use: http://jsfiddle.net/saAGU/2/
Yes, it's possible. The right way to do it is putting a onclick event on the checkbox, capture the event and stop its propagation.
Something like this:
function checkboxClick (e) {
e.stopPropagation();
}
Please tell me if it worked
add click event to every checkbox and stop its propogation .this should work -
e.stopPropagation in event handler for checkbox click
http://jsfiddle.net/saAGU/3/
If you capture the event, and look at the event.toElement, you can see if they clicked on the .row or something else.
http://jsfiddle.net/saAGU/1/
$('.row').click(function(event){
if (event.toElement !== this) // Did the user click on the row directly?
return;
$(this).toggleClass('selected');
});

DOM/jQuery events propagation differences between input types

As we know, returning false from a DOM event handler will stop the propagation of the event. But I recently discovered that the behavior of different elements varies with respect to this event. For example, consider the following:
<div id="container">
<input type="checkbox" name="foo" value="1" /> Check me!
<br />
<select>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<br />
<input type="text" size="30" />
</div>
The surrounding container has a click handler that returns false:
$('#container').click(function(e) {
$('#clicks').append('<span>clicked</span>');
return false;
});​
Within the div, I can still click on the text box and enter text, and can still click on the dropdown to change its value. But clicking on the checkbox does nothing (actually, that's not quite true - it checks the box, fires the handler, then unchecks it). jsfiddle here: http://jsfiddle.net/4ncaq/
This behavior is consistent across browsers so I assume it's by design. But what exactly is the rule that's at work here? How can I know how other kinds of elements might behave in this type of scenario?
What you are seeing is propagation, events 'bubble' up the DOM, so when you click your checkbox it is actually firing the click event on #container (as well as any events you might have bound to the input or other parent elements).
Using return false like this is ambiguous because it is doing 3 things at once, when you most likely only want it to do one. It prevents the default functionality, halts propagation and stops further execution.
It is better practice to be specific and use the methods from the event object to prevent the default action or stop bubbling.
event.preventDefault() will stop things like loading the href from an anchor click, stopping a checkbox from being checked etc.
event.stopPropagation() will cancel propagation, this is useful for situations like your example - http://jsfiddle.net/4ncaq/1/
The other problem with return false is that it may not be executed, a common mistake I see people make in jQuery is having a click event on an anchor with an $.ajax() request followed by return false to stop the browser from loading the linked page. In this scenario, if there is an error coming from ajax() (not a response error, a jQuery error - usually a misspelt param or something) it will never hit return false; and the browser will load the linked page. Using e.preventDefault() entirely removes this problem.
When you click an element, the event will continue propagating the event until some handler decides to cancel the propagation. In this case, when you click the checkbox, it will raise the event for the <input> first and then propagate to #container where you are stopping propagation.
If you want to cancel the propagation from input elements such as checkboxes or textareas you should bind to their click event and stop propagation at that point.
Edited
return false also cancels the default action for the original target element. Checkboxes, links, radio buttons are some of the elements where the default click action is cancelable. The default action for the click event in a checkbox toggles the value of the checkbox while there is no default click action for the select which means it does not get cancelled.
I've tried to find a list of default actions without luck but you can check the links at Is there a standard resource for the "default action" of HTML elements?.

Prevent firing focus event when clicking on div

This question is similar to my previous question, Click action on Focused DIV,
but this time the main topic is, How to prevent focus event from triggering when I click one of the divs.
Last time I had one div with tabindex='-1' to make it focusable on click, now I have a list of divs with tabindex>0 so they can gain focus when tabbing as well.
<div tabindex='1'>Div one</div>
<div tabindex='1'>Div two</div>
<div tabindex='1'>Div tree</div>
<div tabindex='1'>Div four</div>
some styling:
div {
height: 20px;
width: 60%;
border: solid 1px blue;
text-align: center;
}
div:focus {
border: solid 2px red;
outline: none;
}
Now I'm using a flag(action) to fire an action(alert) when clicking the div for 2nd time, and with just one click if it's already focused,with TAB for example.
var action = false;
$('div')
.click(function(e){
e.stopImmediatePropagation();
if(action){alert('action');}
action = true;})
.focus(function(){action = true;})
.blur(function(){action = false;});
The problem with the code above is focus event is being fired, which means stopImmediatePropagation doesn't work the way I expected.. The two click action works commenting the focus event line, but you still need to double click when div gains focus on TAB.
Here is the example: http://jsfiddle.net/3MTQK/1/
DEMO here
I think you are missing some parts here,
event.stopPropagation() is used to stop the event from bubbling. You can read about it here.
event.stopImmediatePropagation() In addition to keeping any additional handlers on an element from being executed, this method also stops the bubbling by implicitly calling event.stopPropagation(). You can read about it here
If you want to stop browser events, then you should use event.preventDefault(). You can read about it here
click = mousedown + mouseup -> The focus will be set on a HTML element when the mouse down is successful. So instead of binding click event, you should use 'mousedown'. See my demo.
You cannot use 1 action boolean value to determine which div is clicked and how many times it has been clicked. Check my Demo to see how you can handle that.
To simply prevent firing focus when clicking on div just use mousedown + event.preventDefault(). Use mousedown rather than click because, as #Selvakumar Arumugam explained, focus fires when mousedown is succesfull and event.preventDefault() will stop browser events (including our focus).
Here's a code example:
$('#div').on('mousedown', function(event) {
// do your magic
event.preventDefault();
});
Simply,
// ❌ Don't (focus already set on mouse down)
onClick={e => e.preventDefault()}
// ✔️ Do (prevent focus)
onMouseDown={e => e.preventDefault()}
For the moment the apparent answer would be to restrict your selector from simply 'div' to a system of toggled class names, thereby you could control on what element your focus event would fire. It is more tedious and requires a bit more exception coding, but would do the job.
var action = false;
$('div')
.click(function(e){
e.stopImmediatePropagation();
if(action){alert('action');}
action = true;})
.focus(function(){action = true;})
.blur(function(){action = false;});

Categories