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 suspect, I ran into spamfilter :-(
    Therefor I post my question under a new Nick and mailadress.

    Hi dbunic,

    I figured out, that the problems isnt the second table. The problem is, that I made the table id=2 scrollele.
    In my css-file, I added in #main_container #right{
    height: 400px;
    overflow:auto;
    If I remove that item, your script works fine again.
    Is there a way to fix it, so that I can youse a scrolleble right Container?
    Best regards, NH

  2. @Newhere2 – This will be hack in two steps but without autoscroll option …
    1) in drag.js after line 157

    // attach onscroll event (needed for recalculating table cells positions)
    window.onscroll = calculate_cells;

    add the following line:

    document.getElementById(‘right’).onscroll = calculate_cells;

    This will force recalculating table cell positions on scroll second table in DIV element

    2) find calculate_cells() function and instead of:

    // backward loop has better perfomance
    for (j = tables[i].rows.length – 1; j >= 0; j–) {
      row_offset[j] = box_offset(tables[i].rows[j]);
    }

    place the following code to take into account vertical offset of the second table:

    // backward loop has better perfomance
    for (j = tables[i].rows.length – 1; j >= 0; j–) {
      row_offset[j] = box_offset(tables[i].rows[j]);
      if (i == 1) {
        row_offset[j][0] -= document.getElementById(‘right’).scrollTop;
        row_offset[j][2] -= document.getElementById(‘right’).scrollTop;
      }
    }

    Well, now I have to think how to implement CTRL feature from previous comment and DIV vertical offset into main drag.js code. ;)

  3. Hi dbunic,
    WoW, great, Thanks very much!
    There are 2-3 things left, to make it work perfect. I hope, you can fix it for me and (I hope) many other interested people here.
    1) Recalculating cells works like a charm, really great. Only at draging/cloning cells in the right table, sometimes the “opacity-DIV” is a few cells away from the now perfectly recalculated background-highlightend-target-table-cell.
    2) If I make both table scrollable, what code do I have to put in drag.js?
    I ask that, because recalculating left table does not work correctly and the “opacity-DIV-function” will be lost by dragging cells if the left DIV is a scrollable DIV. (I mean, opacity-DIV is hide by dragging the cell…but dragging itself works fine)
    Once again big thanks to your work and your fantastic support!
    NH

  4. For the third example ( Schedule). I want to be able to drag one of the boxes in the left menu bar to the table and have it automatically placed. How could I store some information inside those objects? Like days and time. So if one of the boxes on the left menu bar is CS 490 and it is 3 hours long and happens 3 days a week ( Monday, Tuesday and Friday). I want to be able to just place the CS 490 box over the table and have it automatically fill it out. So, if I drag that box on to the table it should expand it’s size to fill 3 hours and not have multiple instances of the box to fill the hours. Could you please point me in the right direction?

  5. Wonderfull script, but I’m having a problem, I added the contents for “ajax” and it seems not work, you know how to solve this problem?

  6. I really like this script but during testing to determine if I could use it in my website I ran into a small but nasty limitation in the interaction. When you drop an item in a column with multiple items it is always dropped at the bottom of the list. Not at the position of the cursor. If users would try to order a list of multiple items now I’m sure they would get rather confused. Of course, once you understand that the items are always placed last it is doable but having the need to first explain this to users would not make for great usability I think. Is there any way to add or enable ‘position sensitive’ dropping of items?

  7. Hello; I am working on a little site and I am so glad I found this library, but I am having a problem with it.

    I have set min-width:50px; in my stylesheet, this forces all cells to remain the same size because in the future they will be holding 50×50 images.

    It is a 26×26 size grid, and it seems that if the grid goes off the screen, your library does not recalculate the horizontal scroll position.

    For example, I have 26 columns. I can drag to about column 17; and then the window starts to scroll. But it will not let me drop in any cells after column 17. It is like it has no calculated any table cell positions that are off to the right edge of the screen.

    Perhaps you can help me?

  8. @Newhere2 – Library has included limit for page height and width. In case with scrollable div, it’s possible that scrollable area extends window size. So, in your case you can comment stopping DIV element on vertical window bound in handler_onmousemove()

    // set left and top styles for the moved element if element is inside window
    // this conditions will stop element on window bounds
    if (evt.clientX > obj_margin[3] && evt.clientX < window_width - obj_margin[1]) {     obj.style.left = (evt.clientX - mouseX) + "px"; } //if (evt.clientY > obj_margin[0] && evt.clientY < window_height - obj_margin[2]) {     obj.style.top = (evt.clientY - mouseY) + "px"; //} If you want to have both scrollable tables, than modification from the previous reply should look: // backward loop has better perfomance for (j = tables[i].rows.length - 1; j >= 0; j--) {     row_offset[j] = box_offset(tables[i].rows[j]);     switch (i) {         // left table         case 0:             row_offset[j][0] -= document.getElementById('left').scrollTop;             row_offset[j][2] -= document.getElementById('left').scrollTop;             break;         case 1:             row_offset[j][0] -= document.getElementById('right').scrollTop;             row_offset[j][2] -= document.getElementById('right').scrollTop;             break;     } } ... assuming left DIV has id="left" and right DIV has id="right"

  9. @Julio – My library is designed to work with table cells. Unfortunatelly it’s not possible to span dropped DIV element through multiple rows or columns on “drop” table. But you can have three tables. Table with subjects, table to drop elements and table which will be controlled with myhandler_dropped() of second table. So, user will be allowed to pick subject and to drop it to the second table. You can design small second table (perhaps with one row and it can look like header of the third table) to fit on top of the third table. After element is dropped, myhandler_dropped() should read some class properties to dynamically fill third table and delete dropped element. This is just an idea, but I think it might work …

    @Erick – Can you be a little specific and explain which AJAX context was used? Generally speaking, combining AJAX and “Drag and drop” library should work. You should only be careful to properly initialize both libraries …

    @Jay – You are right. Ordering elements in one cell is still a problem. Currently, “Drag na drop” library recognizes only table cell and doesn’t see / understand table cell content. Dragged DIV element is appended to the end of the children list of a specified table cell. Simply said, after mouse button is released, appendChild makes the magic happen.

    @drbob – Yes I will help, but can you show me the problematic example? If you drag element to the window bounds, window should autoscroll to the right edge of the screen. After autoscolling stops, you should be able to drop DIV element to columns after column 17. Anyway, if you show me the problem, I will try to help you. Cheers!

  10. dbunic: Thank you for the quick response :-)
    My site that is having the problem is here: http://soggymilk.com/qrpsdrail/grids/1
    You can drag the little pictures on that grid that is A-Zx1-26.

    If you try to drag any of the items over to the right edge the page does autoscroll but for me (using Firefox and Google Chrome) it will not let me drop it in any of those squares.

  11. @drbob – Please try to make DIV id=”drag” visible:

    #drag{
        border: 2px solid red;
    }

    and you will see that drag area is smaller then nested table. Now try to expand horizontally drag area with the “width” style:

    #drag{
        border: 2px solid red;
        width: 1000px;
    }

    Now you should be able drop elements to the most right columns. “Drag area” is region where onmousemove seeks table cells and where all calculations are performed. Script will not search for the current table cells if element is dragged outside of the DIV id=”drag” (optimization). Hope this will solve your problem. Cheers!

  12. Thanks dbunic that worked perfectly! I didn’t even think to turn on the border for the dragging area.

    In case anyone has a similar problem to mine if you use
    #drag {
        display: table;
        table-layout: auto;
    }
    it automatically sizes the DIV id=”drag” to fit the inner content so you don’t have to specify your own width in this situation.

  13. Hi dbunic,
    first of all big thx again to you for supporting here.
    Recalculating cells works now in both tables :-) Great!

    But there is one problem left, that the “opacity-DIV” is hidden in 2 cases:
    1) If you drag from table 1 to table 2 and in the other way (table2 to table1)
    2) If you clone a cell in table 2 and drag it.

    You can see what I mean at: http://www.lookatit.lima-city.de/a1.html

    Can you fix that?
    A big thanks to you and best regards.

    NH

  14. @Newhere4 – Well, if elements in left table will be “clone type” (like you defined first two), then I have a solution. Please try to put DIV id=”obj_new” out from the left container which have overflow: auto; style.

    </div><!– drag container –>
    <!– needed for cloning –>
    <div id=”obj_new”></div>

    This should fix clone type elements in left table.

  15. Hi dbunic,

    first, thanks for fast support from you.

    Im sorry, but your soloution does not work in firefox 3.5. I uploaded it on the url from my last port. Can you take a look at it?

    Best regards, NH

  16. @Newhere4 – Please try to put DIV id=”obj_new” after drag container. This should fix “clone type” DIV elements (you named them 0001 and 0002). I hope you will have only “clone type” elements in left table … ;)

  17. I have a 3 tables on a single webpage, each with 3 column & 5 rows with dragging enabled & the Drop option as switch. When I try to drag any cell from 2nd or 3rd column, it does not switch cells, it shows the selection focus far from where the mouse is & when you drop it, it drops on the cell, where the mouse was.

    However, if I drag the first row/first column cell & drop it anywhere, then dragging works fine for all the cells in all the tables.

    Does anyone know the solution to this issue?

    Thanks & Regards

  18. Hey Awesome Script man.
    Keep up the good work. Works like a charm.

    However :(
    Im having some issues w.r.t desing. My page layout uses the Holy Grail Design 3 Divs side by side. What im doing is placing table cells in middle div and the draggable objects in the right most div.
    But since im using floating divs, the drag and drop doesnt work !! It also doesnt work when i use negative margin.
    I HAVE to use either floar or – margin for the positioning !!

    Is there any other work around for this?? I can provide sample code if needed ( if you didnt get the idea that is)

  19. Hi Dbunic,
    I’m trying out your script and it works great so far. Need your help on the following though:
    1. My draggable object can span over multiple cells horizontally. When I drag the obj, I would like the hover effect to cover the same number of cells. (Likewise when I drop the object). Right now, the hover effect applies to only 1 cell.
    2. I would also like to ensure that I can only drop into cells which are unmarked. For eg, I have an obj spanning 5 cells horizontally. When I move the obj, I’m only allowed to place it on a group of 5 cells which are unmarked.

    Thanks a lot!!

Leave a Comment