How to detect when a Container loses focus - javascript

I have a container (in a form) that has a Table layout with a set of Edit fields (texts, Checkboxes, etc).
I need to capture when the user clicks outside the container (on a menu item for example). There are no event handlers on the container currently.

I know this is an old question, but I recently ran into the same problem so I thought I would try to help any fellow sufferers.
I got stuck for hours creating various incarnations of blur and click event listeners which all seemed to almost work. Blur would fail because it fires even if the focus is on a child element. Click worked but didn't handle keyboard navigation.
My final solution was to capture the focus event at the window level and compare the focus target with my container and it's children. This will only work for browsers that support addEventListener. In my app, I had a captive audience and didn't need to worry about IE < 9.
First create a function to check if the focus target is your container or any of it's child elements.
var LocalTarget = function( el, target )
{
if ( el === target )
{
return true;
}
else if ( el.childNodes )
{
var els = el.childNodes;
for ( var i = 0, n = els.length; i < n; i++ )
{
if ( els[i] === target )
{
return true;
}
else if ( els[i].childNodes )
{
if ( LocalTarget(els[i], target) ) return true;
}
}
}
return false;
};
Note that this will compare all nodes in the container through recursion.
Next, create a listener function.
var Listener = function( e )
{
// Check if receiving element is part of the container.
if ( !LocalTarget([YOUR CONTAINER], e.target) )
{
// Do focus lost stuff here...
// Remove the event listener. [OPTIONAL]
window.removeEventListener( 'focus', Listener, window, true );
}
};
Note that the LocalTarget and Listener functions as well as [YOUR CONTAINER] are closures.
Finally, add the event listener.
window.addEventListener( 'focus', Listener, window, true );
While it seems like a lot of work to go through and the overhead is insane, this is the only concoction I could make work. Hope it helps someone.

Here is a solution using the focusout event, Node.contains method, and the relatedTarget property. This listens for the focusout event on a containing element. When the related target is no longer a descendent of that containing element you know that the focus has gone outside of that element.
let element = document.querySelector('.some-selector');
element.addEventListener('focusout', e => {
if (! element.contains(e.relatedTarget)) {
// Focus has left the element
}
});
This might not work in IE11 since IE11 has only partial support for the Node.contains method.

In jquery you can use $('#container').blur(function() { //some code here });

Related

Prevent all javascript events from firing

I am working on a firebug like javascript element selector, but cannot figure out how to stop all JavaScript events from firing when clicked. The firebug lite plugin (https://getfirebug.com/firebuglite) is doing exactly what I want, but cannot figure out what they are doing.
Any help would be appreciated.
Senario:
User selects element inspector
User clicks on element
onClick, mousedown, mouseup should NOT fire
I have tried the following with no luck:
function stopEvents(el){
for(var key in window) {
if (key.indexOf("on") == 0)
el.addEventListener(key.substr(2), stop, false);
}
}
function StopEvent(pE)
{
stopEvents(pE);
if (!pE)
if (window.event)
pE = window.event;
else
return;
if (pE.cancelBubble != null)
pE.cancelBubble = true;
if (pE.stopPropagation)
pE.stopPropagation();
if (pE.preventDefault)
pE.preventDefault();
if (window.event)
pE.returnValue = false;
if (pE.cancel != null)
pE.cancel = true;
}
EDIT:
$('.somediv').on("click", function(e){
//Stop bubbling and propagation
StopEvent(e);
//EDIT: Still not working with this
e.stopImmediatePropagation();
//RUN only my code here
console.log("My code is running still");
return false;
});
If there is another library such as YUI binding events to the same DOM element. It will fire there event after mine. I cannot seem to hijack the event to stop this from happening.
EDIT:
I cannot use disabled because I need to be able to fire my event. If I did the following, I wouldn't be able to fire the above event. I cannot attach a parent event either because the DOM will stop firing all events on the Tree for that node.
$('.somediv').on("mouseover", function(e){
$(this).attr("disabled", "disabled");
});
EDIT:
The events I want to disable are already created before my script runs. These events could be any javascript library such as YUI, Dojo, jQuery, JavaScript etc...
Disabling all events on the page is very easy. Hard part is to restore them when needed.
document.body.innerHTML = document.body.innerHTML;
This will effectively remove all events bound to DOM nodes by replacing the DOM with it's "virgin" copy.
Most of the time user won't even notice the redraw.
You can't "disable" all of them without also intercepting the actual event binding, so you'd have to end up with something like this:
(function(prototypes) {
prototypes.forEach(function(prototype) {
var eventTracker = {};
var oldAEL = prototype.addEventListener;
prototype.addEventListener = function(a,b,c) {
if (!eventTracker[a]) { eventTracker[a] = true; }
return oldAEL.call(this, a, function(evt) {
console.log(a, eventTracker[a]);
if(eventTracker[a] === true) {
b(evt);
}
},c);
};
prototype.toggleEvent = function(name, state) {
eventTracker[name] = state;
};
});
}([Document.prototype, HTMLElement.prototype, ...]));
example: http://jsfiddle.net/BYSdP/1/
the button gets three click listeners, but if the second button is clicked, the event regulator for "click" is set to false, so none of the events will actually trigger the originally supplied code. Note that this also makes debugging a LOT harder, because you're wrapping handlers in anonymous functions.
event.stopImmediatePropagation() keeps the rest of the handlers from being executed and prevents the
event from bubbling up the DOM tree.
Example:
$( "p" ).click(function( event ) {
event.stopImmediatePropagation();
});
$( "p" ).click(function( event ) {
// This function won't be executed
$( this ).css( "background-color", "#f00" );
});
Source: https://api.jquery.com/event.stopimmediatepropagation/

Firefox firing dragleave when dragging over text

I'm attempting to track a dragenter/leave for the entire screen, which is so far working fine in Chrome/Safari, courtesy of the draghover plugin from https://stackoverflow.com/a/10310815/698289 as in:
$.fn.draghover = function(options) {
return this.each(function() {
var collection = $(),
self = $(this);
self.on('dragenter', function(e) {
if (collection.size() === 0) {
self.trigger('draghoverstart');
}
collection = collection.add(e.target);
});
self.on('dragleave drop', function(e) {
// timeout is needed because Firefox 3.6 fires the dragleave event on
// the previous element before firing dragenter on the next one
setTimeout( function() {
collection = collection.not(e.target);
if (collection.size() === 0) {
self.trigger('draghoverend');
}
}, 1);
});
});
};
function setText(text) {
$('p.target').text(text);
}
$(document).ready(function() {
$(window).draghover().on({
'draghoverstart': function() {
setText('enter');
},
'draghoverend': function() {
setText('leave');
}
});
});
However Firefox is still giving me problems when I drag over text items, here's a fiddle to demonstrate: http://jsfiddle.net/tusRy/6/
Is this a Firefox bug or can this be tamed with JS? Or is there a more robust method for performing all of this?
Thanks!
UPDATE: Updated fiddle to http://jsfiddle.net/tusRy/6/ to reduce clutter a bit. To explain the expected behavior of the fiddle:
Drag a file into the window and p.target should be "ENTER" colored yellow.
Drag a file out of the window and p.target should be "LEAVE" colored red.
Drop a file in the window and p.target should be "LEAVE" colored red.
In firefox, the LEAVE event is triggered when you drag the file over text.
As of version 22.0 Firefox is still doing this. When you drag over a text node it fires two kinds of dragenter and dragleave events: one where the event target and relatedTarget are BOTH the parent element of the text node, and another where the target is the parent element and the relatedTarget is the actual text node (not even a proper DOM element).
The workaround is just to check for those two kinds of events in your dragenter and dragleave handlers and ignore them:
try {
if(event.relatedTarget.nodeType == 3) return;
} catch(err) {}
if(event.target === event.relatedTarget) return;
I use a try/catch block to check the nodeType because occasionally events fire (inexplicably) from outside the document (eg. in other iframes) and trying to access their nodeType throws a permissions error.
Here's the implementation:
http://jsfiddle.net/9A7te/
1) Your dropzone should have only one child element, which might have everything else you need. Something like
<div id="#dropzone">
<div><!--Your contents here--></div>
</div>
2) Use this CSS:
#dropzone * { pointer-events: none; }
You might need to include the :before and :after since the * don't apply to them.
This should be enough to let the drop work in Firefox and Chrome. In your example, it should be enough to add:
body * { pointer-events: none; }
At the end of the CSS. I've done it here:
http://jsfiddle.net/djsbellini/eKttq/
Other examples:
http://jsfiddle.net/djsbellini/6yZV6/1/
http://jsfiddle.net/djsbellini/yR8t8/
I came up with kind of a solution, yet to test on other browsers other than Chrome and FF but working so far. This is how the setTimeout looks now:
setTimeout( function() {
var isChild = false;
// in order to get permission errors, use the try-catch
// to check if the relatedTarget is a child of the body
try {
isChild = $('body').find(e.relatedTarget).length ? true : isChild;
}
catch(err){} // do nothing
collection = collection.not(e.target);
if (collection.size() === 0 && !isChild) {
self.trigger('draghoverend');
}
}, 1);
The entire code here - http://jsfiddle.net/tusRy/13/.
The idea is to check if the related tag is a child of the body, in which case we are still in the Browsers and the draghoverend event should be not triggered. As this can throw errors when moving out of the windows, I used a try method to avoid it.
Well, perhaps somebody with more skills on JS could improve this :)
I found the answer in a non-selected answer to this SO question asking about dragleave firing on child elements. I have a <div> that has many children elements. An semi-opaque overlay <span> becomes visible over the <div> whenever there's a dragenter on the page. As you found, 'dragover' isn't like mouseover. It triggers dragleave whenever you hover over a child element.
The solution? Dragout It makes dragover work more like mouseover. Very short.

MouseOver/MouseOut EventListener inheriting to child nodes?

Edit: Solution
Thanks to Gaby for the help in finding a solution! Didn't quite work exactly how I wanted it to, found a better solution modified from the answers. What I do is only execute the mouseover/mouseout functions when the two elements (target and related target) do not share a parent.
Just modified Gaby's example a bit and have everything working great. As long as your popup is within the same div element as whatever spawns it (even if it's outside the main contents you can append it with overflow visible) and you don't go between non-shared elements on the way over to it, it'll stay alive.
divContents.addEventListener('mouseover', mouseEnter(showPopup, divContents));
divContents.addEventListener('mouseout', mouseEnter(hidePopup, divContents));
Now, the modified mouseEnter...
function mouseEnter(_fn, _parent) {
return function(_evt) {
if(!shareParent(_evt.target, _evt.relatedTarget, _parent)) {
_fn.call(this, _evt);
}
}
};
function shareParent(_object1, _object2, _parent) {
if (_object1 === _object2) {
return true;
}
while (_object1 && _object1 !== _parent) {
_object1 = _object1.parentNode;
}
while (_object2 && _object2 !== _parent) {
_object2 = _object2.parentNode;
}
return _object1 === _object2;
}
Solved my problem A-OK, because what I want to fire for mouseover and mouseout events will only happen when the elements don't share a parent - exactly how I intended on displaying the popups anyhow.
Thanks again for the code example from Gaby, though!
NOTE: No error checking for parent validity in shareParent function, haven't checked but it should always return true when it gets to the top of the tree (assuming the _parent isn't actually a parent of either _object1 or _object2). So make sure the parent object you pass to it is valid.
Original Question:
I'm having a problem in JavaScript right now.
I'm trying to create a div Element that pops up dynamically when something has a mouseover. The div is created directly adjacent to the object that spawns it.
divCreator.addEventListener('mouseover', createPopup, true);
divCreator.addEventListener('mouseout', hidePopup, true);
That creates the popup. Now, in the popup, when I create it, I run this before I append it to the document:
divPopup.addEventListener('mouseover', createPopup, true);
divPopup.addEventListener('mouseout', hidePopup, true);
This ensures that if I mouseover the popup, it keeps it alive (because the divCreator mouseout will fire) and when I mouseout of the popup it disappears.
However, with this method, whenever I mouseover a child element it detects a mouseout event from the parent element (divPopup) and closes the div.
Can I make the children 'transparent' to Events, so-to-speak?
There are two events that handle this case.
The mouseenter W3 DOM3 docs and mouseleave W3 DOM3 docs but they are currently in the DOM3 Events working draft.
They were introduced by Microsoft IE5.5, and Firefox has added support in v10.
A workaround is to manually check and cancel the execution of your handler if the newly moused-over element is a child of your top level one..
code adapted from http://blog.stchur.com/2007/03/15/mouseenter-and-mouseleave-events-for-firefox-and-other-non-ie-browsers/
divCreator.addEventListener('mouseover', mouseEnter(createPopup), true);
divCreator.addEventListener('mouseout', mouseEnter(hidePopup), true);
function mouseEnter(_fn)
{
return function(_evt)
{
var relTarget = _evt.relatedTarget;
if (this === relTarget || isAChildOf(this, relTarget))
{ return; }
_fn.call(this, _evt);
}
};
function isAChildOf(_parent, _child)
{
if (_parent === _child) { return false; }
while (_child && _child !== _parent)
{ _child = _child.parentNode; }
return _child === _parent;
}
Demo at http://jsfiddle.net/gaby/jMvHv/ (open your console for log messages)

Help me understand JavaScript events

I have a table full of cells and i would like to get on which cell the mouse is.
For this i have attached events to all the cells and then i am finding the elements. But i guess there could be a better options. right ?
Is it possible that i attach only single event handler on top and still be able to catch all the information. like which cell user is currently on etc.
Something like below,
<table onMouseOver="monitorFuntion(event)" >...</table>
It's possible to do exactly what you said: You can put a handler on the table, and then find the cell from that. (This is sometimes called "event delegation".) You can do this for some events, including mouseover and mouseout, because they bubble. You can't do it for other events (like blur or focus) because they don't bubble.
Suppose you have a table with the ID "myTable". You can hook up an event handler for mouseover:
var table = document.getElementById("myTable");
if (table.attachEvent) {
table.attachEvent("onmouseover", handleMouseOver);
}
else {
table.addEventListener("mouseover", handleMouseOver);
}
And then handle it like this:
function handleMouseOver(event) {
var target;
// Handle IE event difference from standard
event = event || window.event;
// Find out what element the event actually happened on
// (Another IE difference here, srcElement vs target)
target = event.srcElement || event.target;
// Since that might be an element *within* your cell (like
// a link, or a `span`, or a `strong`, etc.), find the cell
while (target && target.tagName != "TD" && target.tagName != 'BODY') {
target = target.parentNode;
}
if (target && target.tagName != 'BODY') {
// Found one, `target` now points to the cell the mouse is over
}
}
Note that it's important you handle the case where target ends up being null or referring to the body element, because you'll get this event over the table's borders, row padding, etc.
Javascript libraries can help you with this a lot. For instance, the above using Prototype looks like this:
$("myTable").observe("mouseover", handleMouseOver);
function handleMouseOver(event) {
var target;
target = event.findElement("td");
if (target) {
// ...
}
}
jQuery, Closure, and others will similarly help quite a bit.
Based on the code snippet you posted you are looking for event delegation.
Step 1: use jQuery 1.4.2 +
Step 2:
// you can use move, enter, out, over whatever...
$("table").delegate("mouseenter", "td", click, function(){
var tableCell = $(this); // the cell which is currently moused-over.
});
Yes, you can do exactly that, and then use the event object to find the element. The event object differs between IE and other browsers, but getting the "target" is about the same:
function handler(ev) {
ev = ev || window.event;
var targetElement = ('target' in ev) ? ev.target : ev.srcElement;
// ...
}
Now not all events will "bubble up" for you, but I think that the mouse events do. The problems are mostly with "change". Frameworks like jQuery or Prototype generally try to give you more normalized behavior.
edit fixed for IE compatibility

Simple click event delegation not working

I have a div
<div class="myDiv">
somelink
<div class="anotherDiv">somediv</div>
</div>
Now, using event delegation and the concept of bubbling I would like to intercept clicks from any of myDiv, myLink and anotherDiv.
According to best practices this could be done by listening for clicks globally (hence the term 'delegation') on the document itself
$(document).click(function(e) {
var $eventElem = $(e.target);
var bStopDefaultClickAction = false;
if ($eventElem.is('.myDiv'))
{
alert('Never alerts when clicking on myLink or anotherDiv, why????');
bStopDefaultClickAction = true;
}
return bStopDefaultClickAction;
});
See my alert question above. I was under the impression that clicks bubble. And it somewhat does because the document actually receives my click and starts delegating. But the bubbling mechanism for clicks on myLink and anotherDiv doesn't seem to work as the if-statement doesn't kick in.
Or is it like this: clicks only bubble one step, from the clicked src element to the assigned delegation object (in this case the document)? If that's the case, then I need to handle the delegation like this:
$('.myDiv').click(function(e) {
//...as before
});
But this kind of defeates the purpose of delegation as I now must have lots of 'myDiv' handlers and possibly others... it's dead easy to just have one 'document' event delegation object.
Anyone knows how this works?
You should use live event from JQuery (since 1.3), it use event delegation :
http://docs.jquery.com/Events/live
So you code will be :
$(".myDiv").live("click", function(){
alert('Alert when clicking on myLink elements. Event delegation powaa !');
});
With that, you have all the benefices of event delegation (faster, one event listener etc..), without the pain ;-)
The event target will not change. You need to mirror what jquery live does and actually check if $eventElem.closest('. myDiv') provides a match.
Try:
$(document).click(function(e) {
var $eventElem = $(e.target);
var bStopDefaultClickAction = false;
if ( $eventElem.closest('.myDiv').length )
{
alert('Never alerts when clicking on myLink or anotherDiv, why????');
bStopDefaultClickAction = true;
}
return bStopDefaultClickAction;
});
Event.target is always the element that triggered the event, so when you click on 'myLink' or 'anotherDiv' you store a reference to these objects using $(e.target); So what you do in effect is: $('.myLink').is('.myDiv') which returns false, and that's why the alert() is not executed.
If you want to use event delegation this way, you should check wheter event.target is the element or any of its children, using jQuery it could be done like this:
$(e.target).is('.myDiv, .myDiv *')
Seems to work fine to me. Try it here: http://jsbin.com/uwari
Check this out: One click handler in one page
var page = document.getElementById("contentWrapper");
page.addEventListener("click", function (e) {
var target, clickTarget, propagationFlag;
target = e.target || e.srcElement;
while (target !== page) {
clickTarget = target.getAttribute("data-clickTarget");
if (clickTarget) {
clickHandler[clickTarget](e);
propagationFlag = target.getAttribute("data-propagationFlag");
}
if (propagationFlag === "true") {
break;
}
target = target.parentNode;
}
});

Categories

Resources