I have a jQuery plugin that drags and drops elements into different containers, I want to attach some events, for example when an element is over a container. These events used to work perfectly but then they stopped working. for Some reason the Selectable specific events are not fired, but when i bind a click for example it works.
Example:
//these are not working
$('#sortable2').bind("sortover", function(event, ui) {
alert("here");
});
$('#sortable2').bind('sortreceive', function() {
alert('User clicked on "sortable2."');
});
$('.droptrue').bind("sortout", function(event, ui) {
$(this).css("background", "transparent");
});
The related code is:
var selectedClass = 'ui-state-highlight',
clickDelay = 300, // click time (milliseconds)
lastClick, diffClick; // timestamps
$("ul.droptrue li")
// Script to deferentiate a click from a mousedown for drag event
.bind('mousedown mouseup', function(e){
if (e.type=="mousedown") {
lastClick = e.timeStamp; // get mousedown time
} else {
diffClick = e.timeStamp - lastClick;
if ( diffClick < clickDelay ) {
// add selected class to group draggable objects
$(this).toggleClass(selectedClass);
}
}
})
.draggable({
revertDuration: 10, // grouped items animate separately, so leave this number low
containment: '.multiSelect',
start: function(e, ui) {
ui.helper.addClass(selectedClass);
},
stop: function(e, ui) {
// reset group positions
$('.' + selectedClass).css({ top:0, left:0 });
},
drag: function(e, ui) {
// set selected group position to main dragged object
// this works because the position is relative to the starting position
$('.' + selectedClass).css({
top : ui.position.top,
left: ui.position.left
});
}
});
$("ul.droptrue")
.sortable()
.droppable({
drop: function(e, ui) {
$('.' + selectedClass)
.appendTo($(this))
.add(ui.draggable) // ui.draggable is appended by the script, so add it after
.removeClass(selectedClass)
.css({ top:0, left:0 });
}
});
$('#total').text(autoCompleteSourceArray.length);
$('#filter-count').text(autoCompleteSourceArray.length);
//Adding Filtering functionality for the lists
$("#filter").keyup(function () {
var filter = $(this).val(), count = 0;
$("ul.droptrue:first li").each(function () {
if ($(this).text().search(new RegExp(filter, "i")) < 0) {
$(this).addClass("hidden");
} else {
$(this).removeClass("hidden");
count++;
}
});
$("#filter-count").text(count);
});
// bind events in order to show or hide the message in the drop zones
$('ul[id^="sortable"]').live("sortover", function(event, ui) {
$(this).css("background", "#f7f6d7");
});
$('ul[id^="sortable"]').live("sortout", function(event, ui) {
$(this).css("background", "transparent");
});
Thanks a lot
If you have recently updated to jQuery 1.7+ you should notice that the live() method is deprecated.
As of jQuery 1.7, the .live() method is deprecated. Use .on() to
attach event handlers. Users of older versions of jQuery should use
.delegate() in preference to .live().
Related
we trying to integrate multiple drggable and droppable. we are using sortable for ease of clone functionality in this scenario. draggable once dropped need to be draggable again.
how do we limit sortable to receive only one element and revert to original if more than one dropped onto it.
look like out and over functions of sortable are misbehaving in that case.
commented line code is for disabling dropping second element on sortable. which is not working as expected.
Two issues when you enable my commented code:
draggable clone not reverting to original place after moving out of droppable.
draggable element moved from one droppable to another reverting to draggable's original place.
For a demonstration, see this jsfiddle
script:
// jQuery.noConflict();
jQuery( document ).ready(function() { init();});
function init() {
var mouse_button = false;
jQuery('.ui-draggable').live({
mousedown: function () {
mouse_button = true;
},
mouseup: function () {
if (jQuery(this).attr('data-pos') == 'out' && jQuery(this).attr('data-id')) {
var p = jQuery('#' + jQuery(this).attr('data-id'));
var offset = p.offset();
jQuery(this).hide();
jQuery(this).animate({ left: offset.left, top: offset.top, width: jQuery(this).width, height: jQuery(this).height }, 100, function () {
jQuery(this).remove();
$( ".ui-droppable" ).each(function() {
if($(this).children().length == 0) {
$( this ).removeClass("dontDrop");
}
});
//if(p[0].hasAttribute("draggable"))
p.draggable("enable");
// $('.ui-droppable').sortable('option', 'connectWith',$('.ui-droppable').not('.dontDrop'));
// $('.ui-draggable').draggable('option', 'connectToSortable',$('.ui-droppable').not('.dontDrop'));
});
}
mouse_button = false;
},
mouseout: function () {
if (mouse_button) {
mouse_button = false;
}
}
});
jQuery( '.ui-draggable' ).draggable( {
cursor: 'move',
helper: 'clone',
connectToSortable: ".ui-droppable",
revert: function (event, ui) {
}
} );
jQuery(".ui-droppable").sortable({
cursor: "move",
connectWith: ".ui-droppable",
receive: function (event, ui) {
if($(this).children().length >= 1) {
$(this).children().addClass('filled');
$(this).addClass('dontDrop');
$( ".ui-droppable" ).each(function() {
if($(this).children().length == 0) {
$( this ).removeClass("dontDrop");
}
});
// $('.ui-droppable').sortable('option', 'connectWith',$('.ui-droppable').not('.dontDrop'));
// $('.ui-draggable').draggable('option', 'connectToSortable',$('.ui-droppable').not('.dontDrop'));
}else {
$(this).children().removeClass('filled');
}
if (jQuery(this).data().sortable.currentItem) {
jQuery(this).data().sortable.currentItem.attr('data-id', jQuery(ui.item).attr("id"));
// if(jQuery(ui.item)[0].hasAttribute("draggable"))
jQuery(ui.item).draggable("disable");
}
},
out: function (event, ui) { if (ui.helper) { ui.helper.attr('data-pos', 'out'); } },
over: function (event, ui) { ui.helper.attr('data-pos', 'in'); }
});
}
Here's a working example: click here
You can user Jquery's draggable and droppable interactions to achieve what you want. Check the working example.
$(document).ready(function () {
$(".ui-draggable").draggable(draggable_options) //make cards draggable
$(".ui-droppable").droppable({ //handle card drops
greedy: true,
drop: function (event, ui) {
handleDrop(this, event, ui)
},
accept: function () {
return checkIfShouldAcceptTheDraggable(this)
}
})
})
You can do it like this:(Online Demo (fiddle))
var draggable_options = {
helper: 'clone',
cursor: 'move',
revert: 'invalid',
};
$(".ui-draggable").draggable(draggable_options);
$(".ui-droppable").droppable({
drop: function(event, ui) {
var $item = ui.draggable;
$item.draggable(draggable_options)
$item.attr('style', '')
$(this).append($item)
},
accept: function() {
return $(this).find("li").length === 0 // Your condition
}
});
$(".textToImageRightPanel").droppable({
drop: function(event, ui) {
var $item = ui.draggable;
$item.draggable(draggable_options);
$item.attr('style', '');
// Return to older place in list
returnToOlderPlace($item);
}
});
// Return item by drop in older div by data-tabidx
function returnToOlderPlace($item) {
var indexItem = $item.attr('data-tabidx');
var itemList = $(".textToImageRightPanel").find('li').filter(function() {
return $(this).attr('data-tabidx') < indexItem
});
if (itemList.length === 0)
$("#cardPile").find('ul').prepend($item);
else
itemList.last().after($item);
}
Determining when to revert may be best done in .draggable() using revert: function(){}.
Function: A function to determine whether the element should revert to its start position. The function must return true to revert the element.
You can do this:
jQuery('.ui-draggable').draggable({
cursor: 'move',
helper: 'clone',
connectToSortable: ".ui-droppable",
revert: function(item) {
if (!item) {
return true;
} else {
if (item.hasClass("dontDrop")) {
return true;
}
}
return false;
}
});
the revert function is passed false if the draggable item is not accepted. For example, if it is dropped on something that is not a target. If the draggable item is accepted, a jQuery Object is passed back.
See more: jQueryUI sortable,draggable revert event
The logic is a little confusing. If what is passed back is false, we return true to revert letting draggable revert the item to it's position. If what is passed back is not false, then it's an object we can test. If the target is "full", we revert. Otherwise we do not revert.
Sortable still wants add the item for some reason. May need to adjust to update and clear out any items that are not class "filled".
Fiddle: https://jsfiddle.net/Twisty/7mmburcx/32/
Is there a way how to add listener "droppable" to element, which is actually hovered while dragging "draggable" element?
I've tried this, but it does not work.
$("#draggable span.item").draggable({
helper: "clone",
drag: function(event, ui) {
var pos = ui.position;
var element = document.elementFromPoint(pos.left, pos.top);
$(element).droppable({
classes: {
"ui-droppable-hover": "hover"
},
drop: function(event, ui) {
console.log('dropped');
}
});
}
});
I am trying this because I need apply "droppable" to many elements and classic way via jQuery $("#droppable span.item").droppable(); is very slow in this case. So I would like to init "droppable" listener only for elements, which are hovered while dropping.
did you try this
$(document).ready(function() {
var $dragging = null;
$(document.body).on("mousemove", function(e) {
if ($dragging) {
$dragging.offset({
top: e.pageY,
left: e.pageX
});
}
});
$(document.body).on("mousedown", "div", function (e) {
$dragging = $(e.target);
});
$(document.body).on("mouseup", function (e) {
$dragging = null;
});
});
Ok, I have an issue with drag and drop.
What they do, they click a button, it initializes the whole drag and drop.
function sortElements() {
// Place droppable elements
var x = 0;
$("#content-body div[data-type='column'],#content-body div[data-type='carousel']").each(function() {
var el = $(this);
if(x == 0){
el.before('<div class="neoDroppableEle" id="neoDroppableEle-' + x + '"><\/div>');
x++;
}
el.addClass('edit_el').after('<div class="neoDroppableEle" id="neoDroppableEle-' + x + '"><\/div>');
x++;
el.append('<div class="drag-handle"><i class="fa fa-arrows"></i></div>');
var w = el.width();
el.css('width',w+'px');
});
$("#content-body div[data-type='insertable']").each(function() {
var el = $(this);
el.prepend('<div class="neoDroppableEle" id="neoDroppableEle-' + x + '"><\/div>');
x++;
});
// Swap entire columns
$("#content-body div[data-type='column']").draggable({
refreshPositions: true,
helper: "clone",
handle:'.drag-handle',
appendTo: "body",
zIndex: 10000,
start: function( event, ui ) {
$(".neoDroppableEle").addClass('dragging');
},
stop: function( event, ui ) {
$(".neoDroppableEle").removeClass('dragging');
}
});
$(".neoDroppableEle").droppable({
accept: "div[data-type='column']",
tolerance: "pointer",
hoverClass: "focus_in",
activeClass: "focus_in_active",
drop: function(event, ui) {
cur_ele = this.id;
var el = ui.draggable;
var html = el.html();
el.remove();
$("#" + cur_ele).replaceWith('<div class="row" data-type="column">'+html+'</div>');
}
});
// Swap individual photos within columns
$("#content-body div[data-type='imagewrap']").each(function(){
$(this).draggable({
revert: "invalid",
helper: "clone" ,
zIndex: 10001,
});
$(this).droppable({
accept: "div[data-type='imagewrap']",
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
drop: function( event, ui ) {
var draggable = ui.draggable, droppable = $(this);
draggable.swap(droppable);
}
});
});
}
When they are done, they click the button again.
function sortElementsComplete() {
$(".ui-droppable").droppable("destroy");
$(".ui-draggable").draggable("destroy");
$(".edit_el").removeAttr('style').removeClass('edit_el');
$(".neoDroppableEle").remove();
$(".drag-handle").remove();
}
This all runs and works great!
But now I am tryng to save the HTML code after each drop for undo's. And when I save the undo, I need to remove all additional classes and elements my function to drag and drop add's. Because they may not be in the sorting area when they click undo and do not want drag handles and my borders I set up as visual aids just appearing.
So now I have:
$(".neoDroppableEle").droppable({
accept: "div[data-type='column']",
tolerance: "pointer",
hoverClass: "focus_in",
activeClass: "focus_in_active",
drop: function(event, ui) {
cur_ele = this.id;
var el = ui.draggable;
var html = el.html();
el.remove();
$("#" + cur_ele).replaceWith('<div class="row" data-type="column">'+html+'</div>');
setTimeout(function(){
sortElementsComplete();
editor_add();
},1000);
}
});
The above with the timeout code always fails with:
Error: cannot call methods on droppable prior to initialization;
attempted to call method 'destroy'
How so? It IS initialized and running. After the drop I should be able to destroy it, make my save and rebuild it. Using disable gives the same error. To me the error makes no sense.
After editor_add() is ran, it re-builds whatever they were doing, in this case it will fire sortElements(); after the save.
But the below runs fine?
// Swap individual photos within columns
$(this).droppable({
accept: "div[data-type='imagewrap']",
activeClass: "ui-state-hover",
hoverClass: "ui-state-active",
drop: function( event, ui ) {
var draggable = ui.draggable, droppable = $(this);
draggable.swap(droppable);
setTimeout(function(){
sortElementsComplete();
editor_add();
},250);
}
});
It will error if I do not have the timeout above. Seems 250 is the min, anything lower it errors. But the first one will not ever work, no matter how long or short I make the timeout.
Really hope this makes sense.
Maybe if I used
var draggable = ui.draggable, droppable = $(this);
draggable.swap(droppable);
On it instead it would work. o.O
I have been trying to clone and drop a draggable at the position in a droppable at the coordinates where the drop happens. I have found examples online that deal with appending draggables to droppables, but they all seem to move the draggable to a specific part of the droppable on the initial drop.
Here is an example that does just that: - http://jsfiddle.net/scaillerie/njYqA/
//JavaScript from the jsfiddle
jQuery(function() {
jQuery(".component").draggable({
// use a helper-clone that is append to 'body' so is not 'contained' by a pane
helper: function() {
return jQuery(this).clone().appendTo('body').css({
'zIndex': 5
});
},
cursor: 'move',
containment: "document"
});
jQuery('.ui-layout-center').droppable({
activeClass: 'ui-state-hover',
accept: '.component',
drop: function(event, ui) {
if (!ui.draggable.hasClass("dropped"))
jQuery(this).append(jQuery(ui.draggable).clone().addClass("dropped").draggable());
}
});
});
Is there anyway I can make the draggable stay at the coordinates where the drop occured?
you must define the coordinates in the cloned element on the drop:
drop: function(event, ui) {
if (!ui.draggable.hasClass("dropped"))
var clone=jQuery(ui.draggable).clone().addClass("dropped").draggable();
clone.css('left',ui.position.left);
clone.css('top',ui.position.top);
jQuery(this).append(clone);
}
});
and also set the position absolute by css on the cloned components
.ui-layout-center .component {
position:absolute !important;
}
Here is working: http://jsfiddle.net/o2epq7p2/
Edited you code and used appendTo() and set the offset
jQuery(function() {
jQuery(".component").draggable({
// use a helper-clone that is append to 'body' so is not 'contained' by a pane
helper: function() {
return jQuery(this).clone().appendTo('body').css({
'zIndex': 5
});
},
cursor: 'move',
containment: "document"
});
jQuery('.ui-layout-center').droppable({
activeClass: 'ui-state-hover',
accept: '.component',
drop: function(event, ui) {
var _this = jQuery(this);
if (!ui.draggable.hasClass("dropped")) {
var cloned = jQuery(ui.draggable).clone().addClass("dropped").draggable();
jQuery(cloned).appendTo(this).offset({
top : ui.offset.top,
left: ui.offset.left
});
}
}
});
});
ui in the drop handler contains the dragged element's position absolute to the page. You need to transform those values to a position relative to the drop target and absolute position the cloned element inside the drop target using these values.
drop: function(e, ui) {
if (!ui.draggable.hasClass("dropped")) {
var parentOffset = jQuery('.ui-layout-center').offset();
var dropped = jQuery(ui.draggable).clone().addClass("dropped").draggable();
dropped.css('left', (ui.position.left - parentOffset.left) +'px');
dropped.css('top', (ui.position.top - parentOffset.top) +'px');
jQuery(this).append(dropped);
}
}
http://jsfiddle.net/3Lnqocf3/
I will simplify my explanation so you get what I am doing. I have two div's and I set up portlets as shown here, however I am dynamically injecting my portlets, no big problem there.
<div id="mainallapplicant" class="myrow"></div>
<div id="contingent_right" class="myrow"></div>
Here is the JavaScript
$( ".myrow" ).sortable({
connectWith: ".myrow",
revert: true,
beforeStop: function( event, ui ) {}
});
I am trying to allow a maximum of only one droppable into mainallapplicant. If there is one already there, I will show a confirmation dialog and depending on the answer, I cancel the drop or move out the existing item and replace it with the new item. I tried the following but I am getting nowhere.
$( ".myrow" ).sortable({
connectWith: ".myrow",
revert: true,
start: function(event, ui) {
if ($(this).prev().find(".portlet").length == 1) {
ui.sender.draggable("cancel");
}
},
stop: function(event, ui) {
if ($(this).prev().find(".portlet").length == 1) {
ui.item.remove();
// Show an error...
}
}
});
You can use start to get the current count of portlet elements, then use stop to do the checking
Also notice I added class names to each div to allow only one div to have a maximum of 1 portlet
$(document).ready(function () {
$.count = 0;
$(".myrow").sortable({
connectWith: ".myrow",
revert: true,
start: function () {
$.count = $(".myrow").has(".portlet").length;
console.log("Start " + $.count);
},
stop: function (event, ui) {
if ($(ui.item).parent(".myrow").hasClass("left")) {
if ($.count == 2) {
$(".myrow").sortable("cancel");
}
}
}
});
});
DEMO: http://jsfiddle.net/Ue4dq/