[Webinar] Streamline your web hosting managementRegister Today

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 8845
  • Last Modified:

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.

DD
0
DrData68
Asked:
DrData68
  • 8
  • 4
  • 3
  • +1
1 Solution
 
StormyWatersCommented:
Don't know about drag and drop, but this works just as well. Probably nicer:

http://www.mattkruse.com/javascript/optiontransfer/
0
 
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.  

DD
0
 
StormyWatersCommented:
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?
0
Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

 
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?
0
 
sigmaconCommented:
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.
0
 
cwolvesCommented:
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">
<HTML>
<HEAD>
<style>
.Title{
      width:            100%;
      background-color: #E6E6E6;
      color:            #000000;
      font-family:      tahoma;
      font-size:        10px;
      font-weight:      bold;
      padding:          2px;
}
.Drag,.DragH{
      cursor:           hand;
      position:         relative;
      padding:          1px;
      overflow:         hidden;
}
.Window{
      position:         relative;
}
.DragH{
      position:         absolute;
}
</style>
</HEAD>

<BODY>
<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>
<script>
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];
            }
            array.length-=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);
            document.getElementById(ConPrefix+i).innerHTML=Content[i];
            for(var j=0; j<Locations[i].length; j++)
//                  alert((100/Locations[i].length)+'%');
                  document.getElementById(ConPrefix+ModPrefix+'_'+i+'_'+j).style.height=(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){}
      if(e.className=='Drag'){
            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!
            try{
//            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;
      optDrop=D=c;
      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)
            if(mD==5000){
                  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;
            if(D=='Y0'){
                  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;

      if(vert){
            // 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'; }
            }
      }
      if(horiz){
      // 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);
      xmlhttp.send("");
      alert(xmlhttp.responseCode);
      return xmlhttp.responseText;
}

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

ReDrawWindows();
</script>
</BODY>
</HTML>
0
 
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!!

Thanks,
DD
0
 
cwolvesCommented:
not easily.  how well do you kow JS?
0
 
DrData68Author Commented:
I would classify myself as high intermediate to lower advanced.
What do you suggest?
0
 
cwolvesCommented:
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  :-/
0
 
DrData68Author Commented:
Let me take a look at it and see what I can do.
0
 
sigmaconCommented:
DrData, we wouldn't mind seeing the finished product ;-)
0
 
DrData68Author Commented:
I agree.  I got pulled onto other things, so I have not done too much with it.
0
 
sigmaconCommented:
DrData68, do you need more help regarding this question?
0
 
DrData68Author Commented:
Yes, that would be great!!!
0
 
sigmaconCommented:
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?
0
 
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?
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 8
  • 4
  • 3
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now