Drag and Drop between 2 list boxes in Javascript

I want to have the ability to drag and drop between 2 list boxes. If one selection is dragged to other list box, I want the original selection removed.  Conversely, I also want to drag in the opposite direction. So if I have a selection in the second list box and I drag it back to the fist, I want it to be removed from the second list box.  Optimally, if I can have it sort alphabetically by the selections, that would be great.  I need this to be compatible with IE and NS.  

Hope this is clear - thanks for the help.

Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Don't know about drag and drop, but this works just as well. Probably nicer:

DrData68Author Commented:
I am actually a fan of Matt- I used some of his Javascript before, but I really wanted to get the drag and drop thing going as well as the buttons.  

Consider why you want drag-and-drop. Is it because it's "cool," or is there a valid user-interface reason for it?
My suggestion is to just keep it simple. What are you trying to accomplish with drag-and-drop, exactly?
Ultimate Tool Kit for Technology Solution Provider

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy now.

DrData68Author Commented:
I am thinking that I might try and get some sorting going in the list boxes, so it might be name, state, group. So if they want to pull 10 to 20 names from a particular state over, I do not want them to have to click 20 times.  If they resort, I only want to see the remaining selections, and if they want to pull another 20 or 30 people over that belong to a certain group - again, I do not want them to click 20 times.

Make sense?
Since this was an interesting puzzle, I spend a few hours on it - not just to answer your question, but also as a form of geek-entertainement. I have to report that this is probably not going to work as straight-forward as you may have expected it.

First, I got it working fine in Mozilla-based browsers for single list elements, and it's actually pretty cool to click on a list element and be able to drag it to the other list - I even painted the list element itself next to the mouse pointer.

Then the problems started. When testing the finished code in IE, it turned out that the build-in event handler that actually changes the selections of the list gets executed AFTER the event is passed to my custom event handler. So when a user holds down the mouse button on an element in that list, that list's selectedIndex property is not updated when I read it. I tried some hackery with delayed evaluation of the list's selected items, but I would have to delay over 500 ms for that to work, and that makes the whole thing unusable from an interface-experience-perspective. Moreover, the <option> tag in IE does NOT support the onMouseDown event, which was what made it work in Firefox/Mozilla. There may be a possibility with some other hackery to get it working for a single element, but that was not what you wanted anyway.

You wanted drag and drop on multiple elements. Although I was able to write the logic for that to happen in Mozilla & Co., the user will never be able to do it and here is why: When you give a list field the ability to have multiple options selected, you can perform this selection by holding the mouse down and moving it accross the items you want to select. Unfortunately, that is the same process you would use to get it dragged. So everytime you try to grab on to a selection, instead of you being able to drag it, the browser will just start a new selection. I was not able to simply cancel the event after I handled it in Mozilla, and because of IE being screwed up with the simple stuff, I did try there either.

Although the idea is great, its implementation may prove more difficult. There is an entirely different approach that would be much easier to implement and could be made to work cross-browser fairly easily. Instead of using a real form field list element, a scrollable list could be simulated with a <div> containing <span>s or <p>s and having its overflow property set to auto, making sure none of the contents in the div can be wider than the div itself. The simulation of the list-behavior would be handled entirely in JavaScript, updating hidden form fields with the actual internal values for which items are in which list. Let me know whether you think this approach would be worth your 500 points and I give it a try.
something like this?  (sigmacon, this is basically what you suggested, I've already done it though  :-))

the javascript isn't perfect as I havn't had time to finish it, but it should work for this purpose I think

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
      width:            100%;
      background-color: #E6E6E6;
      color:            #000000;
      font-family:      tahoma;
      font-size:        10px;
      font-weight:      bold;
      padding:          2px;
      cursor:           hand;
      position:         relative;
      padding:          1px;
      overflow:         hidden;
      position:         relative;
      position:         absolute;

<div id="Nav0" style="width:100px;float:left;border:1px solid #000000;" h="false"></div>
<div id="Nav1" style="width:100px;float:left;border:1px solid #000000;" h="false"></div>

<div id="Holder" class="DragH" style="display:none;filter:alpha(opacity=50);"></div>

<div id="WindowDefs">
      <div id="Window0" style="display:none;" class="Drag"><div class="Title">Left Option #1</div></div>
      <div id="Window1" style="display:none;" class="Drag"><div class="Title">Left Option #2</div></div>
      <div id="Window2" style="display:none;" class="Drag"><div class="Title">Left Option #3</div></div>
      <div id="Window3" style="display:none;" class="Drag"><div class="Title">Right Option #1</div></div>
      <div id="Window4" style="display:none;" class="Drag"><div class="Title">Right Option #2</div></div>
      <div id="Window5" style="display:none;" class="Drag"><div class="Title">Right Option #3</div></div>
var Containers = 2;
var ConPrefix  = 'Nav';
var ModPrefix  = 'Window';
var Modules    = 6;
var DropColor  = '#FF0000';
var Locations  = new Array(new Array(0, 1, 2), new Array(3, 4, 5));

var Drag       = false;
var e          = null;
var h          = document.getElementById('Holder');
var x, y, t, pOpt, optDrop, D, oBorder;

function addElement(array, addindex, addvalue) {
      size    = array.length;
      validNo = (addindex != "NaN");
      inRange = ( (addindex >= 0) && (addindex <= array.length+1) );

      if (validNo && inRange) {
            for (var i=array.length+1; i>addindex; i--) array[i] = array[i-1];
                  array[addindex] = addvalue;
            array.length = array.length-1;
      else alert("You only add element indexes between 0 and " + (size) + ".");

function deleteElement(array, delindex) {
      size    = array.length;
      validNo = (delindex != "NaN");
      inRange = ( (delindex >= 0) && (delindex <= array.length) );

      if (validNo && inRange && size>0) {
            for(var i=delindex; i<size-1; i++){
                  array[i] = array[i+1];
      else alert("You only delete from element index 0 to " + (size-1) + ".");

function ReDrawWindows(){
      var Content = new Array();
      for(var i=0; i<Containers; i++){
            Content[i] = '';
            for(var j=0; j<Locations[i].length; j++)
                  Content[i]+=document.getElementById(ModPrefix+Locations[i][j]).outerHTML.replace('DISPLAY: none;', '').replace('DISPLAY: none', '').replace(ModPrefix+Locations[i][j], ConPrefix+ModPrefix+'_'+i+'_'+j);
            for(var j=0; j<Locations[i].length; j++)
//                  alert((100/Locations[i].length)+'%');

function MouseDown(){
      e    = event.srcElement;
      for(var i=0; i<30 && e.className!='Drag'; i++)
            try{if(e.parentElement) e = e.parentElement;}catch(err){}
            x    = event.clientX-e.offsetLeft;
            y    = event.clientY-e.offsetTop;
            h.innerHTML        = e.innerHTML;
            h.style.width      = e.offsetWidth;
            h.style.height     = e.offsetHeight;
            h.style.display    = '';
            h.style.pixelTop   = e.offsetTop;
            h.style.pixelLeft  = e.offsetLeft;
            Drag               = true;
            e.style.visibility = 'hidden';

function MouseUp(){
      Drag               = false;
      h.style.display    = 'none';
      if(!e) return false;
      if(typeof(optDrop)!='undefined'){      // we have a valid drop!
//            document.getElementById(ModPrefix+Locations[parseInt(loc[1])][parseInt(loc[2])]).innerHTML=optDrop.innerHTML;
                  var to   = (optDrop.id.indexOf('_')>=0?optDrop.id:ConPrefix+ModPrefix+'_'+optDrop.id.substr(ConPrefix.length)+'_0').split('_');
                  var from = e.id.split('_');
                  var oSrc = Locations[from[1]][from[2]];
                  deleteElement(Locations[from[1]], parseInt(from[2]));
                  addElement(Locations[to[1]], parseInt(to[2])+parseInt(D=='Y1'?1:0)-(to[1]==from[1] && to[2]>from[2]?1:0), oSrc);
            } catch(err) {}
      if(typeof(pOpt)!='undefined') pOpt.style.border=oBorder;
      if(e.className=='Drag') ReDrawWindows();
      e       = null;
      optDrop = null;

function MouseMove(){
      if(!Drag || e==null) return false;
      if(typeof(pOpt)!='undefined') pOpt.style.border=oBorder;
      h.style.pixelTop  = event.clientY-y;
      h.style.pixelLeft = event.clientX-x;

      // find the best spot to drop, if applicable
      var c, o, d, mD=5000, tD;
      for(var i=0; i<Containers; i++){
            c = document.getElementById(ConPrefix+i);

            // check the children of each container
            for(var j=0; j<c.children.length; j++){
                  o  = c.children[j];
                  d  = getDistance(o);
                  tD = checkOpt(o, c.h=='false'?false:true, c.v=='false'?false:true);
                  if(tD.mD<mD) { mD = tD.mD; D = tD.D; optDrop = o; }

            // check the container itself (in case it has no children)
                  d  = getDistance(c);
                  tD = checkOpt(c, c.h=='false'?false:true, c.v=='false'?false:true);
                  if(tD.mD<mD) { mD = tD.mD; D = tD.D; optDrop = c; }
      if (typeof(optDrop)!='undefined'){
            oBorder = optDrop.style.border;
                  optDrop.style.borderTop    = '1px solid #FF0000';
            }else if(D=='Y1'){
                  optDrop.style.borderBottom = '1px solid #FF0000';
            }else if(D=='X0'){
                  optDrop.style.borderLeft   = '1px solid #FF0000';
            }else if(D=='X1'){
                  optDrop.style.borderRight  = '1px solid #FF0000';
      pOpt = optDrop;
      return false;

function getDistance(o){
      return {x:h.offsetLeft-o.offsetLeft, y:h.offsetTop-o.offsetTop};

function checkOpt(o, horiz, vert){
      var mD=5000, d, D;

            // check top
            if(h.offsetTop<=o.offsetTop && (h.offsetTop+h.offsetHeight)>=o.offsetTop && h.offsetLeft<o.offsetLeft+o.offsetWidth && h.offsetLeft+h.offsetWidth>o.offsetLeft) {
                  d = Math.pow(Math.pow((h.offsetTop+h.offsetHeight)-(o.offsetTop), 2)+Math.pow((h.offsetLeft+h.offsetWidth/2)-(o.offsetLeft+o.offsetWidth/2), 2), .5)
                  if(d<mD){ mD = d; D='Y0'; }
            // check bottom
            if(h.offsetTop<=(o.offsetTop+o.offsetHeight) && (h.offsetTop+h.offsetHeight)>=(o.offsetTop+o.offsetHeight) && h.offsetLeft<o.offsetLeft+o.offsetWidth && h.offsetLeft+h.offsetWidth>o.offsetLeft) {
                  d = Math.pow(Math.pow((h.offsetTop)-(o.offsetTop+o.offsetHeight), 2)+Math.pow((h.offsetLeft+h.offsetWidth/2)-(o.offsetLeft+o.offsetWidth/2), 2), .5)
                  if(d<mD){ mD = d; D='Y1'; }
      // check left
            if(h.offsetLeft<=o.offsetLeft && (h.offsetLeft+h.offsetWidth)>=o.offsetLeft && h.offsetTop<o.offsetTop+o.offsetHeight && h.offsetTop+h.offsetHeight>o.offsetTop) {
                  d = Math.pow(Math.pow((h.offsetLeft+h.offsetWidth)-(o.offsetLeft), 2)+Math.pow((h.offsetTop+h.offsetHeight/2)-(o.offsetTop+o.offsetHeight/2), 2), .5)
                  if(d<mD){ mD = d; D='X0'; }
            // check right
            if(h.offsetLeft<=(o.offsetLeft+o.offsetWidth) && (h.offsetLeft+h.offsetWidth)>=(o.offsetLeft+o.offsetWidth) && h.offsetTop<o.offsetTop+o.offsetHeight && h.offsetTop+h.offsetHeight>o.offsetTop) {
                  d = Math.pow(Math.pow((h.offsetLeft)-(o.offsetLeft+o.offsetWidth), 2)+Math.pow((h.offsetTop+h.offsetHeight/2)-(o.offsetTop+o.offsetHeight/2), 2), .5)
                  if(d<mD){ mD = d; D='X1'; }
      return {mD:mD,D:D};

function select(){
      return false;

function getHTML(url) {
      var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      xmlhttp.open("GET", url + "&randomnum=" + Math.floor(Math.random()*1000000), false);
      return xmlhttp.responseText;

document.onmousedown = MouseDown;
document.onmousemove = MouseMove;
document.onmouseup   = MouseUp;
document.onselect    = select;


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
DrData68Author Commented:
cwolves - this is nice.  Is there any way I can get multiple selections going?  I am anticipating a pretty large list,
and if they have the ability to do a multiple select of 20 or 30 at a time and drag it - that would be great!!

not easily.  how well do you kow JS?
DrData68Author Commented:
I would classify myself as high intermediate to lower advanced.
What do you suggest?
basically, 'e' in that code is the selected div tag.  in the mousedown function, you can check the event.ctrlKey property and if it's true, make 'e' an array and add event.srcElement to it.

'h' is the "ghost" element (it's the semi-transparent div tag that actually moves around when you drag the mouse).  Set that to e[0].

And on the mouseup tag, loop through each element in e instead of just referencing e in the try{} statement.

I'd do it myself but I have -no- time today, especially not to debug it  :-/
DrData68Author Commented:
Let me take a look at it and see what I can do.
DrData, we wouldn't mind seeing the finished product ;-)
DrData68Author Commented:
I agree.  I got pulled onto other things, so I have not done too much with it.
DrData68, do you need more help regarding this question?
DrData68Author Commented:
Yes, that would be great!!!
Well, any specific questions? As I pointed out, doing what you where trying to do with actual <select>s is not going to work. Did you try cwolves' approach?
DrData68Author Commented:
I tried playing with the HTML first and started going in the wrong direction.  I just reread his message and understand that I have to add code to the mouse down event.  Looking at it, i am not sure how to handle the code for the determining the className equal to 'Drag' if I have multiple selections and e is an array.  Any ideas?
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.