Vertical Reordering of HTML Table Rows Using Shield UI


Vladimir Georgiev, September 2019
Computer Science Department, American University in Bulgaria

Introduction

HTML table elements are used to represent tabular data in an optimized way. Different data elements are organized in rows and columns, presented on the page. In this article, we will demonstrate how to use the Shield UI Draggable component for changing the order of table rows by dragging them with the mouse.

The default behavior of HTML tables can be enhanced by adding various customizations like styling, buttons and handling specific events. Examples of that are the Bootstrap table CSS and the Shield UI Grid, which can be used to develop complex user interface scenarios.

The example below shows how you can add row reordering to the HTML table element styled with Bootstrap, which is general and can be applied to all table widgets like Shield UI's and others.

Requirements and Dependencies

Our example will use the jQuery, Shield UI and Bootstrap libraries. Hence you need to include the following resources in your HTML head section:

    <!-- jQuery and Shield UI includes -->
    <link id="themecss" rel="stylesheet" type="text/css" href="https://www.shieldui.com/shared/components/latest/css/light/all.min.css" />
    <script type="text/javascript" src="//www.shieldui.com/shared/components/latest/js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="//www.shieldui.com/shared/components/latest/js/shieldui-all.min.js"></script>

    <!-- Bootstrap includes -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

The HTML Markup

To represent our data, we add a standard HTML table element and an "Add Row" button to the page body. The button will be used to add new rows to the table:

<table class="table table-hover" id="myTable">
    <thead>
        <th>&nbsp;</th>
        <th>&nbsp;</th>
        <th>Title</th>
        <th>&nbsp;</th>
    </thead>
    <tbody>
        <tr>
            <td><span class="glyphicon glyphicon-menu-hamburger" style="color:#bbb; cursor:move;"></span></td>
            <td>1.</td>
            <td>Cormen, Leiserson, Rivest, <em>Introduction to Algorithms</em></td>
            <td><button type="button" class="btn btn-sm btn-danger">Delete</button></td>
        </tr>
        <tr>
            <td><span class="glyphicon glyphicon-menu-hamburger" style="color:#bbb; cursor:move;"></span></td>
            <td>2.</td>
            <td>Weiss, <em>Data Structures and Problem Solving Using C++</em></td>
            <td><button type="button" class="btn btn-sm btn-danger">Delete</button></td>
        </tr>
        <tr>
            <td><span class="glyphicon glyphicon-menu-hamburger" style="color:#bbb; cursor:move;"></span></td>
            <td>3.</td>
            <td>Friedman, Koffman, <em>Problem Solving, Abstraction and Design Using C++</em></td>
            <td><button type="button" class="btn btn-sm btn-danger">Delete</button></td>
        </tr>
    </tbody>
</table>
<br />
<button id="myAddButton" type="button" class="btn btn-success">Add Row</button>

HTML Styles

To make the table more visually attractive and improve the drag/drop experience, we add the following styles section to the head:

<style>
    /* table styles */
    #myTable { width: 60%; }
    #myTable tr { cursor: move; }
    #myTable td { vertical-align: middle; }
    #myTable tr td:first-child { width: 80px; }
    #myTable tr td:nth-child(2) { text-align: right; }

    /* drag styles */
    .dragged { background-color: white; color: white; border-color: white; }
    .dragged td * { visibility: hidden; }
</style>

The JavaScript

The final piece is the JavaScript code that connects the HTML table with the Shield UI Draggable component. All the functions below are added to the same jQuery closure having the form:

<script>
jQuery(function($) {
    // add code here ...
});
</script>

First we will add the function that will be called to handle changes in the row order - i.e. on events like dragging, inserting new rows and deletion of rows:

    var onAfterReordering = function() {
        // here we just fix the numbers shown for each row

        // in reality, one can store the order information as an attribute of the row, 
        // or using jQuery.data on the row object, etc
        // this can be used to determine how the order has changed after dragging finishes

        $("#myTable tbody tr").each(function(index, row) {
            $($(row).find("td").get(1)).html((index + 1) + ".");
        });
    };

The most important part is the code that initializes dragging for each row from our table. Below is the definition of the function that does this for a single row:

    var initRowReordering = function(row) {
        $(row).css("cursor", "move");

        $(row).shieldDraggable({
            helper: function (params) {
                // the draggable helper is the element used as a preview of the element being dragged
                // it can be a copy, or the actual element

                // here we create a copy of the dragged row and add it in a table, 
                // so that the styles can be applied
                var helper = $('<table class="table table-hover"></table>');
                var tbody = $('<tbody />').appendTo(helper);
                tbody.append(row.clone());

                // fix the style of the TDs in the helper row - widths are copied from the original row
                // this will make the drag helper look like the original
                helper.find('td').each(function (index) {
                    $(this).width($(row.find('td')[index]).width());
                });
                helper.width(row.width());

                return helper;
            },
            events: {
                start: function (e) {
                    // add a custom class to the dragged element
                    // this will "hide" the row being dragged
                    $(row).addClass("dragged");
                },
                drag: function (e) {
                    // as the element is dragged, determine where to move the dragged row
                    var element = $(e.element),
                        elTopOffset = element.offset().top;

                    var rows = $(row).siblings('tr').not('.dragged').get();

                    for (var i = 0; i  elTopOffset) {
                            $(row).insertBefore($(rows[i]));
                            break;
                        }

                        // if last and still not moved, check if we need to move after
                        if (i >= rows.length - 1) {
                            // move element to the last - after the current
                            $(row).insertAfter($(rows[i]));
                        }
                    }
                },
                stop: function (e) {
                    // dragging has stopped - remove the added classes
                    $(row).removeClass("dragged");

                    // cancel the event, so the original element is NOT moved 
                    // to the position of the handle being dragged
                    e.cancelled = true;
                    e.skipAnimation = true;

                    // call the on-after-reorder handler function right after this one finishes
                    setTimeout(onAfterReordering, 50);
                }
            }
        });
    };

Last, we will add the code that will initialize the row reordering for each row, add the event handlers for the Delete buttons on each row, and the code that gets executed when the "Add Row" button is clicked:

    // initializes the row reordering for each row
    $("#myTable tbody tr").each(function () {
        initRowReordering($(this));
    });

    // clicking the Delete button should delete the row and resync ordering information
    $("#myTable tbody tr button.btn-danger").click(function() {
        $(this).closest('tr').remove();
        onAfterReordering();
    });

    // adding a new row initializes reordering for it, as well as refreshes the ordering information
    $("#myAddButton").click(function() {
        var title = prompt("Enter new title: ");
        if (title) {
            var row = $('<tr>' + 
                '<td><span class="glyphicon glyphicon-menu-hamburger" style="color:#bbb; cursor:move;"></span></td>' + 
                '<td></td>' + 
                '<td>' + title + '</td>' + 
                '<td><button type="button" class="btn btn-sm btn-danger">Delete</button></td>' + 
                '</tr>').appendTo($("#myTable tbody"));
            initRowReordering(row);
            onAfterReordering();
        }
    });

The complete code can be seen on this page.