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. Fantastic bit of code, seems very solid with lots of useful options.

    I’m looking for a way of changing the css class of the div being dragged when the drag is in progress (reverting to the original when it’s dropped), is there a way to do this?

  2. @Keith Z – myhandler_moved() event handler is executed in a moment when dragging element is started. Inside that event handler you can change element style. In a moment when element is dropped, myhandler_dropped() event handler is executed (so there could be the code to return style to the previous condition). For example, here is code to change element background to red color when element is dragged:

    // reference to the REDIPS.drag library
    var rd = REDIPS.drag;
    // initialization
    rd.init();
    // executed in a moment when element is moved
    rd.myhandler_moved  = function () {
        rd.obj.style.backgroundColor = 'Red';
    };
    // executed in a moment when element is dropped
    rd.myhandler_dropped = function () {;
        rd.obj.style.backgroundColor = 'White';
    };
    
  3. Fantastic code…

    How you would in your code enabled only a table?

    Thanks!!!!!!

  4. Hello Darko,

    I wanted to run something by you to see if I was missing something. The basic situation I am encountering is quite similar to your example 8, using two independent grids.

    In my case, I am trying to include a single grid in each of two different ASP.Net Web Controls so that they can be reused. In some cases they would both be on the page, in others only one would be. I’m not sure if your background includes ASP.Net but the web control is basically a unit of code that gets emitted into a web page at run time. Bottom line, it winds up looking a lot like example 8.

    Since the point of the exercise is to have encapsulated chunks of code I can reuse, I would like to have each grid firing different chunks of code, in individual javascript files. So div drag1 fires a myhandler_row_clicked event located in drag1.js while div drag2 fires a myhandler_row_clicked event located in drag2.js.

    I looked through the redips-drag.js code and that kind of use doesn’t seem to be supported at present but I wanted to check with you to make sure I wasn’t missing a feature of the library.

    In my case, I should be able to work around it by setting up an interface in which the user toggles between the two tables. (Setting the tables up in individual tabs is a natural for the application). I think that by swapping the event handlers as the tables change, everything should come out fine.

    I assume I’ll need to start by calling the REDIPS.drag.init function and after that it will be pretty much normal use. Are there any caveats to keep in mind when swapping back and forth like that?

    Regards,
    Greg

  5. You’ve written a great library!

    Unfortunately, I’ve encountered a problem when using rd.drop_option = ‘switching’. (I’m trying to implement the functionality that is shown in your Example 14.)

    If I drag an element down (or up) one row, the element in the target row gets added to the dragged and the target row becomes empty. This doesn’t occur if I drag the element two or more rows down or up, and it doesn’t happen at all with rd.drop_option = ‘switch.’

    My web page is identical to your Example 14 with respect to the javascript in the section and HTML within the section.

    Thanks, Bill

  6. @Fco Javier – If you want to enable/disable elements in one table here is how. Actually this is modification of enable/disable code from example01:

    function toggle_dragging(chk) {
        var flag = chk.checked,
            i, id, divs;
        // collect DIV elements from table with id="table1"
        divs = document.getElementById('table1').getElementsByTagName('div');
        // open loop
        for (i = 0; i < divs.length; i++) {
            // define element id
            id = divs[i].id;
            // enable/disable element
            REDIPS.drag.enable_drag(flag, id);
        }
    }
    

    So, you have to assign id to the table and collect all DIV elements from that table. Open loop and enable/disable each element. Or you can call enable_drag method with third optional parameter to enable/disable DIV elements beneath container element. contanier element could be dragging container or table containing DIV elements.

    function toggle_dragging(chk) {
        var flag = chk.checked;
        // enable/disable elements in table1 
        REDIPS.drag.enable_drag(flag, 'table1', 'container');
    }
    

    @Greg Palmer – Hi Greg, I’m not familiar with ASP.Net, but your description of Web control gives me a good picture. Anyway, REDIPS.drag can have only one definition of each event handler. This may sound a bit rigid, but inside event handler you can place a “router” code which will call first or second JS function depending of dragging container where element belongs. In initialization phase, to every DIV element is assigned reference of container where element belongs. Here is a small modification of example08 where you can see how different functions can be called from the same event handler. Hope this answers your question.

    window.onload = function () {
        // reference to the REDIPS.drag class
        var rd = REDIPS.drag;
        // DIV container initialization
        rd.init('drag1');
        rd.init('drag2');
        // prepare handlers
        rd.myhandler_clicked = function () {
            var container = rd.obj.redips_container.id;
            // router
            switch (container){
            case 'drag1':
                console.log('Container 1');
                // myfunction1();
                break;
            case 'drag2':
                console.log('Container 2');
                // myfunction2();
                break
            }
        };
    }
    

    @Bill Erickson – switch and switching modes are two different working modes. switch mode is simple. In a moment of dropping, elements from the target cell are moved to the source cell and dragged element is dropped. Actually, elements from source and target cells are exchanged. While in switching mode elements are exchanged continuously. Every element from the current cell will be moved to the previous cell in a moment of dragging element. switching mode is more appropriate for kind of sorting elements in table. Try to set switch and switching mode in this demo to see the difference. Cheers!

  7. Hi Darko,

    The hiding and swapping has been working out great for my needs. My particular case is rather unusual since I really don’t want to have the code for the two controls mixed. There really is no dependency between the two. Generally though, the example you provide above would cover most of the use cases with a lot less complexity than something supporting multiple event handlers. The REDIPS code is already complex enough without it! :-)

    One thing which might be a nice addition though would be a function which reverses the init function call. It’s not required for what I’m doing but it still seems like it’d be useful for cases where you wanted to disable the library for a particular table.

    Best regards and thanks for the help!
    Greg

  8. Dear dbunic
    Thank you for great library.

    I am working on the production planning project and have used your library, unfortunately I am not able to save the data into database without hitting save button.
    Can you tell me how to save the data into database after user dropped element in table without hitting save button.

    Many thanks,
    Kitty

  9. Hi Darko,

    I did find one issue with the approach of hiding controls. The div with the obj_new id was staying set as a child of whatever table the library was first initialized against. (You intentionally do that so multiple calls to init just resuse resources which seems pretty reasonable to me.) I just threw the following code in before initializing against the second table and things are working.

    var obj_new = document.getElementById('obj_new');
    if (obj_new) {
        obj_new.parentNode.removeChild(obj_new);
        obj_new = null;
    }
    

    I’d think it would be better to have this sort of function encapsulated in the REDIPS library though. I’d consider the above code to be tinkering with your libraries internals and subject to being broken if you decide to change the div’s id so I’m philisophically opposed to it. It’s also a poor practice that could lead to problems if you add controls or data structures. Anyway, submitted for your consideration!

    Regards,
    Greg

  10. Hi Darko,
    I found another issue in 4.1.1. When there were selects in the table row being dragged they would get cloned by the following code, but, unfortunately their selected value would not be preserved.

    // clone whole table
    table_mini = el.cloneNode(true);
    

    I changed this around to:

    function set_SelectValues(org_row_obj, new_row_obj) {
        var oldSels = org_row_obj.getElementsByTagName('select');
        var newSels = new_row_obj.getElementsByTagName('select');
        for (var idx = 0; idx < oldSels.length; idx++) {
            newSels[idx].selectedIndex = oldSels[idx].selectedIndex;
        }
    }
    
    ...
    
    // delete all rows but clicked table row
    for (var idx = 0; idx = 0; i--) {
        if (i !== row_index) {
            table_mini.deleteRow(i);
        }
    }
    set_SelectValues(row_obj, table_mini.rows[0]);
    ...
    

    Sorry, forgot to mention that I saw the issue running under Internet Explorer 7.0.5730.13
    Submitted for your consideration!

    Regards,
    Greg

  11. @Kitti – Please download latest redips2.tar.gz and inside example03 you will found script1.js file. I prepared separated version just to show how to run save on every element drop or element delete. You will have to replace script.js with script1.js in index.php and comment out last line in save.php (redirection or page refresh after saving is not needed).
    Cheers!

  12. @Greg Palmer – Your suggestion about disabling some tables inside dragging area seems excellent. I will put this on my todo list for future release – thanks!

    obj_new is needed for cloning elements. After element is cloned, it should be attached to some element in the DOM – and that’s the purpose of obj_new element. If automatic creation of obj_new isn’t appropriate (or precise enough), you can manually create <div id=”obj_new”></div> on your page. In that case, automatic creation of obj_new will be skipped. Please try and I hope it will fix the problem you mentioned. Now I see that name obj_new is not well chosen and I will probably change it to redips_clone to avoid collisions with existing elements on page.

    Your FIX with dropdown menu (in case of dragging table rows) will be included in the next REDIPS.drag release (please see last change in GitHub).
    Thank you very much!

  13. Hi Dbunic,

    Thank you for the great library! It’s amazingly. For someone who is new to javascript like me, it’s very easy to use and implement. I’m currently using example 06.

    Is there anyway that I can save the state of all the drag and drop that I’ve made to a cookie? It would be great when I open the window another time they appear as wherei left them?

    Regards,
    Zack

  14. Hi Dbunic,

    thanks v.much for this wonderful tool .

    but i have a series problem with it , when the table is too big and the Div contains the table have a scroll bar something happen when drop the items into the cells , try this situation and tell me your opinion . and how we can fix it ? .

    Regrads,
    Ali siam

  15. Hello, Darko.

    You have an excellent library here. I have a question about how to use redips-drag.

    I have a table that has 5 columns. The first 3 columns are static; I don’t want them to move. But I want sibling cells in the last two columns to move together. It would be as if I were moving the entire row, but only the last 2 cells should move. When I drop the last two cells in their new location, the other rows should shift the last two cells up or down to fill the gap that was left.

    I originally thought about doing by making the last two cells in each row a table of their own. But there are two problems with that approach: (1) the last two columns don’t line up because the data varies in the last two cells (and it is dynamically generated, so I don’t want to set a fixed width), and (2) the rows don’t fill the gap because the only options when swapping divs like this are to swap places (“switch”) or combine (“multiple”).

    Do you have any suggestions on how to accomplish this goal?

  16. Love the usability of your script, but I can’t seem to figure out how to use form element(s).

    In my case I have 5 rows, each with a text box ( ). You can fill each out and if you hit submit all the data transfers via POST, however if you rearrange the boxes using your library, then hit submit the data is “lost” for the rows that were moved. I’ve also tried it with static names.

    Is this working as designed, or have I implemented incorrectly?

  17. Hi,
    Am looking for a solution to move the entire table row instead of cell to another table.Could you pl. point me to an example. Was unable to figure out what should be the approach

  18. @Zack – I don’t have example with saving table content to a cookie, but inside redips2.tar.gz package you will find example03 – School timetable with functionality of saving table content to the database. I also prepared script1.js to shows saving table content on every element drop or element delete.

    @ali siam – Main drag container should not have set overflow:auto CSS property. If you have big table that cannot fit to the main drag container you can create scrollable div container inside main drag container. Here is how:

    <div id="drag">
        <div id="scrollable_container">
            <table>
                ...
                ...
                ...
            </table>
        </div>
    </div>
    
    #scrollable_container {
        overflow: auto;
        position: relative;
    }
    

    Scrollable DIV container should have set overflow and position properties to appear offset in the offsetParent chain. Please take a look to the example05 for a more details.

    @Jonathan – REDIPS.drag can move/drag table content or table rows. There is no possibility to move one part of the row (only complete row can be moved). But, I can suggest you an option to automatically move other DIV element after first element is dropped. REDIPS.drag has two methods to move/relocate DIV elements:

    move_object(ip) – method will calculate parameters and start animation (DIV element to the destination table)
    relocate(from, to) – method relocates all child nodes from source table cell to the target table cell

    move_object moves DIV element with animation while relocate method relocates DIV elements instantly. The only trick is to somehow know ID of other DIV element and destination table cell to move after first is dropped. Download latest redips2.tar.gz package and try the following examples:

    example12 – Select and move more elements
    example17 – Move object (animation)
    example18 – Simple element animation

    @Doc – I just created simple input form and tried to drag DIV elements and rows with text boxes. In both cases (GET and POST), browser submitted names and values to the server. Drag container is wrapped inside form:

    <form method="post" action="submit.php">
        <div id="drag">
            <table>
                ...
                ...
                ...
            </table>
        </div>
    </form>
    

    In a moment of form submitting, browser should collect all form elements and send to the server. Dragging just rearranges order of DIV elements inside table and that should not have any impact on form submission. Maybe the problem lies somewhere else. Did you put drag container inside form?

    @Satya – please see Drag and drop table rows with JavaScript … I hope this example is what you’re looking for.

Leave a Comment