I've developed a javascript drag and drop that mostly uses the standard 'allowdrop', 'drag' and 'drop' events.
I wanted to customise the 'ghosted' dragged object, so I've added a display:none div that get populated with the innerHTML of the draggable element and made visible (display:block;) when the user starts dragging.
The draggable div is absolutely positioned and matches the mouse movements. For this I needed to add 3 event listeners to document.body. They are as follows:
document.body.addEventListener('dragover', function (ev) {
console.log("dragover event triggered");
ev = ev || window.event;
ev.preventDefault();
dragX = ev.pageX;
dragY = ev.pageY;
document.getElementById("dragged-container").style.left = (dragX - dragOffsetX) + "px";
document.getElementById("dragged-container").style.top = (dragY - dragOffsetY - 10) + "px";
if (mostRecentHoveredDropTargetId!="") {
if (dragX<mostRecentHoveredDropTargetRect.left || dragX>mostRecentHoveredDropTargetRect.right || dragY<mostRecentHoveredDropTargetRect.top || dragY>mostRecentHoveredDropTargetRect.bottom) {
document.getElementById(mostRecentHoveredDropTargetId).classList.remove("drop-target-hover");
mostRecentHoveredDropTargetId = "";
}
}
});
document.body.addEventListener('drop', function (ev) {
console.log("drop event triggered");
ev.preventDefault();
var data = ev.dataTransfer.getData("text"); // data set to the id of the draggable element
if (document.getElementById(data)!=null) {
document.getElementById(data).classList.remove("dragged");
document.getElementById("dragged-container").innerHTML = "";
document.getElementById("dragged-container").style.display = "none";
var draggablesClasses = document.getElementById(data).className;
if ((draggablesClasses.indexOf('draggable')==-1 || draggablesClasses=="") && document.getElementById(data).getAttribute('draggable')=="true") {
if (draggablesClasses=="") {
document.getElementById(data).className += "draggable";
} else {
document.getElementById(data).className += " draggable";
}
}
}
});
// resets dragged-container and origin .draggable, when mouse released outside browser window
document.body.addEventListener('mouseleave', function (ev) {
if (jqueryReady==true) {
$(".dragged").addClass("draggable");
$(".dragged").removeClass("dragged");
}
document.getElementById("dragged-container").innerHTML = "";
document.getElementById("dragged-container").style.display = "none";
});
This is all working fine. The drag and drop performs exactly as I expect.
The problem is when I go to another page, obviously those body event listeners are still running.
I've seen a number of answers here and have tried everything I've seen. For starters this:
window.onunload = function() {
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', null);
document.body.removeEventListener('drop', null);
document.body.removeEventListener('mouseleave', null);
return;
}
...but the console.log output doesn't even appear (let alone the 'null's being wrong, I'm pretty sure). I've also tried this, in the jQuery ready function:
$(window).bind('beforeunload', function(){
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', null);
document.body.removeEventListener('drop', null);
document.body.removeEventListener('mouseleave', null);
});
..but, once again, the console isn't even receiving that output.
I have also tried both the above with 'onbeforeunload' AND 'onunload'.
What am I doing wrong? - specifically to do with removing these window.body event listeners, I mean (Anything else I can sort out later).
Thanks.
removeEventListener requires the handler
Don't use anonymous functions is the solution.
Like this:
var dragHandler = function (ev) {
console.log("dragover event triggered");
};
document.body.addEventListener('dragover', dragHandler);
and after:
window.onunload = function() {
console.log("about to clear event listeners prior to leaving page");
document.body.removeEventListener('dragover', dragHandler);
return;
}
I want to listen for events on <p> elements at window or document level as there are too many such elements to attach an onclick event hander for each.
This is what I've got:
window.onload=function()
{
window.addEventListener('click',onClick,false);
}
function onClick(event)
{
alert(event.target.nodeName.toString());
}
I need advice on the code above, is it good?
And also, how can I check if the clicked element is a <p> element other than checking nodeName?
For example, if the <p> element contains a <b> element and that is clicked, the nodeType will be b not p.
Thank you.
I think it is good, and you can perform checking this way
var e = event.target
while (e.tagName != 'P')
if (!(e = e.parentNode))
return
alert(e)
If I was you I'd register for the "load" event in the same way that you are for "click". It's a more modern way of event handling, but might not be supported by some older browsers.
Checking the nodeName is the best way to interrogate the node:
function onClick(event) {
var el = event.target;
while (el && "P" != el.nodeName) {
el = el.parentNode;
}
if (el) {
console.log("found a P!");
}
}
Consider this:
(function () {
// returns true if the given node or any of its ancestor nodes
// is of the given type
function isAncestor( node, type ) {
while ( node ) {
if ( node.nodeName.toLowerCase() === type ) {
return true;
}
node = node.parentNode;
}
return false;
}
// page initialization; runs on page load
function init() {
// global click handler
window.addEventListener( 'click', function (e) {
if ( isAncestor( e.target, 'p' ) ) {
// a P element was clicked; do your thing
}
}, false );
}
window.onload = init;
})();
Live demo: http://jsfiddle.net/xWybT/
First of all, here is a list of event types that are defined by the W3C standards. (This list is based on the onevent attributes defined in the HTML5 standard. I assume that there are dozens of other event types, but this list is long enough as it is.)
abort
afterprint
beforeprint
beforeunload
blur
canplay
canplaythrough
change
click
contextmenu
copy
cuechange
cut
dblclick
DOMContentLoaded
drag
dragend
dragenter
dragleave
dragover
dragstart
drop
durationchange
emptied
ended
error
focus
focusin
focusout
formchange
forminput
hashchange
input
invalid
keydown
keypress
keyup
load
loadeddata
loadedmetadata
loadstart
message
mousedown
mouseenter
mouseleave
mousemove
mouseout
mouseover
mouseup
mousewheel
offline
online
pagehide
pageshow
paste
pause
play
playing
popstate
progress
ratechange
readystatechange
redo
reset
resize
scroll
seeked
seeking
select
show
stalled
storage
submit
suspend
timeupdate
undo
unload
volumechange
waiting
Now, is it possible to define a global event handler that is called when any event originally occurs on any element on the page? (In this case, I don't want to count those events that occurred on elements because they bubbled up from a descendant element - that's why I wrote "originally occurs".)
If that is not possible, is it at least possible to define an event handler that is called when any event bubbles up to the root of the DOM tree (which is either the document object or the window object - both should work)? (I know that it's possible to stop bubbling programmatically, but I would use this event handler on a page that has no other handlers defined on any other elements.) (Also, I believe some events don't bubble up, but let's ignore these cases for the sake of this argument.)
I know that I can do this (using jQuery):
$(document).bind('abort afterprint beforeprint beforeunload etc.', function() {
// handle event
});
but that would be a rather undesirable solution for me.
btw I don't need a cross-browser solution. If it works in just one browser, I'm fine.
Also, Firebug is able to log events, but I would like to be able to catch the event programmatically (via JavaScript) rather then having them simply logged in the console.
/*
function getAllEventTypes(){
if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;
var types = {};
$('.standard-table:eq(0) tr').find('td:eq(1)').map(function(){
var type = $.trim(this.innerText) || 'OtherEvent';
types[type] = types[type] || [];
var event = $.trim(this.previousElementSibling.innerText);
if(event) types[type].push(event);
});
for(var t in types) types[t] = types[t].join(' ');
return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');
}
*/
var DOMEvents = {
UIEvent: "abort DOMActivate error load resize scroll select unload",
ProgressEvent: "abort error load loadend loadstart progress progress timeout",
Event: "abort afterprint beforeprint cached canplay canplaythrough change chargingchange chargingtimechange checking close dischargingtimechange DOMContentLoaded downloading durationchange emptied ended ended error error error error fullscreenchange fullscreenerror input invalid languagechange levelchange loadeddata loadedmetadata noupdate obsolete offline online open open orientationchange pause pointerlockchange pointerlockerror play playing ratechange readystatechange reset seeked seeking stalled submit success suspend timeupdate updateready visibilitychange volumechange waiting",
AnimationEvent: "animationend animationiteration animationstart",
AudioProcessingEvent: "audioprocess",
BeforeUnloadEvent: "beforeunload",
TimeEvent: "beginEvent endEvent repeatEvent",
OtherEvent: "blocked complete upgradeneeded versionchange",
FocusEvent: "blur DOMFocusIn Unimplemented DOMFocusOut Unimplemented focus focusin focusout",
MouseEvent: "click contextmenu dblclick mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup show",
SensorEvent: "compassneedscalibration Unimplemented userproximity",
OfflineAudioCompletionEvent: "complete",
CompositionEvent: "compositionend compositionstart compositionupdate",
ClipboardEvent: "copy cut paste",
DeviceLightEvent: "devicelight",
DeviceMotionEvent: "devicemotion",
DeviceOrientationEvent: "deviceorientation",
DeviceProximityEvent: "deviceproximity",
MutationNameEvent: "DOMAttributeNameChanged DOMElementNameChanged",
MutationEvent: "DOMAttrModified DOMCharacterDataModified DOMNodeInserted DOMNodeInsertedIntoDocument DOMNodeRemoved DOMNodeRemovedFromDocument DOMSubtreeModified",
DragEvent: "drag dragend dragenter dragleave dragover dragstart drop",
GamepadEvent: "gamepadconnected gamepaddisconnected",
HashChangeEvent: "hashchange",
KeyboardEvent: "keydown keypress keyup",
MessageEvent: "message message message message",
PageTransitionEvent: "pagehide pageshow",
PopStateEvent: "popstate",
StorageEvent: "storage",
SVGEvent: "SVGAbort SVGError SVGLoad SVGResize SVGScroll SVGUnload",
SVGZoomEvent: "SVGZoom",
TouchEvent: "touchcancel touchend touchenter touchleave touchmove touchstart",
TransitionEvent: "transitionend",
WheelEvent: "wheel"
}
var RecentlyLoggedDOMEventTypes = {};
for (var DOMEvent in DOMEvents) {
var DOMEventTypes = DOMEvents[DOMEvent].split(' ');
DOMEventTypes.filter(function(DOMEventType) {
var DOMEventCategory = DOMEvent + ' ' + DOMEventType;
document.addEventListener(DOMEventType, function(e){
if(RecentlyLoggedDOMEventTypes[DOMEventCategory]) { return; }
RecentlyLoggedDOMEventTypes[DOMEventCategory] = true;
setTimeout(function(){ RecentlyLoggedDOMEventTypes[DOMEventCategory] = false }, 5000);
var isActive = e.target == document.activeElement;
if(isActive) {
console.info(DOMEventCategory,
' target=', e.target,
' active=', document.activeElement,
' isActive=', true );
} else {
console.log(DOMEventCategory,
' target=', e.target,
' active=', document.activeElement,
' isActive=', false );
}
}, true);
});
}
You can iterate through all properties of dom element and select ones that match /on(.*)/ pattern (for example onclick or onmousemove):
var events = [];
for (var property in element) {
var match = property.match(/^on(.*)/)
if (match) {
events.push(match[1]);
}
}
console.log(events.join(' '))
I highly doubt there's a way to do this in Firefox. Looking at Firebug's source code (particularly the attachAllListeners method), turns out that iterating through a list of event names is obviously the way to go, but this doesn't solve the bubbling issues.
There doesn't seem to be any 'easy-way' to do that.
My idea:
You know which are all the events, so you can handle all events for every DOM element:
var events =
[
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
...
];
var root = document.body;
var elms = root.childNodes;
for(var i = 0; i < elms.length; i++)
{
for(var j = 0; j < events.length; j++)
{
elms[i][events[j]] = globalHandler;
}
}
function globalHandler()
{
alert("Global handler called");
}
That's the 'intuitive idea' but doesn't seem to be very efficient. However, it should work.
Good luck.
How to listen for all events on a specific target Element 👾
For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.
for (const key in target) {
if(/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener);
}
}
The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
if (!alreadyListenedEventTypes.has(event.type)) {
target.addEventListener(event.type, listener, ...otherArguments);
alreadyListenedEventTypes.add(event.type);
}
dispatchEvent_original.apply(this, arguments);
};
🔥 function snippet 🔥
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
addEventListenerAll(window, (evt) => {
console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));
// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
window,
(evt) => { console.log(evt.type); },
true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
A bit late to the party but I did create something that might be useful for others here.
https://codepen.io/phreaknation/pen/QmJjEa
This is an ES6 Class that captures all events from an element that is known to that element. This demo allows you to change the element time in the page, as well as read out the events with clickable links to their MDN page as well as interact with the element and see how the events are triggered with time stamps.
I hope this helps
Class code
class EventSystem {
constructor(element) {
this._ = {
element: null
}
return this;
}
getAllEventTypes({blacklist = [], whitelist = []} = {}) {
const events = [];
for (let property in this._.element) {
const match = property.match(/^on(.*)/);
if (match) {
if ((whitelist.length > 0 ? whitelist.indexOf(match) !== -1 : true) &&
(blacklist.length > 0 ? blacklist.indexOf(match) === -1 : true)) {
events.push(match[1]);
}
}
}
return events;
}
getElementType() {
return this._.element.tagName.toLowerCase();
}
setElement(element) {
this._.element = element;
return this;
}
applyEvents(events, callback) {
events.forEach((event) => {
this._.element.addEventListener(event, (ev) => {
if (typeof callback === 'function') {
callback(event, ev);
}
})
})
}
}
My solution to this problem. I loop through all datatypes on the global context (window, in this case), check if the type extends EventTarget, and then extracts them via checking for the "on" prefix.
const getEventNames = (root) => {
let events = [ ];
const objectHasSubPrototype = (object, comp) => {
let proto = Object.getPrototypeOf(object);
while(proto !== null && proto !== EventTarget) {
proto = Object.getPrototypeOf(proto);
}
return (proto !== null);
};
const addEventNames = (propNames) => {
propNames.filter(x => x.match(/^on\w+$/)).forEach((propName) => {
propName = propName.substr(2);
if(events.indexOf(propName) === -1) {
events.push(propName);
}
});
};
Object.getOwnPropertyNames(root).forEach((name) => {
let value = root[name];
if(value) {
if(objectHasSubPrototype(value, EventTarget)) {
let propNames = Object.getOwnPropertyNames(Object.getPrototypeOf(value).prototype);
addEventNames(propNames);
propNames = Object.getOwnPropertyNames(window);
addEventNames(propNames);
}
}
});
return events;
};
// Attach all events to the window
getEventNames(window).forEach((eventName) => {
window.addEventListener(eventName, (event) => console.log(eventName, event));
});
For the last version of the MDN website:
(function getAllEventTypes(){
if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;
var types = {};
$('.standard-table').map(function(){
if($(this).find('caption').length > 0){
var type = $(this).find('caption')[0].innerHTML || 'OtherEvent';
types[type] = types[type] || [];
$(this).find('tbody tr td code a').each(function(el){
if(this.innerText) types[type].push(this.innerText);
});
}
});
for(var t in types) types[t] = types[t].join(' ');
return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');
})();
I have code like:
document.onmousedown = function(){
alert('test');
}
Now, except the element with ID "box", clicking should call this function, i.e. the equivalent of jQuery's .not() selector.
The jQuery code would be:
$(document).not('#box').mousedown(function(){
alert('test');
});
How can I achieve the same thing without using jQuery?
Edit: I don't want jQuery code, but i want an action similar to the .not() selector of jQuery in Javascript.
Edit: I am making an addthis-like widget. It is a 10kb file which will show a popup when a text is selected. It will not use jQuery.
In my case, when a text is selected, a popup is shown. When the document is clicked somewhere other than the widget, the widget should disappear.
To do this properly, you need to check whether e.target || e.srcElement or any of its parents has id === 'box'.
For example: (with jQuery)
$(document).mousedown(function(e) {
if ($(e.target).closest('#box').length)
return;
//Do things
});
Without jQuery:
function isBox(elem) {
return elem != null && (elem.id === 'box' || isBox(elem.parentNode));
}
document.onmousedown = function(e) {
e = e || window.event;
if (isBox(e.target || e.srcElement))
return;
//Do things
};
Alternatively, you could handle the mousedown event for the box element and cancel bubbling.
Here's one way that should work:
document.onmousedown = function(e){
var event = e || window.event;
var element = event.target || event.srcElement;
if (target.id !== "box") { alert("hi"); }
}
or if you would like it to be reusable with different ids:
function myNot(id, callback) {
return function (e) {
var event = e || window.event;
var element = event.target || event.srcElement;
if (target.id !== id) { callback(); }
}
}
and to use it:
document.onmousedown = myNot("box", function () {
alert("hi");
});
The cleanest way I can come up with for what you're trying to do is to set a document.onmousedown event and then halt event propagation on the box.onmousedown event. This avoids creating a large number of onmousedown events all over the document, and avoids having to recurse through the entire parent hierarchy of a node every time an event is triggered.
document.onmousedown = function() {
alert("Foo!");
};
document.getElementById("box").onmousedown = function(e) {
alert("Bar!");
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
};
I am using jquery to keep the focus on a text box when you click on a specific div. It works well in Internet Explorer but not in Firefox. Any suggestions?
var clickedDiv = false;
$('input').blur(function() { if (clickedDiv) { $('input').focus(); } });
$('div').mousedown(function() { clickedDiv = true; })
.mouseup(function() { clickedDiv = false });
Point to note: the focus() method on a jquery object does not actually focus it: it just cases the focus handler to be invoked! to actually focus the item, you should do this:
var clickedDiv = false;
$('input').blur( function() {
if(clickeddiv) {
$('input').each(function(){this[0].focus()});
}
}
$('div').mousedown(function() { clickedDiv = true; })
.mouseup(function() { clickedDiv = false });
Note that I've used the focus() method on native DOM objects, not jquery objects.
This is a direct (brute force) change to your exact code. However, if I understand what you are trying to do correctly, you are trying to focus an input box when a particular div is clicked when that input is in focus.
Here's my take on how you would do it:
var inFocus = false;
$('#myinput').focus(function() { inFocus = true; })
.blur(function() { inFocus = false; });
$('#mydiv').mousedown(function() {
if( inFocus )
setTimeout( function(){ $('#myinput')[0].focus(); }, 100 );
}
Point to note: I've given a timeout to focussing the input in question, so that the input can actually go out of focus in the mean time. Otherwise we would be giving it focus just before it is about to lose it. As for the decision of 100 ms, its really a fluke here.
Cheers,
jrh
EDIT in response to #Jim's comment
The first method probably did not work because it was the wrong approach to start with.
As for the second question, we should use .focus() on the native DOM object and not on the jQuery wrapper around it because the native .focus() method causes the object to actually grab focus, while the jquery method just calls the event handler associated with the focus event.
So while the jquery method calls the focus event handler, the native method actually grants focus, hence causing the handler to be invoked. It is just unfortunate nomenclature that the name of this method overlaps.
I resolved it by simply replace on blur event by document.onclick and check clicked element if not input or div
var $con = null; //the input object
var $inp = null; // the div object
function bodyClick(eleId){
if (eleId == null || ($inp!= null && $con != null && eleId != $inp.attr('id') &&
eleId != $con.attr('id'))){
$con.hide();
}
}
function hideCon() {
if(clickedDiv){
$con.hide();
}
}
function getEl(){
var ev = arguments[0] || window.event,
origEl = ev.target || ev.srcElement;
eleId = origEl.id;
bodyClick(eleId);
}
document.onclick = getEl;
hope u find it useful