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. @roboe415 – New REDIPS.drag lib will have option to use TD border for highlighting current table cells. Version 4.6.2 will be published within several days from now (probably be the end of week). Here is part from documentation for new hover object:

    /*
    Hover object contains 4 properties: color_td, color_tr, border_td and
    border_tr. color_td and color_tr defines hover color for DIV element and
    table row. If border_td is defined, then highlighted cell will have border.
    If border_tr is defined then highlighted row will have only top or bottom
    border. Top border shows that row will be placed above current row, while
    bottom border shows that current row will be placed below current row.
    Some browsers may have problem with "border-collapse:collapse" table
    style and border highlighting. In that case try without collapsing TD
    borders (e.g. set "border-spacing:0" and smaller "td.border-width").
    */
    // set "#9BB3DA" as hover color for TD
    REDIPS.drag.hover.color_td = '#9BB3DA';
    
    // or set "Lime" as hover color for TR
    REDIPS.drag.hover.color_tr ='Lime';
    
    // set red border for highlighted TD
    REDIPS.drag.hover.border_td = '2px solid red';
    

    @Gaël – I’m glad my answer was helpful. If you will have any other questions don’t hesitate to post a comment. Cheers!

  2. Hi… I have one question… If I have DIV elements like a components of el. circuit and I need put there DIV elements which will represent buttons with their own names which I can select by tag select… is there any way how to load and save data of them with coordinates and id of DIV elements? Thanks for answer and please excuse my mistakes in English.

  3. Now for the absolute perfection:

    Is it possible to have table cells with no height defined?

    I tried to remove the height from the css for the TD tag and layout remains ok but the drag/drop goes crazy.
    The problem is that my content divs will grow according to their texts

    Thanks for the great job so far!
    JB

  4. I based in example 3(School timetable) with Ajax.

    I do switch between two subjects, but when it save change, I only get the “id” of the subject that I push but not get the “id” of the other subject. There are possibility I get two “id”??

  5. Is it possible to add elements to an existing table using Javascript, and those elements be draggable? It seems the dragging only works for table elements loaded at page load, and not added after.

  6. @Renkor – Please see example03 (School timetable) from redips2.tar.gz package. Online demo only shows how to drag and drop school subjecs while version in packege has option to save table content. If you have LAMP server for testing, just create one table in MySQL database and set connection parameters. Example03 contains classical version with Save buton and AJAX version.

    @joao – You can define minimal TD height to allow REDIPS.drag library to calculate dropping grid. If content in TD will contain heigher element (as your described DIV element with some text), then TD and TR will grow also. Higher cell should be correctly highlighted as in case with normal height. calculate_cells() method inside REDIPS.drag lib is one of the most important which takes care about cases like: page scrolling, element dropped to the table cell, TD resizing and so on. In every mentioned case, tables and TD positions are scanned and saved to the internal array (onmouseover of TD element beneath dragged can’t be used). If TD is without defined height, then calculation will not function properly. So please set minimal TD height and REDIPS.drag will “clearly see” table cells.

    @Cristian Juvé – In next REDIPS.drag release, reference of last switched element (table cell could contain more than one DIV element) will be saved in REDIPS.drag.obj_old property. So, you will have reference of dropped (current) element and switched element (obj and obj_old).

    @glwilliams4 -After new element is added to the table (with class=”drag”) you have to attach onmousedown, ontouchstart and ondblclick event listeners to enable element dragging. Fortunately, enable_drag method will do needed actions and here is how:

    // init all DIV elements in dragging area (including newly added DIV element)
    REDIPS.drag.enable_drag('init');
    
    // init added element with known id
    REDIPS.drag.enable_drag(true, 'id123');
    
    // init added element with known reference
    REDIPS.drag.enable_drag(true, my_el);
    

    Hope this will work ;)

  7. Hi,

    Hope you doing well.
    Actually I m facing a critical problem, and hope you will help me out.Problem is as follows:

    When I drag one element & drop it in another cell working fine even it revert is also works.

    I am doing this one to one process (means one element drag and revert it back) again and again still it working fine.

    “Problem is this, when we drag multiple elements (e.g four elements we dragged) after that we try to revert it back than for first element it works. Not for rest of three element.”
    User not able to revert three element.

    Information:I am getting the same target position for all four element thats why the first element revert back successfully but rest of all not revertable because the target position has been filled by first element.

    Hope you will help us
    I am happy for your previous help

    Thanks

  8. @Vikas Saini – Here is complete JS code that stores all the moves and allows undo of all actions (not just the last one). In your case, if user will click three times on Undo button, script will return last three moved elements to the previous position. I think this code will be interesting for others so I published it in this comment.

    var rd,         // REDIPS.drag object
        redips_init,// initialization method
        undo,       // undo method
        move = [];  // moves array
    
    // initialization
    redips_init = function () {
        // set reference and start initialization
        rd = REDIPS.drag;
        rd.init();
        // event handler invoked if element is moved
        rd.myhandler_moved = function () {
            // push move to array
            move.push({
                id: rd.obj.id,
                position: rd.get_position()
            });
        };
    };
    
    // undo method (can be called on button click)
    undo = function () {
        var el;
        // if array contain elements
        if (move.length > 0) {
            // pop element from array and move
            el = move.pop();
            rd.move_object({
                id: el.id,
                target: el.position
            });
        }
    };
    
    // add onload event listener
    if (window.addEventListener) {
        window.addEventListener('load', redips_init, false);
    }
    else if (window.attachEvent) {
        window.attachEvent('onload', redips_init);
    }
    

    @Filipe – REDIPS.drag has save_content() method to scan table content and save DIV positions. Actually, DIV positions will be sent to the server and server script should accept and save data. I will suggest you to save DIV id instead of DIV content (because DIV could contain any HTML not only text). Anyway, if you need to save content of DIV elements, you will have to make tiny modification. Search for the following line in redips-drag.js

    query += 'p[]=' + tbl_cell.childNodes[d].id + '_' + t + '_' + r + '_' + c + '&';
    

    And replace it with:

    query += 'p[]=' + tbl_cell.childNodes[d].innerHTML + '_' + t + '_' + r + '_' + c + '&';
    

    @Cristian Juvé – New REDIPS.drag is published – version 4.6.3. In “switch” mode, reference of last switched element (because TD can contain more elements) will be saved in REDIPS.drag.obj_old property. So it can be used in myhandler_switched() and myhandler_dropped() event handlers.

  9. Hi,
    I have a problem in the code. When I undo my dropped element it gives the only one target position all time for all those element which dropped by me. I did one thing when I drop an element I disable that element not to be droppable. I also added the undo code to revert back the same position to that content but it always gives same position to me.
    Please see into this. Here is my script.js file code:

    var redips_init,      // define redips_init variable
        toggle_animation, // enable / disable animation
        start_positions,  // remember initial positions of DIV elements
        pos = {},
        revert,           // initial positions of DIV elements
        rd = REDIPS.drag; // reference to the REDIPS.drag lib
    
    // redips initialization
    redips_init = function () {
        // initialization
        rd.init();
        // enable animation on shifted elements
        rd.animation_shift = false;
        // save initial DIV positions to "pos" object
        // (it should go after initialization)
        start_positions();
        // in a moment when DIV element is moved, set
        // drop_option property (shift or single)
        rd.myhandler_moved = function () {
            // find parent table of moved element
            var tbl = rd.find_parent('TABLE', rd.obj);
            // if table id is table1
            //alert(tbl);
            if (tbl.id === 'table1') {
                rd.drop_option = 'shift';
            }
            else {
                rd.drop_option = 'single';
            }
            //alert(rd.obj.id);
            rd.enable_drag(false, rd.obj.id);
        };
        // when DIV element is double clicked return it to the init pos
        revert = function () {
            // set dblclicked DIV id
            var id = rd.obj.id;
            rd.enable_drag(true, rd.obj.id);
            alert(pos[rd.obj_old.id]);
            // move DIV element to initial position
            rd.move_object({
                id: id,         // DIV element id
                target: pos[id] // target position
            });
        };
    };
    
    // function scans DIV elements and saves positions to the "pos" object
    start_positions = function () {
        var divs, id, i, position;
        // collect DIV elements from dragging area
        divs = document.getElementById('drag').getElementsByTagName('div');
        // open loop for each DIV element
        for (i = 0; i < divs.length; i++) {
            // set DIV element id
            id = divs[i].id;
            // if element id is defined, then save element position 
            if (id) {
                // set element position
                position = rd.get_position(divs[i]);
                // if div has position (filter obj_new) 
                if (position.length > 0) {
                    pos[id] = position;
                }
            }
        }
    };
    
    // enable / disable animation
    toggle_animation = function (chk) {
        REDIPS.drag.animation_shift = chk.checked;
    };
    
    // add onload event listener
    if (window.addEventListener) {
        window.addEventListener('load', redips_init, false);
    }
    else if (window.attachEvent) {
        window.attachEvent('onload', redips_init);
    }
    

    I added these line of code in example 9 in table id2.

    <!-- clone 2 elements + last element -->
    3<a>Undo</a>
    2<a>Undo</a>
    6<a>Undo</a>
    1<a>Undo</a>
    4<a>Undo</a>
    5<a>Undo</a>
    

    Please see the view source page b/c there are html tag in this.
    Thanks in Advance.

  10. @dbunic – Unfortunately it will only work if rd.drop_option = ‘shift’. For rd.drop_option = ‘switching’ there is some problem that causes the higher divs to be dropped inside an already occupied cell. After drop, I end up with an empty cell and another cell with two divs inside. The weird thing is that sometimes divs will start grouping into one cell even before the drop happens.
    thanks
    joao

  11. @Vikas Saini – In the moment when DIV element is disabled, all event listeners on DIV element are removed. So, you have to manually add ondblclick event listener. In that case rd.obj.id can’t be used to find id of clicked element. Anyway, here is modification of revert() method that should work:

    revert = function (e) {
        // define event (cross browser)
        var evt = e || window.event,
            id;
        // set source element ID for IE and FF
        if (evt.srcElement) {
            id = evt.srcElement.id;
        }
        else {
            id = evt.target.id;
        }
        // enable DIV element
        rd.enable_drag(true, id);
        // move DIV element to initial position
        rd.move_object({
            id: id,         // DIV element id
            target: pos[id] // target position
        });
    };
    

    @joao – Yes, the problem might be related to resizing TDs while dragging lasts (in case of switching drop option). switching will work fine if all TD dimensions are defined and static (like in example14). Actually, in REDIPS.drag call of calculate_cells() already exists after DIV element is switched to other cell but it seems that lib needs more polishing. If it’s needed, you can send me zipped example for analysis and I will try to fix it (maybe only styles needs to modify). I will not promise because last time I was tearing my hair while working on switching option. :)

  12. Hi I’m sorry for my next question but can’t find the way how to change something inside the new cloned element… When I have basic element () then after clone I need show inside new element his ID (xc1)… I need it for next work with these elements. Thanks for Answer

  13. HI ,

    Thanks Again for the immediate response.

    I try this revert method but it is not working can you please send me the example of this if possible.

    Thanks In Advance

  14. @Renkor – Here is JS code invoked after element is cloned. Inside event handler you have reference of cloned element, so you only need to assign element Id to the innerHTML property.

    // set reference to the REDIPS.drag object
    rd = REDIPS.drag;
    // event handler
    rd.myhandler_cloned = function () {
        // obj is reference of cloned element
        rd.obj.innerHTML = rd.obj.id;
    };
    

    @Vikas Saini – No problem, I have already sent you an e-mail.

  15. I have a table of thumbnail images that are draggable. When I change the margins of an image after it has been dragged, the change affects the orgin and destination cells. How can I prevent styling changes from affecting the original cell.

  16. @David – Applying styles to image inside DIV element should not affect TD styles. Do you have an example to show so I can see where’s the problem and eventually fix the bug (if there is any)? Cheers!

  17. How can I change the CSS properties of the td that currently has on object being hovered over it? I would like to add a dashed border to a td whenever I am holding an object above that particular td. Any suggestions? Thanks in advance.

Leave a Comment