I am doing browser automation using C#, and I would like to modify or possibly just eliminate event handlers on some of the html elements in webpages that I am looking at. E.g., suppose there is a button there which might (or might not) have an attached onClick event. How do I go about:
- finding out if there are any event handlers attached to onClick for it?
- removing them?
Replacing element with its own clone should effectively discard all of its event listeners (well, technically listeners are still on an element, but since an element is replaced with its own clone, it looks as if listeners were simply removed):
el.parentNode.replaceChild(el.cloneNode(true), el);
Unfortunately, this won't work in IE, since IE erroneously transfers event listeners of an element on clone. To work around that you can reassign innerHTML of element's parentNode:
el.parentNode.innerHTML = el.parentNode.innerHTML;
Note that this will not only remove event listeners of an element, but also listeners of all of element's siblings.
Alternatively, you can work around IE issue by reassigning outerHTML of an element:
el.outerHTML = el.outerHTML;
This depends on how the event handlers have been attached to the element.
If they are attached using addEventListener or one of the proprietary addWhatever listener methods, there is no way to list them.
If they are attached by modifying the event property, ie. node.onclick = whatever, then you can read the value of the property to get the function and it'll work the same as any other JS func.
There is a third way too:
You can override the default addEventHandler/addListener behavior if the code you automate uses those. By doing this, you can replace the default behavior by one which pushes each handler into an array, which you can then loop over yourself.
The following code might work:
var oldAddEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function(event, handler, bubbling) {
/* do whatever you want with event parameters */
oldAddEventListener.call(this, event, handler, bubbling);
}
As far as I know, it's not currently possible to use javascript to get all the event handlers attached to an element.
See this link for more info:
http://www.howtocreate.co.uk/tutorials/javascript/domevents
Related
My code is this:
document.addEventListener('click', function(ev){
if(ev.path[0].className == 'linkTogether'){//do something}
if(ev.path[0].id == "createNewPage"){//do something}
});
Which has actually been working well for dynamically created buttons and nodes, but something just feels off about it. So I'm wondering if this is best practice or if there is a better way to add event listeners to dynamically created elements.
Thanks,
Jack
In specific cases where you have huge amount of objects that behave in the same way you can use this technique (adding event listener to their parent) to improve the performance of your script.
In a general page however you just have to many different objects and iterating trough all of them to check which one you've clicked is not faster.
Here is an article for the technique you are referring to - event delegation.
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
However, it does have performance implications. Every time you click anywhere in the document, the code will run, and have to go through the entire list of event bindings that you need to check. You can improve this by adding your event listener to a more specific element. If the dynamic elements are always added inside a specific DIV, add your listener to that DIV rather than document.
This also avoids another pitfall of event delegation. Event delegation depends on the event bubbling up from an inner element to all its containers. But if there are any handlers along the way that call event.stopPropagation, the event won't make it out to document. If you add the listener to a lower element, you're less likely to have a conflict like that.
And I would like to add, if you create elements dynamically, better you include the listeners inline and filter in the function what you want to do.
<div class="form_ID1" onclick="myfunction(this, event);">
... children bubbling
... Use if statements in caught js myFunction function.
In myfunction function you will capture the child element by event.target and the element that contains the listener by this.className.
There are scenarios however that you need a universal ( document ) click event. e.g You need to close a pop up when you click outside of a pop up !! It is like: e.g.
if (clicked.className != popup.className) popup.remove()
Even in this case there is a workaround by inserting an onblur="myfunction(this); in the parent DIV of the popup.
I have a set of inputs where I would like to add another input when the last input currently displayed receives focus. The form starts out with 2 inputs.
$('.answer_fields input:last-child').focus(function(event) {
var nextfield = $('.answer_fields input').length + 1;
$('.answer_fields').append('<input class="form-control answer_field" placeholder="Answer option #' + nextfield +'" id="answer' + nextfield +'">');
});
As it currently stands, additional fields are only appended when the 2nd input (the original "last-child") receives focus.
Checking the source seems to show that the inputs are getting added to the DOM as expected. What am I missing?
The jQuery selector is evaluated ONCE at the time you run the code to install the .focus() event handler. It isn't adjusted live as things change. You can switch to event delegation if you want that type of behavior.
A similar event handler using event delegation that will be evaluated live would look like this:
$(some common parent selector).on("focus", ".answer_fields input:last-child", function() {
// code here
});
Working demo: http://jsfiddle.net/jfriend00/ZZLPJ/
First off, your original code did not work because you were attaching event handlers to specific elements that existed at the time you ran your first code. Which events you had event handlers attached to did not change as your structure changed in the future so new elements would not have event handlers on them and the :last-child portion of your selector would not be re-evaluated.
Event delegation works because certain events that occur on a given element will bubble up the DOM hierarchy through each parent. You can attach a single event handler to a common parent and when that parent is notified that a given event occurred, it can then dynamically evaluate the selector you specified vs. the child item that originated the event and if it's a match, fire your event handler. This gives you the "real-time" selector evaluation that you want in your case.
To give you an idea, these are what I call "static" event handlers:
$(".test:last-child").focus(fn);
$(".test:last-child").on("focus", fn);
This a delegated (and thus dynamic) event handlers:
$("#parent").on("focus", ".test:last-child", fn);
For more info on this topic, see these other answers:
jQuery .live() vs .on() method for adding a click event after loading dynamic html
Does jQuery.on() work for elements that are added after the event handler is created?
Should all jquery events be bound to $(document)?
Just checked to be sure and the focus event does bubble event delegation will work with it.
This is a common question for people starting to use jQuery. It turns out that the named eventListeners (i.e. $.fn.click, $.fn.focus, etc.) attach an event listener to each matching DOM element when the page is first loaded. These listeners are defined on the matching element themselves, meaning that each one is unique to the element it is attached to.
You will hear this method compared to what is commonly known as event delegation. Event delegation involves attaching an even listener to a shared parent element that will check the event.target attribute to see if it matches your criterion (in your case, .answer_fields input:last-child). This differs from attaching a unique listener to each DOM element you wish to target because it allows you to add new nodes to the DOM dynamically, which will then be handled by the parent node's listener like any other node.
To accomplish event delegation using jQuery, use the .on() function on a common ancestor (I'll use document as an example) and listen for all matching elements as the second argument of the function call:
$(document).on('focus', '.answer_fields input:last-child', function(event) {
// your code here
});
Not only will this solve your problem with dynamically created DOM elements, but event delegation will greatly improve the performance of your page by reducing the total number of event listeners attached to elements in the DOM.
For more information about event delegation, I'd encourage you to check out this tutorial and this SO question
Is it possible to remove all event listeners on an object even those declared externally, I have the following code at http://jsfiddle.net/E5n7g.
I use the built-in
addEventListener()
to add an event on the "a tag", I then try and remove this using jQuery, however it seems jQuery can only unbind events its bound itself.
The reason I am asking this, is because I am trying to use jQuery on combination with a product which uses Dojo. When I bind the "click" event, its added to the list of event handlers however whenever I click that link its erasing that event handler and only leaving Dojo's intact.
One method I found that I used (and worked rather well) was the cloneNode function.
var new_el = el.cloneNode(true); //true means a deep copy
el.parentNode.replaceChild(new_el,el);
When you clone the element, it does not clone any events.
Generally, when I want to bind some event to an element, I will bind the event to the element directly. For example, I want to bind the click event to the "li" element:
<ul id="ul_list">
<li class="t">xxxx</li>
<li class="t">xxxx</li>
.....
</ul>
var lis=document.getElementById("ul_list").children();
for(var i=0;i<lis.length;i++){
lis[i].onclick=function(){
console.info(this.innerHTML);
}
}
It works.
But in some open source code, I find that people prefer to bind the event to the parent element:
document.onclick=function(e){
e=e==null?:window.event:e;
var target=e.target; //the browser is not considered here
if(target.className=='t' && target.localName='LI'){
console.info(target.innerHTML);
}
}
I wonder which is better?
Also, when handling drag events, people bind the mousemove event to the whole document. Why?
people prefer to bind the event to the parent element
This is referred to as event delegation and is especially useful when you want to trigger the same event handler for multiple elements. Instead of binding an event handler to each those elements, you bind it to a common ancestor and then check from which element the event originated. This works, because events bubble up the DOM tree.
I wonder which is better?
It depends, both approaches have their pros and cons.
Event delegation can be slower, as the event has to bubble up first. You also might have to perform DOM traversal because the event might not originate at the element you test for. For instance, in your example, the li elements might have other children, lets say an a element. To find out whether the clicked a element is a descendant of one of the lis, you have to traverse the ancestors and test them.
On the other hand, binding the handler directly is faster in the sense that the event is processed directly at the element. But if you bind a lot of event handlers and don't do it properly (like in your example) you use more memory than you actually need. Older browsers (I think especially IE) might also perform worse if there are many event handlers.
Also,sometime when handle the drag effect,people always bind the mousemove event to the whole document,why?
The problem is that while dragging an element, the mouse often moves faster than the element and leaves it. If you bind the mousemove event only to the dragged element, whenever the cursor leaves the element, the movement would stop. To avoid this, the event handler is bound to the whole document (for the duration of the dragging) so that the movement is smoothly.
Linking to the parent means you are adding one event handler instead of multiple. That is a boost for performance in your application! Also if you add new elements to the page, you do not have to worry about binding them.
Only time when that model is a bad design is when you need to cancel the event. For example, you have links in the li and you need to prevent them from doing their default action.
In case of click event, it's better to bind directly to the affected elements, no point binding to everything - why do you need to have your function trigger when you click something that is totally not relevant to you?
It can be useful when the elements are spread over the document and hard to "collect" them, e.g. when they only have the same class - getElementsByClassName is not very efficient in pure JavaScript as you need to iterate over all the elements, so in such case it's better to trigger the function always and check what has been clicked.
Bind handlers to the most specific event and element possible.
Note that (just to be pedantic!) you bind a handler to an event for an element, not 'bind some event to an element'.
I have a long a web form. I'm wondering if I should bind() to each <input> element separately (what I really want) or should I only define one bind() all of the input elements then do an if-then inside the handler to handle the specific element?
jQuery's event handlers are implemented in JavaScript (they have to be).
In general, it's best to use a single handler for the entire form, rather than a large number of individual elements. But jQuery's live feature can help quite a lot, it does a lot of the plumbing for event delegation for you. For example:
$("#formid input").live('focus', function(event) {
var field = $(this);
// `field` now references the field that was focussed
});
...watches all fields in your formid form for focus events. If you're using jQuery 1.4, that works even with the focus event in that example (even though focus doesn't bubble, and so is usually tricky to use with event delegation). Prior to 1.4 that won't work, but bubbling events like click and such do.
I would bind to all the inputs, but you can use classes to narrow it down to just the inputs you want.
Instead of
$("#formid input").click(function() {
if($(this).attr('something') == 'special_field') {
//do A
}
});
add a class to the elements you are interested in <input class='special_field' ... >, and change your selector:
$("#formid input.special_field").click(function() {
//do A
});
That will limit the handler to only the desired elements without any if statement checks.
You can repeat this for anything that needs special handling.
Final note on the .live() method. It can be really handy for a couple reasons:
It doesn't actually create a binding for every matching element, it creates a listener on the body element by default(you can choose another element as the listener) and waits for an even to bubble up to it. When it bubbles, it checks the triggered element against the selector, and if its a match fires the event.
Because the selector is applied when the event fires, it means that new elements added via Javascript or AJAX will fire the code without needing to bind or rebind them.
There is one listener for many elements, meaning for large sets it can be much quicker.