Today we are going to take a look at another little nugget that many javascript libraries have these days - a drag and drop reorderable list. As usual, what we will be presenting is not a packaged solution that you should just pick up and drop on your own site (although you probably can) - it is more of an exploration on how you would go about implementing your own drag & drop list.
The example that we will be creating allows the user to pick up and drag any of the blocks in the list. The block picked up becomes partially transparent, and a light gray insertion marker (the size of the object picked up) appears in the spot the picked up block would be placed if the user dropped it. This insertion marker will move as the user moves the picked up item, always showing what will occur when the user drops the item.
Have fun with that? I thought so. So onto the code!
The first thing to note (and this is probably not surprising) is that we need code for dragging objects. Well, guess what - we already have that code, from our Draggable Elements tutorial. So if you have not read that tutorial , I suggest you do so - because that dragging code actually comprises most of the code we need for this drag and drop list.
Ok, lets take a look at the html behind this example list:
<div id="list" style="position:relative;border:solid 1px black;width:150px;">
<div id="e1" style="height:50px;background-color:Green;" class="list"></div>
<div id="e2" style="height:140px;background-color:Blue;" class="list"></div>
<div id="e3" style="height:100px;background-color:Red;" class="list"></div>
<div id="e4" style="height:30px;background-color:Yellow;" class="list"></div>
<div id="e5" style="height:70px;background-color:Fuchsia" class="list"></div>
</div>
So we have a surrounding div, and each of the list elements is another
div inside the surrounding div. The heights are all set differently to
give a more interesting list (and for testing to make sure the list
works with arbitrarily sized elements). And it looks like all the list
items have their style class set as list. I guess that would be a good
reason to look at the style sheet for the drag and drop list:
.drag, .list
{
width: 150px;
}
.drag
{
z-index: 100;
position:absolute;
opacity: .50;
filter: alpha(opacity=50);
}
.list
{
position:relative;
z-index: 1;
opacity: 1;
filter: alpha(opacity=100);
top: 0px;
left: 0px;
}
Ok, so we have two classes in this style sheet. One, list is for items
in the list. The second, drag is for the item being dragged. There are
a couple subtle things to note here (and some obvious stuff). First, the
fact that list uses relative position and drag uses absolute
position is critical. Using relative position for the list items lets us
not care about the exact position of each item - we just stack them on.
But using absolute position for the item we are dragging lets the
z-index property actually work (so that the item being dragged will
always be on top of everything else), and it also makes our life much
easier when we need to figure out the exact pixel coordinates of the
item being dragged.
The other interesting thing here is setting the opacity. A couple of
browsers (notably IE 6 and 7) don't support the opacity style tag yet
- it is part of the CSS 3 spec. So while setting opacity works for
Firefox, Opera, and Safari, for IE we have to set a filter style tag,
as you can see. Fortunately, whichever property the browser doesn't
understand, it just ignores - so having both properties lets opacity
work in all 4 major browsers.
That is it for html and css, lets move on into the javascript code:
var List;
var PlaceHolder;
function load()
{
List = document.getElementById("list");
PlaceHolder = document.createElement("DIV");
PlaceHolder.className = "list";
PlaceHolder.style.backgroundColor = "rgb(225,225,225)";
PlaceHolder.SourceI = null;
new dragObject("e1", null, null, null, itemDragBegin, itemMoved,
itemDragEnd, false);
new dragObject("e2", null, null, null, itemDragBegin,
itemMoved, itemDragEnd, false);
new dragObject("e3", null, null, null, itemDragBegin,
itemMoved, itemDragEnd, false);
new dragObject("e4", null, null, null, itemDragBegin,
itemMoved, itemDragEnd, false);
new dragObject("e5", null, null, null, itemDragBegin,
itemMoved, itemDragEnd, false);
}
The drag and drop list code uses two global variables - List and
PlaceHolder. List is set in the load function to the div
surrounding all the list elements - mostly so we don't have to
continuously call getElementById later on. PlaceHolder is actually
created from scratch in the load function, and is the div we will use
as a placeholder/insertion marker when an item is being dragged. We
create the element, set the style class, give it a light gray background
color, and initialize a property (that we will use extensively later on)
to null.
The next thing we do in the load function is create drag objects for
each of the elements in the list. We don't need to store these anywhere,
we just need to create them and let them do their thing in the
background. The important parts here are the functions that we pass in
as the callbacks. For when a drag begins, we have the itemDragBegin
function, for each move during a drag we have the itemMoved function,
and for when the drag completes we have the itemDragEnd function.
First, lets attack the itemDragBegin function:
function itemDragBegin(eventObj, element)
{
element.style.top = element.offsetTop + 'px';
element.style.left = element.offsetLeft + 'px';
element.className = "drag";
PlaceHolder.style.height = element.style.height;
List.insertBefore(PlaceHolder, element);
PlaceHolder.SourceI = element;
}
This function is called when the user clicks on an item in the list and
initiates a drag. As arguments to this function (courtesy of the drag
object), we get the mouse down event object, and the element that was
clicked on. First here we want to transition the element from being an
item in the list to an item being dragged. We set the top and left
positions to the offset top and left - which is so that the element
stays in the same spot as we switch it from being relatively to
absolutely positioned. The offsetTop and offsetLeft are useful
properties - they hold the number of pixels between the top/left edge of
the current element and the top/left edge of its parent container. Now
that we have the position set, we set the style class to "drag".
Next we make the PlaceHolder the same height as the element being
dragged - that way the insertion marker will be the correct size. Then
we insert PlaceHolder into the list, right before the element being
dragged. This ensures that the PlaceHolder element will appear in the
exact spot that the element being dragged originally was in. Finally we
set SourceI property on PlaceHolder to the element being dragged. We
use this SourceI to keep track of the PlaceHolder is currently
inserted before (since we will be moving the PlaceHolder around a lot
as we need to change the location of the insertion marker).
So now the drag has begun. After this point, everytime the user moves
the mouse, the drag element will be moved, and itemMoved will get
called:
function itemMoved(newPos, element, eventObj)
{
eventObj = eventObj ? eventObj : window.event;
var yPos = newPos.Y + (eventObj.layerY ? eventObj.layerY : eventObj.offsetY);
var temp;
var bestItem = "end";
for(var i=0; i<List.childNodes.length; i++)
{
if(List.childNodes[i].className == "list")
{
temp = parseInt(List.childNodes[i].style.height);
if(temp/2 >= yPos)
{
bestItem = List.childNodes[i];
break;
}
yPos -= temp;
}
}
if(bestItem == PlaceHolder || bestItem == PlaceHolder.SourceI)
return;
PlaceHolder.SourceI = bestItem;
if(bestItem != "end")
List.insertBefore(PlaceHolder, List.childNodes[i]);
else
List.appendChild(PlaceHolder);
}
}
I actually had to make a slight tweak to the dragging code here - where this callback gets called in the dragging code, it used to only pass the new position and the element - but I found that I needed the event object as well. So if you are using a copy of the dragging code from here, don't forget to add that in.
The first thing we do in this function is determine the exact y position
(relative to the list container div) of the element being dragged. We
already kind of know this, the top/left position of the element is
stored inside of newPos, but most people don't expect the top edge of
the element to be what triggers the movement of the insertion marker
from one spot to another - they expect it to be relative to the cursor
position. So we use the event object and grab the cursor position
relative to the drag element, and add it to the top position of the drag
element. For Firefox/Opera/Safari, this relative cursor position is
stored inside the layerY variable on the event, and for IE it is
stored in the offsetY variable.
Now that we have the position, we iterate through the items in the list. We get each item's height, and if the position of the the drag element is less than half that height, we have found our insertion point, and we break out of the loop. Otherwise, we subtract that height from the position variable. This means that if your mouse is in the top half of an element on the list (or in the bottom half of the element above it), we break out of the loop. If we complete the loop without finding anything, it means that the insertion marker should be at the bottom of the list.
If the list item we found is the PlaceHolder, we already have the
insertion marker exactly where it needs to be, so we return without
doing anything. This is also the case if the item we found is equal to
the item we currently have in the SourceI variable on PlaceHolder.
Otherwise, if we found an item, insert the PlaceHolder before that
item. The act of inserting the PlaceHolder will automatically remove
it from its previous poition (if it was in one), because an element can
only be in one place at on time. If we found no item, we just append
PlaceHolder at the end of the list.
And that takes care of moving the insertion marker as the user moves around the drag element. Now on to what happens when the user releases the mouse button and drops the element:
function itemDragEnd(element)
{
if(PlaceHolder.SourceI != null)
{
PlaceHolder.SourceI = null;
List.replaceChild(element, PlaceHolder);
}
element.className = 'list';
element.style.top = '0px';
element.style.left = '0px';
}
This is actually really simple. If PlaceHolder has ever been placed in
the list as an insertion marker, SourceI will not be null, so we reset
SourceI back to null, and replace PlaceHolder in the list with the
element that was just dropped. This means that the element being dropped
gets added to the list exactly where the PlaceHolder was, and the
PlaceHolder is removed from the list.
The only other thing to do is to clear the position values and set the
element that was dropped back to the list style class.
And there you go! A drag and drop list in javascript. You can download the code here. Of course, this is only one possible implementation of a drag and drop list - perhaps you don't want the insertion marker to be as big as the item being dragged. Perhaps you want some animation for the movement of the blocks. Perhaps you would like other things that I have never even thought of. In which case, I encourage you to build off the concepts here and create some crazy drag and drop lists.
Source Files:
Hi, This example really helps me a lot for my requirement. thanks a lot.
can you tell me how to place items in multiple columns with the same drag & drop effect.
i tried for it, but its not working. please take it as hi-priority.
"Please take it as hi-priority" Thanks for that, cheered up my day :D
And while you're at it, could you clean the windows... :p
When it says -
"First here we want to transition the element from being an item in the list to an item being dragged. We set the top and left positions to the offset top and left - which is so that the element stays in the same spot as we switch it from being relatively to absolutely positioned."
How is it actually switched from relative to absolute positioning? Is it simply done by setting the top and left style positions?
I tried this. I read the previous "draggable elements" and that worked. I moved on to this one, followed everything step by step and it doesn't work.
A common problem is if the load function is called before the html. If you post your error, I might be able to help.
I have the same problem Kyle. I have tried to put the load function after the html like what we did in the "draggable elements". But it doesn't work :(
oh.. is okay now.
I found it necessary to move both the load and dragObject functions further down the page.
I'm using Visual Studio 2008 Express Edition to create C# ASP.Net forms and was getting an 'Expected an object' message from the script processor.
It's OK now - and the code is a whole lot simpler than other examples you see on this topic - thanks.
I would like to do this exact same thing...the only problem is that i actually need to be able to keep the order that you move it in because i need to be able to order project elements from most important to least important for clients. I am using php. Any help would be appreciated.
Use MySQL to store an individuals order in a row
| User | Top | Next | ... | Last | ---------------------------------------------- | Usr1 | | | | | | Usr2 | | | | | | . | | | | | | . | | | | | | UsrN | | | | |
Sub: How to do Drag and Drop action in listbox through javaScript
Q:
Hi friend
I am having single listbox its contain some data like(one,two,three,four), i want to drag and drop action in listbox.
Example:- first time it's looks like this
one two three four
if i drag one to four (after drag and drop action) it'll display
two three four one
now if i drag three to four (after drag and drop action) it'll display
two four three one
Please help me it's urgent and send me code also in my email ID (achyutanand@gmail.com).
Some changes to allow display:inline, create dragObjects through a loop and start only when an element is clicked.
Could be optimized of course ...
http://www.caravanemedia.com/frigolite/www/example.htm
I love it. But I was wondering if you also have got a cropped version of your script?
gr J
Me again. Any change in updating this script so I can use multiple lists?
thanks for this example..but how can i save the changes that happening to the list in cookie ..because when i do a refresh to the page the order return to the original case...
thanks
One possible enhancement is to figure out a cross-browser replacement for the element.height calls. As I understand it, element.height is only available for elements with height already defined, e.g.,by way of a stylesheet. In my case, I have a series of DIVs whose height is not known in advance. I've substituted element.height with element.offsetHeight, which works OK in IE 7. I've yet to figure out Firefox, however.
If two divs have the same height, does this script still work? The function itemMoved() seems to depend on each DIV being unique by virtue of its height, which would break if two DIVs had identical heights...
I have to create two list box in One box I have to display the data from data base table and in another list box user will bring the data after dragging from the first list. And after that the user has to submitt button and on clicking on this button all the values will inserted into the database. I have to implement it in web based application. So tell me whether I have to use Java script in my code and how can i do this operation. Please help me.and provide the complete code
pawan2k5@gmail.com
very very cool tutorial
this is good one...
This works wonderfully and is one of the simplest implementations I have seen. I implemented the class on a TABLE element so whole rows of data are moving up and down in a list. One thing I am struggling to do is catch a mousedown without firing the drag events. Kind of like YUI has a drag_delay. If the user clicks, a timer starts to see if this was just a click or if (after 250ms) the user is still dragging.
Does anyone see how that can be implemented here?
Hi, Can any one help in downloading the total source code. I copied the code mentioned above to an html file. When i execute the code it has not worked.
Can any help me in providing the complete code.
The source file is included at the very bottom of the article under the "Source Files" block. I'll throw them here also - html and js.
Brilliant.
It’s nice & clean. But I found out one thing that when being dragged it doesn’t scroll the bars if overflow is set to auto. Any suggestion would be highly appreciated...
ma chi bo riri... mu rici picchi nno sito ti funziona e quannu mu fazzu io no? ma t'ammucci a ruobba?! unnu riri cu sa fari e un mi rici cumu si fa.. prima s'allarga e ntantu un funziona nienti
Good code it works like as charm.
I'm modifying this code for dynamic menu system that can be edited and rearranged by an authorised user. I have made modifications so that if you drag and hover 1 item over another so the sencond item turns yellow, when you drop it adds a margin to left to indicate it's a subcategory of the the above item. Otherwise it will just rearrange the list like normal. You can see a working example of this at:
http://sandbox.vieswebdesign.com/myMenu.html
My problem is that I want to be able to drag the subscategories with parent category drop them all together in the new location.
have modified load function like so (online version: http://sandbox.vieswebdesign.com/myMenu_1.html):
have also added a call to the load() function in itemDragEnd() function so dragObject associations are updated.
This allows me to drag objects together but has a few bugs. Whne I drop the items only parent node has moved and the children return to their orignal positions. Also I need to be able to clear the dragObject() associations and recreate them coz otherwise even though the items appear seperate on the menu, the children still move if the parent is moved and if just the children move the can't be added as children to another node.
Kinda complicated to explain but hope it makes sense. Let me know if you have any ideas or a better way of moving multiple items at once.
Thanks
Nice, small script. I want to add a up and down button witch can be clicked too, to place the div 1 position higher or lower. But I don't understand how the script is working exactly (right now). What should I place in the onclick event. Should I write a new function? Somebody can give a little help? Thanks!
I already wrote a function to determine de sort. Usabable if you want to save it in a database for example.
Anybody knows how to make a button to move a div 1 position up or down?
I just tried your code and it failed. I copied it exactly as you have it. I separated the .js file and .css file.. Any comments it doesn't worked?
I mean, "Any comments why it does't worked?"
did you mean my code? or the original
My function above your post returns the title of div's (in the drag-and-drop sorted order). So you have to set a title to every div to make it work. (or change
to
I added button in the box and its event is not working. Please help.
Hi, How can I print (alert) the position of source and target item.I am tired to print the exact position of current item and target where want to drop the item .Please do let me know how can i do it.
I'm waiting your answer.
Many Thanks, Pads
Where does the final order of the list get stored? If it all?
I would like to add a function which can turn the dragging off or is there something in the code that could do this already?
I would like to add a function which can turn the dragging off or is there something in the code that could do this already?
You don't want draggin in "drag&drop lists"? So you just want lists then?
No the ability to turn the dragging on and off.
I have solved the cancel function if anyone is interested..
Basically this method
this.cancelOrder = function (){ unhookEvent(attachElement, "mousedown", dragStart); }
Is added to the main class and separate cancel function is used to call this method when 'cancel' is pressed. A for loop in the cancel an object instantiating function makes sure each object is given a unique variable name.
Nice looking tutorial here. I love drag 'n drop in JavaScript and think any web app that can utilise this feature, should.
It helps make your web apps look more professional and elegant, helps give you a better user experience and also makes for a more user-friendly app.
Interestingly, I've created a tutorial on JavaScript drag & drop which allows for any element to be freely dragged and dropped around the page simply by attaching a class "draggable" to it. If you're interested, check it out here: http://www.sitesupplier.co.uk/index.php/blog/86-tutorials/javascripttutorials/170-javascript-drag-and-drop
hi
I'm surprised no one has mentioned it before, perhaps it's a problem particular to my IE9 settings, but IE9 doesn't allow for a drag-n-drop upwards.
Anyone got a reason or solution to this?
Thanks
No, I would like to ammend the above. IE9 has an incorrect offset or something. I need to move a dragable far above when it would normally insert to have the other dragables move to make way.
Looks like I missed something here. I changed: [language]var yPos = newPos.Y + (eventObj.layerY ? eventObj.layerY : eventObj.offsetY);[/language]
to: [language]var yPos = newPos.Y;[/language]
And it worked in FF and IE! What's going on?
I think IE started using a different method for giving us the position data since this tutorial was written and now we don't have to use the offset functions. I just tested this the other day and came across the same thing.. thanks for the solution
I was able to use this in a html form I was making where I wanted to change the order in the list. I know there's some easy to use JQuery stuff out there now but this was a cool way to learn about javascript for me. Anyways here's what I did.
and then this js function.
when you click the submit button on the form it gathers the element data in the new order the input tags are in then rewrites the value data in numerical order. All you need to do is capture that data into SQL and the list will be in the same order every time you load it!