Related
I'm adding an html5 drag and drop uploader to my page.
When a file is dropped into the upload area, everything works great.
However, if I accidentally drop the file outside of the upload area, the browser loads the local file as if it is a new page.
How can I prevent this behavior?
Thanks!
You can add a event listener to the window that calls preventDefault() on all dragover and drop events.
Example:
window.addEventListener("dragover",function(e){
e = e || event;
e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
},false);
After a lot of fiddling around, I found this to be the stablest solution:
var dropzoneId = "dropzone";
window.addEventListener("dragenter", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
}, false);
window.addEventListener("dragover", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
});
window.addEventListener("drop", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
});
<div id="dropzone">...</div>
Setting both effectAllow and dropEffect unconditionally on the window causes my drop zone not to accept any d-n-d any longer, regardless whether the properties are set new or not.
To allow drag-and-drop only on some elements, you could do something like:
window.addEventListener("dragover",function(e){
e = e || event;
console.log(e);
if (e.target.tagName != "INPUT") { // check which element is our target
e.preventDefault();
}
},false);
window.addEventListener("drop",function(e){
e = e || event;
console.log(e);
if (e.target.tagName != "INPUT") { // check which element is our target
e.preventDefault();
}
},false);
For jQuery the correct answer will be:
$(document).on({
dragover: function() {
return false;
},
drop: function() {
return false;
}
});
Here return false will behave as event.preventDefault() and event.stopPropagation().
Note: Although the OP did not ask for an Angular solution, I came here looking for that. So this is to share what I found to be a viable solution, if you use Angular.
In my experience this problem first arises when you add file drop functionality to a page. Therefore my opinion is that the component that adds this, should also be responsible for preventing drop outside of the drop zone.
In my solution the drop zone is an input with a class, but any unambiguous selector works.
import { Component, HostListener } from '#angular/core';
//...
#Component({
template: `
<form>
<!-- ... -->
<input type="file" class="dropzone" />
</form>
`
})
export class MyComponentWithDropTarget {
//...
#HostListener('document:dragover', ['$event'])
#HostListener('drop', ['$event'])
onDragDropFileVerifyZone(event) {
if (event.target.matches('input.dropzone')) {
// In drop zone. I don't want listeners later in event-chain to meddle in here
event.stopPropagation();
} else {
// Outside of drop zone! Prevent default action, and do not show copy/move icon
event.preventDefault();
event.dataTransfer.effectAllowed = 'none';
event.dataTransfer.dropEffect = 'none';
}
}
}
The listeners are added/removed automatically when component is created/destroyed, and other components using the same strategy on the same page do not interfere with each other due to the stopPropagation().
Here's a little more modernized version of this answer using ES6 syntax.
let dropzoneId = 'dropzone'
const dragEventHandler = e => {
if (e.target.id !== dropzoneId) {
e.preventDefault
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = 'none'
}
}
// window.addEventListener("dragenter", dragEventHandler, false)
// window.addEventListener("dragover", dragEventHandler, false)
// window.addEventListener("drop", dragEventHandler, false)
['dragenter', 'dragover', 'drop'].forEach(ev => window.addEventListener(ev, dragEventHandler, false))
<div id="dropzone">...</div>
try this:
document.body.addEventListener('drop', function(e) {
e.preventDefault();
}, false);
Preventing all drag and drop operations by default might not be what you want. It's possible to check if the drag source is an external file, at least in some browsers. I've included a function to check if the drag source is an external file in this StackOverflow answer.
Modifying Digital Plane's answer, you could do something like this:
function isDragSourceExternalFile() {
// Defined here:
// https://stackoverflow.com/a/32044172/395461
}
window.addEventListener("dragover",function(e){
e = e || event;
var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
if (IsFile) e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
if (IsFile) e.preventDefault();
},false);
To build on the "check the target" method outlined in a few other answers, here is a more generic/functional method:
function preventDefaultExcept(predicates) {
return function (e) {
var passEvery = predicates.every(function (predicate) { return predicate(e); })
if (!passEvery) {
e.preventDefault();
}
};
}
Called like:
function isDropzone(e) { return e.target.id === 'dropzone'; }
function isntParagraph(e) { return e.target.tagName !== 'p'; }
window.addEventListener(
'dragover',
preventDefaultExcept([isDropzone, isntParagraph])
);
window.addEventListener(
'drop',
preventDefaultExcept([isDropzone])
);
I have an HTML object (embed) that fills the width and height of the page. The answer by #digital-plane works on normal web pages but not if the user drops onto an embedded object. So I needed a different solution.
If we switch to using the event capture phase we can get the events before the embedded object receives them (notice the true value at the end of the event listener call):
// document.body or window
document.body.addEventListener("dragover", function(e){
e = e || event;
e.preventDefault();
console.log("over true");
}, true);
document.body.addEventListener("drop", function(e){
e = e || event;
e.preventDefault();
console.log("drop true");
}, true);
Using the following code (based on #digital-plane's answer) the page becomes a drag target, it prevents object embeds from capturing the events and then loads our images:
document.body.addEventListener("dragover", function(e){
e = e || event;
e.preventDefault();
console.log("over true");
}, true);
document.body.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
console.log("Drop true");
// begin loading image data to pass to our embed
var droppedFiles = e.dataTransfer.files;
var fileReaders = {};
var files = {};
var reader;
for (var i = 0; i < droppedFiles.length; i++) {
files[i] = droppedFiles[i]; // bc file is ref is overwritten
console.log("File: " + files[i].name + " " + files[i].size);
reader = new FileReader();
reader.file = files[i]; // bc loadend event has no file ref
reader.addEventListener("loadend", function (ev, loadedFile) {
var fileObject = {};
var currentReader = ev.target;
loadedFile = currentReader.file;
console.log("File loaded:" + loadedFile.name);
fileObject.dataURI = currentReader.result;
fileObject.name = loadedFile.name;
fileObject.type = loadedFile.type;
// call function on embed and pass file object
});
reader.readAsDataURL(files[i]);
}
}, true);
Tested on Firefox on Mac.
I am using a class selector for multiple upload areas so my solution took this less pure form
Based on Axel Amthor's answer, with dependency on jQuery (aliased to $)
_stopBrowserFromOpeningDragAndDropPDFFiles = function () {
_preventDND = function(e) {
if (!$(e.target).is($(_uploadBoxSelector))) {
e.preventDefault();
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
}
};
window.addEventListener('dragenter', function (e) {
_preventDND(e);
}, false);
window.addEventListener('dragover', function (e) {
_preventDND(e);
});
window.addEventListener('drop', function (e) {
_preventDND(e);
});
},
For what its worth, I use the following. Nice and explicit if not particularly elegant perhaps?
var myDropZone = document.getElementById('drop_zone');
// first, inhibit the default behaviour throughout the window
window.addEventListener('drop', () => {
event.preventDefault();
} );
window.addEventListener('dragover', () => {
event.dataTransfer.dropEffect = 'none'; // dont allow drops
event.preventDefault();
} );
// Next, allow the cursor to show 'copy' as it is dragged over
// my drop zone but dont forget to stop the event propagating
myDropZone.addEventListener('dragover', () => {
event.dataTransfer.dropEffect = 'copy';
event.stopPropagation(); // important !!
event.preventDefault();
} );
// In my drop zone, deal with files as they are dropped
myDropZone.addEventListener('drop', myDropHandler);
When i'm handle click to edit area. i use folowing function
Function.prototype.closureListener = function() {
var __method = this, args = bkLib.toArray(arguments), object = rgs.shift();
return function(e) {
e = e || window.event;
if(e.target) { var target = e.target; } else { var target = e.srcElement };
return __method.apply(object, [e,target].concat(args) );
};
}
and add event
this.elm.addEvent('mousedown',this.selected.closureListener(this))
But when i click at the end of the line
asdasdasd<b>sdasdasdasd<b>
sometimes it's select like a target all body(as Element) or only sdasdasdasd.
What should i add that he took only sdasdasdasd but not all body.
P.S When i click somewhere in the middle it's always takes sdasdasdasd
P.S.S nicEdit i'm using this nicEdit panel.
Here it is:
//Disable KeyboardNavigation
document.getElementById("author").onfocus = function() {
document.onkeyup = null;
};
document.getElementById("email").onfocus = function() {
document.onkeyup = null;
};
document.getElementById("url").onfocus = function() {
document.onkeyup = null;
};
document.getElementById("comment").onfocus = function() {
document.onkeyup = null;
};
//Enable KeyboardNavigation
document.getElementById("author").onblur = function() {
document.onkeyup = KeyCheck;
};
document.getElementById("email").onblur = function() {
document.onkeyup = KeyCheck;
};
document.getElementById("url").onblur = function() {
document.onkeyup = KeyCheck;
};
document.getElementById("comment").onblur = function() {
document.onkeyup = KeyCheck;
};
I believe it's definitely possible to write a better code with a loop but I really don't know how to make it work. I tried the following:
var formfields= ["author", "email", "url", "comment"];
for (i=1; i<=3; i++){
//Don't really know what to put in here.
}
Thank you in advance for your help!
EDIT : Whole code is below. You should know that I got some help to get to this result:
document.onkeyup = KeyCheck;
var pages = [
"http://",
"http://",
"http://",
"http://",
"http://"];
function leftarrowpressed() {
location.href = pages[ Math.max(0, 0 - 1) ];
//The second '0' here changes from 0 to 4, according to the page.
}
function rightarrowpressed() {
location.href = pages[ Math.min(pages.length - 1, 0 + 1) ];
//The second '0' here changes from 0 to 4, according to the page.
}
function KeyCheck(e)
{
var KeyID = (window.event) ? event.keyCode : e.keyCode;
switch(KeyID)
{
// left arrow key
case 37:
leftarrowpressed();
break;
// right arrow key
case 39:
rightarrowpressed();
break;
}
}
Hope this can help a little more. By the way, thank you everyone. I really don't know which solution to choose.
It looks like what you are doing is trying to prevent keystrokes in an input element from affecting navigation. What you could do instead is check event.target in KeyCheck and only perform the action if it was not triggered by an input element.
function KeyCheck(e) {
var target = e ? e.target : event.srcElement, //standards vs IE
tagname = target.tagName.toLowerCase();
if( tagname !== "input" && tagname !== "textarea" && tagname !== "select") {
//Not from an input, NAVIGATE!
}
}
If using jQuery then you can go a more straight-forward way: inside KeyCheck, check whether any of the elements is focused, and don't do anything in that case. You won't need any of the above.
function KeyCheck(e) {
if($("#author, #email, #url, #comment").is(":focus")) {
return; // ignore if any of these elements has focus
}
// ...
}
Make sure to bind KeyCheck using jQuery too:
$("body").on("keyup", KeyCheck);
var formfields= ["author", "email", "url", "comment"];
for (i=0; i<=3; i++){
var field = document.getElementById(formFields[i]);
field.onfocus = function() {
document.onkeyup = null;
};
field.onblur = function() {
document.onkeyup = KeyCheck;
};
}
or more proper way would be to use something like this
jQuery.each("author email url comment".split(" "), function(i, name) {
$('#' + name).focus(function() {
// do whatever you want to do
}).blur(function() {
// do whatever you wnat to do
));
});
Neat and readable:
var formfields = ["author", "email", "url", "comment"],
i, elem,
blur = function() { document.onkeyup = KeyCheck; },
focus = function() { document.onkeyup = null; };
for (i=0; i<=3; i++) {
elem = document.getElementById(formFields[i]);
elem.onblur = blur;
elem.onfocus = focus;
}
look for the nearest common parent for these elements and add a handler to it. we can use the powers of delegation using the .on() as well as method chaining to bind a hander only to the parent (in this case, 2 handlers for all, not 8 where 2 per element) to take effect on all 4 elements.
var selectors = '#author, #email, #url, #comment';
$('nearest_parent_element').on('focus', selectors, function() {
document.onkeyup = null;
}).on('blur', selectors, function() {
document.onkeyup = KeyCheck;
});
jQuery way:
$("#author, #email, #url, #comment").on({
focus: function() {
$(document).on('keyup', null);
},
blur: function() {
$(document).on('keyup', KeyCheck);
}
});
It all depends on how good you are at JavaScript. I would recommend for you to use event delegation: http://jsfiddle.net/teresko/PkCuZ/3/
It might look a bit complicated , but the add_listener() function would be shared throughout the whole code , so the payload actually looks like this:
var handlers = {
keyout: function(e){
var event = e || window.event,
target = event.target || event.srcElement;
console.log( 'leaving ' + target.name );
},
keyin: function(e){
var event = e || window.event,
target = event.target || event.srcElement;
console.log( 'entering ' + target.name );
}
},
container = document.getElementById('container');
add_listener( container, 'blur' , handlers.keyout );
add_listener( container, 'focus' , handlers.keyin );
This would work with any number of form elements.
As for the add_listener() function , it contains a small fix for blur/focus on IE, and a per-application choice of which method of attaching events to use. It's kinda an universal function which you can just drop in, when you need a common interface for attaching listeners:
var add_listener = (function () {
var fix = {
'focus': 'focusin',
'blur': 'focusout'
};
if ( window.addEventListener ) {
return function ( element, type, callback ) {
element.addEventListener(type, callback, typeof(fix[type]) !== undefined );
};
}else{
return function ( element, type, callback ) {
type = fix[type] || type;
element.attachEvent('on' + type, callback);
};
}
})();
I want to get href attribute when a use clicks on a URL.
I've tried with:
var e = clickedElement || window.event;
var t = e.target || e.srcElement;
alert(t.href);
This is working fine in Chrome and Firefox, but there's a problem with IE; its give an error null.
What is possible solution to get href from event object?
Depends what clickedElement is, given that its either the event object argument placeholder or a this argument;
function event_obj(event) {
if (!event) var event = window.event;
var element = event.target || event.srcElement;
alert(element.href);
return false;
}
function element_obj(element) {
alert(element.href)
return false;
}
.
.
foo
bar
Use this function to get the event target
function getEventTarget(event) {
var targetElement = null;
try {
if (typeof event.target != "undefined") {
targetElement = event.target;
}
else {
targetElement = event.srcElement;
}
} catch (ex) { alert("getEventTarget failed: " + ex); }
return targetElement;
};
Then call it when you trigger the event
function evtrigger(ev) {
alert(getEventTarget(ev).href);
}
Note the getEventTarget() function works for all events so the DOM object returned can be manipulated any way you like, not just to get the href.
I'm adding an html5 drag and drop uploader to my page.
When a file is dropped into the upload area, everything works great.
However, if I accidentally drop the file outside of the upload area, the browser loads the local file as if it is a new page.
How can I prevent this behavior?
Thanks!
You can add a event listener to the window that calls preventDefault() on all dragover and drop events.
Example:
window.addEventListener("dragover",function(e){
e = e || event;
e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
},false);
After a lot of fiddling around, I found this to be the stablest solution:
var dropzoneId = "dropzone";
window.addEventListener("dragenter", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
}, false);
window.addEventListener("dragover", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
});
window.addEventListener("drop", function(e) {
if (e.target.id != dropzoneId) {
e.preventDefault();
e.dataTransfer.effectAllowed = "none";
e.dataTransfer.dropEffect = "none";
}
});
<div id="dropzone">...</div>
Setting both effectAllow and dropEffect unconditionally on the window causes my drop zone not to accept any d-n-d any longer, regardless whether the properties are set new or not.
To allow drag-and-drop only on some elements, you could do something like:
window.addEventListener("dragover",function(e){
e = e || event;
console.log(e);
if (e.target.tagName != "INPUT") { // check which element is our target
e.preventDefault();
}
},false);
window.addEventListener("drop",function(e){
e = e || event;
console.log(e);
if (e.target.tagName != "INPUT") { // check which element is our target
e.preventDefault();
}
},false);
For jQuery the correct answer will be:
$(document).on({
dragover: function() {
return false;
},
drop: function() {
return false;
}
});
Here return false will behave as event.preventDefault() and event.stopPropagation().
Note: Although the OP did not ask for an Angular solution, I came here looking for that. So this is to share what I found to be a viable solution, if you use Angular.
In my experience this problem first arises when you add file drop functionality to a page. Therefore my opinion is that the component that adds this, should also be responsible for preventing drop outside of the drop zone.
In my solution the drop zone is an input with a class, but any unambiguous selector works.
import { Component, HostListener } from '#angular/core';
//...
#Component({
template: `
<form>
<!-- ... -->
<input type="file" class="dropzone" />
</form>
`
})
export class MyComponentWithDropTarget {
//...
#HostListener('document:dragover', ['$event'])
#HostListener('drop', ['$event'])
onDragDropFileVerifyZone(event) {
if (event.target.matches('input.dropzone')) {
// In drop zone. I don't want listeners later in event-chain to meddle in here
event.stopPropagation();
} else {
// Outside of drop zone! Prevent default action, and do not show copy/move icon
event.preventDefault();
event.dataTransfer.effectAllowed = 'none';
event.dataTransfer.dropEffect = 'none';
}
}
}
The listeners are added/removed automatically when component is created/destroyed, and other components using the same strategy on the same page do not interfere with each other due to the stopPropagation().
Here's a little more modernized version of this answer using ES6 syntax.
let dropzoneId = 'dropzone'
const dragEventHandler = e => {
if (e.target.id !== dropzoneId) {
e.preventDefault
e.dataTransfer.effectAllowed = 'none'
e.dataTransfer.dropEffect = 'none'
}
}
// window.addEventListener("dragenter", dragEventHandler, false)
// window.addEventListener("dragover", dragEventHandler, false)
// window.addEventListener("drop", dragEventHandler, false)
['dragenter', 'dragover', 'drop'].forEach(ev => window.addEventListener(ev, dragEventHandler, false))
<div id="dropzone">...</div>
try this:
document.body.addEventListener('drop', function(e) {
e.preventDefault();
}, false);
Preventing all drag and drop operations by default might not be what you want. It's possible to check if the drag source is an external file, at least in some browsers. I've included a function to check if the drag source is an external file in this StackOverflow answer.
Modifying Digital Plane's answer, you could do something like this:
function isDragSourceExternalFile() {
// Defined here:
// https://stackoverflow.com/a/32044172/395461
}
window.addEventListener("dragover",function(e){
e = e || event;
var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
if (IsFile) e.preventDefault();
},false);
window.addEventListener("drop",function(e){
e = e || event;
var IsFile = isDragSourceExternalFile(e.originalEvent.dataTransfer);
if (IsFile) e.preventDefault();
},false);
To build on the "check the target" method outlined in a few other answers, here is a more generic/functional method:
function preventDefaultExcept(predicates) {
return function (e) {
var passEvery = predicates.every(function (predicate) { return predicate(e); })
if (!passEvery) {
e.preventDefault();
}
};
}
Called like:
function isDropzone(e) { return e.target.id === 'dropzone'; }
function isntParagraph(e) { return e.target.tagName !== 'p'; }
window.addEventListener(
'dragover',
preventDefaultExcept([isDropzone, isntParagraph])
);
window.addEventListener(
'drop',
preventDefaultExcept([isDropzone])
);
I have an HTML object (embed) that fills the width and height of the page. The answer by #digital-plane works on normal web pages but not if the user drops onto an embedded object. So I needed a different solution.
If we switch to using the event capture phase we can get the events before the embedded object receives them (notice the true value at the end of the event listener call):
// document.body or window
document.body.addEventListener("dragover", function(e){
e = e || event;
e.preventDefault();
console.log("over true");
}, true);
document.body.addEventListener("drop", function(e){
e = e || event;
e.preventDefault();
console.log("drop true");
}, true);
Using the following code (based on #digital-plane's answer) the page becomes a drag target, it prevents object embeds from capturing the events and then loads our images:
document.body.addEventListener("dragover", function(e){
e = e || event;
e.preventDefault();
console.log("over true");
}, true);
document.body.addEventListener("drop",function(e){
e = e || event;
e.preventDefault();
console.log("Drop true");
// begin loading image data to pass to our embed
var droppedFiles = e.dataTransfer.files;
var fileReaders = {};
var files = {};
var reader;
for (var i = 0; i < droppedFiles.length; i++) {
files[i] = droppedFiles[i]; // bc file is ref is overwritten
console.log("File: " + files[i].name + " " + files[i].size);
reader = new FileReader();
reader.file = files[i]; // bc loadend event has no file ref
reader.addEventListener("loadend", function (ev, loadedFile) {
var fileObject = {};
var currentReader = ev.target;
loadedFile = currentReader.file;
console.log("File loaded:" + loadedFile.name);
fileObject.dataURI = currentReader.result;
fileObject.name = loadedFile.name;
fileObject.type = loadedFile.type;
// call function on embed and pass file object
});
reader.readAsDataURL(files[i]);
}
}, true);
Tested on Firefox on Mac.
I am using a class selector for multiple upload areas so my solution took this less pure form
Based on Axel Amthor's answer, with dependency on jQuery (aliased to $)
_stopBrowserFromOpeningDragAndDropPDFFiles = function () {
_preventDND = function(e) {
if (!$(e.target).is($(_uploadBoxSelector))) {
e.preventDefault();
e.dataTransfer.effectAllowed = 'none';
e.dataTransfer.dropEffect = 'none';
}
};
window.addEventListener('dragenter', function (e) {
_preventDND(e);
}, false);
window.addEventListener('dragover', function (e) {
_preventDND(e);
});
window.addEventListener('drop', function (e) {
_preventDND(e);
});
},
For what its worth, I use the following. Nice and explicit if not particularly elegant perhaps?
var myDropZone = document.getElementById('drop_zone');
// first, inhibit the default behaviour throughout the window
window.addEventListener('drop', () => {
event.preventDefault();
} );
window.addEventListener('dragover', () => {
event.dataTransfer.dropEffect = 'none'; // dont allow drops
event.preventDefault();
} );
// Next, allow the cursor to show 'copy' as it is dragged over
// my drop zone but dont forget to stop the event propagating
myDropZone.addEventListener('dragover', () => {
event.dataTransfer.dropEffect = 'copy';
event.stopPropagation(); // important !!
event.preventDefault();
} );
// In my drop zone, deal with files as they are dropped
myDropZone.addEventListener('drop', myDropHandler);