Javascript - onclick - bubbling - orignating element - javascript

Background:
I'm currently writing a greasemonkey script that embeds/modifies a specific page's html. The page is setup with 3 nested divs. Of those 3 divs, I can only add an event listener to the outer most div(This is due to other events and the such already added to the elements). With the inner most div, links are added via AJAX/COMET. As with the inner divs, I can not add event handlers to these links due to the page's current event handlings.
What I need: When a link in the inner most div is clicked, the event will bubble up to the outer most div. From the outer most div, is there a way 1: to tell if it was a link clicked in the inner most div, and if so, 2: how to get that link's href
Please no jQuery solutions. It seems a bit over doing it sense this is the only javascript that will be embedded into the page

I would just use a simple cross-browser event listener and the event.target (or event.srcElement in IE), like so:
var addEvent = function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false);
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn);
} else {
el['on' + ev] = fn;
}
};
var outer = document.getElementById('outer');
addEvent(outer, 'click', function(ev){
ev = ev || window.event;
var target = ev.target || ev.srcElement;
alert(target.href);
});
See demo

Related

DOM - timing of simultaneous events vs setTimeout

Suppose I have an element containing several children and want to run some code whenever the mouse enters or leaves the container. If I naively write:
var onHover = function (el, f) {
el.addEventListener('mouseover', function () {
f(true);
});
el.addEventListener('mouseout', function () {
f(false);
});
};
Then I get the desired behavior in some cases - depending on the nature of the callback f. However, when the mouse moves from child to child within the container, f(false) runs immediately followed by f(true). I don't want this to happen - I only want f to be run when the mouse enters or leaves the container as a whole, not called machine-gun style as the user drags their mouse over the elements that are inside the container.
Here's the solution that I came up with:
var onHover = function (el, f) {
var previousMouseover = false;
var receivedMouseover = false;
var pushing = false;
var pushEv = function () {
if (pushing) { return; }
pushing = true;
setTimeout(function () {
pushing = false;
if (previousMouseover !== receivedMouseover) {
f(receivedMouseover);
previousMouseover = receivedMouseover;
}
});
};
el.addEventListener('mouseover', function () {
receivedMouseover = true;
pushEv();
});
el.addEventListener('mouseout', function () {
receivedMouseover = false;
pushEv();
});
};
This solution, like the first solution, assumes and works by the virtue that the mouseout event is sent before the mouseover event is. I would also like to know whether that is formally specified by any W3C documentation, but that is not the topic of this question, and even if it were not the case, it would be easy to write a functioning algorithm in spite of that by setting two separate variables, say receivedMouseover and receivedMouseout inside of the mouseover and mouseout callbacks, both of which are then inspected inside of the setTimeout callback.
The question is: Is it required that both the mouseover and mouseout events be processed before any setTimeout callbacks signed up by either event are run?
Use the mouseenter and mouseleave events instead of mouseover and mouseout.
Since you have attached the event listener to the parent element you may compare the event origin (event.target) with the parent element (this or event.currentTarget) before you take an action. You may do as follows;
var onHover = function (el, f) {
el.addEventListener('mouseover', function (evt) {
this === evt.target && f(true);
});
el.addEventListener('mouseout', function (evt) {
this === evt.target && f(false);
});
};
Most of the elements bubble so at some point this might be the right way to do this job.
Edit: As mentioned in the comments the mouseover and mouseout events can be problematic under some circumstances such as when the parent element has no padding or margins defined and children cover all the parent. Even if they don't the speed of the mouse could be fast enough to make the JS engine fail to sample the mouse over the parent element. This fact is beautifuly explained in this article.
So, as mentioned in the accepted answer, i suppose the mouseenter and mouseleave events are there to solve this problem. Accordingly the right code should be like;
var onHover = function (el, f) {
el.addEventListener('mouseenter', () => f(true));
el.addEventListener('mouseleave', () => f(false));
};
Edit 2: Well... Actually there is a safe way of using mouseover and mouseout in this particular condition. It's about using CSS pointer-events property on the children which disables them from event emitting for mouse activity.
var container = document.getElementById('container');
container.addEventListener('mouseover', function (ev) {
console.log(container === ev.target);
});
container.addEventListener('mouseout', function (ev) {
console.log(container === ev.target);
});
#container * {
pointer-events: none
}
<div id="container">
<div>
<span>text</span>
</div>
</div>

How to detect if mouse cursor is out of element?

I have a listener which runs when I click on document.
document.addEventListener('click', print);
function print(element)
{
doSomething();
}
It creates div id=panel, where I print some information.
When I run the print function I would like to detect whether I clicked outside of the div#panel (The panel exists when I click second time).
I wish not to use the mouseout event listener because I think it is redundant to use listener for mouse movements when the event click is already fired.
How to detect when I clicked out of div#panel?
You can check the target of jQuery's click event, which element it was:
$(document).click(function(e) {
var target = $(e.target);
if( !target.is("#panel") && target.closest("#panel").length === 0 ) {
// click was not on or inside #panel
}
});
Your event handler gets passed an event object, not an element. Since you are listening for the click event, the event will be of type MouseEvent and that event object will have a target property which you can use to check if the target element matches your desired element.
function handler(event) {
if (event.target == document.getElementById("panel")) {
// Do stuff
}
}
document.addEventListener('click', handler);
Edit: I intentionally gave the vanilla JS answer since your own code fragments don't use jQuery. But jQuery wouldn't change anything as its event handling API is almost just a thin wrapper over JS.
I am just using event from the click. Here it is
var elem=document.getElementById("elem");
var rects=elem.getBoundingClientRect();//get the bounds of the element
document.addEventListener('click', print);
function print(e)
{
//check if click position is inside or outside target element
if(e.pageX<= rects.left +rects.width && e.pageX>= rects.left && e.pageY<= rects.top +rects.height && e.pageY>= rects.top){
console.log("Inside element");
}
else{
console.log("Outside element");
}
}
JS Bin link : https://jsbin.com/pepilehigo/edit?html,js,console,output
A different approach, using only javascript is:
function print(evt) {
if (!(evt.target.tagName == 'DIV' && evt.target.classList.contains('myDiv'))) {
var div = document.createElement('div');
div.classList.add('myDiv');
div.textContent="new div";
document.body.appendChild(div);
}
}
window.onload = function() {
document.addEventListener('click', print);
}
.myDiv {
border:1px solid green;
}

Why does jQuery's one fire immediately when it's added to an element?

Here's a fiddle illustrating the problem. I am adding a jQuery one binding on the click of one element to the 'html' element. I am not expecting the 'one' event handler to fire until the next click, but it fires on the click that adds the binding. This seems to not be a problem if it is a more specific element that the 'one' event handler is added to, but it happens when I use 'html' or 'body' as the element, which is what I want to do.
This doesn't make sense to me, I'd think the first click would add the one for the next click and it wouldn't fire on the click on the link.
By the way, my actual problem could probably be solved in a better way, but I came across this and was curious why it didn't work as I expected.
Code:
html:
<div id='hello'>hello</div>
<a class="title" href="#">this example</a> is a test​
js:
$(function() {
$('a.title').click(function() {
var htmlClickBind = function (e) {
console.log('clicked on html, e.target = ' + e.target);
console.log(e.target == '');
if (!$(e.target).is('a') ) {
console.log('cleared click event');
}
else {
$('html').one('click', htmlClickBind);
}
};
$('html').one('click', htmlClickBind);
});
});​
The click event on the a.target element bubbles up to the html element, where your (just-added) handler sees it.
To prevent this, use event.stopPropgation in your a.target click handler (or return false, which does stopPropagation and preventDefault).
Updated code (see the comments): Live copy
$(function() {
// Accept the event arg ----v
$('a.title').click(function(e) {
// Stop propagation
e.stopPropagation();
var htmlClickBind = function (e) {
console.log('clicked on html, e.target = ' + e.target);
console.log(e.target == '');
if (!$(e.target).is('a') ) {
console.log('cleared click event');
}
else {
$('html').one('click', htmlClickBind);
}
};
$('html').one('click', htmlClickBind);
});
});​

Which HTML element was Double Clicked in the DOM

I would like to detect which HTML element was double clicked. Seems to something not fire in my code. Following is my HTML code structure where you double click detect which item is clicked.
<div id="mainWrapper">
<div id="Banner" name="Banner" class="editable">This is the banner</div>
<div id="MainMenu" class="editable">This is the main menu</div>
<div id="LeftSideBar" class="editable">This is the submenu or left sidebar content</div>
<div id="MainContent"class="editable">Here is the main content</div>
<div id="RightSideBar" class="editable">Here are commercial ads</div>
<div id="Footer"class="editable">This is the footer
Go Home
</div>
</div>
External JavaScript
window.onload = function(){
// Listen to the double click event.
if ( window.addEventListener )
document.body.addEventListener( 'dblclick', onDoubleClick, false );
}
Get the element which fired the event. This is not necessarily the element to which the event has been attached.
function onDoubleClick( ev ){
var element = ev.target || ev.srcElement; //target = W3C, srcElement = Microsoft
alert(ev.type); //displays which event has fired
var targ;
if (!ev) var e = window.event;
if (ev.target) targ = ev.target;
else if (ev.srcElement) targ = ev.srcElement;
alert(ev.target); //displays which type of html element has been clicked (it shows div but not which div)
// Find out the div that holds this element.
var name;
do {
element = element.parentNode;
}
while ( element && ( name = element.nodeName.toLowerCase() ) && ( name != 'div' ||
element.className.indexOf( 'editable' ) == -1 ) && name != 'body' )
alert("The class name for the element is " + element.className); // I get nothing
alert("The node name for the html element is " + element.nodeName);// I get "body"
}
I'm not sure exactly what it is you're trying to accomplish. Is it so people can edit things? I'd be tempted to apply the onclick event listener just to those items you want to be editable. If they all have "editable" css classes, doing so is trivial with jquery:
$('.editable').dblclick(dblclickFunc)
This would apply an event listener to every element with a class of editable. However, to make it more useful, I'd change that to
$('.editable').dblclick(function(e){ dblclickFunc(e, this); })
and for the function
dblclickFunc(e, el){
alert('received an event of type ' + e.type + ' on ' + el.tagName);
}
So you've got a reference to the element that sent the event. From there, you could check IDs, or even go so far as to loop through all your editable elements and compare them to the one that got passed to you. Once you have a match, you know precisely which element was clicked on.
You are using JavaScript in your example, but you also tagged the question with jQuery, so I assume jQuery is OK to use. In fact, exactly this type of event handling is greatly simplified using jQuery’s API, since it normalizes the events for all modern browsers. Highly recommended.
You can delegate the event to the document and detect all double clicks in the entire document using jQuery using the on() function:
$(document).on('dblclick', function(e) {
console.log(e.target); // target is the element that triggered the event
alert("The class name for the element is " + e.target.className);
alert("The node name for the html element is " + e.target.nodeName);
});
If you want to listen on certain elements inside a specific container, try this:
$('#mainwrapper').on('dblclick', 'div', function(e) {
console.log(e.target);
});
This will listen for any double clicks inside #mainwrapper, but only trigger the handler if a DIV element was the target.
You can use .on()
$(".editable").on("dblclick", function(e){
$(this).attr('class') //Class Name
});

Clear Every Event Handler

I would like to cancel every event handler defined in a page with JavaScript. In other words, if the page previously set up many event listeners on many different DOM levels, this method would simply delete every one of them. Is there a way to do this in JavaScript?
For clearing all the events dynamically attached to elements within the body of the page, you can do:
document.body.innerHTML = document.body.innerHTML;
For clearing events attached to the window object, you can do:
window.onscroll = function() {};
and so on..
Nope there's no native event list to see what is bound to what. JQuery has its own, if you want better event management.
Here's how it's done in JQuery:
(function($) {
$.eventReport = function(selector, root) {
var s = [];
$(selector || '*', root).andSelf().each(function() {
var e = $.data(this, 'events');
if(!e) return;
s.push(this.tagName);
if(this.id) s.push('#', this.id);
if(this.className) s.push('.', this.className);
for(var p in e) s.push('\n', p);
s.push('\n\n');
});
return s.join('');
}
$.fn.eventReport = function(selector) {
return $.eventReport(selector, this);
}
})(jQuery);
You can call it various ways to suit your needs:
// all events
alert($.eventReport());
// just events on inputs
alert($.eventReport('input'));
// just events assigned to this element
alert($.eventReport('#myelement'));
// events assigned to inputs in this element
alert($.eventReport('input', '#myelement'));
alert($('#myelement').eventReport('input')); // same result
// just events assigned to this element's children
alert($('#myelement').eventReport());
alert($.eventReport('*', '#myelement'); // same result
From there, unbinding is simple.

Categories