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> </th> <th> </th> <th>Title</th> <th> </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.