Why can't we bind functions to :disabled input fields? - javascript

When i try to bind JavaScript/jQuery .hover or .click functions to an :disabled input field, none of the functions gets fired.
See the snippet below.
$(document).ready(function() {
var button_1 = $("#button_1");
button_1.hover( function() {
console.log("Hover event button 1");
});
button_1.click( function() {
console.log("Click event button 1");
});
});
function button_2_click() {
console.log("Click even button 3");
}
function button_2_hover() {
console.log("Hover even button 3");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Hover over one of the buttons, and you'll see none of the console messages gets logged. When removing the `disabled` property, all events are fired.</p>
<button id="button_1" disabled>Disabled 1</button>
<button id="button_2" onmouseover="button_2_hover();" onclick="button_2_click();" disabled>Disabled 2</button>
MDN says the following about the disabled property:
Indicates whether the element is disabled or not. If this attribute is
set to true the element is disabled. Disabled elements are usually
drawn with grayed-out text. If the element is disabled, it does not
respond to user actions, it cannot be focused, and the command event
will not fire.
What i tried to archieve is, when a user :hover's over an button:disabled, a message would be shown.
I know i could use a title="text" for that. However, thats not the case. I use an small popup at the right top corner with an message. The message is stored inside an javascript object.
My question:
How can i make an workaroud for this? Or is it just not possible to bind .hover() or onmousemove functions the disabled input fields?

The click part is per specification:
A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.
It would appear that the user agents you're testing in take it further and also prevent the events related to hovering (Chrome and IE seem to, for instance). Other user agents may not (Firefox allows the events related to hover, for instance.)
If you want those events, you could leave the buttons enabled and then style them to look disabled and prevent clicking them doing what they otherwise do, although it may make for a confusing user experience.
Alternately, you could put a span inside the button, and hook the events on that:
$(document).ready(function() {
var button_1 = $("#button_1 span");
button_1.hover( function() {
console.log("Hover event button 1");
});
button_1.click( function() {
console.log("Click event button 1");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Hover over one of the buttons, and you'll see none of the console messages gets logged. When removing the `disabled` property, all events are fired.</p>
<button id="button_1" disabled><span>Disabled 1</span></button>
There are parts of the button that won't trigger the events (bits of the surround), though you could probably fix that with CSS...

Related

Difference between attr('disabled',true) vs unbind('click')

I need to disable a button between the duration when it is clicked and the response arrival. My goal is to stop the onclick function getting executed if someone clicks the button again before the response has arrived.
Which is the correct function to use in this case.
$('#mybutton').unbind('click');
$('#mybutton').attr('disabled', true);
Disable button should be a must for a better user experience (optionally button text can be changed along with a different style), removing only onclick event can't provide good user experience as user can still click on the button which doesn't have any impact. Not necessarily to be followed but it can be an approach: remove onclick (extra cautious) and then disable the button.
$('#mybutton').unbind('click');
unbind() function removes onClick event handler from the selected element.
$('#mybutton').attr('disabled', true);
This code sets a disabled attribute to the selected element. A disabled button cannot be clicked and is usually styled differently.
A preferred way to disallow user to click a button is to make it disabled. If some conditions change and you want to make it clickable, you can remove this attribute and don't need to attach onClick event again.
$(".myButton").prop("disabled", true);
$(".myCheckbox").click(function() {
$(".myButton").prop("disabled", false);
});
It is more appropriate to remove onclick. Disabling the field is not equivalent: browsers may add styling to it and/or not include it in the form submission, should it be submitted.
You may also find that the onclick event fires even when the button is disabled.

jQuery Click Event Triggering On Multiple Elements (I only want 1)

When my overlay comes up, everything works well, but I added some code to close out the overlay, but this code gets triggered even when I'm just clicking my arrows. The following is the code that's being triggered, which is fine when I'm not clicking the arrows to change the image. But when I click the arrows, the background which is the overlay is also being trigger, so the image is changing but the overlay is also hiding.
$('#overlay').click(function() {
$(this).fadeOut('slow');
});
How can I be able to use the arrows without it also clicking on the background overlay? If you open up the project, you will see what I'm saying.
To open the project:
https://htmlpreview.github.io/?https://github.com/rodriguesandrewb/photo_gallery_v1/blob/master/index.html
To open the repository:
https://github.com/rodriguesandrewb/photo_gallery_v1
You want to use event.stopPropagation(): https://api.jquery.com/event.stoppropagation/
This prevents the event from bubbling (being triggered by other elements)
Your outter most element is #overlay. It means that no matter where you click you'll be always clicking on your #overlay element. That is way your callback is being always triggered and closing your image.
To fix your problem and make your image close only when clicking on it you could use:
$('#changeImage').click(function() {
$(this).closest('#overlay').fadeOut('slow');
});
Ok, there's a ton of code to sort out, so I'm guessing your overlay is
<div id="overlay" style="display: block;"></div>
and your event.target is deep down inside this:
<div class="mainCenter">
<div class="container">
<div id="topFixed">
<input type="text" id="search" placeholder="Search">
</div>
<ul id="gallery">
.......
I'm not 100% sure where your event.target is, (the element you want to click and not everything else). But it's safe to assume that after you click your intended button, the event continues to bubble up the event chain. The event chain is basically your event.target's ancestors which includes#overlay` which is at the very top of the event chain.
To prevent event bubbling (btw bubbling is the default behavior but in instances such as your's it's not desired.) try placing stopPropagation() after or inside at the end of your event handler.
I wish I could be more specific as to where and how to apply this code as it pertains to your source, but you didn't provide the specific areas that concern your eventListeners, eventHandlers, etc...
The #overlay is used in this example but I suggest you use the event.target parent instead. The purpose of this code is to accept an event like 'click' on an element (i.e. button) or multiple elements (i.e. buttons) through their mutually shared parent. That's one place to click for potentially several different buttons. At first you'd think that's non-sense and you'd say, "Sure that button is clicked because the parent was clicked, but now everything the parent is chained to will trigger everything else."
That would be correct except we have stopPropagation(); at the very end of your eventHandler. That will stop propagation of the event bubbling back up the event chain, so there's no more rogue triggers lighting up everywhere. Rogue Triggers® sounds like a great band name. :P
For details and a much better explanation: http://www.kirupa.com/html5/handling_events_for_many_elements.htm
var overlay = document.querySelector("#overlay");
theParent.addEventListener("click", doSomething, false);
function doSomething(e) {
if (e.target !== e.currentTarget) {
var clickedItem = e.target.id;
alert("Hello " + clickedItem);
}
e.stopPropagation();
}

How to handle "click" event even if element is moved from beneath the cursor between mousedown and mouseup?

I have an <input> and a <button>.
The input has an onblur event handler that (sometimes) results in the <button> being moved; if, with focus on the <input>, the user goes to click on the <button> (and it is thereby moved from beneath the pointer before the click completes) they must currently move their pointer to the button's new location and click it a second time. This experience is suboptimal.
I would like for users to have to click the <button> only once (but must retain the functionality of the button moving when the <input> loses focus). However, if a user mousedowns over the button and then moves their mouse pointer away (includes switching focus from the application) before mouseup, no click event should be triggered (as normal).
I can't see any approach based on handling onmousedown and/or onmouseup that would not be prone to errors in some edge cases. All that I can think is to forcibly move the cursor in the onblur handler so that the click completes (if indeed it would?)—but this is probably a poor user experience too.
How can this best be handled?
$('button').click(
$('<li><input></li>')
.children('input')
.blur(function(){
if (!this.value.length)
this.parentElement.remove();
})
.end(),
function(e){
e.data.clone(true)
.appendTo($(this).data('target'))
.children('input')
.focus();
}
);
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
List 1<button data-target="#list1">+</button><ul id="list1"></ul>
List 2<button data-target="#list2">+</button><ul id="list2"></ul>
One approach would be to fade the element out over the course of, say, a second rather than immediately, which gives the user time to complete the click before the button moves:
$(this.parentElement).fadeOut("slow", function() {
$(this).remove();
});
Live example:
$('button').click(
$('<li><input></li>')
.children('input')
.blur(function(){
if (!this.value.length)
$(this.parentElement).fadeOut("slow", function() {
$(this).remove();
});
})
.end(),
function(e){
e.data.clone(true)
.appendTo($(this).data('target'))
.children('input')
.focus();
}
);
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
List 1<button data-target="#list1">+</button><ul id="list1"></ul>
List 2<button data-target="#list2">+</button><ul id="list2"></ul>
That said, I think I'd try to find a way that the space between the list elements didn't change at all, because this still means things move around, just at a different time — hopefully at a time the user will be less bothered by it, but... Tricky in this case, though, because of the lists being on top of each other — the second list is going to move at some point. If you made it that there was always an input box for the user to type in, then the second list would just move at a different time (when the box does have a value in it and you add a new blank one for the next value). Could be done with a fixed list size and scrolling within, but it depends on the overall design.
I had a related issue where I was styling buttons to shrink on being pressed, using the :active pseudo-class to resize the button on mouse-down and resetting its size when the button gained focus with the :focus pseudo-class. However, if the button was clicked close enough to its edge, it still was activated and shrank, but the cursor was now just outside of the bounds of the button and the button would no longer gain focus.
This is a result of how the click event itself works. For the onclick event to be fired on a given element, the cursor must be "both pressed and released while the [cursor] is located inside the element." However, if the cursor moves outside of the element before being released, the event is instead fired on the "most specific ancestor element" containing both elements. In specific, this results in a mousedown event being fired on the element and a mouseup event being fired on its ancestor element.
A solution, then, is to monitor a button's mousedown event, and click the button if its parent's mouseup event is fired (i.e., the cursor was pressed over a button but released over its parent).
Vanilla JS code:
// The CSS selector for the buttons to monitor
const selectors = "button";
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(selectors).forEach(button => {
// Watch for button press
button.addEventListener('mousedown', event => {
// The button is the event's original target
const element = event.target;
// Click if the cursor is released over the button's parent
element.parentElement.addEventListener('mouseup', () => {
element.click();
}, { once: true });
});
});
});
This works best if the button's parent is sized the same as the button (such as with width: fit-content). Otherwise, unintentional clicks can occur if the cursor is released far away from the button, but still on the button's parent element.

Strange Behaviour on changing the property of button in Jquery

I am disabling a button on click handler and it is strange that once the button is disabled, it doesn't get submitted after that.
It varies from browser to browser. Firefox allows the submission of the form after disabling the submit button but chrome and IE are blocking it
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
</script>
<script>
$(function(){
$("#btn").on("click",function(){
$(this).prop('disabled','true');
alert('clicked');
});
$("#frm").on("submit",function(){
alert("submit1");
});
$("#frm").on("submit",function(){
alert("submit2");
});
})
</script>
</head>
<body>
<form id="frm" action="#">
<input type='submit' id="btn"/>
</form>
</body>
This is a strange behavior that submit is being prevented on disabling the buttton.
what could be the probable reason.
output which i am getting in Chrome and IE are
click is executed and then script close
why isnt it in order
execution of click
then execution of submits
I think you're saying that when you disable the submit button as it is clicked, the form doesn't get submitted and you want it to.
I believe the reason for that is that the click event occurs before the button's activation behavior (which is the HTML5 spec's term for "what it does"). The activation behavior of an enabled ("mutable" as the spec puts it) submit button is, of course, to trigger form submission. But by the time the browser is deciding on the activation behavior, which is after the click handlers have been fired, you've disabled the button. According to the spec, a disabled ("non-mutable" as they put it) submit button has no activation behavior, and so the form is not submitted.
So the question becomes: When does the browser decide what activation behavior to use, before the click handlers or after them? For the answer to that, we turn to the DOM Events spec, which tells us that first the click occurs, then the activation behavior occurs. So it would appear that Chrome is deciding after the click event is complete, which is a reasonable read of the spec; Firefox would appear to be deciding before the click event is complete.
Pragmatically, though, the reality is that if you disable the button within the click handler, you are not guaranteed to get the form submission. To work around that, you can very briefly delay disabling the button, so the event completes without the button having been disabled:
$("#btn").on("click",function(){
var btn = this;
setTimeout(function() {
btn.disabled = true;
}, 0);
alert('clicked');
});
Live Example | Source
Side note: In the above, I've also (effectively) replaced $(this).prop("disabled", true"); with (effectively) this.disabled = true;. There's no reason to wrap the element in a jQuery wrapper just to set a simple property value.
Should fix your issue:
$("#btn").on("click", function (e) {
e.preventDefault();
$(this).prop('disabled', true).closest('form').submit();
//to submit FORM without firing jquery's attached handlers
//$(this).prop('disabled', true).closest('form').get(0).submit();
alert('clicked');
});
DEMO

onclick() and onblur() ordering issue

I have an input field that brings up a custom drop-down menu. I would like the following functionality:
When the user clicks anywhere outside the input field, the menu should be removed.
If, more specifically, the user clicks on a div inside the menu, the menu should be removed, and special processing should occur based on which div was clicked.
Here is my implementation:
The input field has an onblur() event which deletes the menu (by setting its parent's innerHTML to an empty string) whenever the user clicks outside the input field. The divs inside the menu also have onclick() events which execute the special processing.
The problem is that the onclick() events never fire when the menu is clicked, because the input field's onblur() fires first and deletes the menu, including the onclick()s!
I solved the problem by splitting the menu divs' onclick() into onmousedown() and onmouseup() events and setting a global flag on mouse down which is cleared on mouse up, similar to what was suggested in this answer. Because onmousedown() fires before onblur(), the flag will be set in onblur() if one of the menu divs was clicked, but not if somewhere else on the screen was. If the menu was clicked, I immediately return from onblur() without deleting the menu, then wait for the onclick() to fire, at which point I can safely delete the menu.
Is there a more elegant solution?
The code looks something like this:
<div class="menu" onmousedown="setFlag()" onmouseup="doProcessing()">...</div>
<input id="input" onblur="removeMenu()" ... />
var mouseflag;
function setFlag() {
mouseflag = true;
}
function removeMenu() {
if (!mouseflag) {
document.getElementById('menu').innerHTML = '';
}
}
function doProcessing(id, name) {
mouseflag = false;
...
}
I was having the exact same issue as you, my UI is designed exactly as you describe. I solved the problem by simply replacing the onClick for the menu items with an onMouseDown. I did nothing else; no onMouseUp, no flags. This resolved the problem by letting the browser automatically re-order based on the priority of these event handlers, without any additional work from me.
Is there any reason why this wouldn't have also worked for you?
onClick should not be replaced with onMouseDown.
While this approach somewhat works, the two are fundamentally different events that have different expectations in the eyes of the user. Using onMouseDown instead of onClick will ruin the predictability of your software in this case. Thus, the two events are noninterchangeable.
To illustrate: when accidentally clicking on a button, users expect to be able to hold down the mouse click, drag the cursor outside of the element, and release the mouse button, ultimately resulting in no action. onClick does this. onMouseDown doesn't allow the user to hold the mouse down, and instead will immediately trigger an action, without any recourse for the user. onClick is the standard by which we expect to trigger actions on a computer.
In this situation, call event.preventDefault() on the onMouseDown event. onMouseDown will cause a blur event by default, and will not do so when preventDefault is called. Then, onClick will have a chance to be called. A blur event will still happen, only after onClick.
After all, the onClick event is a combination of onMouseDown and onMouseUp, if and only if they both occur within the same element.
Replace on onmousedown with onfocus. So this event will be triggered when the focus is inside the textbox.
Replace on onmouseup with onblur. The moment you take out your focus out of textbox, onblur will execute.
I guess this is what you might need.
UPDATE:
when you execute your function onfocus-->remove the classes that you will apply in onblur and add the classes that you want to be executed onfocus
and
when you execute your function onblur-->remove the classes that you will apply in onfocus
and add the classes that you want to be executed onblur
I don't see any need of flag variables.
UPDATE 2:
You can use the events onmouseout and onmouseover
onmouseover-Detects when the cursor is over it.
onmouseout-Detects when the cursor leaves.
onFocus / onBlur are events that don't bubble. There are however focus events that do bubble. These being focusin and focusout.
Now to the solution: We wrap both the input and our dropdown into a div-element and set the tabindex of that div to -1 (so that it can recieve focus / but does not appear in the tab order). We now add an eventlistener for focusin and focusout to this div. And since these events do bubble a click on our input element will trigger our divs focusin event (which opens the drop-down)
The neat part now is that a click on our dropdown will also trigger the focusin event on our div (so we basically maintain focus which means: focusout/blur never fires and our dropdown stays open)
You can try this out with the code snippit below (the dropdown only closes on loss of focus - but if you want it to close when clicking on the dropdown aswell just uncomment the one line of JS)
const container = document.getElementById("container")
const dropDown = document.getElementById("drop-down")
container.addEventListener("focusin", (event) => {
dropDown.classList.toggle("hidden", false)
})
container.addEventListener("focusout", (event) => {
dropDown.classList.toggle("hidden", true)
})
dropDown.addEventListener("click", (event) => {
console.log("I - the drop down - have been clicked");
//dropDown.classList.toggle("hidden", true);
});
.container {
width: fit-content;
}
.drop-down {
width: 100%;
height: 200px;
border: solid 1px black
}
.hidden {
display: none
}
<div class="container" id="container" tabindex="-1">
<input id="input" />
<div class="drop-down hidden" id="drop-down" > Hi I'm a drop down </div>
</div>
there arises however one issue if you want to add your dropdown into the tabbing order, have buttons in your dropdown or in general have an element in the dropdown, that can recieve focus. Because then a click will give the element in the dropdown focus first. This triggers our container div to lose focus which closes the dropdown so the focus event can't bubble further and therefore can't trigger the focusin on our container.
We can solve this issue by expanding the focusout eventlistener a bit.
The new eventlistener is as follows:
container.addEventListener("focusout", (event) => {
dropDown.classList.toggle("hidden", !container.matches(":hover"))
})
We basically say: "don't you close that dropDown if someone is hovering over it" (This solution only considers mouse-use; but in that case this is fine, because the problem this tries to fix only ever occured when using a mouse, when tabbing onto/through the dropDown everything worked fine from the start)
change onclick by onfocus
even if the onblur and onclick do not get along very well, but obviously onfocus and yes onblur. since even after the menu is closed the onfocus is still valid for the element clicked inside.
I did and it worked.
An ideal solution I found to work for me was to simply add a timeout in my onBlur function. I used 250ms, that provided smooth behaviour for my blur event and allowed my onClick to fire before the onBlur. I used this example as a reference https://erikmartinjordan.com/onblur-prevents-onclick-react
You can use a setInterval function inside your onBlur handler, like this:
<input id="input" onblur="removeMenu()" ... />
function removeMenu() {
setInterval(function(){
if (!mouseflag) {
document.getElementById('menu').innerHTML = '';
}
}, 0);
}
the setInterval function will remove your onBlur function out from the call stack, add because you set time to 0, this function will be called immediately after other event handler finished

Categories

Resources