JQuery: Event handler unbound after ussing .toggle() - javascript

I have a select element which I have bound to the change event as follows:
$("#selectId").on("change", handler);
I've also tried this with .bind() and .change(), and everything works as expected. The select itself is inside a div that is hidden/shown when the user clicks on it, by means of the .toggle() method. The problem is that when the div is hidden and then shown again, the change event no longer triggers when it should. I can fix this by binding the event again, but I want to know whether this is normal behavior or a bug in JQuery (1.7.1) (or otherwise an error in my code).
Has anyone else had this problem before?
EDIT
This is the code that hides the div:
$('.accordion .accordionHeader').click(function()
{
if ($(this).next().is(":visible"))
{
$("#mainForm").data("validator").reset();
$(this).removeClass("expandedHeader");
$(this).find("em").removeClass("expandedHeader");
}
else
{
$(this).addClass("expandedHeader");
$(this).find("em").addClass("expandedHeader");
}
$(this).next().toggle('fast');
return false;
})

Related

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

Why doesn't my call to on() (or live()) work with elements added using append()?

I've got an issue preventing a hyperlink from being clicked using on(). I've researched on Stackoverflow but have yet to find an answer.
The code looks like this:
$(document).ready(function() {
$(document).on('click', '.spoiler > a', function(e)
{
e.preventDefault();
return false;
});
});
Then, later, a link gets added like this:
$("#chatContainer").append("<div class='spoiler'><a href='blah'>lorem ipsum</a></div>");
But when I click on the link, the link is still followed. I've confirmed that it's because the click handler isn't being called at all. I've also confirmed that the $(document).ready() function is being called, and that $('.spoiler > a') correctly selects the <a> element after it's been added.
So, Why isn't it working!? Isn't on() supposed to bind to all elements that match its selector, even ones added later?
[Edit] If I put the code in the onclick of the <a> element, it works:
$("#chatContainer").append("<div class='spoiler'><a href='blah' onclick='return doNothingWhenClicked();'>lorem ipsum</a></div>");
...
function doNothingWhenClicked()
{
return false;
}
So why doesn't it work when using on()!?
Do'h! I found it - it was in code I hadn't posted, sorry.
Elsewhere, I had a click handler set for the .spoiler div, which removed the .spoiler class.
$('.spoiler').on('click', function(e)
{
$(this).removeClass('.spoiler');
});
Since the parent's click handler is called before the child's, when I clicked on the link, the .spoiler class was being removed from the parent... which simultaneously removed the click binding from the child!
Normally I'd delete this question, but since I consider this a pretty tricky bug, I'll leave this question/answer here, in case it helps someone else in the future.
Also, I'm open to suggestions about how best to handle this situation...

Why is checkbox changed twice?

I do this:
$("input[type='checkbox']").each(function() {
var current = $(this);
$("label[for='" + current.attr("id") + "']").on('click', function() {
current.change();
alert('1');
});
});
$("input[type='checkbox']").on("change", function() {
alert('2');
});
Now, when I click on the label, the checkbox first alert(1) is showing up once, but the second(2) showing up twice. Can you tell me why? (The checkbox is hidden, seems like the change happens twice somehow)
You don't have to add a separate event handler for the <label> tag. It will trigger the "click" on the <input>.
You're better off using "click" instead of "change" (especially with the newest jQuery). Old (perhaps new) IE versions don't trigger "change" on checkboxes until the focus changes.
edit the reason that the newest jQuery makes life better is that it fixes a long-standing bizarre "feature" of the library. Previously, programmatically triggering "click" would result in a call to any "click" handlers with the element in the state before the effect of the click took place. That is, if you call
$(myCheckbox).trigger("click");
and the element was checked, it would be checked in the call to the handler. However, when a real click happens, the browser flips the state of the "checked" attribute before calling handlers. That made life pretty weird, but as of 1.9 I'm pretty sure that's fixed.

How do I hide a div if the user clicks outside of the div?

I have a button which, when clicked, changes the visibility of a div as such:
<button type="button" id="editbutton" onclick="plus()" alt="Edit"></button>
function plus() {
var f = document.getElementById('edit');
if (f.style.visibility == 'hidden') {
f.style.visibility = 'visible';
}
else
f.style.visibility = 'hidden';
}
This works fine. But now I want to add the functionality so that if the user clicks anywhere outside of the div, the div will go back to being hidden.
Initially I tried my own methods, but was unsuccessful because whenever I click the button to make the div visible, it is made hidden again by the new code. I've looked into some of the jQuery examples others have posted, however none of the ones I have tried fix this problem. Any help would me much appreciated.
EDIT: To clarify, the issue is that because the div is initially made visible by the user clicking on a button, whenever I click the button, the div is made visible but is immediately hidden again because that counts as clicking on the body of the page.
You could call 'e.stopPropagation()' on the event from within your click event handler when a user clicks within your div - this will stop the event from 'bubbling' up the DOM.
And then if you attach an event handler to the BODY element listening for clicks, you can use that event handler to hide the DIV; knowing that a click on the DIV itself won't bubble the event up to the BODY handler.
In this way any click on another element, unless the handler also calls stopPropagation(), will get caught by your handler. So if there are other cases where you don't want the div to hide you can stop it happening by calling stopPropagation() as required.
E.g.:
$("#edit-button").click( function(e) {
$("#edit").toggle();
e.stopPropagation();
});
$('#edit').click(function(e) {
e.stopPropagation();
});
$("body").click( function(e) {
$("#edit").hide();
});
Set a click handler on the body element. In the event you can check event.target to see if it matches the div, if it does: do nothing, else: hide it.
document.body.onclick = function(event) {
if (event.target === f) {
// user clicked on the div, do nothing
} else {
f.style.visibility = "hidden";
}
};
This becomes a bit more complex if you have elements within the edit div (and the .target doesn't match), but I'm sure you can come up with a solution :) (it can be done).
you seem like you know what you're doing so I'll keep it short.
write a .click function in jquery.
you give your button an id (or the div it is in)
$('#buttonidordiv').click(function(){
$('#divtoshow').show() // put in a number to create a fade in seconds.
//your stuff here
}
and if someone clicks anywhere outside it... I think you should be able to use window. Else you can use another div (like your wrapper).
so
$('window').click(function(){
$('#samediv').hide() //same as above
//your stuff here
}
do realize, I suck with the )}; so you might have to add one of those somewhere ;)
best of luck!

jQuery .not() isn't filtering elements from function - Ideas?

I'm trying to create a function that disables voting button after an Ajax POST success. The voting buttons are enabled until POST completes, and then are fixed with 'disabled' styling and are in-clickable. I'm trying to use jQuery .not() to disable starting the function from clicked buttons (with the class 'disabled') but I'm not having much luck. Here's a fiddle of my function so you can see my problem, I'm not sure what I'm missing but I'm hoping someone can help me find my error, it's frustrating me : )
jQuery Code:
$("a.votebutton").not(".disabled").each(function(index) {
var el = $(this);
$(this).click(function() {
el.addClass("active disabled").unbind("click");
el.siblings().addClass("disabled");
});
});
​
The problem is that your code:
$("a.votebutton").not(".disabled")
selects all of the links that are not disabled at the time that line of code runs, and then you loop through assigning click handlers. These click handlers remain bound to those links even if they happen to be given the "disabled" class at a later time - so when you add the "disabled" class to the clicked element's siblings those siblings still have a working click handler. If you unbind the click from the siblings that should fix it:
// change
el.siblings().addClass("disabled");
// to be
el.siblings().addClass("disabled").unbind("click");
Note that you don't need the .each():
$("a.votebutton").not(".disabled").click(function() {
$(this).addClass("active disabled").unbind("click")
.siblings().addClass("disabled").unbind("click");
});
Another way to do it would be to use delegated event handling:
$("div.panel").on("click", "a.votebutton:not(.disabled)", function() {
$(this).addClass("active disabled")
.siblings().addClass("disabled");
});
That way the clicks are only handled once they bubble up to the containing div ("div.panel"), at which time your click handler is only run if the event's source element matches the selector that is the second parameter to .on().
Updated demo: http://jsfiddle.net/RSezp/2/

Categories

Resources