Saturday, 5 November 2016

DataTable Row Re-order

I got a little stumped by a Question on stackoverflow. The poster asked about swapping rows within a DataTable and I've never had a need to do that so I got to thinking about how it might be done. The poster also got into a wee bit of tizzy over the use of this so that was the first thing I corrected. Afterwards, it kept playing on my mind as an interesting challenge so this morning I decided to scratch that itch. I clocked that I needed an artificial index so added a hidden column to the table upon which to sort the rows:

<div class="container">
    <table cellpadding="0" cellspacing="0" border="0" class="table" id="example">
        <thead>
            <tr>
                <th>Index</th>
                <th>Rendering engine</th>
                <th>Browser</th>
                <th>Platform(s)</th>
                <th>Swap Down</th>
                <th>Swap Up</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <th>0</th>
                <td>Trident</td>
                <td>Internet Explorer 4.0</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>1</th>
                <td>Trident</td>
                <td>Internet Explorer 5.0</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>2</th>
                <td>Trident</td>
                <td>Internet Explorer 5.5</td>
                <td>Win 95+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>3</th>
                <td>Trident</td>
                <td>Internet Explorer 6</td>
                <td>Win 98+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>4</th>
                <td>Trident</td>
                <td>Internet Explorer 7</td>
                <td>Win XP SP2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>5</th>
                <td>Trident</td>
                <td>AOL browser (AOL desktop)</td>
                <td>Win XP</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>6</th>
                <td>Gecko</td>
                <td>Firefox 1.0</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>7</th>
                <td>Gecko</td>
                <td>Firefox 1.5</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>8</th>
                <td>Gecko</td>
                <td>Firefox 2.0</td>
                <td>Win 98+ / OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>9</th>
                <td>Gecko</td>
                <td>Firefox 3.0</td>
                <td>Win 2k+ / OSX.3+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>10</th>
                <td>Gecko</td>
                <td>Camino 1.0</td>
                <td>OSX.2+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>11</th>
                <td>Gecko</td>
                <td>Camino 1.5</td>
                <td>OSX.3+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>12</th>
                <td>Gecko</td>
                <td>Netscape 7.2</td>
                <td>Win 95+ / Mac OS 8.6-9.2</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>13</th>
                <td>Gecko</td>
                <td>Netscape Browser 8</td>
                <td>Win 98SE+</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th>14</th>
                <td>Misc</td>
                <td>Lynx</td>
                <td>Text only</td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>
</div>

This JS seems to do the trick in terms of swapping rows:

$(function() {
    var table = $("#example").DataTable({
        "order": [
            [0, 'asc']
        ],
        "paging": true,
        "columns": [{
            "visible": false
        }, {
            "orderable": false
        }, {
            "orderable": false
        }, {
            "orderable": false
        }, {
            "render": function(d) {
                return $("<i/>", {
                    "class": "fa fa-caret-down swapable swapDown"
                }).prop("outerHTML");
            },
            "orderable": false
        }, {
            "render": function(d) {
                return $("<i/>", {
                    "class": "fa fa-caret-up swapable swapUp"
                }).prop("outerHTML");
            },
            "orderable": false
        }]
    });
    $('#example tbody').on('click', 'td', function(event) {
        // We're only interested in cells with a class of swapable
        if ($(this).find(".swapable")) {
            // Helper variable
            var _this = $(this).find(".swapable");
            // Total number of rows (including hidden rows (zero based index))
            var tableRows = table.data().length - 1;
            // Index of current row
            var rowIndex = table.row(this).index();
            // Data of current row
            var rowData = table.row(this).data();
            // Index value of row (artifical because it's ours) - also tempval
            var artificalIndex = ~~rowData[0];
            /*
             * If we're on the bottom row of the table and the direction of travel is downwards  or if we're
             * on the top row and the direction of travel is upwards then we need to just swap the top and
             * bottom rows
             */
            if(
                    (_this.hasClass("swapDown") && artificalIndex === tableRows)
                    ||
                    (_this.hasClass("swapUp") && artificalIndex === 0)
            ){
                var topIndex, bottomIndex;
                table.rows().every(function(rowIdx, tableLoop, rowLoop) {
                    var data = this.data();
                    if(~~data[0] === 0){
                        topIndex = rowIdx;
                    }
                    if(~~data[0] === tableRows){
                        bottomIndex = rowIdx;
                    }
                });
                table.cell(topIndex, 0).data(tableRows);
                table.cell(bottomIndex, 0).data(0);
            }else{
                var movingIndex, tempData;
                table.rows().every(function(rowIdx, tableLoop, rowLoop) {
                    var data = this.data();
                    // Moving down
                    if (_this.hasClass("swapDown") && ~~data[0] === artificalIndex + 1) {
                        movingIndex = rowIdx;
                        tempData = data[0];
                    }
                    // Moving up
                    if (_this.hasClass("swapUp") && ~~data[0] === artificalIndex - 1) {
                        movingIndex = rowIdx;
                        tempData = data[0];
                    }
                });
                table.cell(rowIndex, 0).data(tempData);
                table.cell(movingIndex, 0).data(artificalIndex);
            }
            table.draw(false);
        }
    });
});

I'm happy that it works but it does seem a little inefficient and I'm interested in how it might be improved if anyone can come up with any ideas? The working JSFiddle is here so please use that as a base upon which to work.

One of my main issues was figuring out how to cope with people clicking the up icon on the top row or the down icon on the bottom row, then I clocked that it merely meant that the top and bottom rows needed to be swapped. I also ran into difficulty figuring out the index of rows as I'm pretty sure it isn't updated after a draw as I kept running into issues... this is the reason I introduced my own, artificial, index in the hidden first column.