Events are not called when changing visibility of an element - javascript

I have a page that need to toggle the visibility of a div at regular interval. The issue is that the click events related to one of the parent element are often not called. If I stop the visibility toggle or if I slow it down (for example 300ms instead of 100ms) the click events are fired normally.
I have created a short example illustrating this issue.
You can test it here: http://threeds.me/0xC0DE/
Clicking the blue/red square should in theory fire the click event. When there is no animation (default behaviour here) clicking the blue square will always fire the onclick event. If you click on 'play/pause' the visibility of the red square will start to toggle every 100ms. When the animation is playing the click event is not call everytime you click on the square.
I would suspect that changing the visibility is somehow messing up with the DOM which as a result stop events for some milliseconds. I also tried to toggle the css visibility property to minimize the impact on the page but it has the same result.
But it's not only events. If I encapsulate the whole container in an tag, clicking the element will not be always taking into account by the browser. So it's like that changing the visibility of a child element will make a parent blind for (quite) some time which I find really strange.
This issue is consistent with Firefox, Chrome and Opera (all on mac, cannot test ie) so it means there is something I don't get. So even if I don't get a solution, I would like to understand what is the problem.
Here the source for your immediate reference
<!DOCTYPE html>
<html>
<head><title>Wiggle problem</title>
<style>
#dback {
width: 200px;
height: 200px;
background-color: red;
}
#dfront {
width: 200px;
height: 200px;
background-color: blue;
}
</style>
<script type="text/javascript">
var run = false;
function playpause() {
if ( run ) {
run = false;
} else {
run = true;
update();
}
}
function update() {
if ( run==false ) {
return;
}
var el = document.getElementById('dfront');
if ( el.style.display != 'none' ) {
el.style.display = 'none';
}
else {
el.style.display = '';
}
setTimeout("update();", 100);
}
</script>
</head>
<body>
play/pause
<div id="container" onclick="d=new Date();t=d.getTime();document.getElementById('info').innerText='click '+t;">
<div id="dback">
<div id="dfront"></div>
</div>
</div>
<span id="info"></span>
</body>
</html>

That is really weird! However, I made some tests and fiddled with your code a bit, and now I think it has nothing to do with bubbling (you can see on my test that clicks on elements with visibility: hidden bubble just fine).
Here is what I think could be going on: since your timer interval is so short, there is not enough time for an actual click to be registered – a click being a mousedown event, followed by a mouseup event, on the same element. What happens in your case is that mouseup is frequently being fired on the other element, not the same one that fired mousedown, and thus there is no click event. See the second link above, where I reduced the interval to 1 sec, and try to click on the blue square, and release the mouse button on the red square (or vice-versa). Nothing happens. I may be completely wrong, but that looks plausible.
I know this is not a solution, but you said:
So even if I don't get a solution, I would like to understand what is the problem.
I hope this can help you find one!
Last but not least, a couple of side notes on your code:
The way you're calling setTimeout (passing a string) is not recommended, because it will force the use of eval internally, and eval should be avoided as much as possible. Instead of passing the string with the function call code, just pass the function itself:
setTimeout(update, 100);
It's considered a best practice today to use addEventListener (or attachEvent for IE) to bind an event to an element, instead of using inline events like your onclick="...". That has several advantages, like helping separate behavior, structure and layout, and making the event object automatically available to the event handler (as the first parameter to the handler function).

Acctually, when "dfront" is visible, its hovering over "container", and when you make a click, event goes to "dfront". but there is no onclick event, for "dfront" element.
Onclick event of "container" is not propagating on child elements - its normal, you can workaround it by using jquery it has "event propagation" option for events.
Or just set onclick event for "dfront".
Also you can create transparent layer over "container" and "dfront" and bind event to it.
Or, make onmouseclick event on window object, which will find out, if mouse position is over "dfront" by checking their coordinates and if true then event handler will be fired.

Related

Mouseup/down on two different elements within a jQuery click delegate cancels the click event

I have a clickable item, which contains child elements, too. My problem stems from wanting to animate the :active state of the clicked item by using a 1px translation.
The mousedown of the click lands on a child element
The 1px translate moves the child element from under the mouse pointer
The mouseup event now happens on the parent element
This results in the click getting canceled. I can prevent the child element from catching the click using pointer-events: none in CSS, but since it's both new and unstable, I'd love to find a more compatible fix. For now, I've settled on just sticking a transparent DIV on top of the whole item, but that's ugly.
Exaggareted demo in this pen: http://codepen.io/JonFabritius/pen/mJuzy
Try clicking the bottom half of the orange bar, the pointer remains on top of the child element. Then click on the top half, which causes the element to move from under the pointer.
It's probably staring me in the face, but I haven't been able to find a simple fix- any ideas appreciated.
Disclaimer: I don't jQuery.
This is a response better considered as a comment, though with the addition of code.
You say that "the click target is the parent" - Sorry to be a little thick here, but when you say it's the click target, is the parent the element that has the event listener attached to it, or by click target, do you mean that it is the value returned by evt.target? (Where evt is the single variable passed to the event handling function)
To better illustrate what I'm getting at, consider the following code:
JS portion:
window.addEventListener('load', mInit, false);
function mInit()
{
document.getElementById('clickTarget').addEventListener('click', handleClick, false);
}
function handleClick(evt)
{
console.log(this.id + ': is the id of the "this" element');
console.log(evt.target.id + ": was the id of the evt.target");
}
HTML portion:
<body>
<div id='clickTarget'><a id='link1'>Link1</a><a id='link2'>Link2</a></div>
</body>
Now then, when you click on link1, the output shown in the console is:
clickTarget: is the id of the "this" element
link1: was the id of the evt.target
Predictably, clicking on link2 shows the following:
clickTarget: is the id of the "this" element
link2: was the id of the evt.target
So, you can clearly see that in this example, the <div> is the parent of the two <a> elements.
Yet, in each instance the target is different - it's not the parent, it's the actual element that was clicked.
You've used jQuery, which, while simple to add functionality, hides implementation details, slows JS execution and (usually) adds unnecessarily to page-weight. Your code is very short and sweet, yet what is being returned requires some investigation - it's certainly not obvious.
As a side note, a quick look at the jQuery Docs: jQuery .delegate tells us that as of jQuery 1.7, the preferred method use to achieve this functionality is .on() - it's likely of no consequence, but one never knows..
Turns out if you use the current .on syntax, it works fine:
$(function() {
// target ON event, delegate
$('#container').on('click', '.item', function() {
$('.item').append('*');
});
});
UPDATE Could you do this with a custom class? This seems to work so long as the pointer doesn't leave the .item element. Child elements behave as expected.
$(function() {
$('#container').on('mousedown', '.item', function() {
$(this).addClass('active');
$('.item').append('*');
}).on('mouseup', '.item', function() {
$(this).removeClass('active');
});
});
DEMO: http://codepen.io/anon/pen/lponk

Webkit <button onclick> does not fire in certain situations

I noticed that in Webkit the <button> element does not fire the onclick event when the mouse is moved from/to child elements of the button during the click. With other words: when the mousedown and mouseup events do not happen on the same element - even if both are children of the button.
The same happens when clicking/releasing on/out of the pixels of the button text.
To clarify I made a testcase: http://jsfiddle.net/gx9B3/
It works fine in FireFox. Fails in Chrome 15 and QtWebkit 4.7.1
Is there a way around this? I need a solution specifically for Webkit because my project is targeted to this browser only.
Solution
I could solve this problem based on the method suggested by Jan Kuča (the solution I accepted). Some additional tweaks were necessary, especially introducing a timer to avoid double clicks. Have a look at my fully working solution at JSFiddle:
http://jsfiddle.net/mwFQq/
You could set up a mousedown listener on document.body (to fix the problem on the whole page). You would check if the mousedown event originated from an HTMLButtonElement (or from any of its child elements) and if it did, you set up a mouseup listener (on the button so it does not have to bubble too much) that will check the target property of the mouseup event. If it is contained in the button and is different from the target of the mousedown event, you fire a click event like this:
var e = document.createEvent('Events');
e.initEvent('click', true, true);
button.dispatchEvent(e);
(Do this only for WebKit-based browsers so that you don't get multiple click events in other browsers. Or you could call the preventDefault method of the mousedown event as it should also prevent firing the click event.)
You could try adding this CSS style:
button * {
pointer-events: none;
}
Child elements will then ignore mouse events and the click will come from the button element itself. Here's an example http://jsfiddle.net/Tetaxa/gx9B3/2/
You can also solve it by pure CSS trick. Just place pseudo element over <button> to cover text:
button {
position:relative;
}
button:after {
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
content:'';
}

If you delete a DOM element, do any events that started with that element continue to bubble?

What behavior should I expect if I delete a DOM element that was used to start an event bubble, or whose child started the event bubble - will it continue to bubble if the element is removed?
For example - lets say you have a table, and want to detect click events on the table cells. Another piece of JS has executed an AJAX request that will eventually replace the table, in full, once the request is complete.
What happens if I click the table, and immediately after the table gets replaced by a successful completion of an AJAX request? I ask because I am seeing some behavior where the click events don't seem to be bubbling - but it is hard to duplicate.
I am watching the event on a parent element of the table (instead of attaching the event to every TD), and it just doesn't seem to reach it sometimes.
EDIT: Encountered this problem again, and finally got to the root of it. Was not a event-bubbling issue at all! See my answer below for details.
Empirically: It depends on what browser you're using; IE cancels the event, everything else (as far as I can tell) continues it. See the test pages and discussion below.
Theoretically: Andy E's head helpfully found that DOM2 says the event should continue because bubbling should be based on the initial state of the tree. So the behavior of the majority is correct, IE's on its own here. Quelle surprise.
But: Whether that relates to what you're seeing is another question indeed. You're watching for clicks on a parent element of the table, and what you suspect is that very rarely, when you click the table, there's a race condition with an Ajax completion that replaces the table and the click gets lost. That race condition can't exist within the Javascript interpreter because for now, Javascript on browsers is single-threaded. (Worker threads are coming, though — whoo hoo!) But in theory, the click could happen and get queued by a non-Javascript UI thread in the browser, then the ajax could complete and replace the element, and then the queued UI event gets processed and doesn't happen at all or doesn't bubble because the element no longer has a parent, having been removed. Whether that can actually happen will depend a lot on the browser implementation. If you're seeing it on any open source browsers, you might look at their source for queuing up UI events for processing by the interpreter. But that's a different matter than actually removing the element with code within the event handler as I have below.
Empirical results for the does-bubbling-continue aspect:
Tested Chrome 4 and Safari 4 (e.g., WebKit), Opera 10.51, Firefox 3.6, IE6, IE7, and IE8. IE was the only one that cancelled the event when you removed the element (and did so consistently across versions), none of the others did. Doesn't seem to matter whether you're using DOM0 handlers or more modern ones.
UPDATE:
On testing, IE9 and IE10 continue the event, so IE noncompliance with spec stops at IE8.
Test page using DOM0 handlers:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body {
font-family: sans-serif;
}
#log p {
margin: 0;
padding: 0;
}
</style>
<script type='text/javascript'>
window.onload = pageInit;
function pageInit() {
var parent, child;
parent = document.getElementById('parent');
parent.onclick = parentClickDOM0;
child = document.getElementById('child');
child.onclick = childClickDOM0;
}
function parentClickDOM0(event) {
var element;
event = event || window.event;
element = event.srcElement || event.target;
log("Parent click DOM0, target id = " + element.id);
}
function childClickDOM0(event) {
log("Child click DOM0, removing");
this.parentNode.removeChild(this);
}
function go() {
}
var write = log;
function log(msg) {
var log = document.getElementById('log');
var p = document.createElement('p');
p.innerHTML = msg;
log.appendChild(p);
}
</script>
</head>
<body><div>
<div id='parent'><div id='child'>click here</div></div>
<hr>
<div id='log'></div>
</div></body>
</html>
Test page using attachEvent/addEventListener handlers (via Prototype):
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>Test Page</title>
<style type='text/css'>
body {
font-family: sans-serif;
}
#log p {
margin: 0;
padding: 0;
}
</style>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js'></script>
<script type='text/javascript'>
document.observe('dom:loaded', pageInit);
function pageInit() {
var parent, child;
parent = $('parent');
parent.observe('click', parentClick);
child = $('child');
child.observe('click', childClick);
}
function parentClick(event) {
log("Parent click, target id = " + event.findElement().id);
}
function childClick(event) {
log("Child click, removing");
this.remove();
}
function go() {
}
var write = log;
function log(msg) {
$('log').appendChild(new Element('p').update(msg));
}
</script>
</head>
<body><div>
<div id='parent'><div id='child'>click here</div></div>
<hr>
<div id='log'></div>
</div></body>
</html>
It's been quite some time since I originally posted this question. Although T.J.Crowder's answer was very informative (as was Andy E's), and told me it should work, I continued to see a problem. I put it aside for some time, but revisited it today when I encountered the same issue again in another web application.
I played around with it for a while, and I came to realize how to duplicate the problem every-time (at least in FF3.6 and Chrome 8). The problem wasn't that the event bubble was getting cancelled, or lost when the DOM element was removed. Instead, the problem is that if the element is changed between mousedown and mouseup, the 'click' does not fire.
Per the Mozilla Development Network:
The click event is raised when the user clicks on an element. The click event will occur after the mousedown and mouseup events.
So, when you have a DOM element that is changing at all, you can encounter this problem. And I erroneously believed the event bubble was being lost. It just happens that if you have a frequently updating element, you see it more often (which is my case) and are less likely to pass it off as a fluke.
Additional testing (see the example on jsfiddle) shows that if one clicks, holds the button down and waits for the DOM element to change, and then releases the button, we can observe (in the jquery live context):
The 'click' event does not fire
The 'mousedown' event fires for the first node
The 'mouseup' event fires for the updated node
EDIT: Tested in IE8. IE8 fires mousedown for first node, mouseup for updated node, but does in fact fire 'click', using the updated node as the event source.
Yes, it should continue to propagate. Events have no real attachment to the event they fired on, except for the target property. When you remove the element, the internal code propagating the event should not have any "awareness" that the original element has gone from the visible document.
As an aside, using removeChild will not delete an element right away, it just detaches it from the document tree. An element should only be deleted/garbage collected when there are no references to it. Therefore, it's possible that the element could still be referred to via the event.target property and even re-inserted before being garbage collected. I haven't tried it though, so it's just speculation.
T.J. Crowder's comment made me decide to knock up a quick example. I was right on both counts, it does bubble and you can still get a reference to the removed node using event.target.
http://jsbin.com/ofese/2/
As T.J. discovered, that is not the case in IE. But the DOM Level 2 Events specification does define it as correct behavior [emphasis mine]:.
Events which are designated as bubbling will initially proceed with the same event flow as non-bubbling events. The event is dispatched to its target EventTarget and any event listeners found there are triggered. Bubbling events will then trigger any additional event listeners found by following the EventTarget's parent chain upward, checking for any event listeners registered on each successive EventTarget. This upward propagation will continue up to and including the Document. EventListeners registered as capturers will not be triggered during this phase. The chain of EventTargets from the event target to the top of the tree is determined before the initial dispatch of the event. If modifications occur to the tree during event processing, event flow will proceed based on the initial state of the tree.

Javascript: Multiple mouseout events triggered

I'm aware of the different event models in Javascript (the WC3 model versus the Microsoft model), as well as the difference between bubbling and capturing. However, after a few hours reading various articles about this issue, I'm still unsure how to properly code the following seemingly simple behavior:
If I have an outer div and an inner div element, I want a single mouse-out event to be triggered when the mouse leaves the outer-div. When the mouse crosses from the inner-div to the outer-div, nothing should happen, and when the mouse crosses from the outer-div to the inner-div nothing should happen. The event should only fire if the mouse moves from the outer-div to the surrounding page.
<div id="outer" style = "width:20em; height:20em; border:1px solid #F00" align = "center" onmouseout="alert('mouseout event!')" >
<div id="inner" style = "width:18em; height:18em; border:1px solid #000"></div>
</div>
Now, if I place the "mouseout" event on the outer-div, two mouse-out events are fired when the mouse moves from the inner-div to the surrounding page, because the event fires once when the mouse moves from inner to outer, and then again when it moves from outer to the surrounding page.
I know I can cancel the event using ev.stopPropagation(), so I tried registering an event handler with the inner-div to cancel the event propagation. However, this won't prevent the event from firing when the mouse moves from the outer-div to the inner-div.
So, unless I'm overlooking something, it seems to me this behavior can't be accomplished without complex mouse-tracking functions. In the future, I plan to reimplement a lot of this code using a more advanced framework, like JQuery, but for now, I'm wondering if there is a simple way to implement the above behavior in regular Javascript.
The mouseout event on the inner div ‘bubbles’ to the outer div. To detect that this has happened from the outer div, check the target property of the event:
<div id="outer">
<div id="inner">x</div>
</div>
document.getElementById('outer').onmouseout= function(event) {
// deal with IE nonsense
if (event===undefined) event= window.event;
var target= 'target' in event? event.target : event.srcElement;
if (target!==this) return;
...
};
The usual problem with mouseout is you get it when the pointer moves “out” of the parent even if it's only moving “in” to the child. You can detect this case manually by looking up the ancestor list of the element the mouse is moving into:
var other= 'relatedTarget' in event? event.relatedTarget : event.toElement;
while ((other= other.parentNode).nodeType===1)
if (other===this) return;
This is the mousein/mouseout model: it is only interested about which element is the mouse's immediate parent. What you more often want is the mouseenter/mouseleave model, which considers element trees as a whole, so you'd only get mouseleave when the pointer was leaving the element-or-its-descendants and not moving directly into the element-or-its-descendants.
Unfortunately mouseenter/mouseleave is currently an IE-only event pair. Hopefully other browsers will pick it up as it is expected to be part of DOM Level 3 Events.

how to add event for all elements except which have a certain class?

I want to add mouse over event on all elements on the page except which has class "no_inspect",
i want to add this event after loading all page elements, i tried to write it like that:
$('*:not(.no_inspect)').mouseover(MouseOverEvent);
but its not working, seams something missed.
UPDATE Not working means:
The event is attaching to all elements on the page "have no_inspect class or haven't" which is not the behavior i want.
UPDATE MouseOverEvent Code:
function DIOnMouseOver(evt) {
element = evt.target;
// set the border around the element
element.style.borderWidth = '2px';
element.style.borderStyle = 'solid';
element.style.borderColor = '#f00';
}
The mouseover event bubbles. Try mouseenter instead.
Also, why are you applying the styles to evt.target? Why not 'this'?
function DIOnMouseOver(evt) {
$(this).css({
border: '2px solid #f00'
});
}
As mentioned by Matchu (in the comments), another way to avoid propagation is to call event.stopPropagation() within your event handler.
The code you posted should work, are you sure the mouseover event is not firing for an element that is wrapping the one you are mousing over?
Are you using the latest and greatest version of JQuery. I know older versions had some sort of class bug in the not selector.
You may want to try
$('*').not('.no_inspect').mouseover(MouseOverEvent);

Categories