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. @dbunic – Yeah, I verified my layout and I’m in good shape. I’m sure it must be something on my end, but I’ll be damned if I can find it. It’s only the fields that get dragged that loose their data. It’s like the element name isn’t making the jump. Any row that stays put, or is displaced by the dragged row retains it’s data and is passed fine. Could it be that my inputs are an array (name=title[])?

  2. @dbunic – I believe I have it, the form tag was actually outside the table (between rows). Sloppy on my part, fixed up now. Sorry to bother you with my self-made issue. Thanks again for great code!

  3. @Doc – I tried with form names as array title[] and it works. Anyway, I will send you my fast prepared example – maybe it will help to solve the problem.

  4. Darko, thanks for referencing example 12 in regard to my earlier question. It seems the best path forward is for me enable moves in one column. I’ll do as your example 12 did and add a REDIPS.myhandler_dropped().

    In the handler, to move the related cells from the row, I would need to reference the REDIPS.source_cell.rowIndex and REDIPS.target_cell.rowIndex and just make those moves with my own code (as the related cells will not be in the “drag” class).

    Thanks for the example.

  5. FYI, here’s the handler I wrote. The draggable cells serve as handles and are in the last cell in each row. The associated content I wanted to move with the handle is in cells indexed 3, 4, and 5. Here’s the drop handler.

    REDIPS.drag.myhandler_dropped = function () {
        var tableNode = document.getElementById("myTable");
        var sourceRowIndex = REDIPS.drag.source_cell.parentNode.rowIndex;
        var targetRowIndex = REDIPS.drag.target_cell.parentNode.rowIndex;
        var sourceRowData = [
            tableNode.rows[sourceRowIndex].cells[3].innerHTML,
            tableNode.rows[sourceRowIndex].cells[4].innerHTML,
            tableNode.rows[sourceRowIndex].cells[5].innerHTML];
        var currentRowIndex;
        var increment = (sourceRowIndex > targetRowIndex) ? -1 : 1;
    
        for (
        currentRowIndex = sourceRowIndex;
        currentRowIndex != targetRowIndex;
        currentRowIndex += increment) {
            tableNode.rows[currentRowIndex].cells[3].innerHTML = tableNode.rows[currentRowIndex + increment].cells[3].innerHTML;
            tableNode.rows[currentRowIndex].cells[4].innerHTML = tableNode.rows[currentRowIndex + increment].cells[4].innerHTML;
            tableNode.rows[currentRowIndex].cells[5].innerHTML = tableNode.rows[currentRowIndex + increment].cells[5].innerHTML;
        }
    
        tableNode.rows[targetRowIndex].cells[3].innerHTML = sourceRowData[0];
        tableNode.rows[targetRowIndex].cells[4].innerHTML = sourceRowData[1];
        tableNode.rows[targetRowIndex].cells[5].innerHTML = sourceRowData[2];
    };
    
  6. @Jonathan – Thank you for posting your final myhandler_dropped() code. This will certainly help if someone will have the similar case. Cheers!

  7. Nice examples, espeically example 03 which was the one i was searchng for. Thanx.

    But,In the school timetable management, the application is not able to delete the last element in the right hand side table, whether it is with script.js or script1.js.

    While using the one with the save button, its able to delete but throws an error and you have to go back and hit refresh to see it.

    With the one that automatically saves the changes, its not allowing to empty the right hand side table at all.

  8. These are the errors that come:

    Warning: Invalid argument supplied for foreach()
    in .../example03/save.php on line 15
    
    Warning: Cannot modify header information - headers already sent by
    (output started at .../example03/save.php:15)
    in .../example03/save.php on line 30
    

    Please help …

  9. Dear dbunic,

    I’m new here and I was wondering, when I drag items onto the trash cell, how do I get it to delete from my database that I created and not just from the table?

    Thanks in advance

  10. Dear dbunic,

    I tried making different tables within many tabs, but I could only seem to make the table in the first tab to work.
    Is there a way where I can make tables within all tabs draggable?

    Thanks in adnvance

  11. Dear dbunic,

    I was wondering, does your draggable tables work in multiple tabs?
    Could you also please tell me how I can edit the trash cell so that it would do some additional functions?

    Thank you very much

  12. @Sayan – save.php is now corrected and notices should not appear any more. Please download latest redips2.tar.gz package and try with new save.php file. The changes I made can be seen on github

    @Teddy – Please see script1.js from example03 where save() function is called after element is deleted. REDIPS.drag has implemented many event handlers and in case of executing actions after element is deleted you can write JavaScript code inside myhandler_deleted event handler.

    I didn’t test REDIPS.drag library within tabs, so if you have example to show the problem, I will try to fix it.
    Cheers!

  13. thanx……worked like a charm…….

    but if i want to merge example 3 and example 16. then am having a hell lot of difficulty as i am a novice and learning, experimenting and implementing at the same time….

    will you guide me in the proper direction……

    thanx again….

  14. Sir Darko,

    First of all, thank you for your wonderful script, it is really a big help to me in developing a plugin for a forum. But I am encountering a problem right now when dealing with too many draggable cells. I have over 900 cells that must sorted/moved and have their current positions sent to the server (after clicking a save button). However, I am getting an error:

    Request-URI Too Large

    The requested URL’s length exceeds the capacity limit for this server.

    I understand that it is because the url being long. I already edited the script so it will only have the format p[]=id&p[]=id&…etc but the error is still there (for too many cells).

    Any idea/tip/work around for this? Thank You in advance, Sir!

  15. @Sayan – My guess is that you want to save content of the example16. That should not be a problem and if you will need guidance don’t hesitate to ask.

    @Kris – I’m glad to hear that REDIPS.drag works with big tables. Actually you faced URI GET limit. Solution to this problem is to use POST method. You can create form and add p[] input elements instead of string concatenation. That means to dynamically create form, add input elements and submit form. Much elegant way would be to use JSON method and POST such createad object via AJAX. If you have a little more than basic JavaScript skills you will be able to modify save_content() method and enable saving of big tables.

  16. what is the need of clearing out the data from table everytime.???

    Can we just not insert the data which is getting dragged and dropped????

    In a big fix right now……..PLz help me….

  17. @Sayan – I prepared AJAX variant of School timetable example. Now database table is not deleted on each drop of school object. New subjects are inserted to the database while moved subjects are updated. New code is located in example03/ajax directory.

  18. I have a sample of example1 working on my server. I only need to be dragging and dropping. No deleting. No cloning.

    After each drag-and-drop event, I want to be able to capture the id that moved, and the table/row/col that it moved from, and the table/row/col that it moved to. For now, simply displaying the data with a javascript alert() will suffice. Note that I will NOT be going back to the server with this data. It will be for client side use only to change some subtotals on screen.

    Can anyone help me to do this?

    Thanks

Leave a Comment