I am working on a tool based on jQuery UI draggable functionality.
I have a number of boxes in the left column of the table. When they are dragged in yellow area, I would expect the remaining divs to move upwards to fill the space left by the box that was moved.
But it's not happening. Why?
It is pretty difficult to test but from my knowledge on the question here is a possible cause/solution to this.
The droppable plugin does not remove the dragged element from its original markup position, it is visually moved to the droppable element (with some option allowing to accept to drop certain elements or not, events, etc).
The elements have a position: relative css rule, which represents the "normal flow" for elements (in the order they appear in the markup). So even if the element is visually placed elsewhere on the page with css, its place in the markup is still the same and it is still taking the space it normally should.
This fiddle illustrate what i'm trying to explain :-)
By looking at the source code form the "working website", they actually remove the dragged element from the original draggable list and re-create it in the droppable list !
When they define the .droppable() they do this:
h.droppable({
tolerance: "intersect",
accept: ".card",
greedy: true,
drop: function (a, b) {
card = b.draggable;
putCardIntoStack(card, c)
}
});
On the drop event, they call putCardIntoStack(card, c) passing the currently dragged element as the card parameter. Within this method, they remove the original "card" (a.remove()) and re-create it in the dropzone (newcard = createCard();):
function putCardIntoStack(a, b) {
progressVal = $('#progBarRd').width();
card_idDOM = a.attr('id');
card_idDB = card_idDOM.substr(IDPREFIX_CARD.length, card_idDOM.length - IDPREFIX_CARD.length);
stack_idDB = b.substr(IDPREFIX_STACK.length, b.length - IDPREFIX_STACK.length);
$.ajax({
url: URL_DRAGDROPAJAX,
type: 'POST',
data: 'action=movecard&cardid=' + card_idDB + '&tostack=' + stack_idDB + '&prog=' + progressVal
});
// 'a' is the card
// they extract the id/content from the dragged card
cardId = a.attr('id');
cardLabel = a.text();
// they remove the card from the DOM
a.remove();
// they create a new card with the extracted info
newcard = createCard(cardId, cardLabel);
newcard.addClass('stackcard');
// and append it to the dropzone
$('#' + b).removeClass("empty").find('.stackDrop').append(newcard);
globalcheck()
}
jQuery UI does a similar thing on the droppable demo page. On the drop event, they call a function deleteImage() which removes the dragged image from the original markup and appends it to the drop zone.
I hope I'm clear enough :-)
I also hope I'm right, it is pretty difficult to test quickly but it makes sense :-)
Related
I apologize that I have a very surface level understanding on Jquery and I just cant seem to find the right keywords to google the answer, or perhaps there just isnt.
Basically my developers are working on this website which uses Jquery sortable. I have products which I draw from a database on the left and placeholders on the right. the workflow can be seen in the picture workflow. I have products which I can drag and drop to the basket. For the products, I can scroll through the pages and drag and drop anytime. However, I now need to create another "basket". Which requires me to switch between the basket anytime, possibly with a "next" and "prev" button. My developers was explaining to me that they are unable to do it because,
the position of the items in my basket is important, i.e. first box will always be item 1, second box will always be item 2, and so on. (items will be displayed later according to the numbered boxes )
they need to refresh the page in order to switch basket due to point (1).
refreshing the page might cause the values for the products to reset, i.e. if I scrolled through the pages and I switch basket, I will end up back at product page 1, which means i have to scroll through the pages again.
May I know if that is the limitations of jquery sortable? or is there something that they will be able to do in order to create another "basket" and able to scroll the "basket" without refreshing the page? Is there anything else which I can use for this issue?
EDIT: here is the script!
<script>
$(function () {
$("#sortable1, #sortable2").sortable({
connectWith: ".connectedSortable",
update: function (event, ui) {
var licont = $("#sortable2 .ProItem").length;
},
stop: function (event, ui) {
if (event.target.id == 'sortable1' && ui.item[0].title != "") {
$(".proli:last").remove();
}
//console.log(ui.item.index());
$("#sortable2 li").each(function(i,o){
$(this).find('h4').html(i+1);
})
}
}).disableSelection();
});
</script>
your help is greatly appreciated!! thanks!
I would like to drag a svg element onto HTML element.
Not sure what a smart way to do so.
However, I think my method is dirty, but I think I can start drag from svg element with d3.behavior.drag() and on drag function of d3, I am going to clone a copy(please think it is simply a circle object) and let jquery ui handle the drag events.
The problem is I don't know how to trigger drag event on newly created jquery element.
var drag= d3.behavior.drag()
.on("drag", function(d) {
// make a Clone html object .dragging-node
$('.dragging-node').attr('draggable', true);
$('.dragging-node').trigger('dragstart');
});
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.call(drag);
Any idea how to start drag for this newly created clone object?
Or is there any smarter way to get what I want?
Any help would be appreciated!
I did something like this once, and it worked ok for simple stuff, but after some time it became clear that using jQueryUI's Draggable is saner, since it's much more robust. I also needed to use Droppable functionality in conjunction; maybe your needs are less demanding. If so:
You don't need to artificially trigger events from the cloned element (aka "drag helper" in jQueryUI's draggable terminology). Instead, let the events continue to be triggered on the element that initiated the drag and has the drag behavior applied to it, but update the helper's position.
Something like this:
// Helper is the cloned element, which doesn't exist until dragging begins
// (alternatively it could pre-exist but be hidden)
var $helper = null
// the parent container of $helper,
// which presumably is outside of the SVG
var $helperParent = $('body')
var drag = d3.behavior.drag()
.on("dragstart", function(d) {
$helper = ... // somehow make the cloned helper, on dragstart (not on drag)
.appendTo($helperParent)
})
.on("drag", function(d) {
// determine the mouse position relative to the helper's parent
// (not relative to the SVG element that initiated the drag)
mousepos = d3.mouse($helperParent[0])
// update the helper's position
$helper.css({
left: mousepos[0],
top: mousepos[1]
});
})
.on("dragend", function(d) {
// remove (or hide) the helper
$helper.remove();
});
I'm trying to build an interface tool which essentially allows users to build a grid out of common UI elements.
Here's a jsFiddle: http://jsfiddle.net/FX4Fw/
Essentially, the idea is that you drag content elements (picture, headline, standfirst, etc) into the grey placeholder at the bottom. Once they're in there, they should no longer be Draggables (because this breaks the CSS grid system they inherit) and they can then be resized. The original items in the UI should stay where they are, so the user is essentially cloning them into the box to be positioned.
This almost works in my demo, but when the user grabs a UI element and drags it into the placeholder, I then remove the ui-draggable class from the cloned element that ends up inside the placeholder. This also removes it from the original source element (I want this to stay where it is) so it's no longer usable.
Is there a way to combine these things so they work in tandem? Hopefully it's clear what I'm trying to do.
Never mind - found the answer here: https://stackoverflow.com/a/3041887/176615
(basically this code)
stop: function(event, ui) {
//check it wasn't here previously
if(!ui.item.data('tag') && !ui.item.data('handle')) {
ui.item.data('tag', true); //tag new draggable drops
ui.item.removeClass('ui-draggable'); // dirty hack
}
},
I'm using jQuery and I have the following problem:
In my site I have a chessboard with pieces. Every square is a simple div with the background property to show white or black. Over these squares (inside the divs) I've put an img tag referencing the piece that must be over that square. Something like:
<div id="a8" class="square" style="background-image: url('/images/background_white.png')">
<img id="piece_a8" class="piece" src="/images/rook_black.png" />
</div>
I can control the movement of the pieces using jQuery. Every piece-class img is a draggable and every square-class div is a droppable. I already have a server-side function that, given a set of coordinates, returns "VALID" if the movement is valid, and "INVALID" if otherwise. My idea is, if the server returns "INVALID", the piece must return to its origin square, and if the server returns "VALID", the piece must stay in its new square, deleting every other piece inside the target square.
My problem is, I don't know how can I enforce this return value in my jQuery code. I've tried putting functions in the revert property of the draggable, and in the accept and drop functions of the droppable, but I haven't found how to make $.get return false or true.
Any help would be really appreciated.
Thanks in advance,
Léster
Nevermind, answered.
In case someone needs to know, the trick is in 2 parts:
First: In the draggable definition, under the start event, add a function that saves the original position. Like this:
$('item_to_drag').draggable({
start: function(){
$(this).data("origPosition",$(this).position());
}
});
Second: In the droppable definition, under the drop event, do your .get and use a function to process the answer; in case your conditions are not met, animate the draggable back to its original position. Like this:
drop: function (event, ui) {
$.get(url,function(data) {
if (data == '"INVALIDO"')
{
ui.draggable.animate(ui.draggable.data("origPosition"),"slow");
}
else
{
//store new positions, whatever;
}
}
);
}
That'll do the trick.
Part of the answer came from here: In jQuery, how to revert a draggable on ajax call failure? .
I have a sortable list (of tasks). It helps prioritize tasks, so I want to keep this functionality. Now I want to add subtasks to the functionality: I want to enable users to drag one task over to another task and drop it there to turn it into a subtask.
Applying .draggable() and .droppable() to items that are already sortable has no effect. What could I do?
I put together a demo of how to do this... but it may not be the best method. Here are some problems I've discovered:
Because this code uses the placeholder to figure out where you are moving the list, you can only drop an item inside another item if you approach it from the top. I did get a working version where you could drop an item anywhere inside the base item, but the code was just too messy and cumbersome.
Sometimes when an item from the other list is dropped in an item, it becomes stuck. I'm not sure why, but it becomes unstuck when the list group is moved to the other list.
I'm sure there is a better method, one that calculates the intersection of list items (just like the sortable script does). But this is a quick and dirty method.
$(function() {
var phTop, container, indx;
$("#sortable1, #sortable2").sortable({
connectWith: '.connectedSortable',
beforeStop: function(e,ui){
phTop = ui.placeholder.position().top;
container = ui.placeholder.parent();
indx = ui.placeholder.index();
},
stop: function(e,ui){
var list = container.find('> li').eq(indx);
// 15 is a pixel tolerance between the two items (dragging in from the top)
if ( Math.abs( phTop - ui.position.top ) < 15 ) {
// prevent list depth > 1
if (ui.item.find('li').length > 0) { return; }
// add ul inside of li to make subgroup
if (!list.find('ul').length) { list.append('<ul></ul>'); }
ui.item.appendTo( list.find('> ul') );
}
container.find('li > ul:empty').remove(); // remove empty subgroups
}
}).disableSelection();
});
After I posted my question I had no patience, and I decided to ignore UI.sortable altogether, building the required functionality from draggable and droppable and using special divs as spacers that would swell up on dragover to facilitate dropping in between tasks.
That worked to some degree, except it's all much more code and it's a lot more jittery and bugprone than sortable, even with the refreshPositions option set to true. Still, there might be other valid reasons to want to circumvent UI.sortable.
In very brief faux code: $(.taskitem).draggable
revert: invalid
start: animate height of spacers from 0 to 5
$(.spacer).droppable
over: animate height from 5 to 50
out: animate height back to 5
drop: insert draggable after spacer
find spacer with same index as draggable and move it along