I have various elements on the page that can be dragged onto various drop zones. However for the textarea I can't find a way to show to the user that a draggable image suitable for a different zone, may not be dropped in the textarea.
I tried all kinds of combinations handling the ondragenter and ondragover events but it has been impossible to show the "no drop" icon when the image is dragged over the textarea.
There are lots of tutorials and tips on how to made a drop zone accept everything. I want to know how to make a dropzone and a textarea in particular reject a drag item. Turning of drag behavior for the item is not an option because there are other zones that should accept that image.
This JS fiddle shows that by default an image can be dragged into a textarea resulting in the URL being shown. I would love some help showing me how to stop that.
function dragstart(event) {//stuff}
function dragenter(event) {event.preventDefault();}
function dragover(event) {event.preventDefault();}
function dragdrop(event) {event.preventDefault();}
http://jsfiddle.net/mWKd3/16/
You aren't binding/attaching your events, for the attributes ondragstart, ondragenter, ondragover, and ondragdrop are not defined.
Here is a new fiddle that displays it http://jsfiddle.net/mWKd3/18/
In-Short - the Javascript (I'm using jQuery to attach the events)
$("img").bind("dragstart",function(e){
});
$("textarea").bind("dragenter",function(e){
e.preventDefault();
});
$("textarea").bind("dragover",function(e){
e.preventDefault();
});
$("textarea").bind("dragdrop",function(e){
e.preventDefault();
});
The following was an alternative method of doing drag-n-drop.
Referencing http://css-tricks.com/snippets/jquery/draggable-without-jquery-ui/ and extending your jsfiddle
HTML*
<div style='height:2em;display:block;'></div>
<img id='imgarea' src="http://www.planetinaction.com/images/gexplorer_logo48.png" draggable="true">
<textarea id='tarea' class="textzone"></textarea>
<div id='debugger' style='top:0em;left:5em;right:0em;height:2em;width:auto;position:absolute;display:block;'>Debug Window</div>
CSS*
.targetzone {
width: 200px;
height: 200px;
background-color: yellow;
}
.textzone {
width: 200px;
height: 200px;
background-color: white;
}
Javascript*
(function($) {
$.fn.dragstart = function(opt) {
opt = $.extend({handle:"",cursor:"move"}, opt);
if(opt.handle === "") {
var $el = this;
} else {
var $el = this.find(opt.handle);
}
return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
if(opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
}
var z_idx = $drag.css('z-index'),
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
$(this).data("start_pos_x",$drag.offset().left);
$(this).data("start_pos_y",$drag.offset().top);
$(this).data("start_z_idx",z_idx);
$drag.css('z-index', 1000).parents().on("mousemove", function(e) {
$('.draggable').offset({
top:e.pageY + pos_y - drg_h,
left:e.pageX + pos_x - drg_w
});
}).on("mouseup", function(e) {
$(this).removeClass('draggable').css('z-index', z_idx);
$("#debugger").append(document.elementFromPoint(e.pageX,e.pageY).id + " was selected!");
})
e.preventDefault(); // disable selection
}).on("mouseup", function() {
if(opt.handle === "") {
$(this).removeClass('draggable');
} else {
$(this).removeClass('active-handle').parent().removeClass('draggable');
}
$(this).offset({
top: $(this).data("start_pos_y"),
left: $(this).data("start_pos_x")
});
});
}
})(jQuery);
$("img").dragstart();
Related
I've got an issue while I'm trying to combine touchstart and mousedown in 1 function. I've used an a tag as the target element of the function for going to the link directly when I touched or clicked the tag.
The issue is when I touch the middle of a tag, link doesn't respond. it only works when I click the element or touch the edge of the a tag, and the output fires mousedown.
In the mobile mode, try to click the edge of a tag as much as you would possible like a grey dot in the picture above. I've created an CodePen example for looking, testing and understanding better.
How would I fix this issue?
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
e.preventDefault();
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
return false;
}
apply() {
this.$el.bind('touchstart mousedown', (e) => this.start(e));
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'), {passive: false});
setSlider.apply();
a {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
<a id="anchor" href="https://google.co.uk">Tap or Click Me</a>
<p class="textbox"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
========= Progress Update ==========
I've just added move & end function then I have to click twice for moving on to the linked website. It keeps getting worse and have no idea how to solve this issue.
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
e.preventDefault();
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
this.$el.bind('touchmove mousemove', (e) => this.move(e));
this.$el.bind('touchend mouseup', (e) => this.end(e));
return false;
}
move(e) {
var type = e.type;
if (type === 'touchstart' || type === 'mousedown') this.paragraph.text(this.paragraph.text() + ' ' + type);
return false;
}
end(e) {
console.log('test');
this.$el.on('click');
this.$el.off('touchstart touchend');
return false;
}
apply() {
this.$el.bind('touchstart || mousedown', (e) => this.start(e));
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'));
setSlider.apply();
======== Progress Updated After Bounty (Latest) ========
After dozens of tried, I've finally figured out and solve the previous problem but I've faced up a new issue that can't draggable and redirecting instantly.
When I use the preventDefault in the start function, all of the events work fine. The only issue of this case is dragging doesn't prevent redirecting link from the a tag. It always send me to the website no matter which ways to call the functions, clicked or dragged.
when I don't use the preventDefault, dragging doesn't work. it only works clicking the elements.
My final goal is to prevent redirecting link of the a tag from the both events, touchmove and mousemove. I've been searched about on google so many times but haven't got any of the clues.
I've written an example in Codepen and this is what I've done so far:
class Slider {
constructor($el, paragraph) {
this.$el = $el;
this.paragraph = paragraph;
}
start(e) {
var type = e.type;
if (type === 'touchstart') {
this.paragraph.text(this.paragraph.text() + ' ' + type);
} else if (type === 'mousedown') {
this.paragraph.text(this.paragraph.text() + ' ' + type);
}
}
move(e) {
var type = e.type;
}
end(e) {
var type = e.type;
if (type === 'touchend') {
console.log('touchstart enabled');
} else if (type === 'mouseup') {
console.log('mousedown enabled');
}
}
apply() {
this.$el.bind({
touchstart: (e) => this.start(e),
touchmove: (e) => this.move(e),
touchend: (e) => this.end(e),
mousedown:(e) => this.start(e),
onmousemove: (e) => this.move(e),
mouseup: (e) => this.end(e)
});
}
}
const setSlider = new Slider($('#anchor'), $('.textbox'));
setSlider.apply();
a {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
<a id="anchor" href="https://google.co.uk">Tap or Click Me</a>
<p class="textbox"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I think I found the solution about this. I add this code for some people in the future who are trying hard to search the same problem such as me.
function start(event, etype, condition) {
console.log(etype); // track the type of event
if (!condition) event.preventDefault(); // compare etype(eventType). Set preventDefault if condition is falsy.
items.off('click');
items.on({
['touchmove mousemove']: (event) => move(event, etype, condition),
['touchend mouseup']: end
});
}
function move(event, etype, cnd) {
if (cnd) event.preventDefault();
console.log(cnd); // track the type of event from the condition
items.on('click', function(event) {event.preventDefault();});
}
function end(event) {
items.off('touchmove mousemove touchend mouseup');
}
var items = $('.item a');
items.on('touchstart mousedown', function() {
var eventType = event.type;
var condition = (eventType === 'touchstart' || eventType === 'mousedown');
start(event, eventType, condition);
});
#anchor {
display: block;
width: 100px;
height: 100px;
background-color: orange;
}
.item {
background-color: gray;
}
.item + .item {
margin-top: 10px;
}
.item a {
cursor: pointer;
display: inline-block;
background-color: blue;
color: white;
padding: 9px;
width: 100%;
height: 100%;
}
<div id="items" class="items">
<div class="item">
<a target="_blank" href="https://google.com">Anchor</a>
</div>
<div class="item">
<a target="_blank" href="https://google.com">Anchor</a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
i think i figured out little solution for you. enable the preventDefault and afterwards enable the draggable for the a tag. Let me know if this works for you.
start(e) {
e.preventDefault();
...//rest of the code
apply() {
$el.draggable("enable");
...//rest of the code
I'm trying to prevent mutiple clicks on a link and I need to wait until the function's complete before allowing another click on the same link.
However, everything I do, the multiple clicks is always allowed.
This is my code:
var active = false;
$('#rotRight').live('click', function(){
if (active) {
return;
}
$(this).attr('id', 'rotRight1');
var curAngle = parseInt($(".selected").getRotateAngle()) || 0;
if($(".selected").rotate({
angle: curAngle,
animateTo: curAngle - 90
})){
active = true;
}
$(this).attr('id', 'rotRight');
active = false;
});
I know I'm in the right path. I just need someone to let me know what i'm missing or if I'm doing something wrong please.
Any help would be appreciated.
Thanks
The rotate plugin has a callback where you can reset the active flag value
var active = false;
$('#rotRight').live('click', function() {
if (active) {
return;
}
var curAngle = parseInt($(".selected").getRotateAngle()) || 0;
active = true;
$(".selected").rotate({
angle: curAngle,
animateTo: curAngle - 90,
callback: function() {
active = false;
}
})
});
Try preventing default behaviour first before returning
var active = false;
$('#rotRight').live('click', function(e){
if (active) {
e.preventDefault(); // prevent default behaviour before returning
return;
}
...
});
You could always create an overlay (loading screen), stopping the user doing anything till the process is completed via a full screen div with the following css:
#overlay {
background-color: rgba(0, 0, 0, 0.3);
z-index: 999;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: none;
}
This will do the following: https://gyazo.com/aa833914eda1c39d3b8198db2b32dc41
Makes all content un-clickable until after the loading screen has finished.
You can try to use the Data attribute on your link. Like that, you can set the "active" property on the link itself and act in consequence. Jquery handle that system with $().data(string) method. I think it's cleaner than using a global "active" var.
$('#rotRight').live('click', function() {
if ($(this).data("active") != undefined && $(this).data("active") == true) {
return;
}
var curAngle = parseInt($(".selected").getRotateAngle()) || 0;
$(this).data("active") = true;
var self = $(this);
$(".selected").rotate({
angle: curAngle,
animateTo: curAngle - 90,
callback: function() {
self.data("active") = false;
}
})
});
$('#rotRight').live('click', function(e) {
if (e.handled!=true) {
e.preventDefault(); // prevent default behaviour before returning
return;
}
e.handled=true;
});
I have been developing mobile apps using Cordova/Phonegap and this time I had to enable a double tap on rows of one specific table. I don't want to use a plugin or another library for it as it is only for one part of the app. How do I check double tap on Javascript?
So I searched "How do I check for double tap in javascript?", and voila! using #mfirdaus answer in this question I solved it. But I came to another issue, I cant scroll. So I searched "How do I enable scrolling while checking for double tap?" and found the answer here very useful.
I compiled both answers to create a function that attaches a double tap event on a given element:
var tapped = false;
var isDragging = false;
function attachDoubleTap(elem, callbacks) {
callbacks = callbacks || {};
callbacks.onSingleTap = callbacks.onSingleTap || function() {}
callbacks.onDoubleTap = callbacks.onDoubleTap || function() {}
$(document)
.on('touchstart', elem, function(e) {
$(window).bind('touchmove', function() {
isDragging = true;
$(window).unbind('touchmove');
});
})
.on('touchend', elem, function(e) {
var wasDragging = isDragging;
isDragging = false;
$(window).unbind("touchmove");
if (!wasDragging) { //was clicking
//detect single or double tap
var _this = $(this);
if(!tapped){ //if tap is not set, set up single tap
tapped=setTimeout(function(){
tapped=null
//insert things you want to do when single tapped
callbacks.onSingleTap(_this);
},200); //wait 300ms then run single click code
} else { //tapped within 300ms of last tap. double tap
clearTimeout(tapped); //stop single tap callback
tapped=null
//insert things you want to do when double tapped
callbacks.onDoubleTap(_this);
}
}
})
}
$(document).ready(function() {
attachDoubleTap('#canvas', {
onSingleTap: function() {
$('.msg').text('single tap');
},
onDoubleTap: function() {
$('.msg').text('double tap');
},
onMove: function() {
$('.msg').text('moved');
}
});
});
var tapped = false;
var isDragging = false;
function attachDoubleTap(elem, callbacks) {
callbacks = callbacks || {};
callbacks.onSingleTap = callbacks.onSingleTap || function() {}
callbacks.onDoubleTap = callbacks.onDoubleTap || function() {}
callbacks.onMove = callbacks.onMove || function() {}
$(document)
.on('touchstart', elem, function(e) {
$(window).bind('touchmove', function() {
isDragging = true;
callbacks.onMove();
$(window).unbind('touchmove');
});
})
.on('touchend', elem, function(e) {
var wasDragging = isDragging;
isDragging = false;
$(window).unbind("touchmove");
if (!wasDragging) { //was clicking
//detect single or double tap
var _this = $(this);
if (!tapped) { //if tap is not set, set up single tap
tapped = setTimeout(function() {
tapped = null
//insert things you want to do when single tapped
callbacks.onSingleTap(_this);
}, 200); //wait 300ms then run single click code
} else { //tapped within 300ms of last tap. double tap
clearTimeout(tapped); //stop single tap callback
tapped = null
//insert things you want to do when double tapped
callbacks.onDoubleTap(_this);
}
}
})
}
body {
font-family: arial;
}
#canvas {
width: 500px;
height: 450px;
background-color: #b0dddf;
border: 1px solid #ccc;
}
.msg {
border: 1px solid #ccc;
margin: 0 auto;
width: 300px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="canvas">
<ul>
<li>Try to tap once</li>
<li>Try to tap twice</li>
<li>Try to tap and drag</li>
</ul>
<div class='msg'></div>
</div>
I hope this helps someone in the future. (I only included the Move() callback on the snippet to show it is working, it is up to you to add it to the function)
I have multiple dropzones for uploading files on a webpage
How to highlight all dropzone elements as soon as a file is dragged into the browser, so the user knows where to drop the file? And when a file is dragged over one of the dropzones I need to add an additional class to indicate the user can release the file
update
kurideja pointed me in the right direction to Dragster
https://github.com/bensmithett/dragster
Now it almost works :)
If you drag over one dropzone and drag back out without releasing the file all dropzones are hidden
http://jsfiddle.net/L7v2f96z/9/
html
<div class="dropzone"></div>
<div class="dropzone"></div>
javascript
// Add/remove class when file is dragged over the dropzone. Hover effect
$('.dropzone').dragster({
enter : function(){
$(this).show().addClass('hover');
},
leave : function(){
$(this).hide().removeClass('hover');
}
});
// Show/hide dropzones until a file is dragged into the browser window. Hide dropzones after file is dropped or dragging is stopped
var w = $(window).dragster({
enter : function(){
$('.dropzone').show();
},
leave : function(){
$('.dropzone').hide();
}
})
// Prevent defaults (file is openened in the browser) if user drops file outside a dropzone
.on('dragover', function(e){
e.preventDefault();
})
.on('drop', function(e){
e.preventDefault();
w.trigger('dragleave');
});
css
.dropzone {
width:200px;
height:200px;
background:#fff;
display:none;
border:2px dashed rgba(0,0,0,0.5);
box-shadow:0 2px 5px rgba(0,0,0,0.1), inset 0 0 40px rgba(0,0,0,0.1);
border-radius:2px;
margin:10px;
}
.dropzone.hover {
background:#e3e3e3;
}
Main problem was: after leaving the dropzone area, dragster triggered leave twice, both on .dropzone and window. Simply adding e.stopPropagation() solves the problem. There are also few more fixes (removed show() and hide() inside dropzone dragster). Your code on Fiddle and also below:
// Add/remove class when file is dragged over the dropzone. Hover effect
$('.dropzone').dragster({
enter: function() {
$(this).addClass('hover');
},
leave: function(e) {
e.stopPropagation(); //-- Critical point
$(this).removeClass('hover');
}
});
// Show/hide dropzones until a file is dragged into the browser window. Hide dropzones after file is dropped or dragging is stopped
var w = $(window).dragster({
enter: function() {
$('.dropzone').show();
},
leave: function() {
$('.dropzone').hide();
}
})
// Prevent defaults (file is openened in the browser) if user drop file outside a dropzone
.on('dragover', function(e) {
e.preventDefault();
})
.on('drop', function (e) {
e.preventDefault();
w.trigger('dragleave');
});
You can use e.originalEvent.pageXand e.originalEvent.pageY on dragover and check if its in a range of the box. For this example I have copied the dropzone and I know the width and height of the div so I could hardcode the condition. You will have to come up with a way to store the position(top and left) of the dropzone areas and use that for comparison.
var drag_timer;
$(document).on('dragover', function (e) {
var dt = e.originalEvent.dataTransfer;
if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) {
if (e.originalEvent.pageX <= 200 && e.originalEvent.pageY <= 200) {
$('.dropzone').removeClass('highlight');
$('.dropzone:eq(0)').addClass('highlight');
} else if (e.originalEvent.pageX <= 400 && e.originalEvent.pageY <= 400) {
$('.dropzone').removeClass('highlight');
$('.dropzone:eq(1)').addClass('highlight');
} else {
$('.dropzone').removeClass('highlight');
}
$('.dropzone').show();
window.clearTimeout(drag_timer);
}
})
.on('dragleave', function (e) {
drag_timer = window.setTimeout(function () {
$('.dropzone').hide();
}, 50);
});
Demo Fiddle
You can use the target member of the event to get the proper element:
var drag_timer;
$(document).on('dragover', function(e){
var dt = e.originalEvent.dataTransfer;
if(dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))){
$('.dropzone').show();
window.clearTimeout(drag_timer);
}
})
.on('dragleave', function(e){
drag_timer = window.setTimeout(function(){
$('.dropzone').hide();
}, 50);
});
$('.dropzone')
.on('dragenter', function(e){
$(e.originalEvent.target).addClass('highlight');
})
.on('dragleave', function(e){
$(e.originalEvent.target).removeClass('highlight');
});
Fiddle: http://jsfiddle.net/mzcqxfq3/
Some drag events are fired on EACH element, so basically there is not a one continuous drag, but a sequence of drags over all elements below the mouse.
Just use this plugin: http://javascript.hew.io/bensmithett/dragster
In my case I wanted to change the style of the class the moment I put
a new file and when the dropzone was filled, I did the following:
.dz-drag-hover , .dz-started {
border: 2px solid #0CB598;
}
My solution would be very similar to your aproach.
When a file is draged into the window, add a css-class to the element which contains all drop-zones (body if neccessary). Then you can style your drop-zones on drag accordingly:
$(document).on('dragover', function(e){
var dt = e.originalEvent.dataTransfer;
if(dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))){
$('body').addClass('dragging'); // Adding a class to the body
}
})
.on('dragleave', function(e){
$('body').removeClass('dragging')
});
The css would be:
/* style the drop-zone */
.dropzone {
height:200px;
width:200px;
display:none;
border:2px dashed black;
}
/* show the dropzone when file is dragged into window */
body.dragging .dropzone{
display:block;
}
/* highlight box when hovered but only when file is dragged */
body.dragging .dropzone:hover{
background:gray;
}
If this isn't what you wanted pleas tell me in a comment ;)
EDIT
Of course you have to remove the class when the file is droped
$(document).on('drop', function(event) {
$('body').removeClass('dragging');
}
IE11 bug caused by dt.types.indexOf, e.originalEvent.dataTransfer.types is a DOMStringList object in ie.
So you shoud use dt.types.contains instead ofdt.types.indexOf.
The following works:
var drag_timer;
$(document).on('dragover', function(e) {
var dt = e.originalEvent.dataTransfer;
if (dt.types != null &&
(dt.types.indexOf ? dt.types.indexOf('Files') != -1 :
(dt.types.contains('Files') ||
dt.types.contains('application/x-moz-file')))) {
$('.dropzone').show();
window.clearTimeout(drag_timer);
}
})
.on('dragleave', function(e) {
drag_timer = window.setTimeout(function() {
$('.dropzone').hide();
}, 25);
});
.dropzone {
height: 100px;
width: 100px;
background: red;
display: none;
}
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<div class="dropzone"></div>
I have a problem with element which is both draggable and also has a click event.
$('.drag').mousedown(function() {
//...
});
$('.class').click(function() {
//...
)};
<div class="drag class"></div>
When I drag and drop the element, the click event gets fired, too. How to prevent that?
Also you could probably do something with the mousemove and mousedown events together to disable the click event:
var dragging = 0;
$('.drag').mousedown(function() {
$(document).mousemove(function(){
dragging = 1;
});
});
$(document).mouseup(function(){
dragging = 0;
$(document).unbind('mousemove');
});
$('.class').click(function() {
if (dragging == 0){
// default behaviour goes here
}
else return false;
)};
You should be able to do that by stopping the propagation on the mousedown event.
$('.drag').mousedown(function(event){
event.stopPropagation();
});
You may have to make sure that this event is attached before the click event though.
In my case selected answer didn't worked. So here is my solution which worked properly(may be useful for someone):
var dragging = 0;
$(document).mousedown(function() {
dragging = 0;
$(document).mousemove(function(){
dragging = 1;
});
});
$('.class').click(function(e) {
e.preventDefault();
if (dragging == 0){
alert('it was click');
}
else{
alert('it was a drag');
}
});
I noticed that if the drag event is registered prior to click event then the problem described will not happen. Here is an example code:
This code create the mentioned problem:
var that = this;
var btnId = "button_" + this.getId();
var minView = $("<div>", {"id":btnId, style:"position:absolute; top:"
+ this.options.style.top + ";left:" + this.options.style.left + ";border:1px solid gray;padding:2px"});
minView.html(this.getMinimizedTitle());
minView.click(function expendWidget(event) {
$("#" + btnId).remove();
that.element.css({"left":that.options.style.left, "right":that.options.style.right});
that.element.show();
});
minView.draggable();
minView.on("drag", this.handleDrag.bind(this));
this.element.parent().append(minView);
this code does not create the problem:
var that = this;
var btnId = "button_" + this.getId();
var minView = $("<div>", {"id":btnId, style:"position:absolute; top:"
+ this.options.style.top + ";left:" + this.options.style.left + ";border:1px solid gray;padding:2px"});
minView.html(this.getMinimizedTitle());
minView.draggable();
minView.on("drag", this.handleDrag.bind(this));
minView.click(function expendWidget(event) {
$("#" + btnId).remove();
that.element.css({"left":that.options.style.left, "right":that.options.style.right});
that.element.show();
});
this.element.parent().append(minView);
It's ok, but you should alway remember, that user can move mouse slightly during the click and don't notice that. So he'd think hi clicked and you – that he dragged