Solved

Drag and Drop between 2 list boxes in Javascript

Posted on 2004-10-21
8,829 Views
Last Modified: 2008-01-09
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
Question by:DrData68
    17 Comments
     
    LVL 13

    Expert Comment

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

    http://www.mattkruse.com/javascript/optiontransfer/
    0
     

    Author Comment

    by:DrData68
    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
     
    LVL 13

    Expert Comment

    by:StormyWaters
    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
     

    Author Comment

    by:DrData68
    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
     
    LVL 8

    Expert Comment

    by:sigmacon
    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
     
    LVL 9

    Accepted Solution

    by:
    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
     

    Author Comment

    by:DrData68
    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
     
    LVL 9

    Expert Comment

    by:cwolves
    not easily.  how well do you kow JS?
    0
     

    Author Comment

    by:DrData68
    I would classify myself as high intermediate to lower advanced.
    What do you suggest?
    0
     
    LVL 9

    Expert Comment

    by:cwolves
    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
     

    Author Comment

    by:DrData68
    Let me take a look at it and see what I can do.
    0
     
    LVL 8

    Expert Comment

    by:sigmacon
    DrData, we wouldn't mind seeing the finished product ;-)
    0
     

    Author Comment

    by:DrData68
    I agree.  I got pulled onto other things, so I have not done too much with it.
    0
     
    LVL 8

    Expert Comment

    by:sigmacon
    DrData68, do you need more help regarding this question?
    0
     

    Author Comment

    by:DrData68
    Yes, that would be great!!!
    0
     
    LVL 8

    Expert Comment

    by:sigmacon
    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
     

    Author Comment

    by:DrData68
    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

    Write Comment

    Please enter a first name

    Please enter a last name

    We will never share this with anyone.

    Featured Post

    The Complete Ruby on Rails Developer Course

    Ruby on Rails is one of the most popular web development frameworks, and a useful tool used by both startups and more established companies to build strong graphic user interfaces, and responsive websites and apps.

    This article shows how to create and access 2-dimensional arrays in JavaScript.  It includes a tutorial in case you are just trying to "get your head wrapped around" the concept and we'll also look at some useful tips for more advanced programmers. …
    In this article, we'll look how to sort an Array in JavaScript, including the more advanced techniques of sorting a collection of records either ascending or descending on two or more fields. Basic Sorting of Arrays First, let's look at the …
    The viewer will learn the basics of jQuery, including how to invoke it on a web page. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery.: (CODE)
    The viewer will learn the basics of jQuery including how to code hide show and toggles. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery…

    933 members asked questions and received personalized solutions in the past 7 days.

    Join the community of 500,000 technology professionals and ask your questions.

    Join & Ask a Question

    Need Help in Real-Time?

    Connect with top rated Experts

    15 Experts available now in Live!

    Get 1:1 Help Now