I'm trying to highlight any DOM element of a certain Web page when the user clicks on it. For that purpose, I iterate through all elements of the current element and disable any functionality (so e.g. I can click and not to be redirected):
var elems = window.document.getElementsByTagName('*');
for (var i = 0; i < elems.length; i++) {
elems[i].onclick = function(e){
e.preventDefault();
}
The thing is, when user clicks, a lot of click events are fired, because every element has tas behaviour, and I need to avoid unhighlight.
Any idea of how to improve that or any alternative?
Thanks a lot!
P/d: It has to be pure JS
You'll need to do two things: first of all, you want to actually add an event, not redefine its click behaviour, and secondly, uou want it to not bubble up through the dom. Try this:
var elems = window.document.getElementsByTagName('*');
for (var i = 0; i < elems.length; i++) {
// Use addEventListener to listen for events.
// Older version of IE use attachEvent, but I guess thats irrelevant.
elems[i].addEventListener("click", function(e){
e.preventDefault();
// Use stopImmediatePropagation to stop the element from passing this click to wrapping parents - because a click on a child is technically a click on the parent as well.
e.stopImmediatePropagation();
});
}
Heres more to read:
Add Event Listener: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
Stop Immediate Propagation: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation
As a brave editor says, you can also pass false as a third argument to the addEventListener and it does basically the same thing but is less comprehensible and does some additional things as well, so I have opted not to default to this just because its easier to understand that you are stopping propagation.
I made a jsfiddle that does it:
https://jsfiddle.net/zgqpnaey/
var elems = window.document.getElementsByTagName('*');
for (var i = 0; i < elems.length; i++) {
myFunction(elems[i]);
}
function myFunction(element) {
element.onclick = function(e) {
e.preventDefault();
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
element.style.background = 'red';
};
}
somethinghere explained it above.
Also, you shouldn't bind onclick inside a loop, it's known to cause errors; it's better to call a function and pass the proper parameters.
Related
what is the equivalent to the following statement in Javascript in a single line ?
$("#element1,#element2").on("click", runFunction);
For example:
var f = function() { /* Event handler */ },
els = document.querySelector(/* selector */);
for(var i=0, l=els.length; i<l; ++i)
els[i].addEventListener('click', f, false);
Just remove the line breaks and it will be in a single line.
But in ES6 you can get close:
for(var el of document.querySelector(/* selector */)) el.addEventListener('click', f, false);
Simply Do that by not keeping any space in your code
e1=document.getElementById('element1'),e2=document.getElementById('element2');if(addEventListener){e1.addEventListener('click',runFunction,false);e2.addEventListener('click',runFunction,false);}else{e1.attachEvent('click',runFunction);e2.attachEvent('click',runFunction);}
Almost certainly, what you're really looking for is called:
jQuery's Event Delegation (see jQuery's .on() documentation)
It's what everyone is looking for, even if they don't quite realize it yet ;)
Don't bind your handlers onto your elements directly -- bind your handler to a parent element (perhaps <body>) instead, and use jQuery's event delegation model to specify a selector by which bubbled sub-events are filtered. In the following case, we place a click handler on the <body> which is specified to trigger for a[href] link clicks.
// Handles all link clicks under the <body> tag (superior methodology -- much optimum)
$('body').on('click','a[href]',function(jqEvent){
var $a = $(this);
console.log("Link Clicked:",$a.attr('href'));
});
// Direct binding, which is essentially what the original asker had (inferior)
$('a[href]','body').on('click',function(jqEvent){
var $a = $(this);
console.log("Link Clicked:",$a.attr('href'));
});
when i use the .preventDefault on a link it still goes to the link - also is this even doable with JS or is it a jquery only method?
var avoidlink = getElementsByTagName("a");
avoidlink.addEventListner("click",function(evt){
evt.preventDefault();
},false);
Click here
Three problems :
getElementsByTagName must be called on the document or on an element
You can't add an event listener directly on the node list returned by getElementsByTagName, you must iterate over the elements it contains :
you have a typo in addEventListener
Here's a fixed code :
var avoidlink = document.getElementsByTagName("a");
for (var i=0; i<avoidlink.length; i++) {
avoidlink[i].addEventListener("click",function(evt){
evt.preventDefault();
},false);
}
If you want to be able to attach an event listener to a node list, you may enrich NodeList.prototype :
NodeList.prototype.addEventListener = function(){
for (var i=0; i<this.length; i++) {
Element.prototype.addEventListener.apply(this[i] , arguments);
}
}
Demonstration
Modifying the prototype of objects you don't own is generally frowned upon but this change is rather innocuous and natural.
Usually if many elements need the same event listener you can add the event listener to the container and filter out what element you would like to take action upon:
document.addEventListener("click",function(e){
if(e.target,e.target.tagName.toLowerCase()==="a"){
e.preventDefault();
}
});
If you were to add extra anchor tags through script they will trigger the preventDefault too.
I've been struggling with what seems to be a simple problem for a few hours now. I've written a REGEX expression that works however I was hoping for a more elegant approach for dealing with the HTML. The string would be passed in to the function, rather than dealing with the content directly in the page. After looking at many examples I feel like I must be doing something wrong. I'm attempting to take a string and clean it of client Events before saving it to our Database, I thought jQuery would be perfect for this.
I Want:
Some random text click here and a link with any event type
//to become:
Some random text click here and a link with any event type
Here's my code
function RemoveEvilScripts(){
var myDiv = $('<div>').html('testing this Do it! out');
//remove all the different types of events
$(myDiv).find('a').unbind();
return $(myDiv).html();
}
My results are, the onClick remains in the anchor tag.
Here's a pure Javascript solution that removes any attribute from any DOM element (and its children) that starts with "on":
function cleanHandlers(el) {
// only do DOM elements
if (!('tagName' in el)) return;
// attributes is a live node map, so don't increment
// the counter when removing the current node
var a = el.attributes;
for (var i = 0; i < a.length; ) {
if (a[i].name.match(/^on/i)) {
el.removeAttribute(a[i].name);
} else {
++i;
}
}
// recursively test the children
var child = el.firstChild;
while (child) {
cleanHandlers(child);
child = child.nextSibling;
}
}
cleanHandlers(document.body);
working demo at http://jsfiddle.net/alnitak/dqV5k/
unbind() doesn't work because you are using inline onclick event handler. If you were binding your click event using jquery/javascript the you can unbind the event using unbind(). To remove any inline events you can just use removeAttr('onclick')
$('a').click(function(){ //<-- bound using script
alert('clicked');
$('a').unbind(); //<-- will unbind all events that aren't inline on all anchors once one link is clicked
});
http://jsfiddle.net/LZgjF/1/
I ended up with this solution, which removes all events on any item.
function RemoveEvilScripts(){
var myDiv = $('<div>').html('testing this Do it! out');
//remove all the different types of events
$(myDiv)
.find('*')
.removeAttr('onload')
.removeAttr('onunload')
.removeAttr('onblur')
.removeAttr('onchange')
.removeAttr('onfocus')
.removeAttr('onreset')
.removeAttr('onselect')
.removeAttr('onsubmit')
.removeAttr('onabort')
.removeAttr('onkeydown')
.removeAttr('onkeypress')
.removeAttr('onkeyup')
.removeAttr('onclick')
.removeAttr('ondblclick')
.removeAttr('onmousedown')
.removeAttr('onmousemove')
.removeAttr('onmouseout')
.removeAttr('onmouseover')
.removeAttr('onmouseup');
return $(myDiv).html();
}
I am adding a custom data attribute data-js-href to various HTML elements, and these elements should behave just like a link when clicked. If a link within such an element is clicked, the link should take precedence and the data-js-href functionality should be ignored, though. Furthermore, the solution also needs to work with elements that are dynamically added at a later time.
So far, I have come up with the following solution. It basically checks if the click was performed on a link, or any child element of a link (think <a href='…'><img src='…' alt='…' /></a>).
// Make all elements with a `data-js-href` attribute clickable
$$('body').addEvent('click:relay([data-js-href])',
function(event, clicked) {
var link = clicked.get('data-js-href');
if (link && !event.target.match('a')) {
var parents = event.target.getParents();
for (var i = 0; i < parents.length && parents[i] != clicked; i++) {
if (parents[i].match('a')) {
return;
}
}
document.location.href = link;
}
});
It works, but it feels very clumsy, and I think that there has to be a more elegant solution. I tried something along the lines of
$$('body').addEvent('click:relay([data-js-href] a)',
function(event, clicked) {
event.stopPropagation();
}
but to no avail. (I littered the code with some console.log() messages to verify the behavior.) Any idea is welcome.
you can do this with 2 delegated events - no reverse lookups and it's cheap as they will share the same event. the downside is, it is the same event so it will fire for both and there's no stopping it via the event methods (already bubbled, it's a single event that stacks up multiple pseudo event callbacks and executes them in order--the event has stopped but the callbacks continue) That's perhaps an inconsistency in mootools event vs delegation implementation but it's a subject of another issue.
Workarounds for now can be:
to have the 2 event handlers communicate through each other. It will scale and work with any new els added.
to add the delegators on 2 different elements. eg. document.body and #mainWrap.
http://jsfiddle.net/dimitar/J59PD/4/
var showURL = function(howLong) {
// debug.
return function() {
console.log(window.location.href);
}.delay(howLong || 1000);
};
document.id(document.body).addEvents({
"click:relay([data-js-href] a))": function(e) {
// performance on lookup for repeat clicks.
var parent = this.retrieve("parent");
if (!parent) {
parent = this.getParent("[data-js-href]");
this.store("parent", parent);
}
// communicate it's a dummy event to parent delegator.
parent.store("linkEvent", e);
// let it bubble...
},
"click:relay([data-js-href])": function(e) {
// show where we have gone.
showURL(1500);
if (this.retrieve("linkEvent")) {
this.eliminate("linkEvent");
return;
}
var prop = this.get("data-js-href");
if (prop)
window.location.href = prop;
}
});
Discussed this with Ibolmo and Keeto from the mootools team on IRC as well when my initial attempt failed to work and both callbacks fired despite the event.stop: http://jsfiddle.net/dimitar/J59PD/
As a result, there was briefly a ticket open on the mootools github issues: https://github.com/mootools/mootools-core/issues/2105 but it then went into a discussion of what the right thing to do from the library standpoint is and how viable it is to pursue changing the way things work so...
If I have code like this:
<script>
function determine()
{
// ????
}
</script>
blah1
blah2
Is there a way in determine() to see which link was clicked?
(Yes, I know, the easy and correct thing to do would be to pass this to determine(), but in this case that's not going to be easy to do because of legacy code issues.)
EDIT: I probably should have mentioned this at the beginning...our site is not currently using (and cannot use, for the time being) jQuery, so jQuery answers (while valuable in general for this type of question) won't actually help me.
Check out this link from quirksmode. You can get the event target.
function doSomething(e) {
var targ;
if (!e) var e = window.event;
if (e.target) targ = e.target;
else if (e.srcElement) targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
}
You can with straight up JavaScript, but I prefer to use something like jQuery:
blah1
<script type="text/javascript">
$('a[href=blah1]').click(function() {
var link = $(this); // here's your link.
return false; // acts like the link was not clicked. return true to carry out the click.
});
</script>
Assuming you are using the $().click() functionality, $(this) will give you the link.
If you cannot change the onclick="determine()" in your HTML, but you can change the determine() function, then I think your best bet is to:
Leave the determine() function blank so it doesn't do anything.
Use javascript (as described by other answers) to add a real click handler to each link, and use the event to determine which link was clicked then execute the desired code.
you could add events to each link like so
links = document.getElementsByTagName('a');
for (link in links) {
link.onclick = function() {
alert(this.id);
determine(); // or some other important code?
}
}
Another solution:
Add an onclick handler to the document. When a user clicks the link, the click event will "bubble" up to the window, and you will have access to the event to determine which link was clicked.
This might be useful if you only want the code to run for those links that already have onclick="determine()" - you could set the determine() function to set a variable. Then when the user clicks the link, the determine() function runs to set the variable, and when the document click handler runs you could check for the variable - then you will know that the link had onclick="determine()".
Let me know if I can make this a little more complicated for you... :-)
If you can't change the onclick attribute, patch it via JavaScript:
// use a DOMContentLoaded hack or `onload` as fallback
onload = function() {
var links = document.links;
for(var i = 0; i < links.length; ++i) {
// there might be a better way to check which links to modify
// don't know without further details
if(/determine\(\)/.test(links[i].onclick))
links[i].onclick = determine;
}
};
function determine() {
// the link is now available as `this`
alert(this.href);
}
Perhaps an even better solution would be to patch in a global, IE-style event object for standards compliant browsers:
if(document.addEventListener) {
document.addEventListener('click', function(e) {
window.event = e;
}, true);
}
function determine() {
var src = event.target || event.srcElement;
alert(src.href);
}