This is quite a complicated question so I am not looking for "full examples", but instead ideas of implementing a solution to my problem.
I am creating a drag-and-drop page where users can drag and drop tools into their workspace. This is currently working fine through the implementation of draggable() (jQuery UI).
However, the system is getting complicated with new features I am implementing:
When dragged onto the workspace, the user can freely move the items around their page. However I would like the user to be able to drag items on top of other divs- and this dropped item should "morph" into this div (hopefully by using append()). This specific div that the element is dropped onto implements sortable(), so where ever the dropped element is placed should be its specific position on this sortable list.
EXAMPLE: If the div contains a number of dropped elements; lets say 5 items, if another item is dropped in between the 2nd and 3rd items, it should be appended to this div in that position.
Secondly, any element that is appended to a sortable div should then have the ability of being dragged out of this sortable div (un-appended) and back onto the main area of the workspace (I have no clue of how to do this!) BUT it should still holds its ability of being sorted (list should still be sortable).
I am using jQuery + jQuery UI to complete this project and may use other javascript-based extensions if they can complete my desired outcome easily.
Type of implementation I have at the moment
This implementation is very unfinished.
$("div.layout.lo-content > div.content").droppable(
{
drop: function(e, ui)
{
$(ui.draggable).appendTo($(this));
if($(this).hasClass("ui-sortable"))
{
$("div.content").sortable('refresh');
}
}
});
^^ when doing sortable('refresh') it breaks the system with error:
Uncaught Error: cannot call methods on sortable prior to
initialization; attempted to call method 'refresh'
The sortable list which the item is dragged onto:
$("div.layout.lo-content > div.content").sortable(
{
opacity:0.7,
axis:'y',
start: function(e, ui)
{
$(this).children("div.ui-sortable-placeholder").css("height",$(ui.helper).css("height"));
}
});
You could try using AngularJS with ng-sortable.
It looks like it can do exactly what you have described.
From readme
as-sortable can be added to the root element.
as-sortable-item can be added in item element, and follows
ng-repeat.
Here is a DEMO
Instead of ui.draggable you can use only https://jqueryui.com/sortable/ with the configuration parameter for connected lists.
It allows you to drag drop between different lists and also sort items per list.
API: http://api.jqueryui.com/sortable/
Related
I am working on a custom workflow style application. I am using jQuery UI for a drag and drop toolkit.
On dropping an item, I am rendering a bootstrap panel with various items in the body (depending on what was dropped).
Now, what I would like to do is allow these panels to be draggable again within the drop area, but I can't seem to achieve it.
Below is a link to the gist of the html. Ignore the linkme stuff, that's another aspect i'm working on. Ideally, the objects would be draggable anywhere within the solutions div.
https://gist.github.com/ajberry/0ab2c0adca4f92855b26b63f929b108d
I'm assuming it's something daft, and I have tried the following with no success:
$( ".panel-default" ).draggable();
As I thought this would allow each panel to be draggable. I'm guessing it's to do with my droppable event or something.
Regards
I added in the draggable attribute on the drop event. As prior to that the objects didn't exist so couldn't apply it.
function handleDropEvent( event, ui ) {
var rownum = $("#solution .panel-default").length;
console.log(rownum);
var draggable = ui.draggable;
var mytype = draggable.attr('class');
if (!(mytype.includes("panel-default"))){
renderChildControls(draggable.attr('id'), rownum)
}
$('.panel-default').draggable();
}
I have a mini form control where I drag several draggables onto a scale. See my plunk
Ideally, whenever I drop a draggable, I would like to do in the stop( event, ui ) method:
check if the droppable already has a draggable (let's call it draggable2)
if there is draggable2, I would like to scan my droppables for one that does not have a draggable
then move draggable2 to this droppable
Are there APIs/ways to do this programmatically? I know this sounds similar to sortables, but I am strictly looking for the ability to
move a draggable to a droppable
and
to know if a droppable already has a draggable, and be able to identify the draggable
Any help will be greatly appreciated. Thank you.
I have found a way to accomplish this. Although there are no options from the jQuery UI draggables/droppables API, I have managed to track the draggables and droppables by adding unique "id" attributes to track them.
Here is how I approached the following:
move a draggable to a droppable
My approach: Find the coordinates of the target droppable, and update the css of the draggable.
To figure out which dragger was dragged and which dropper received it, I added attributes dragger-id and dropper-id so I could identify them during the drop event:
drop: function( event, ui ){
// Get the current dragger and dropper ID's
dropperId = $(this).attr('dropper-id');
draggerId = ui.draggable.attr('dragger-id');
// ... more code
}
then I calculated the top and left coordinates for the dragger based on the droppable's values. For this, I used the jQuery's offset() method.
drop_x = $('.droppable').offset().left - $('.draggable').offset().left;
drop_y = $('.droppable').offset().top - $('.draggable').offset().top;
Note: I had to do a difference calculation since draggables are positioned relative to where they start.
Finally, I set the top and left css values:
ui.draggable.css('top', drop_y+'px'); // y-axis
ui.draggable.css('left', drop_x+'px'); // x-axis
to know if a droppable already has a draggable, and be able to identify the draggable
For this, I created yet another attribute on the droppable elements to track the dragger-id values. I called it current-dragger so that it would be unique and I could query by it. I used -1 as my default value, meaning "no dragger".
I assigned current-dragger in the drop method as well:
drop: function( event, ui ){
// Get the current dragger and dropper ID's
dropperId = $(this).attr('dropper-id');
draggerId = ui.draggable.attr('dragger-id');
// Remove current-dragger value from old dropper (-1 means no dragger)
$('[current-dragger="' + draggerId + '"]').attr('current-dragger', '-1');
// Update current-dragger on this dropper
$(this).attr('current-dragger', draggerId);
// ... more code
}
I suppose there's really no need to do updates to the DOM attributes, so in prod I will be tracking the current dragger inside of a JavaScript mapping object.
For a full working example, see my updated plunk
How do you append an item being dragged to a target element on drop, using jQueryUI's draggables/dropables? It looks like the way jQuery UI works right now is it just moves the items around on the screen via absolute positioning. Unfortunately, this functionality makes form submissions useless, as you cannot get the positions of the values on submission.
Thanks in advance for any items/pointers!
If I understood correctly, you want the dragged element to be detached from it's current parent and be appended to the new one, right? You can use a helper to do the dragging (so the original element is not affected) and, upon drop, detach it and append it to the target (thanks #Oleg and #Brian for improving over my original answer).
$(myDraggable).draggable({
helper:"clone",
containment:"document"
});
$(myDroppable).droppable({
drop:function(event, ui) {
ui.draggable.detach().appendTo($(this));
}
});
Working example at jsFiddle
When a draggable attribute is enabled on a parent element(<li>) I cant make contenteditable work on its child element (<a>).
The focus goes on to child element (<a>),but I cant edit it at all.
Please check this sample
http://jsfiddle.net/pavank/4rdpV/11/
EDIT: I can edit content when I disable draggable on <li>
I came across the same problem today, and found a solution [using jQuery]
$('body').delegate('[contenteditable=true]','focus',function(){
$(this).parents('[draggable=true]')
.attr('data-draggableDisabled',1)
.removeAttr('draggable');
$(this).blur(function(){
$(this).parents('[data-draggableDisabled="1"]')
.attr('draggable','true')
.removeAttr('data-draggableDisabled');
});
});
$('body') can be replaced by anything more specific.
If new contenteditable elements are not added in the runtime, one can use bind instead of delegate.
It makes sense that the draggable and contenteditable properties would collide. contenteditable elements, like any text field, will focus on mousedown (not click). draggable elements operate based on mousemove, but so does selecting text in a contenteditable element, so how would the browser determine whether you are trying to drag the element or select text? Since the properties can't coexist on the same element, it appears that you need a javascript solution.
Try adding these two attributes to your anchor tag:
onfocus="this.parentNode.draggable = false;"
onblur="this.parentNode.draggable = true;"
That works for me if I add it to the <a> tags in your jsFiddle. You could also use jQuery if it's more complicated than getting the parentNode.
Note: This is a workaround since I believe the inability for these two functionalities to work together resides in the HTML spec itself (i.e. the not working together thing is intentional since the browser can't determine whether you want to focus or drag on the mousedown event)
I noticed you explicitly set 'no libraries', so I will provide a raw javascript/HTML5 answer
http://jsfiddle.net/4rdpV/26/
This was my crack at it.
First of all, it might be better to include the data in one single localStorage item, rather than scatter it.
storage={
'1.text':'test 1',
'2.text':'test 2'
}
if(localStorage['test']){
storage=JSON.parse(localStorage['test'])
}
this creates that ability, using JSON to convert between object and string. Objects can indeed be nested
I also added (edit) links next to the items, when clicked, these links will transform the items into input elements, so you can edit the text. After hitting enter, it transforms it back and saves the data. At the same time, the list items remain draggable.
After saving, hit F12 in chrome, find the console, and look in the localStorage object, you will see all the data was saved in localStorage['test'] as an Object using JSON.stringify()
I tried my best to design this to be scaleable, and I think I succeeded well enough; you just need to replace the HTML with a container and use a javascript for loop to write out several items, using the iterator of your choice to fill the parameter for edit(). For example:
Say you changed storage to hold "paradigms" of lists, and you have one called "shopping list". And say the storage object looks something like this:
{
"shopping list":{
1:"Milk",
2:"Eggs",
3:"Bread"
}
}
This could render that list out:
for(i in storage['shopping list']){
_item = storage['shopping list'][i];
container.innerHTML+='<li draggable=true><a id="item'+i+'">'+_item+'</a> (edit)</li>'
}
Of course, if you were to edit the structure of the storage object, you would need to edit the functions as well.
The output would look something like this:
Milk (edit)
Eggs (edit)
Bread (edit)
Don't worry about the input elements if that worries you; CSS can easily fix it to look like it didn't just change.
If you don't want the (edit) links to be visible, for example, you can do this in CSS:
a[href="#"]{
display:none;
}
li[draggable="true"]:hover a[href="#"]{
display:inline;
}
Now the edit links will only appear when you hover the mouse over the list item, like this version:
http://jsfiddle.net/4rdpV/27/
I hope this answer helped.
Using html5sortable and newer JQuery events (delegate is deprecated, answer 3 years after initial question), bug still affects Chrome 37. Contenteditable spans and html5sortable seem to play nice in other browsers. I know this is only partially relevant, just keeping documentation on changes I've noticed.
$(document).on('focus', 'li span[contenteditable]', function() {
$(this).parent().parent().sortable('destroy'); // removes sortable from the whole parent UL
});
$(document).on('blur', 'li span[contenteditable]', function() {
$(this).parent().parent().sortable({ connectWith: '.sortable' }); // re-adds sortable to the parent UL
});
I'm playing with Drag and drop funcitonality for the first time so I'm not entirely sure what I'm doing!
I need to add a class to a "portlet" while it is being dragged. I don't want to use the clone functionality because I want the user to drag the actual element, I just want to nodify the element while it is being dragged and reset it when it's dropped.
Can anybody help?
Perhaps there's some sort of a 'beforedrag' event you can bind to? It would be easier to add the class to an element before the user actually starts dragging it, rather than during.
If you're using jQuery UI, there's a 'start' event on draggable you can use:
http://docs.jquery.com/UI/Draggable#events
Also, you can use the "helper" option like this:
helper : function(ev, el) {
return ($(el).clone().addClass("beingDragged"));
}
Should your portlets become in the future too heavyweight to drag, you could use that to build a simplified version while dragging to smooth things out :)