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 think there is a bug in Mobile Safari (iOS 4.3.3 as well as 5.0.1). Once an element is dragged (or after being cloned), it cannot be dragged again.
    A dirty solution is to call init() after each drop.

  2. hi dbunic… I’m sorry, but I can not solve other problems … when I load objects from DB to table I create new div with a id 1c0, 1c1 … When I copy the same object from the “toolbar” once again it creates a copy of the initial (0) and I also fail to apply clone limit on it. Maybe solution should be to call a copy function and give there required properties and position, but I don’t know how. Thanks for your time. Renkor

  3. @glwilliams4 – To change border of current table cell please take a look to Drag and drop table rows with JavaScript example. It’s a bit tricky to change cell border in case of table border-collapse (I wrote a comment in example15/style.css) but it’s possible.

    @Filip – Yes, in a moment when DIV element is moved, registration of event listeners are lost in Safari Mobile browser (I saw it on a friend’s IPhone). So, after element is moved it becomes unmovable(?!) Anyway, thank you for pointing to the problem. I will have to make more tests to see where is the problem. Any help in further diagnosis is more than welcome.

    @Renkor – If the problem is related to conflict between DIV elements retrieved from database and cloned on table maybe you can generate different id for database elements. I had similar situation in example03 (School timetable). Please open example03/config.php and see around line 92 … In short, cloned elements will have added c1, c2, c3 suffix to id of source element. In a moment when cloned element will be saved to the database you can detach id to original part and cloned part. This way, different id can be prepared for cloned elements retrieved from database. Lets say source element have id=”aa”. Cloned elements will have id=”aac1″, id=”aac2″, id=”aac3″ … Before saving to database you can cut c1, c2, c3 suffix and save only “aa”. In a moment of displaying DIV element on page, id can be composed of “aa” and lets say “b1” , “b2”, “b3” so there will not be conflict between new cloned elements and elements displayed from database (“aab1”, “aab2” …). Saving “aab1” will also cut “b1” suffix and save only “aa” to the database (with defined table position). The same logic is used in example03.

  4. Thank you for the quick reply, I had this solution yesterday and maybe it’s best idea. But I can not solve the problem with clone Limit. When it upload elements of DB and then give them another ID (instead of 11c1 it create 11) need to be covered by the same limit as the basic elements. For example: I create element from the DB (Power_source_b0) and it may be in area ​​only one … so that can not be created new (Power_source), but after delete I need to permit to create new one… Thanks Renkor

  5. sorry for my next message but maybe I have an idea how to solve this problem. Easy soulution should be disable possibility of cloning and deleting these elements… Then I can create these element once on area where they can be only draged, saved and loaded from DB… But how to create irremovable element? Thanks Renkor

  6. Hi,

    Great script !

    One question : is it possible to make the clickable zone for drag smaller than the draggable zone ?

    I mean : I want to make only a small portion of a div able to be clicked for moving the whole div elsewhere.

    Is it possible and how ?

    Thanks at advance.

  7. @Renkor – Element can be disabled in script (more examples are in documentation):

    // disable element with id="id123"
    rd.enable_drag(false, 'id123');
    

    or remove class “drag” in case of displaying elements from database.

    @WolveFred – DIV element with class=”drag” is container and listens for onclick event on its whole surface. Unfortunately, REDIPS.drag library currently doesn’t have option to define smaller clickable zone but it can be shown like it has. Please see Example 11: Drag handle on titlebar. Actually it only changes cursor (from “move” to “default”) but you can click on DIV element anywhere to drag it around. I know it’s not a clean solution but maybe it can help.

  8. Thanks for your answer.

    Maybe I have a solution and I will test it later :
    – Make the panel not dragable.
    – When the user clicks and hold on the small button of the panel, we make the whole panel dragable.
    – When the user release the mouse click, so the panel becomes not dragable again.

  9. Hi,

    My requirement is, The element should be drag and drop in the same Table, But When I try to drag it to another table, it should not. How can I achieve this?

    Thanks,
    Nivas

  10. @Nivas : It is easy : set the id “drag” for the table that autorize drag and drop, and set nothing for the other table. It implies that the second table is not nested inside the first.

  11. @WolveFred – Sounds nice – hope it will work and thanks for helping others.

    @Nivas – The easiest way is to create separated drag containers like in Example 8: Tables in separated containers. Just peek to the source. Here is code snippet of how to initialize each DIV container:

    // reference to the REDIPS.drag class
    var rd = REDIPS.drag;
    // DIV container initialization
    rd.init('drag1');
    rd.init('drag2');
    
  12. Hi,

    In my case, i know row, col and table number and i want to get id of the cell (ie., i know position of my cell, but not the id of the element) Is there any method available to get id of div element ?

    if not can you please let me know, how can we achieve this?

  13. @Lakshman – REDIPS.drag doesn’t have such method but here is JS code that might help you:

    // display Id of first DIV element inside table cell with coordinates
    // tableIndex, rowIndex and cellIndex
    function displayId(tableIndex, rowIndex, cellIndex) {
        // define local variables
        var tables, cell, div;
        // get tables inside REDIPS.drag container
        // (this can be globally set to skip possible overhead)
        tables = document.getElementById('drag').getElementsByTagName('table');
        // set reference to the table cell
        cell = tables[tableIndex].rows[rowIndex].cells[cellIndex];
        // mark cell to become visible (just for testing purpose)
        cell.style.backgroundColor = 'lime';
        // get references of DIV element(s) inside table cell
        div = cell.getElementsByTagName('div');
        // test if there is any DIV element
        if (div.length > 0) {
            // display Id of first DIV element
            alert(div[0].id);
        }
    }
    

    … and how to use it:

    // display DIV Id for first table, third row and third column (cell)
    displayId(0, 2, 2);
    
  14. @dbunic, first of all thank you for immediate response, the solution you have given will be very much useful.

    I have one more question, I have two tables (say table1 & table2) and in JavaScript I have set:

    rd.drop_option = 'switch'; 
    

    What I want is, if I click on any element in table1, certain cells in table2 (not all elements only certain DIV elements with some specific id) should be disabled and should not allow to swap the content even though cell choosed from table1 is dropped on these elements.

    Issue I am facing is: as drop option is set to switch, even though cell is set to disabled, after dropping the element from table1 on disabled cell of table2 is swapping the elements.

    Can you please suggest me, Is there any way to achieve this?

  15. @Lakshman – drop_option can be dynamically changed. Please see Example 9: Single and shift mode. Here is code snippet from that example:

    // in a moment when DIV element is moved, set drop_option (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
        if (tbl.id === 'table1') {
            rd.drop_option = 'shift';
        }
        else {
            rd.drop_option = 'single';
        }
    };
    

    On the similar way you can test DIV element id and set “switch” or “multiple” option:

    rd.myhandler_moved = function () {
        // set DIV element id
        var id = rd.obj.id;
        ...
    
  16. Thanks for the library.

    Two quick questions:
    1) is it possible to dynamically generate new table elements without user interaction?

    2) is it possible to have two [div id=”drag”] sections on the page? When I do that only one is active.

    Thanks

  17. 1)
    Yes, it’s possible to create a new DIV element and place it manually to the table. New DIV element should contain drag class name. After element is added to the table it should be “enabled” – please see documentation for enable_drag() method.

    2)
    REDIPS.drag library can have many separated drag containers like in example08 – Tables in separated containers. Elements from first container can’t be dropped to the second container and vice versa. If you want to disable all elements in a table, that can be done with enable_table() method. So with using event handlers, enable/disable (toggle) table elements should not be a problem.

  18. Thanks again,

    Any pointers on how to trigger an animation shift afterwards?

    I’m creating the new DIV, then appending it to the last cell of the table, then moving it to the first cell on the table, but the existing element of that table stays there and now both elements share the cell even though shift is set to true.

    var InsertOnDiv = document.getElementById('lastCellOnTable');
    var newdiv = document.createElement('div');
    var divIdName = 'thisNewDiv';
    newdiv.setAttribute('id',divIdName);
    newdiv.setAttribute("class", "drag");
    newdiv.innerHTML = 'This is the content of the cell';
    InsertOnDiv.appendChild(newdiv);
      
    REDIPS.drag.init();  //Enables drag on all the new Divs
      
    REDIPS.drag.drop_option = 'shift';
    REDIPS.drag.animation_shift = true;
    REDIPS.drag.animation_pause = 20;
    
    REDIPS.drag.move_object({
        id: 'thisNewDiv',
        target: [0, 0, 0]
    });
    

    Any advice to trigger the shift?
    Thanks

  19. @dman – New REDIPS.drag library is published (version 4.6.7) and here are changes:

    - method shift_cells() is declared as public
    - fixed bug in case of enable_drag() for newly added DIV element to the table
    

    So, now it’s possible to call shift_cells() method. Please download latest lib and try this code (actually it’s modification of your JS code):

    // set reference to the first/last table cell and create DIV element
    var firstCell = document.getElementById('firstCellOnTable'),
        lastCell = document.getElementById('lastCellOnTable'),
        newDiv = document.createElement('div');
    
    // set attributes for new DIV element
    newDiv.setAttribute('id', 'thisNewDiv');
    newDiv.setAttribute('class', 'drag');
    newDiv.innerHTML = 'New';
    
    // append DIV element to the table
    lastCell.appendChild(newDiv);
    
    // enable drag on newly added element to the table
    REDIPS.drag.enable_drag(true, newDiv);
    
    // set drop option and other properties
    REDIPS.drag.drop_option = 'shift';
    REDIPS.drag.animation_shift = true;
    REDIPS.drag.animation_pause = 20;
    
    // shift table content
    REDIPS.drag.shift_cells(lastCell, firstCell);
    
    // move DIV element from current position (last) to the first cell
    REDIPS.drag.move_object({
        id: 'thisNewDiv',
        target: [0, 0, 0]
    });
    

    Cheers!

Leave a Comment