Drag and drop table rows with JavaScript

REDIPS.drag was initially built to drag and drop table content. After publishing first version of REDIPS.drag, I received a lot of questions about dragging table rows also. Now is possible to drag and drop table rows as well as table content. First column and contained DIV element in this demo is a row handler, so you can try to drag table rows.


REDIPS.drag example15

It is very easy to define a row handler. Actually, it is only needed to place DIV element to the column (first, last or any other) and to define class=”drag row”. This DIV element will become a row handler. When row dragging begins, source row will change color and content will become transparent. This way, source row as start point is visible and obvious. It is possible to return row to the start point – in this case, event.rowDroppedSource() will be fired. In a moment when row is dropped to the destination table, source row will be removed. How REDIPS.drag works? The trick is to clone a mini table from the source table and to remove all rows except selected row. It looks like a row, but it is a table with only one row – a mini table. New functionality also brings new event handlers:

  1. event.rowChanged
  2. event.rowClicked
  3. event.rowCloned
  4. event.rowDeleted
  5. event.rowDropped
  6. event.rowDroppedBefore
  7. event.rowDroppedSource
  8. event.rowMoved
  9. event.rowNotCloned
  10. event.rowNotMoved
  11. event.rowUndeleted

Each event handler has access to the obj and objOld objects. For example, event.RowClicked() sees only obj object and this is reference to the source row. event.rowMoved() is fired in a moment when dragging starts and in this case, obj is reference to the mini table (previously mentioned) while objOld is reference to the source table row.

REDIPS.drag has a new method: rowOpacity(el, opacity, color) to change color and opacity of the source row and mini table. This way it is only needed to call rowOpacity() method in event handlers to have row effects like in this demo. Here is code for event.rowMoved() used in this demo:

rd.event.rowMoved = function () {
    // set opacity for moved row
    // rd.obj is reference of cloned row (mini table)
    rd.rowOpacity(rd.obj, 85);
    // set opacity for source row and change source row background color
    // rd.objOld is reference of source row
    rd.rowOpacity(rd.objOld, 20, 'White');
    // display message
    msg.innerHTML = 'Moved';
};

REDIPS.drag takes care about background color of table cells and table rows. When dragging begins, color of each table cell is saved to the array and returned in a moment of dropping or highlighting current table row. Source code of REDIPS.drag library with examples can be download from “download icon” below post title. If you want to see more drag and drop examples based on REDIPS.drag, click on Drag and Drop category.

Happy dragging and dropping!

178 thoughts on “Drag and drop table rows with JavaScript”

  1. Hi dbunic,

    sorry about that. Im using IE 9, Also i am using the Yahoo UI Library, could that be a cause? Im trying to eliminate the possibility of a compatibility issue between libraries.

  2. Hi dbunic,

    sorry to waste your time, i did find the issue with my problem, turns out the doctype my application was using wasnt working with redips. I changed the doctype and it works.

    thanks, and i must say Redips is an amazing piece of code, keep up the awesome work.

  3. Hello dbunic, not to sure if you or anyone else is still monitoring these pages but in case you are I have what i hope to be a simple question for you.

    I’m currently using a modified version of your example 16 to display content on a right table from a draggable div ID on the left.

    I’m trying to further modify this example but instead of it displaying the page link from the ID on the right after you drop it from the left table to the dropzone I would like to be able to submit a query string with the values/IDs of the items that have been placed in the drop zone.

    I have tried ways of using a form to post the values of buttons or to a page that would then turn those values into a query string but so far I haven’t had much luck :(. I am hoping since you obvioulsy understand javascript better than I do that you might be able to help me with this.

    Again in short, I would like to submit the values of the ‘items’ dropped in the dropzone to a querystring to load a dynamically built page. I really hope you will be able to assist me.

    If you would like to talk more, I am available on Gtalk, AIM, MSN and always through email. I won’t post my screen names as of now, but if you wish you speak more I will certainly PM them to you.

  4. @Steven – No problem I will help you. It would be easier for me if you can zip and send me modified example16, so I can continue with scripting. When scripts will be finished, I will send you back the result. Hope this sounds OK. My email address is written on “About” page.

  5. Is it possible to drop a row to the BOTTOM of an existing row in a target table? Currently the behavior is to place the dragged row above the last row in the target table.

  6. @Jany – In REDIPS.drag version 4.6.20 is added REDIPS.drag.row_position public parameter with possible values before and after. The purpose of this parameter is to define position of dragged row where it will be dropped regarding highlighted row (before or after highlighted row). This property has effect only if row is dropped to other tables. Please see example15 and try to drag rows from upper table to the bottom table or vice versa:

    http://www.redips.net/my/preview/REDIPS_drag/example15/

    Here is snippet from script.js file:

    // drop row after highlighted row (if row is dropped to other tables)
    // possible values are "before" and "after"
    rd.row_position = 'after';
    

    In case of only one table position is defined relatively to source row position (middle row will be dropped before highlighted row if dragged to the table top or after highlighted row in other case).

  7. Thanks for the quick reply. It also appears that when I move from one table to another table programmatically (i.e. using the “rd.move_object() mode: ‘row’, etc) if I move all existing rows from the source table to the target table, there is no way to reintroduce rows back into the source table. In other words, the source table becomes locked (apparently due to lack of a empty placeholder row).

    Any suggestion for dealing with this issue?

  8. @Jany – It’s possible that you have found a bug in REDIPS.drag library. I will try to see where’s the problem and publish a fix soon. Thank you very much!

  9. Thanks for the “empty row” fix, I’m in the process of testing it out.

    Meanwhile, I’ve noticed that any code that is in the callback function of the REDIPS.drag.move_object function actually gets called before the row is situated in the destination table. For example, I want to do some calculation on the last row (in a series of moves) that is moved to my destination table. Unexpectedly I discovered that the callback method executes the code before the last row is a member of the destination table.

    I’m tempted to add a dummy move (i.e. source and destination row are the same) to the end of the last move so that the table has the correct number of anticipated rows but I’m just wondering if you have any advice on dealing with this issue.

  10. @Jany – As you can see from source code:

        // jump to the line 3484 of redips-drag.js
        // animation is finished
        else {
            // reset object styles
            reset_styles(p.obj);
            // set animation flag to false (to enable DIV dragging)
            p.obj.redips.animated = false;
            // if moved element is cell then append element to the target cell
            if (p.mode === 'cell') {
                // if overwrite parameter is set to true then empty target_cell
                if (p.overwrite === true) {
                    // empty target cell
                    empty_cell(p.target_cell);
                }
                p.target_cell.appendChild(p.obj);
                // register event listeners (FIX for Safari Mobile)
                register_events(p.obj);
            }
            // else element is row
            else {
                // take care about real table index
                row_drop(get_table_index(p.target[0]), p.target[1], p.obj);
            }
            // execute callback function if callback is defined and send ref of moved element
            if (typeof(p.callback) === 'function') {
                p.callback(p.obj);
            }
        }
    };
    

    … callback function is called after row (or DIV) is appended to the table. I think the problem is in browser behaviour to minimize DOM changes because DOM is slow and that it’s one of the most common sources of performance issues. Here is link with complete explanation:

    http://www.nczonline.net/blog/2009/02/03/speed-up-your-javascript-part-4/

    In other words, callback function is called before browser updates DOM (reflow). Maybe the solution is to force browser to reflow before calling callback function. Detailed explanation of how to force reflow can be found here (search for the Browsers are smart part):

    http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/

    So, maybe the reading offsetTop property will force browser to reflow page. Can you try to add the following lines before callback to see if this will do the trick:

            }
            // else element is row
            else {
                // take care about real table index
                row_drop(get_table_index(p.target[0]), p.target[1], p.obj);
                // force browser to reflow (flush DOM changes)
                var tbl_idx = get_table_index(p.target[0]);
                var reflow = tables[tbl_idx].style.offsetTop;
            }
            // execute callback function if callback is defined and send ref of moved element
            if (typeof(p.callback) === 'function') {
                p.callback(p.obj);
            }
        }
    };
    

    Hope this will do the positive result. Thanks in advance for the feedback.

  11. EXCELLENT code dbunic – just what I was looking for. However, I’m having a problem with posting form values after dragging and dropping rows.

    I have a table inside a form with form fields in the rows. If I change the form fields and submit the page, I can see all of the fields from the Request.Form collection.

    Now, if I re-order the table rows using your drag and drop functionality and then submit the form, any rows which I have dragged are removed from the form and do not show in the Request.Form collection.

    Any ideas? Thanks.

    Tony

  12. Sorry, to clarify my above question:

    The rows drag and drop successfully, but when I submit the form which contains the table with dragged and dropped rows, the form items which had been in the dragged & dropped rows do not appear in the Request.Form collection of form objects. Prior to dragging and dropping, all form items appear correctly in the Request.Form collection of form objects.

    Tony

  13. @Tony – I have made changes locally in example15. Three text boxes (f1, f2 and f3) are added to the row in upper table. Both tables are wrapped with <form action=”save.asp”>. After entering strings to the text boxes “123”, “456” and “789”, drag-n-drop row across both tables and clicking on the Submit button I can see URL with complete query string like:

    localhost/example15/save.asp?f1=123&f2=456&f3=789
    

    It seems that all parameters are send to the server side correctly. I have made tests in IE8 and latest Chrome. Anyway, I will zip prepared example and send it to you so we can discuss further if needed.

    … and I’m glad that you like my work – thanks!

  14. Thanks dbunic,
    I tested your fix for the “empty rows” in 4.6.21 and it works as long as one is only using the move method to transfer rows from one table to another; whenever I try and add a manual drag and drop into the routine and then use the move method afterward, I get an error (SCRIPT5007: Unable to get value of the property ‘cells’: object is null or undefined, redips-drag.js, line 3365 character 3) from the redips library in the move method where it encounters the empty row and it’s looking for the cells[] property but it apparently doesn’t exist for that empty row. This error occurs when I’m trying to reintroduce rows back into an empty table that was previously populated.

    As for the reflow problem I was having, I believe that you have properly diagnosed my problem but the two lines that you wanted me to add to the the redips code did not seem to slow down IE8/IE9 although I’m not sure I’d know if it did slow it down beyond the fact that my callback code was still being executed before the rows were situated in the target table. Perhaps, I have to experiment with other calls. Nevertheless, as you have noted, this issue is really a browser one and has nothing to do with this wonderful row dragging library. It’s also not a show stopper as I have employed a suitable workaround; I gather some data in my callback method that helps me understand what DOM changes have gone into effect that way I can decide what I need to do next.

    Incidentally, I’ve noticed that when dragging and dropping rows, you need to be somewhat precise so as to make sure you drop the rows exactly within the confines of the target table. Now if the target table only has one row and one cell, this becomes a small target area for the person dragging to work with. I’m just wondering if there is any way to make the target dropping area larger than the actual table (table width-wise) so that there is a little room for error? Not a big issue, just wondering.

    Finally, I have noticed that if one creates multiple tables within a div that has the drag class (i.e. like your Example 10) but instead of dragging cells the table is outfitted with rowhandlers and I drag rows from one table to another, when I scroll up in that scrollable div the other tables aren’t drag and drop enabled and yet the move method still works. Once I make the div stationary (i.e. not scrollable) then the tables seem to work again and all is well. Have you noticed this behavior before? Is there a property that I’m not setting or is the row dragging limited to stationary tables?

    Thanks for all your responses so far!

  15. @Jany – It seems that added lines work only in case of move_object() method and not when user drags rows. Added lines have to be better polished but if there isn’t any improvement in forcing browser to reflow DOM than maybe other style requests will do the trick. This is a trial and error approach.
    ;)

    If I understood well, you have found a workaround solution and forcing a browser to reflow before callback function isn’t critical at this moment. I’m glad this issue is resolved. Well, JavaScript is wild, everything is executed in paralell – non-blocking way. On the other hand browsers are trying to be as much efficient/fast as they can (using all variants of tricks) and JavaScript developer is somewhere in between.

    When dragging DIV element or a row, target TD/TR is defined by the boundaries of the table cell/row below mouse pointer. So, only mouse pointer location is used to calculate current position of cells/rows in drag and drop process no matter how large object is dragged. REDIPS.drag currently doesn’t have a property to define highlighting extension in case of dragging table rows, maybe this will be added in the future releases.

    Drag and drop table rows and scrollable DIV container should work nicely. Here are steps how to modify example15 with scrollable container. Wrap second table with DIV element:

    &lt:div class="sc">
        <table>
        <colgroup>
        ...
    </div>
    

    And add the following styles for “sc” class:

    /* scrollable container */
    .sc {
        height: 200px;
        overflow: auto;
        position: relative;
    }
    

    Position style should be set to something other than the default “static”. This way, it will appear in the offsetParent chain and scroll position will be calculated properly. Hope this answer will be useful for you.
    Cheers!

  16. Each of my tables are surrounded in divs that have a class with the information that you suggest (i.e. height: 200px; overflow: auto; position: relative;) but where my setup is different is that all of these table-containing divs with the “sc” class are inside of one larger all encompassing div which itself is scrollable and I think that is where the problem is. Whenever I scroll the larger div that contains all the smaller “sc” class divs, the drag area is lost. Another way to realize this issue is to make the scrollable by adding a scrollable class to it. All the inner divs with tables are ok (i.e. drag and drop-able) as long as you don’t scroll the “” container.

  17. @Jany – Now I understand. Unfortunately, nested scrollable DIV containers are not supported. In this case one scrollable area is overwritten with another and error as you described is happening. Everyting is fine until inner scrollable DIV is on the top position. In a moment when inner DIV is scrolled then some part of table becomes unaccessible. I suggest to avoid top scrollable DIV container if possible.

Leave a Comment