I am building custom calendar and I have the following issue: since I have to create different events on that calendar, that start and end on certain dates, and I need to resize them (left and right), I would need to know if I am starting that resize from the left side or from the right side, so I know how to set those dates.
Basically, it doesn't select the proper dates when I resize from start and when I resize from the end.
EDIT: My draggable elements also get some sort of top/left values that mess up everything, and I cannot resize it to left more than their start width (to the right side it does what it should).. It is reducing width (- 47px) instead of adding that width.
This is the code I have for resizable now:
$(".draggable").resizable({
classes: {
"ui-resizable": "highlight"
},
containment: '.calendar-row',
grid: [47, 10],
handles: 'e, w',
resize: function (event, ui) {
lastEvent = event;
},
start: function (event, ui) {
uiEvent = true;
$( event.originalEvent.target ).one('click', function(e){ e.stopImmediatePropagation(); } );
},
stop: function (event, ui) {
setTimeout(function(){
uiEvent = false;
}, 300);
$( event.originalEvent.target ).one('click', function(e){ e.stopImmediatePropagation(); } );
if(isChrome || isEdge)
{
var resizedDate = new Date($(lastEvent.toElement).attr('data-date'));
// left to right
if(ui.size.width > ui.originalSize.width)
{
console.log($(lastEvent.toElement).attr('id'));
console.log($(lastEvent.toElement).attr('data-date'));
}
else{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY).previousElementSibling;
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
}
if(isFirefox)
{
var resizedDate = new Date($(lastEvent.target).attr('data-date'));
$(lastEvent.target).hide();
// left to right
if(ui.size.width > ui.originalSize.width)
{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY);
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
else{
var elemFromPoint = document.elementFromPoint(lastEvent.pageX, lastEvent.pageY).previousElementSibling;
console.log($(elemFromPoint).attr('id'));
console.log($(elemFromPoint).attr('data-date'));
}
$(lastEvent.target).show();
}
}
});
I'm trying to write some kind of action to drag multiple elements with jQuery UI draggable. Dragging already works, but I'm wondering if there is a way to make an action on other elements, not the one I'm dragging, but these elements which I append at the bottom of dragged elements and move them in drag action. I want them to revert on their original positions, and then remove them (those cloned). Any ideas?
$taskItem.draggable({
cursor: "default",
appendTo: "body",
helper: "clone",
revert : function(event, ui) {
if(event === false){
return !event;
}
},
start: function(e, ui){
if($(ui.helper).hasClass("tasks-list__item--selected") === true){
$taskItem.addClass("tasks-list__item--currently-dragged");
tasksVC.$tasksList.find(".tasks-list__item--selected:not(.tasks-list__item--currently-dragged)").each(function(index, item){
var draggedItem = $(item).clone();
draggedItem.addClass("dragged-item");
$('body').append(draggedItem);
$(draggedItem).css({
position: 'absolute'
});
});
}
},
drag: function(e, ui){
if($(ui.helper).hasClass("tasks-list__item--selected") === true){
$(".dragged-item").each(function(index, item){
$(item).css({
top: ui.position.top + (index+1)*32,
left: ui.position.left
});
});
}
if($(ui.helper).hasClass("tasks-list__item--selected") === false){
}
},
stop: function(e, ui){
$('.dragged-item').remove();
$taskItem.removeClass("tasks-list__item--currently-dragged");
}
});
I don't want to use any plugins but jquery UI draggable.
https://jsfiddle.net/ianderso222/y9ynouxs/36/
I hope this makes sense, If I need to elaborate I will do so.
Right now the code is working pretty well, doing everything I need it to except this one issue. I just need to alert the order that the squares have been dropped in, after all 4 have been placed.
So, if square4 is placed in the large container, that would show up first in the alert. If square2 is in the last, smallest box it would be last in the list, and so on.
I would use sortable but I am afraid it would not work with the current setup. The resizing to different sized containers would not work, or at least I was not able to get it to work. If there is a way to keep the current structure of resizing to fill container and sliding into place I would say do that, but from everything I have seen I feel I would have to essentially start from scratch.
Here is the JavaScript, pardon the messy code:
$('.holderList li').droppable({
drop: function(event, ui) {
var droppable = $(this);
var draggable = ui.draggable;
console.log(draggable.attr('id') + ' is ' + droppable.attr('id'));
var $this = $(this);
ui.draggable.position({
my: "center",
at: "center",
of: $this,
using: function(pos) {
$(this).animate(pos, 200, "linear");
}
});
ui.draggable.addClass('dropped');
ui.draggable.data('droppedin', $(this));
$(this).droppable('disable');
setTimeout(function() {
var dragID = ui.draggable;
if (!$(".ui-droppable").not(".ui-droppable-disabled").length) {
alert(draggable.attr('id'));
}
}, 400);
},
});
$(".square").draggable({
stack: ".square",
revert: function(event, ui) {
//overwrite original position
$(this).data("ui-draggable").originalPosition = {
width: 50,
height: 50
}
//return boolean
return !event;
},
drag: function(event, ui) {
var draggable = $(this).data("ui-draggable");
$.each(draggable.snapElements, function(index, element) {
ui = $.extend({}, ui, {
snapElement: $(element.item),
snapping: element.snapping
});
if (element.snapping) {
if (!element.snappingKnown) {
element.snappingKnown = true;
draggable._trigger("snapped", event, ui);
}
} else if (element.snappingKnown) {
element.snappingKnown = false;
draggable._trigger("snapped", event, ui);
}
});
if ($(this).data('droppedin')) {
$(this).data('droppedin').droppable('enable');
$(this).data('droppedin', null)
$(this).removeClass('dropped')
}
},
snap: ".holder",
snapMode: "inner",
snapTolerance: 8,
snapped: function(event, ui) {
var squareWidth = ui.snapElement.width();
var squareHeight = ui.snapElement.height();
ui.helper.css({
width: squareWidth,
height: squareHeight
});
}
});
Take a look at my solution:
Demo
First it assigns a data-current attribute to the droppable holder on every drop and sets it to the id of the draggable.
Then it itterates trough all the .holder elements and prints their data-current
Simple but works.
// On single drop
drop: function(event,ui){
...
droppable.attr('data-current', draggable.attr('id') );
}
//On all dropped
$('.holder').each(function(el){
console.log($(this).attr('data-current'));
});
I am trying to implement a simple Drag and Drop and was wandering if there are other methods of implementing it that doesn't require mouse down, mouse move and mouse up events. The reason is because my mouse up event fires an attached event on the page before my drag and drop mouse up event. I don't know how to get it to fire appropriate so I'm seeking alternative methods.
You could try using jquery UIs http://jqueryui.com/draggable/ and http://jqueryui.com/droppable/ to achieve what you want, the combination of both allows you to make a lot and its easy as the documentation has some examples to get started and you can find lots of examples like this one: http://www.elated.com/articles/drag-and-drop-with-jquery-your-essential-guide/
to make it work on mobile devices (touch events) compatible use this script. makes jquery ui mobile compatible: touchpunch.furf.com
I made a jsfiddle that shows an example of how you could use jquery ui droppable and draggable:
http://jsfiddle.net/mMDLw/2/
// jquery closure
(function($, window, undefined) {
// on dom ready
$(function() {
initializeDropzone();
initializeDraggables();
});
/**
*
* intializing the dropzone element
*
*/
var initializeDropzone = function() {
// initialize the dropzone to make the dropzone a droppable element
// http://jqueryui.com/droppable/
$('#dropzone').droppable({
accept: '.foo', // only elements with this class will get accepted by the dropzone
hoverClass: 'drop_hover', // css class that should get applied to the dropzone if mouse hovers over element
greedy: true,
drop: function onDrop(event, ui) {
console.log('#dropzone drop');
var droppedElement = ui.draggable;
console.log(droppedElement);
// create an object and fill it with data we extract from element that got dropped
var droppedElementData = {};
droppedElementData.id = parseInt(droppedElement.attr('data-foo-id'));
droppedElementData.name = droppedElement.text();
var dropLogElement = $('#dropLog').find('ul');
droppedElementData.position = dropLogElement.children().length + 1;
// create the list html to add a row about the dropped element to our log
var rowHtml = '';
rowHtml += '<li id="' + droppedElementData.position + '_' + droppedElementData.id + '">';
rowHtml += '<span class="position">' + droppedElementData.position + ') </span>';
rowHtml += '<span class="name">' + droppedElementData.name + '</span>';
rowHtml += '</li>';
var row = $(rowHtml);
// append the new row to the log
dropLogElement.append(row);
}
});
};
/**
*
* intializing draggable elements
*
*/
var initializeDraggables = function() {
// http://jqueryui.com/draggable/
// make all elements that have the class "foo" draggable
$('#droppables').find('.foo').draggable({
refreshPositions: true, // refresh position on mouse move, disable if performance is bad
revert: function(event) {
console.log('a "foo" got dropped on dropzone');
// if element is dropped on a dropzone then event will contain
// an object, else it will be false
if (event) {
// the event exists, this means the draggable element got dropped inside of the dropzone
console.log('YEP the event is not false');
// the element that is being dropped
var draggedElement = $(this);
// add explosion effect to dragged element
draggedElement.effect(
'explode',
1000,
function() {
console.log('drop scale effect finished');
console.log(draggedElement);
// put the element back to its original position
draggedElement.css('left', '0');
draggedElement.css('top', '0');
// make the element visible again by fading it in
draggedElement.show('fade', {}, 1000);
}
);
return false;
} else {
// false because draggable element got dropped outside of the dropzone
console.log('NOPE no object, the event is false');
return true;
}
},
start: function(event, ui) {
// the user started dragging an element
console.log('#droppables .foo drag start');
},
stop: function(event, ui) {
// the user has released the draggable element
console.log('#droppables .foo drag stop');
}
});
// make all elements that have the class "bar" draggable (but the dropzone wont accept them)
$('#nonDroppables').find('.bar').draggable(
{
revert: true, // if the element did not get dropped in the dropzone or not accepted by it, then revert its position back to its original coordinates
start: function(event, ui) {
// the user started dragging an element
console.log('#nonDroppables .bar drag start');
},
stop: function(event, ui) {
// the user has released the draggable element
console.log('#nonDroppables .bar drag stop');
}
}
);
};
})(jQuery, window);
I have elements on the page which are draggable with jQuery. Do these elements have click event which navigates to another page (ordinary links for example).
What is the best way to prevent click from firing on dropping such element while allowing clicking it is not dragged and drop state?
I have this problem with sortable elements but think it is good to have a solution for general drag and drop.
I've solved the problem for myself. After that I found that same solution exists for Scriptaculous, but maybe someone has a better way to achieve that.
A solution that worked well for me and that doesn't require a timeout: (yes I'm a bit pedantic ;-)
I add a marker class to the element when dragging starts, e.g. 'noclick'. When the element is dropped, the click event is triggered -- more precisely if dragging ends, actually it doesn't have to be dropped onto a valid target. In the click handler, I remove the marker class if present, otherwise the click is handled normally.
$('your selector').draggable({
start: function(event, ui) {
$(this).addClass('noclick');
}
});
$('your selector').click(function(event) {
if ($(this).hasClass('noclick')) {
$(this).removeClass('noclick');
}
else {
// actual click event code
}
});
Solution is to add click handler that will prevent click to propagate on start of drag. And then remove that handler after drop is performed. The last action should be delayed a bit for click prevention to work.
Solution for sortable:
...
.sortable({
...
start: function(event, ui) {
ui.item.bind("click.prevent",
function(event) { event.preventDefault(); });
},
stop: function(event, ui) {
setTimeout(function(){ui.item.unbind("click.prevent");}, 300);
}
...
})
Solution for draggable:
...
.draggable({
...
start: function(event, ui) {
ui.helper.bind("click.prevent",
function(event) { event.preventDefault(); });
},
stop: function(event, ui) {
setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
}
...
})
I had the same problem and tried multiple approaches and none worked for me.
Solution 1
$('.item').click(function(e)
{
if ( $(this).is('.ui-draggable-dragging') ) return false;
});
does nothing for me. The item is being clicked after the dragging is done.
Solution 2 (by Tom de Boer)
$('.item').draggable(
{
stop: function(event, ui)
{
$( event.originalEvent.target).one('click', function(e){ e.stopImmediatePropagation(); } );
}
});
This works just fine but fails in one case- when I was going fullscreen onclick:
var body = $('body')[0];
req = body.requestFullScreen || body.webkitRequestFullScreen || body.mozRequestFullScreen;
req.call(body);
Solution 3 (by Sasha Yanovets)
$('.item').draggable({
start: function(event, ui) {
ui.helper.bind("click.prevent",
function(event) { event.preventDefault(); });
},
stop: function(event, ui) {
setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
}
})
This does not work for me.
Solution 4- the only one that worked just fine
$('.item').draggable(
{
});
$('.item').click(function(e)
{
});
Yep, that's it- the correct order does the trick- first you need to bind draggable() then click() event. Even when I put fullscreen toggling code in click() event it still didn't go to fullscreen when dragging. Perfect for me!
I'd like to add to this that it seems preventing the click event only works if the click event is defined AFTER the draggable or sortable event. If the click is added first, it gets activated on drag.
I don't really like to use timers or preventing, so what I did is this:
var el, dragged
el = $( '#some_element' );
el.on( 'mousedown', onMouseDown );
el.on( 'mouseup', onMouseUp );
el.draggable( { start: onStartDrag } );
onMouseDown = function( ) {
dragged = false;
}
onMouseUp = function( ) {
if( !dragged ) {
console.log('no drag, normal click')
}
}
onStartDrag = function( ) {
dragged = true;
}
Rocksolid..
lex82's version but for .sortable()
start: function(event, ui){
ui.item.find('.ui-widget-header').addClass('noclick');
},
and you may only need:
start: function(event, ui){
ui.item.addClass('noclick');
},
and here's what I'm using for the toggle:
$("#datasign-widgets .ui-widget-header").click(function(){
if ($(this).hasClass('noclick')) {
$(this).removeClass('noclick');
}
else {
$(this).next().slideToggle();
$(this).find('.ui-icon').toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
}
});
A possible alternative for Sasha's answer without preventing default:
var tmp_handler;
.sortable({
start : function(event,ui){
tmp_handler = ui.item.data("events").click[0].handler;
ui.item.off();
},
stop : function(event,ui){
setTimeout(function(){ui.item.on("click", tmp_handler)}, 300);
},
In jQuery UI, elements being dragged are given the class "ui-draggable-dragging".
We can therefore use this class to determine whether to click or not, just delay the event.
You don't need to use the "start" or "stop" callback functions, simply do:
$('#foo').on('mouseup', function () {
if (! $(this).hasClass('ui-draggable-dragging')) {
// your click function
}
});
This is triggered from "mouseup", rather than "mousedown" or "click" - so there's a slight delay, might not be perfect - but it's easier than other solutions suggested here.
In my case it worked like this:
$('#draggable').draggable({
start: function(event, ui) {
$(event.toElement).one('click', function(e) { e.stopPropagation(); });
}
});
After reading through this and a few threads this was the solution I went with.
var dragging = false;
$("#sortable").mouseover(function() {
$(this).parent().sortable({
start: function(event, ui) {
dragging = true;
},
stop: function(event, ui) {
// Update Code here
}
})
});
$("#sortable").click(function(mouseEvent){
if (!dragging) {
alert($(this).attr("id"));
} else {
dragging = false;
}
});
the most easy and robust solution? just create transparent element over your draggable.
.click-passthrough {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: transparent;
}
element.draggable({
start: function () {
},
drag: function(event, ui) {
// important! if create the 'cover' in start, then you will not see click events at all
if (!element.find('.click-passthrough').length) {
element.append("<div class='click-passthrough'></div>");
}
},
stop: function() {
// remove the cover
element.find('.click-passthrough').remove();
}
});
Have you tried disabling the link using event.preventDefault(); in the start event and re-enabling it in the drag stopped event or drop event using unbind?
Just a little wrinkle to add to the answers given above. I had to make a div that contains a SalesForce element draggable, but the SalesForce element has an onclick action defined in the html through some VisualForce gobbledigook.
Obviously this violates the "define click action after the drag action" rule, so as a workaround I redefined the SalesForce element's action to be triggered "onDblClick", and used this code for the container div:
$(this).draggable({
zIndex: 999,
revert: true,
revertDuration: 0,
start: function(event, ui) {
$(this).addClass('noclick');
}
});
$(this).click(function(){
if( $(this).hasClass('noclick'))
{
$(this).removeClass('noclick');
}
else
{
$(this).children(":first").trigger('dblclick');
}
});
The parent's click event essentially hides the need to double-click the child element, leaving the user experience intact.
I tried like this:
var dragging = true;
$(this).click(function(){
if(!dragging){
do str...
}
});
$(this).draggable({
start: function(event, ui) {
dragging = true;
},
stop: function(event, ui) {
setTimeout(function(){dragging = false;}, 300);
}
});
for me helped passing the helper in options object as:
.sortable({
helper : 'clone',
start:function(),
stop:function(),
.....
});
Seems cloning dom element that is dragged prevented the bubbling of the event. I couldnĀ“t avoid it with any eventPropagation, bubbling, etc. This was the only working solution for me.
The onmousedown and onmouseup events worked in one of my smaller projects.
var mousePos = [0,0];
function startClick()
{
mousePos = [event.clientX,event.clientY];
}
function endClick()
{
if ( event.clientX != mousePos[0] && event.clientY != mousePos[1] )
{
alert( "DRAG CLICK" );
}
else
{
alert( "CLICK" );
}
}
<img src=".." onmousedown="startClick();" onmouseup="endClick();" />
Yes, I know. Not the cleanest way, but you get the idea.