Drag and Drop table content with JavaScript

Content of HTML table cells can be dragged to another cell or another table. It isn’t difficult to define onMouseMove handler and change top / left element styles to move the object. In case with tables, you will have to determine somehow target cell. Attaching onMouseOver handler on TD elements will not work, because browser doesn’t fire events to the elements below the dragged object.

Anyway, after taking care of the current scroll position and calculating TD positions, REDIPS.drag should work in recent major browsers like Google Chrome, Firefox, Safari, Internet Explorer, Opera and mobile devices as well. Click on image below, will open live demo where you can drag green, blue or orange bordered DIV elements, change properties (radio button and check-boxes) and click on “Save” button.

Download latest version redips2.tar.gz


REDIPS.drag example01

In this example “Save” button will scan table content, create query string and send to PHP page. Demo shows how to collect content and accept parameters on the server side. More about accepting parameters you can read at Reading multiple parameters in PHP. “Clone” elements (orange in this demo) will be duplicated first because of “redips-clone” keyword contained in class name. If you drop object on cell named “Trash”, object will be deleted from the table (with or without confirmation). Library has built in autoscroll and option to forbid landing to non empty cells or cells named with class “redips-mark”. Table can contain rowspan / colspan TDs and different background color for every cell.

Here are minimal steps to enable content dragging in table:

  • put <script type=”text/javascript” src=”redips-drag-min.js”></script> to the head section
  • initialize REDIPS.drag library: <body onload=”REDIPS.drag.init()”>
  • place table(s) inside <div id=”redips-drag”> to enable content dragging
  • place <div class=”redips-drag”>Hello World</div> to the table cell

Other features of REDIPS.drag library:

  • methods and data structure are defined in namespace (easier integration with other JS frameworks)
  • all JavaScript code is checked with ESLint
  • REDIPS.drag documentation generated with JsDoc Toolkit
  • drag and drop table rows
  • movable DIV element can contain other HTML code (images, forms, tables …)
  • forbidding or allowing TDs marked with class name “redips-mark”
  • option to define exceptions and allow dropping certain DIV elements to the marked cell
  • option to define single content cell on the table declared with “multiple” drop option
  • cloning
    • for unlimited cloning add “redips-clone” class name to the DIV object
      <div class=”redips-drag redips-clone”>Hello World</div>
    • to limit cloning and transform last object to the ordinary movable object add ‘climit1_X’ class name
      <div class=”redips-drag redips-clone climit1_4″>Hello World</div>
    • to limit cloning and transform last object to immovable object add ‘climit2_X’ class name
      <div class=”redips-drag redips-clone climit2_4″>Hello World</div>
    • where X is integer and defines number of cloned elements (in previous examples, each climit will allow only 4 cloned elements)
  • unlimited nested tables support
  • dropping objects only to empty cells
  • switch cell content
  • switching cell content continuously
  • overwrite TD content with dropped element
  • shift table content
  • table cell with “redips-trash” class name becomes trashcan
  • enabled handlers to place custom code on events: changed, clicked, cloned, clonedDropped, clonedEnd1, clonedEnd2, dblClicked, deleted, dropped, droppedBefore, finish, moved, notCloned, notMoved, shiftOverflow, relocateBefore, relocateAfter, relocateEnd, rowChanged, rowClicked, rowCloned, rowDeleted, rowDropped, rowDroppedBefore, rowDroppedSource, rowMoved, rowNotCloned, rowNotMoved, rowUndeleted, switched and undeleted
  • deleting cloned DIV if the cloned DIV is dragged outside of any table
  • enabling / disabling dragging
  • animation (move element/row to the destination cell/row)
  • added support for touch devices (touchstart, touchmove, touchend)

How REDIPS.drag works?

Script will search for DIV elements (with class name “redips-drag”) inside tables closed in <div id=”redips-drag”> and attach onMouseDown event handler. When user clicks with left mouse button on DIV element, onMouseMove and onMouseUp handlers will be attached to the document level.

While dragging DIV element, script changes its “left” and “top” styles. This is function of the onMouseMove handler. When user releases left mouse button, onMouseUp event handler will unlink onMouseMove and onMouseUp event handlers. This way, browser will listen and process mousemove events only when DIV element is dragged.

As I mentioned, onMouseDown is defined on the elements you want to drag. Elements beneath the dragged object will not be able to catch onMouseOver event. Why? Because you are dragging object and that object only can catch the onMouseOver event.

So, to detect destination table cells, script calculates all cell coordinates (with scroll page offset) and store them to the array. Array is searched inside onMouseMove handler and after left mouse button is released, DIV will drop to the current (highlighted) table cell.

In redips2.tar.gz package you will find many examples including example of how to save/recall table using PHP and MySQL. Package also contains and redips-drag-min.js – a compressed version of REDIPS.drag library (compressed with Google Closure Compiler).

Happy dragging and dropping!

1,195 thoughts on “Drag and Drop table content with JavaScript”

  1. I would like to generate a text file using the table data. My file structure is as below.

    CL-1 A01
    CL-2 A02
    CL-3 A03
    Ok A04
    NOT OK A05

    I could able to get the new location using the save button but how could i able to get the value of id which is being dropped in to new location.

  2. Hi
    I’m looking at your example 01 and have a couple of questions.

    When I submit / save is it possible to get results from ALL tables, or all but excluding Table01 ?
    I’m thinking of using table01 to hold a list of values, which can then be assigned to one of multiple tables. The number of tables generated will depend on what the user is actually doing, so it could be one, it could 100 !

    Is it possible to remove the Trash and Message cells ?

    Thanks :)

  3. @giuseppe – It’s not possible to have two drop modes at the same time but it’s possible to switch modes on some event. You can see Example09: Single and shift mode how it’s accomplished.

    Or you can set drop mode to default value (“multiple”) and within droppedBefore() event handler test what is inside cell. If cell contains DIV element then you can return “false” (from this event handler) and dropped DIV element will be returned to the start position. This way will be simulated “forbidden” cells and free cells with buttons.

    @Surya – Save button on this page scans DIV elements and sends all found elements to the server. If you need to know which DIV element is new on the table you can make difference between saved state and submitted (on the server side).

    Other approach can be to have an action on each DIV drop. Example03 is a good start point. Inside example03/ajax directory you’ll find AJAX variant of this demo. This modification saves each DIV drop via AJAX. Maybe this logic answers your question.

    @Tom – saveContent() method needs Id or reference of table that will be scanned. Current code doesn’t allow to call this method without input parameter. But, you can prepare your own “save()” method that will be wrapper for REDIPS.drag.saveContent(). Inside your method you can reference several tables and in loop call saveContent() method. Returned result for each call can be concatenated or appended (in case of JSON format).

    Trash and message cells are placed here to show what is possible to produce with REDIPS.drag library. This cells can be removed from example (just be sure to remove reference to message cell from JS code also). You can download redips2.tar.gz package with sources files and browse prepared examples. Maybe there you’ll find a better example to start with your project.

  4. Thanks for the reply I’ll look at making a saveAll method and see how I get on. One other question looking at Example01 the plain output I get is similar to p[]=d1_1_0, where Id=d1 Row=1 Cell=0

    Is there any way to add the actual value from the entity dropped in to the cell ? So I’d get :

    Id=d1 Row=1 Cell=0 Value=Tom etc ?

    Thanks

  5. @Tom – To save content (value) of DIV element you’ll have to modify saveContent() method or you can create your own from source code (copy saveContent() method to your script.js and make modification). Here is what to change:

    // prepare query string
    query += pname + '[]=' + cn.id + '_' + r + '_' + c + '&';
    

    you can write:

    // prepare query string
    text = cn.innerText || cn.textContent;
    query += pname + '[]=' + cn.id + '_' + r + '_' + c + '_' + text + '&';
    

    “text” variable needs to be declared at the method beginning.

  6. Thanks – one last question !

    Using example01 as a starting base. If I move an entry in a cell to another cell, can that move be submitted live back to another page or direct to a DB ? So I don’t have to submit all changes but each change as it happens ?

    Thanks for your help, time and advice :)

  7. @Tom – It’s possible to create an action on dropped event handler – REDIPS.event.dropped() . Here is JS code snippet from example03/ajax/script.js file:

    // save - after element is dropped
    rd.event.dropped = function () {
        // get element position (method returns array with current and
        // source positions - tableIndex, rowIndex and cellIndex)
        var pos = rd.getPosition();
        // save dropped DIV element
        sendRequest('ajax/db_save.php?p=' + rd.obj.id + '_' + pos.join('_'));
    };
    

    Example03 have implemented two models for saving table content to the database. Default is to save all DIV elements at once and second (modification within example03/ajax directory) saves only dropped DIV element. Server side is done with PHP. So, if you have basic PHP skills, you can customize back-end code for your purpose. Hope this tip will be helpful.

  8. last one.. :D

    <td id="0001"><div id="AAA" class="drag t1">Dave</div></td>
    <td id="0002"><div id="BBB" class="drag t1">Mike</div></td>			
    <td id="0003"><div id="CCC" class="drag t1">Sam</div></td>
    

    Can I retrieve the TD ID as well as the DIV ID ?

  9. Is there a limit to the number of columns you can have ?
    I have a table with 15 columns and 2 rows.

    I don’t seem to be able to move anything into either row above column eight.

    Thanks

  10. @Tom – REDIPS.drag should work nicely with really wide and long tables. Described problem is related to the size of DIV#drag container. Please add the following style to the style.css file:

    #drag {
        border: 1px solid lime;
    }
    

    This will make DIV#drag visible. So, if DIV#drag is smaller than table, some columns may not work properly or will not be reachable at all. Just set correct width to the drag region and dragging should work fine. This problem is mentioned Appendix A documentation.

    Or you can make drag container extensible with “display: table” style:

    #drag {
    	border: 1px solid lime;
    	display: table;
    }
    
  11. Thank you so much for your suggestions that helped a lot. I am trying to figure out that is there any way upon clicking a button all the table cells being filled with values retrieved from database.

    For Instance:

    I have values 1 to 10 on the left side of a table upon clicking a button i want them to moved into empty table cells on the right side.

  12. I am also looking for a way where we can able to select multiple table cells and drop them at single instance. Is that possible to implement this functionality.

  13. @Surya – On button click you can call AJAX method to retrieve stored positions of DIV elements in database. In callback procedure, returned values can be used to use moveObject() method. Please take a look to the Drag and drop table content plus animation example where JS code copies each user move. This code can be a good start point for AJAX modification.

    Moving more than one DIV element at a time is not possible with REDIPS.drag library, but I have prepared example12 Select and move more elements where elements are selected and then with dragging one elements, other are moved to the destination cell too. Here is code snippet from example12/script.js where DIV elements are moved:

    // loop through div2 array and move elements to the target table cell
    for (i = 0; i < div2.length; i++) {
        // element will be moved to the dropped table cell
        rd.moveObject({obj: div2[i]});
        // try to comment upper line and uncomment this line
        // (elements will be relocated without animation)
        //rd.td.target.appendChild(div2[i]);
    }
    
  14. Thanks for your suggestion its working pretty fine. I am currently using the below code.

     rd.moveObject({
        id: 'd1',
        target: go0
    });
    

    Everything is fine but I have 100 odd drag items to be moved on a single instance. My DIV id starts from d1 to d100 and my table data id starts from go0 to go99. When I am implementing a recursive loop for all the elements but its isn’t working. Could you help me out.

    Out of my curiosity is there any way to improve the speed of the moveObject() method.

  15. Before writing my JavaScript with move object the drag mode option is set to be single. But when I added additionally code on my script it wasn’t working. Could you help me out with this error. Before Move object this was my code:

    REDIPS.drag.dropMode = 'single';
    

    Afterwards I added this piece of code:

    // reference to the REDIPS.drag class
    var rd = REDIPS.drag;
    // initialization
    rd.init();
    rd.dropMode = 'single';
    

    Still it isn’t working.

  16. @Surya – I would suggest you to start testing your code with smaller number of moving elements – let’s say 10 or 15. Browser should run all animations at once and that might be a quite load. In case of many DIV elements, you can avoid animation with appendChild() method. To set different animation speed, I have implemented REDIPS.drag.animation object with pause and step properties. Please read docs and try to test with some values to see what is the most appropriate for your project.

    … and regarding your second comment. Both JS lines seems OK. If only this is written then REDIPS.drag should work in “single” mode for sure. Maybe somewhere else in your code dropMode is overwritten and that’s why it’s not working. BTW, if you know how to set breakpoints for JS code (in Chrome or FF) you can easily trace the code and find the problem.

  17. I have tried by adding the following piece of code in my script. But i am unable to set a different animation speed.

    REDIPS.drag.shift.animation.step = 20;
    REDIPS.drag.shift.animation.pause = 10;
    

    I was wondering what was wrong with the code.

  18. @Surya – REDIPS.drag.shift object contains properties for “shift” mode. If you want to change animation values, please try to change the following properties:

    // animation pause (lower values means the animation will go faster)
    // default value is 20 milliseconds
    REDIPS.drag.animation.pause = 20;
    
    // value defines number of pixels in each step
    // higher values means bigger step -faster animation but with less smoothness
    // default value is 2px
    REDIPS.drag.animation.step = 2;
    

Leave a Comment