stopPropagation in Chrome and IE - javascript

I've been having trouble getting stopPropagation to work for my dataview. Basically the issue is as follows;
Select node in the dataview, this fires the select, selectionchange events
Selected node has an image with an onClick event, this creates a report in a pop up window.
When image is clicked I call stopPropagation to prevent the event being bubbled up to the dataview which would cause the deselect, selectionchange events to be fired.
stopPropagation only seems to work in Firefox for me. Chrome and IE it seems to have no effect as the node is deselected and the unwanted events fired.
I've tried the following function on the onClick event
handleBubbleEvent: function(e) {
if (!e) {
var e = window.event;
}
e.cancelBubble = true;
e.returnValue = false;
if (e.stopPropagation) {
e.stopPropagation();
}
}
Have also seen stopImmediatePropagation, preventDefault, stopEvent but these also has had no effect
I am doing all this inside an Ext XTemplate
I'm wondering if this is an ExtJS 5 issue and the same code is working for an older version of ExtJS. I just can't seem to stop the click event bubbling back up to the dataview which then fires the deselect and selectionchange events.
Could it be that it is a timing/order issue. I use ExtJS's selectionchange event on the dataview whereas in the XTemplate I am using my own listener function? I see stuff online referencing capturing/bubbling of events and as I'm not a web developer I'm struggling on this.
UPDATE:
I'm now looking at events and capturing/bubbling, it seems the capturing is going up to the parent and calling the deselect, selectionchange then going down into the actual click handler at which point I then call stopPropagation but it is too late at this stage. Looking at creating my own listener for selectionchange with either target/delegate set so that it is only called when class != 'some class' and then a listener on 'some class' click to handle what I want and stopPropagation, if that makes any sense!

Try using a setTimeout on any dom manipulation triggered by the event.target element's event handler so that the event bubbling completes before any of the dom manipulation occurs.
I had the same issue with Chrome, and (like with you) Firefox worked. I discovered that Chrome seems to get confused if you modify the dom before the event bubbling completes.
In my case, I was adding two elements to the document.body (a canvas and a fieldset) and by the time chrome bubbled the event, the event.target was incorrect (it thought "BODY" was the event.target.tagName -- WRONG!). I wrapped the DOM manipulation portion in a setTimeout like this:
setTimeout(()=>
{
document.body.appendChild(canvas);
document.body.appendChild(fieldSet);
},0);
After this, chrome started reporting the correct element.target on the bubbled body click event. Maybe this same technique will help you get e.stopPropagation() to do its thing before the dom manipulation occurs.

Related

mouseup event on document.documentElement does not fire with alert

I need to detect mouseup event after mousedown on the document.
I have tried to add an event listener to document and document.documentElement with no success.
I need possibly a cross platform solution without jquery.
Notes: problem appears on not all browsers using alert().
http://jsfiddle.net/0f7vrzh7/8/
document.documentElement.addEventListener('mousedown', function(){
alert('mousedown');
});
document.documentElement.addEventListener('mouseup', function(e){
alert('mouseup')
});
In certain browsers the first alert would stop the second event. It works even with alert in IE11 for example. In the browsers where you experience this issue the alert box blocks the UI before the mouseup event is processed or propagated to the element you have the event handler attached to. Change to console.log() statements in your event handlers and the events are fired as you expect them to. Updated fiddle.
it's yours alert block the mouseup event. try with
document.documentElement.addEventListener('mousedown', function(){
document.getElementById("test").style.backgroundColor = "#ff0";
});
document.documentElement.addEventListener('mouseup', function(e){
alert('mouseup')
});
http://jsfiddle.net/0f7vrzh7/16/

Add on load event for img elements now and in future?

I want to add a load event on an image that would affect that image even when that image is added to the document after the page's initial load. For a click event I would do someting like this:
$(document).on('click', '.elem', function(e) {
// do stuff
});
When I try something similar with the load event, however, it does not to work. This is what I have tried:
$(document).on('load', '.image', function() {
// do stuff
});
This event is simply never triggered. Does anyone know what I may be doing wrong, or how to achieve this?
This answer is incorrect. It's possible to do this using the capture phase, see Dhia Louhichi's answer. I'll delete this answer when I can (i.e.., once it's no longer the accepted answer).
By their nature, delegated handlers only work for events that bubble. Not all do, and load is one of the ones that doesn't. The jQuery documentation even highlights this:
In all browsers, the load, scroll, and error events (e.g., on an element) do not bubble.
You'll have to add the load handlers to the images when you add them.
What I mean by "delegated handlers only work for events that bubble":
Events that bubble work like this (in the "bubbling" phase, which is the phase you normally work with): The event is fired on the element where it originates, and then on that element's parent, then that element's parent, etc. until it gets to the document element (html). This diagram from the DOM3 events spec may help make this clearer:
Using a delegated handler (the kind you're using in your question) relies on bubbling. Consider this HTML:
<div id="container">
<div class="content">xxxx</div>
<div class="content">xxxx</div>
<div class="content">xxxx</div>
</div>
If you do $("#container").on("click", ".content", ...) you're not hooking the event on the "content" divs, even though jQuery will make it seem a bit like you are. You're hooking the event on the "container" div. When the event bubbles down to the container div, jQuery looks at where it started and sees whether it passed through any "content" divs during its bubbling. If it did, jQuery calls your handler as though (mostly) you'd hooked the event on the "content" div. That's why delegated handlers work when you add elements later; the event isn't hooked on the element, but on the container.
So that's why it won't work for load: load doesn't bubble, so even though it fires on the img elements you add, it doesn't bubble to the parent and so on, and so you never see it. To see it, you have to hook it on the specific element, not an ancestor of it.
This code shows handling the load event for img elements created in the future, without explicitly adding a listener/handler to them, by using the capture phase of the event process on document.body (also works when attached to document, but not window because of backward compatibility issues):
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
Live Example:
document.body.addEventListener(
"load",
function (event) {
var elm = event.target;
if (elm.nodeName.toLowerCase() === 'img') {
console.log("Loaded: " + event.target.src);
}
},
true // Capture phase
);
// Brief wait, then add an image
setTimeout(function() {
document.body.insertAdjacentHTML(
"beforeend",
"<img src='https://via.placeholder.com/150/202080?text=Some+Image'>"
);
}, 400);
This is tested and works in at least the following:
IE9+
Chrome and other Chromium-based browsers (Opera, Edge, Vivaldi, ...)
Firefox
iOS Safari
The behavior is also documented. In fact, coincidentally the specification gives this example mentioning load by name (scroll down slightly from that link):
EXAMPLE 5
The following is one way to interpret the above tables: the load event will trigger event listeners attached on Element nodes for that event and on the capture and target phases. This event is not cancelable. If an event listener for the load event is attached to a node other than Window, Document, or Element nodes, or if it is attached to the bubbling phase only, this event listener would not be triggered.
That's saying load will be fired in the capture and target phases, but not the bubbling phase (since the event doesn't bubble).
By default, when you use addEventListener, the handler is attached for the target phase of the element you call addEventListener on and the bubbling phase for any element within that element. If you add the third argument with the value true, though, it attaches the handler for the target phase of the element you call addEventListener on (as before) and the capture phase for any element within that element. So the code above will handle load for document.body during the target phase (except document.body doesn't fire load) and also handle load for the capture phase of any element within document.body.
More about event flows in the specification, including this handy diagram:

Determining whether focus was received as result of a click event

I have an <input> element that can either have the focus set via code, or as the result of a mouse click.
If the user clicks on the input, then the click event handler will fire - all well and good. If the element receives the focus via some other way (e.g. via code) then I want to manually trigger the click event so that the handler will also fire.
I could do this:
$elem = $('input');
$elem
.on('focus', function() { $(this).trigger('click') })
.on('click', function() { alert('Clicked!') });
However, this will result in click handler being fired twice; once for the click event and once for the focus event.
Is there any way to selectively trigger the click handler only if the focus was not received as the result of a click event?
UPDATE
This is a very simplified version of my problem, so I can't do things like bind both handlers to the focus event etc. I'm trying to merge two third-party pieces of code.
The .trigger() function adds a property isTrigger in the event object to identify that the event was triggered by its usage. Although, it is not documented the property is still present in jQuery 1.8.3 but it seems to only be used internally.
Anyways, you can make use of the extraParameters parameter to add a custom property to the event object. For instance,
$(this).trigger('click', {
isTrigger: true
});
It will keep the compatibility with isTrigger even if it is gone in a future release.
After doing some more research it appears that there is no way of guaranteeing which event will fire first: click or focus. (There doesn't seem to be a standard that dictates the order of events.)
This means that when the focus event fires there's no way to determine if a click event will or will not be triggered by the browser shortly afterwards.
I managed to solve the issue by using setTimeout() to run a test about 100ms after the focus event fired to check if the click event had fired. The third-party code that I was using (bound to the click event) added an extra class to the <input>, so I was able to check for that.
You can tap into the mousedown event which fires before the focus event. When you click a focusable object the order of events is as follows... mousedown, focus, mouseup, click.
You could set a flag in the mousedown event and then check for it in the focus event to see if the focus came from a mouse click. Obviously make sure to clear the flag in the focus event handler. Every application is different, but tapping into the mousedown event allows you to figure out a solution.
Here is a JSFiddle demonstrating the order of events... http://jsfiddle.net/ek7v7/
$elem = $('input');
$elem
.on('focus', function() { alert("Focused!") })
Focus can be fired by focusing the input by using tab, clicking it, or by using .focus()
Is there a reason for on('click', ...)?

What's the cross-browser way to capture all single clicks on a button?

What's the best way to execute a function exactly once every time a button is clicked, regardless of click speed and browser?
Simply binding a "click" handler works perfectly in all browsers except IE.
In IE, when the user clicks too fast, only "dblclick" fires, so the "click" handler is never executed. Other browsers trigger both events so it's not a problem for them.
The obvious solution/hack (to me at least) is to attach a dblclick handler in IE that triggers my click handler twice. Another idea is to track clicks myself with mousedown/mouseup, which seems pretty primitive and probably belongs in a framework rather than my application.
So, what's the best/usual/right way of handling this? (pure Javascript or jQuery preferred)
Depending on your situation you can use different approaches, but I would suggest using namespaced event handlers with jQuery like this:
function eventHandler(event) {
// your handler code here
doSomeMagic();
}
var element = $('#element');
element.one('click.someNameSpace', function(event){
// first we unbind all other event handlers with this namespace
element.unbind('.someNameSpace');
// then we execute our eventHandler
eventHandler();
}).one('dblclick.someNameSpace', function(event){
// If this fires first, we also unbind all event handlers
element.unbind('.someNameSpace');
// and then execute our eventHandler
eventHandler();
});
I'm not sure this will work the way you want it, but it's a start, I guess.
Mousedown and mouseup works just like the click functions, unfortunately so much that when IE omits a click because of a doubleclick it will also omit the mousedown and mouseup. In any case, you can add both click and dblclick to the same object and feed the clicks through a function that sort out any click happening too close to the last.
<div onclick="clk()" ondblclick="clk()"></div>
lastclicktime=0
function clk(){
var time=new Date().getTime()
if(time>lastclicktime+50){
lastclicktime=time
//Handle click
}
}
I by the way just found out that, at least in Firefox the dblclick event is not given an event time, therefore I had to resolve to the Date method.

jQuery live('click') firing for right-click

I've noticed a strange behaviour of the live() function in jQuery:
normal
live
$('#normal').click(clickHandler);
$('#live').live('click', clickHandler);
function clickHandler() {
alert("Clicked");
return false;
}
That's fine and dandy until you right-click on the "live" link and it fires the handler, and then doesn't show the context menu. The event handler doesn't fire at all (as expected) on the "normal" link.
I've been able to work around it by changing the handler to this:
function clickHandler(e) {
if (e.button != 0) return true;
// normal handler code here
return false;
}
But that's really annoying to have to add that to all the event handlers. Is there any better way to have the event handlers only fire like regular click handlers?
It's a known issue:
It seems like Firefox does not fire a
click event for the element on a
right-click, although it fires a
mousedown and mouseup. However, it
does fire a click event on document! Since .live catches
events at the document level, it sees
the click event for the element even
though the element itself does not. If
you use an event like mouseup, both
the p element and the document
will see the event.
Your workaround is the best you can do for now. It appears to only affect Firefox (I believe it's actually a bug in Firefox, not jQuery per se).
See also this question asked yesterday.
I've found a solution - "fix" the the live() code itself.
In the unminified source of jQuery 1.3.2 around line 2989 there is a function called liveHandler(). Modify the code to add one line:
2989: function liveHandler(event) {
2990: if (event.type == 'click' && event.button != 0) return true;
This will stop the click events from firing on anything but the left-mouse button. If you particularly wanted, you could quite easy modify the code to allow for "rightclick" events too, but this works for me so it's staying at that.
You can actually rewrite it as:
function reattachEvents(){
$(element).unbind('click').click(function(){
//do something
});
}
and call it when you add a new dom element, it should have the expected result (no firing on the right click event).
This is an unfortunate consequence of how live is implemented. It's actually uses event bubbling so you're not binding to the anchor element's click event, you're binding to the document's click event.
I solved this by using mousedown events. In my situation the distinction between mousedown and click didn't matter.

Categories

Resources