Is there a way to detect if a browser supports event capturing as well as event bubbling? I have checked http://modernizr.com/ but can't see any information in the documentation.
Am I correct in thinking that IE < 9 doesn't support event capture but in other browsers it should be ok?
For clarity I want to detect if the browser supports the event capture phase of the event DOM model as well as the event bubble phase.
There may be better approaches to this, but this is the first thing I came up with. You would need to create an element, bind an event handler to it in capture phase, fire an event on it, and check the eventPhase property in the event handler:
var button = document.getElementById("example");
document.addEventListener("click", function (e) {
console.log(e.eventPhase); // 1 === capture, 2 === target, 3 === bubble
}, true);
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
button.dispatchEvent(evt);
You would probably want to add in all sorts of other checks (e.g. for dispatchEvent support) and you'd need to actually create the button element in the code (and insert it into the DOM, hidden).
Here's a fiddle containing the above code to get you started.
Related
I am working with an existing web app, in the app there are a variety of submit buttons on different forms, some using regular http post, some defining an onClick function, and some binding a js event handler to the button using a class on the element.
What I want to do, is bind another event handler to these buttons by just adding a class to the buttons, but what I want to determine is will the new event handler be guaranteed to be executed, or could one of the form submit actions happen before it does meaning my new function isn't hit.
The example scenario is I want to add a class to these buttons that bimds them all to a common js function that simply logs usage to some api. Is there a risk that the logging function isn't called because the form submit has navigated away from the page?
I've not done loads of js development, and I could test this 100 times over and just get lucky with it firing.
Below is some code I have tested with for one of the examples - again, I'm not asking how to bind multiple events, the question is to about my understanding of the spec and whether execution of all handlers is guaranteed.
$(document).ready(function(){
$('.testingBtn').click(function() {
window.location.replace("http://stackoverflow.com");
});
$( ".testingBtn" ).click(function(){
alert('submitting!');
});
});
<input class="testingBtn" type="submit" id="submitform" value="Complete Signup" />
As seen above, I can bind the multiple events, and in this example, just directed to another url, but this could be a form.submit() etc. In my testing the alert has always fired first, but am I just getting lucky with the race conditions?
In JS, you don't really have control over what order the event handlers are called, but with careful delegation and well-placed listeners, it is possible.
Delegation is one of the most powerful features of the event model. As you may or may not know: in JS, an event is handed to the top of the dom, from where it propagates down to the element onto which the event should be applied. It stands to reason, therefore, that an event listener attached to the global object will call its handler prior to a listener that has been attached to the element itself.
window.addEventListener('click',function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
console.log('window noticed you clicked something');
console.log(target);//<-- this is the element that was clicked
}, false);//<-- we'll get to the false in a minute
It's important to note we actually have access to the event object in the handlers. In this case, we left the event object untouched, so it'll just continue to propagate down to the target, on its way down, it might meet with something like this:
document.getElementById('container').addEventListener('click', function(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (target.tagName.toLowerCase() !== 'a' || target.className.match(/\bclickable\b/))
{
return e;//<return the event, unharmed
}
e.returnValue = false;
if (e.preventDefault)
{
e.preventDefault();
}
}, false);
Now, this handler will be called after the listener at the window level calls its helper. This time, the event is changed if the clicked element didn't have the clickable class, or the element is a link. The event is canceled, but it lives on, still. The event is still free to propagate further down the dom, so we might encounter something like:
document.getElmentById('form3').addEventListener('click',function(e)
{
e = e || window.event;
if (e.returnValue === false || e.isDefaultPrevented)
{//this event has been changed already
//do stuff, like validation or something, then you could:
e.cancelBubble = true;
if (e.stopPropagation)
{
e.stopPropagation();
}
}
}, false);
Here, by calling stopPropagation, the event is killed off. It can't propagate further down the dom to its target unless the event was already altered. If not, the event object travels further down the DOM, as if nothing happened.
Once it reaches its target node, the event enters its second phase: the bubble phase. Instead of propagating down into the deeps of the DOM, it climbs back up, to the top level (all the way to the global object, where it was dispatched... from whence it came and all that).
In the bubble phase, all the same rules apply as in the propagation phase, only the other way around. The event object will encounter the elements that are closest to the target element first, and the global object last.
There's a lot of handy, and clear diagrams for this here. I can't put it any better than good 'ol quirksmode, so I suggest you read what they have to say there.
Bottom line: when dealing with 2 event listeners, attach them both on a different level to sort-of queue them the way you like.
If you want to guarantee both are called, only stop the event from propagating in that handler that will be called last.
When you've got two listeners, attached to the same element/object for the same event, I've never come across a situation where the listener that was attached first, wasn't also called first.
That's it, I'm off to bed, hoping I made sense
jQuery makes this easy.
$(document).on('click', '.someclass', function() {
doStuff();
});
$(document).on('click', '.someclass', function() {
doMoreStuff();
});
Handlers then both will fire on click. jQuery keeps a queue of handers for you. And handles document clicks that match a selector of your choice so that they can be triggered no matter when your buttons are created.
I am/was having a similar issue as this. However I can not affect the order of/delegate the pre-existing 'click' events (added by Wicket framework).
But I still need to execute a new custom event before any of the 'click' or 'change' events handled by the framework.
Luckily there are several events that are actually executed in order. The 'mousedown' and the 'mouseup' happens to happen before the 'click'.
http://en.wikipedia.org/wiki/DOM_events
$(document).on('mousedown', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
console.log(target + ' before default event'); // Hold mouse button down to see this message in console before any action is executed
});
OR
$(document).on('mouseup', function (event) {
event = event || window.event
var target = event.target || event.srcElement;
alert(target + ' before default event'); // You may not notice this event fires when the page changes unless this is an alert
});
This will allow the logging to be done (e.g. via ajax) before the actual event is executed e.g. a page change via (ajax) link.
Of course you may need to have more sophisticated means to detect for what the additional event handling should be done, but you can use for example the 'target' information for this. => This script monitors everything on the page, as this is how I need this to be done.
I am thinking about making a Firefox add-on of my own and doing some experiments for the functionality I might be adding in it.
As I am just checking the feasibility of things for now, I just got a skeleton created from Mozilla add-on builder and started working in it. What I am trying right now is to send mouse click or key press events.
I have tried the available ways to send event but somehow it's not working for key events
I tried it using dispatchEvent:
onMenuItemCommand: function(e) {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var evt1 = document.createEvent("MouseEvents");
evt1.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
//it's returning me null for document.getElementById... so I changed it.
var cb1 = gBrowser.selectedBrowser.contentDocument.getElementById("strict");
var canceled1 = !cb1.dispatchEvent(evt1);
var evt = document.createEvent("KeyEvents");
evt.initKeyEvent("keydown", true, false, window,
false, false, false, false, 0x42, 0);
var cb = gBrowser.selectedBrowser.contentDocument.getElementById("filter");
var canceled = !cb.dispatchEvent(evt);
if(canceled)
{
// A handler called preventDefault
alert("canceled");
}
else
{
// None of the handlers called preventDefault
alert("not canceled");
}
}
When I tried this code in Firefox, it did updated the checkbox which means click event worked, but nothing happened in textbox where I was expecting it to print a character. But it showed alert box with "not Cancelled" proving that event was not cancelled!
As event was not cancelled, I decided to put a keypressed handler on window.document... and it got invoked when add-on send these events! Which means the events are getting generated and are bubbling as well.
Then why only mouse events are working and key events are not? Am I missing something here?
(I have also tried sendKeyEvent with nsIDOMWindowUtils. still had no luck with it.)
btw, I am using Firefox 3.6.15 with Gecko :1.9.2.15
You have to focus the element before you can dispatch a key event to it.
EDIT:
This is only true for web pages. Extensions can dispatch keypress events to any text field.
EDIT:
Text entry is done via keypress events, not keydown events.
EDIT:
You won't get any characters inserted if you don't provide a character code. (Sorry for overlooking that, it should have been obvious.) Also although it seems to work with your window you should pass in the browser's contentWindow as the defaultView.
With buttons, I can call the click() method on them to have a click generated. DIVs however don't have this method on all browsers. Yet I can attach click event listeners to them (by either setting .onclick="..." or adding an event listener).
Is there any way for me to "synthesize" a click on such an element programmatically, but without using jQuery? Ideally this will not be dependent on a specific way of the listeners being registered (so simply calling eval(div.onclick) will not work for me), and work in all modern browsers.
(For the curious, I need this for automated testing, not tricking users.)
I recently wrote a function to do just this into my library, it can be found in the GitHub repository here.
It is completely cross browser and triggers the specified event on the elements returned by the selector engine. I am sure you will be able to extract the code you need from it.
If not, here is what you need. Replace element with, well, the element. And type with the type of event, in this case, click.
// Check for createEventObject
if(document.createEventObject){
// Trigger for Internet Explorer
trigger = document.createEventObject();
element.fireEvent('on' + type, trigger);
}
else {
// Trigger for the good browsers
trigger = document.createEvent('HTMLEvents');
trigger.initEvent(type, true, true);
element.dispatchEvent(trigger);
}
Here is an example implementation.
function simulateEvent(element, type) {
// Check for createEventObject
if(document.createEventObject){
// Trigger for Internet Explorer
trigger = document.createEventObject();
element.fireEvent('on' + type, trigger);
}
else {
// Trigger for the good browsers
trigger = document.createEvent('HTMLEvents');
trigger.initEvent(type, true, true);
element.dispatchEvent(trigger);
}
}
If your browser is DOM compatible you can use the dispatchEvent(evt) method of the DIV element.
For more see the dom w3c spec.
I need to simulate a click or mouse event. I tried various things but the lib i am using doesnt seem to respond to it or does respond but only on specific browsers. ATM i do $('#target').val($('#target2').val()); which works on firefox and opera. Fails on chrome, IE8 and safari.
I could add events to the libs but i wouldnt know which event to add (or how to do it properly). Anyways how do i solve this? basically i am setting the textarea text with .val() and the lib doesnt seem to pick up that event.
You could use the DOM Level 2 Event Model, like:
function simulateClick(element) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
cb.dispatchEvent(element);
}
Demo: http://www.jsfiddle.net/4yUqL/66/
This will truly simulate a mouseclick on an element. Regardless how events were bound.
.trigger('click') in jQuery might achieve what you're trying to do. It will fire all the handlers attached to the click event.
In case anyone bumps into this looking for a framework agnostic way to fire any HTML and Mouse event, have a look here: How to simulate a mouse click using JavaScript?
I want to simulate a click to an anchor tag with all extras like correct target handling.
There seems to be a "[click()][3]" method for anchor's DOM object but not all browsers support that. Firefox throws this error:
Error: anchorObj.click is not a function
It also works strangely on Opera 10 and Konqueror, causing infinite clicks to happen when it's called inside onclick handler of a surrounding div. I guess only IE8 works fine with it. Anyway I don't want it since major browsers mostly have problems with it.
I found this alternate solution for Firefox in Mozilla forums:
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
anchorObj.dispatchEvent(evt);
This seems too ugly and cumbersome for me. I don't know how compatible it is and I want to avoid writing browser specific code as much as possible.
I can't use location.href = anchorObj.href; because it doesn't handle "target" attribute. I can do some hard coding based on target's value but I'd like to avoid that as well.
There is suggestion of switching to JQuery but I'm not sure how well it handles target property either since I haven't worked with it before.
Here is a complete test case that simulates the click event, calls all handlers attached (however they have been attached), maintains the "target" attribute ("srcElement" in IE), bubbles like a normal event would, and emulates IE's recursion-prevention. Tested in FF 2, Chrome 2.0, Opera 9.10 and of course IE (6):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
function fakeClick(event, anchorObj) {
if (anchorObj.click) {
anchorObj.click()
} else if(document.createEvent) {
if(event.target !== anchorObj) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
var allowDefault = anchorObj.dispatchEvent(evt);
// you can check allowDefault for false to see if
// any handler called evt.preventDefault().
// Firefox will *not* redirect to anchorObj.href
// for you. However every other browser will.
}
}
}
</script>
</head>
<body>
<div onclick="alert('Container clicked')">
<a id="link" href="#" onclick="alert((event.target || event.srcElement).innerHTML)">Normal link</a>
</div>
<button type="button" onclick="fakeClick(event, document.getElementById('link'))">
Fake Click on Normal Link
</button>
<br /><br />
<div onclick="alert('Container clicked')">
<div onclick="fakeClick(event, this.getElementsByTagName('a')[0])"><a id="link2" href="#" onclick="alert('foo')">Embedded Link</a></div>
</div>
<button type="button" onclick="fakeClick(event, document.getElementById('link2'))">Fake Click on Embedded Link</button>
</body>
</html>
Demo here.
It avoids recursion in non-IE browsers by inspecting the event object that is initiating the simulated click, by inspecting the target attribute of the event (which remains unchanged during propagation).
Obviously IE does this internally holding a reference to its global event object. DOM level 2 defines no such global variable, so for that reason the simulator must pass in its local copy of event.
Quoted from https://developer.mozilla.org/en/DOM/element.click
The click method is intended to be used with INPUT elements of type button, checkbox, radio, reset or submit. Gecko does not implement the click method on other elements that might be expected to respond to mouse–clicks such as links (A elements), nor will it necessarily fire the click event of other elements.
Non–Gecko DOMs may behave differently.
Unfortunately it sounds like you have already discovered the best solution to your problem.
As a side note, I agree that your solution seems less than ideal, but if you encapsulate the functionality inside a method (much like JQuery would do) it is not so bad.
There is a simpler way to achieve it,
HTML
Bootstrap is life !
JavaScript
// Simulating click after 3 seconds
setTimeout(function(){
document.getElementById('fooLinkID').click();
}, 3 * 1000);
Using plain javascript to simulate a click along with addressing the target property.
You can check working example here on jsFiddle.
well, you can very quickly test the click dispatch via jQuery like so
$('#link-id').click();
If you're still having problem with click respecting the target, you can always do this
$('#link-id').click( function( event, anchor )
{
window.open( anchor.href, anchor.target, '' );
event.preventDefault();
return false;
});
None of the above solutions address the generic intention of the original request. What if we don't know the id of the anchor? What if it doesn't have an id? What if it doesn't even have an href parameter (e.g. prev/next icon in a carousel)? What if we want to apply the action to multiple anchors with different models in an agnostic fashion? Here's an example that does something instead of a click, then later simulates the click (for any anchor or other tag):
var clicker = null;
$('a').click(function(e){
clicker=$(this); // capture the clicked dom object
/* ... do something ... */
e.preventDefault(); // prevent original click action
});
clicker[0].click(); // this repeats the original click. [0] is necessary.