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. Hello sir

    I need Drag & Drop Forms Filed Using Javascript
    I really Need it Please Support Me!!!!!

    Thanks

  2. Thanks for the code Dbunic. It works pretty fine.

    I was wondering thus the drag div has the functionality to drag a text field too.

  3. My code is as follows:

    Here i tried to drag the text field, it wasn’t moving but upon writing some text inside the div element it could able to move.

    Test

    I was scared to use the above code as i would not able to get the value inside the div but if i try to implement the above code i would always get the value test instead of the input field data Test is being written inside the div tag.

  4. Here is another interesting thing which i tried to move the text field.

    In my index.php page, i had used a iframe.

    My code in the sample page is as follows:

    <?php 
    session_start();
    if ($_SERVER['REQUEST_METHOD']=="POST")
     {
      $_SESSION['arr'] = $_POST['manual']; 
      echo "";
      echo " ";
      echo "". $_SESSION['arr'] ."";
      echo "";
     }
    ?>
    

    HTML code:

    <form name="order" method="post" action="self">
    <input type="text" name="manual" size="10">
    <input type="submit" name="sub" value="Ok">
    

    What i tried to do in here is echo out whatever is being written in the text field and tried to drag the echoed data but the scope is being limited to only that particular iframe.

    Is there any other way to solve my problem.

  5. @bindu – You need to start building HTML form and wrap it inside drag container. If you want my support, I’ll suggest to use http://jsfiddle.net/ site (playground for your JavaScript, HTML, CSS) where is possible to create and test JS code online. Please see this jsFiddle example:

    http://jsfiddle.net/dbunic/v4qhdmzL/

    @Surya – Any HTML code placed inside DIV element is possible to drag and drop to the HTML table. In example on this page, you can find text input box with “content” word written by default and it can be moved like other DIV elements.

    Just in case, here is how to save DIV content as well as ID+positions. So, to save content (value) of DIV element you’ll have to modify saveContent() method or you can create your own from source code (copy saveContent() method to your script.js and make modification). Here is what to change:

    // prepare query string
    query += pname + '[]=' + cn.id + '_' + r + '_' + c + '&';
    

    you can write:

    // prepare query string
    text = cn.innerText || cn.textContent;
    query += pname + '[]=' + cn.id + '_' + r + '_' + c + '_' + text + '&';
    

    “text” variable needs to be declared at the method beginning.

    Hope this mod will be helpful for you. If you still want to proceed with classic form, just be sure that whole drag container is placed inside HTML form. This way, on submit browser will scan (form elements) and send name/value pairs to the server.

  6. Dbunic Thanks for the help. I just missed the div tag for the text field.

    Is there any way to count and display the number of div elements dragged in to the table at the top of the page.

  7. @Surya – Here is small JS function to count number of DIV elements in drag container:

    function myCount() {
            // set reference to drag container
        var dragContainer = document.getElementById('drag'),
            // DIV collection
            divs = dragContainer.getElementsByTagName('DIV'),
            // count DIV elements
            num = divs.length;
        // display DIV number
        alert(num);
    }
    
  8. Thanks for the code Dbunic. It helps only for the static div id.

    Currently i am retrieving the records from the database and upon dragging specific records or moving all records on the other side of the table. The problem in here is all the records which are retrieved and the records which are dragged in appear in the same table but to different table row. How could i able to count only the specific set of records instead of using the tag drag or div because all the entries which are retrieved from the database consists of both the tags.

    One of the other issue which i have currently is upon dragging the elements its fine for to take a print out of the current page. But i need to generate a PDF file after dragging the elements. Could you help me out with this issue.

  9. Just for fun, I have written a Sudoku game with hints and solutions that uses your excellent code as its basis. However, I am having one problem which is driving me a little nuts. I allow numbers (in divs) to be put into the Sudoku table with either drag and drop or keyboard. All of this works fine for setup of the board and play.

    The last piece I want to do is hang an event listener for keypress on a div that has been placed in one of the table cells. This works fine if the div is not drag enabled.The event listener does not work once the div gets drag enabled. I need the div enabled so the user can move it to another square or to the trash. (The only reason I need the keypress is so the user has the additional option of using the keyboard to delete the div, as opposed to only deleting by dragging to the trash).

    I have dug through your code and feeling kinda dumb in that I am not seeing what is blocking the keypress event listener. I have even tried adding a custom event for the keypress and that didn’t work either). Interestingly, the “click” event listener does seem to work as expected.

    What am I missing?? Is there a way to hang a keypress (or keyup or keydown) event listener on a drag enabled div?

    Also, as an aside, I did find a bug in the code having to do with detection of the left hand most cell in a row with borders. Let me know if you are interested in knowing what it is and try to describe it. It can be demonstrated pretty easily with the tic-tac-toe example.

    Thanks in advance for your help. And again, really nice piece of code.

  10. @Surya – If you need to count (retrieve) only some subset of DIV elements this can be done on the following way. First, add additional class name to DIV elements that are populated from database. Something like this:

    <div id="d2" class="drag t1 fromDatabase">and</div>
    

    OK, when DIV elements from database are marked with “fromDatabase” class name, the following JS function will count them easily:

    function myCount() {
        // set reference to drag container
        var dragContainer = document.getElementById('drag'),
            // DIV collection
            divs = dragContainer.getElementsByTagName('DIV'),
            // set counter to 0
            counter = 0,
            // loop variable
            i;
        // loop goes through all DIV elements
        for (i = 0; i < divs.length; i++) {
            // increase counter if className of DIV element
            // contains name "fromDatabase"
            if (divs[i].className.indexOf('fromDatabase') > -1) {
                counter++;
            }
        }
        // display DIV number
        alert(counter);
    }
    

    And regarding PDF document. On the server side this should not be a problem. Just post all DIV elements to (say) PHP where you will know theirs positions and generate PDF document. You will have to construct HTML doc and convert it to PDF.

    Second approach is to create PDF document directly on the client side. This is new to me as well and quick search pointed me to:

    http://stackoverflow.com/questions/742271/generating-pdf-files-with-javascript

    Hope this tips will be helpful.
    Regards

  11. @David – Event registration for DIV elements is done on the single spot inside REDIPS.drag code. Just open redips-drag-source.js file and search for the “registerEvents” method (it should be around line 1386). There you can add onkeypress event to be sure that this event listener will be attached for sure.

    Next, I can’t force the problem on my prepared example. First I had to enable setting focus on DIV element with “tabindex” attribute:

    <div id="d1" class="drag t1" tabindex="0">Drag element</div>
    

    Next step was to set event listener with JavaScript to the DIV element with id=”D1″ (this function was called on page initialization):

    function setEvenListener() {
        // set reference to the DIV element
        var div = document.getElementById('d1');
        // set onkeypress event handler
        div.onkeypress = function () {
            console.log('key pressed');
        };
    }
    

    So, when focus was set (with tab key) to this DIV element, any keypress was printed to the console (I was testing code in chrome browser). All DIV elements were disabled/enabled and event listener was still attached.

    Of course, if DIV elements are cloned, then it’s expected that keypress event handler should be attached each time after cloning element. Anyway, if you can prepare some simple example on jsFiddle, we can together work on this issue. Here is my last demo that can be used as a start point:

    http://jsfiddle.net/dbunic/cexn1wvn/

    If you found a bug in lib, I will be very grateful if you can share it and I’ll try to improve REDIPS.drag library.

    Thank you very much!

  12. I hope you don’t mind but I put up a working example of the border bug on our server instead of using jsfiddle. It is just easier for me to do it this way, I hope it is ok.

    The address for the example is http://www.piko.biz/redips/border

    Anyway, you will see the example is a simplified version of the sample tic tac toe game (with no game logic, just a couple of tables). There are two “target” tables with the only difference between them being that the bottom one (in cells marked 4,5 and 6) have a left hand border applied.

    If you drag the x-div to cells marked 4, 5 or 6 and approach it ONLY from the direct left, those cells do not accept the dragged div. If you approach 4, 5 or 6 from any other direction (top, right or bottom), they do work fine. On the top table, cells marked 1, 2 and 3 work fine with no border in all directions. Again, I only experience the problem coming directly from the left to the right on a left hand outer cell with a border.

    If this format is alright with you, I will also create an example of the keypress issue I was experiencing when I get a little time.

    Thanks!.

  13. I had a little time and put together a simple example of the keypress issue. But while doing this, I discovered the reason for the issue and there may not be a “solution”.

    I put together two “scenarios” (on a familiar looking table), both demonstrating the same thing. The first scenario has a div (marked “1”) hardcoded in the target table. If you try to do a keypress on this div, you will not get an alert. Beneath the table are two buttonswhich simply toggle the drag enabling for that div. If you disable the div, and then do a keypress, it will work fine.

    You can also drag over the X div to the target table and go through the same exercise. The way to toggle the enabling for these guys is to simply double click the div in the target table. And you get the same situation – works when it is disabled, doesn’t when enabled. And for both static and cloned nodes.

    Not sure why it took me so long to figure this out, but the reason that it doesn’t work is that the redips mousedown event ultimately returns false for an enabled cell(div) and hence, the enabled div never actually gets focus. And with no focus, no keypress event. Duh.

    Anyway, I don’t believe there is a way around this, but if you think of something, certainly let me know!

    If you should want to take a look at it, the example is at
    http://www.piko.biz/redips/keypress

    And again, thanks!

  14. @David – First I would like to thank you for preparing examples and detailed descriptions. First issue is shown in FireFox and IE8, but Chrome and Safari work just fine. I’m not sure which is your primarily browser but I will test REDIPS.drag code on “border” example with Mozilla FireFox (this will take some time).

    … and the second issue. Returning “false” from handlerOnMouseDown method is needed to disable text selection for non IE browsers. Try to add the following event handler to set DIV focus in case when user just clicks (and release) mouse button. Actually DIV element is not moved and this can be “a trigger” to set focus on DIV element:

    rd.event.notMoved = function () { 
        rd.obj.focus();
    };
    

    Hope this short code snippet will solve the “focus” problem.

  15. Understand the need to return false from the mousedown event, so no issue there. After I wrote the above note, though, I actually did think of a solution to the keypress and the approach is along the lines you mentioned. I just manually add focus (or blur depending on the condition) in the dropped event function and that is a very workable approach for me. The not moved event is a very good idea also and I will take a look at it. So hooray!!

    Insofar as the border example, I was able to do some CSS tricks to get around it, so it doesn’t affect me at all. It is just for your information if you should want to take a look at it at some point. But again, I am fine, so don’t do anything on my account!.

    Thanks so much for the conversation and feedback. I really do appreciate your time

  16. @David – Thanks for the feedback and I’m glad that problems are solved.

    Regards,
    Darko

  17. Very interesting library. Is it possible to dynamically change row-span and col-span of table cells whenever wider or taller content is moved around ?
    Let’s say you want to make a form editor, and have a table with source elements to insert into form. Some elements require two cells, and some need three. Dropping a three-cell element then would change colspan to “3” for the destination cell, and the two next cells removed.
    Thank you very much for this !!

  18. @Ole – Yes, REDIPS.drag and REDIPS.table libs can be combined and you will have drag and drop with dynamic table layout. With some basic JS skills it’s possible to build a nice front-end app.

    Thanks and and I’m glad you’re using REDIPS libs.

Leave a Comment