简体   繁体   中英

How to sort table rows using Javascript

Description

Table rows must be swapped at arbitrary positions in the table, ie row i and row j must change positions, where i and j are not necessarily adjacent. See current implementation below. Table rows should be sorted by column; a column is specified by sort_index which is generated by pressing one of the table headers.

The problem with the implementation is that the table is sorted incrementally by each click of a header, while it should be sorted by a single click.

var table_index = {
    watched: 0,
    title: 1,
    director: 2,
    year: 3
};

var sort_index = 0;

$(document).ready(function()
{
    var table_headers = document.getElementById("header-item").children;
    for (var k = 0; k < table_headers.length; k++)
    {
        $("#" + table_headers[k].id).bind("click", function(e)
        {
            sort_index = table_index[e.target.id];
            var table = document.getElementById("film-list");
            for (var i = 1; i < table.rows.length - 1; i++)
            {
                var a = table.rows[i].getElementsByTagName("td")[sort_index].innerHTML;
                for (var j = i + 1; j < table.rows.length; j++)
                {
                    var b = table.rows[j].getElementsByTagName("td")[sort_index].innerHTML;
                    var swap = 0;
                    switch (sort_index)
                    {
                    // Alphabetic sort
                    case 0:
                    case 1:
                    case 2:
                        if (b.toLowerCase() < a.toLowerCase())
                            swap = 1;
                        break;
                    // Numeric sort
                    case 3:
                        if (b - a < 0)
                            swap = 1;
                        break;
                    }
                    if (swap == 1)
                    {
                        $(".row-item").eq(i - 1).after(table.rows[j]);
                        $(".row-item").eq(j - 1).after(table.rows[i]);
                    }
                }
            }
        });
    }
});

Edit

It appears the real problem was related to closures inside loops. When a header is clicked, only the last table-row swap is actually updated in DOM, and as such multiple clicks are needed to sort the table properly.

I will post my own solution to this, to clarify the real problem.

I agree with Mr Polywhirl that doing this in the DOM itself probably isn't ideal (though it's entirely possible, see below). Modern web development leans toward using model/view/controller-style architectures (of various types) where your model (your actual data) is held separately from the view of it (the DOM), and the controller (the browser, DOM, and your code operating together), which takes actions upon your model (which are then reflected in the view). There are many popular MVC-style frameworks, probably the most significant as I write this (it changes over time) are React , Vue.js , and Angular . I've included a React example below.

But again, you can do this directly on the DOM. I see you're using jQuery, so I've used it below — See inline comments.

 // Hook `click` on the table header, but only call our callback if // that click passes through a `th` $(".sortable thead").on("click", "th", function() { // Which column is this? var index = $(this).index(); // Get the tbody var tbody = $(this).closest("table").find("tbody"); // Disconnect the rows and get them as an array var rows = tbody.children().detach().get(); // Sort it rows.sort(function(left, right) { // Get the text of the relevant td from left and right var $left = $(left).children().eq(index); var $right = $(right).children().eq(index); return $left.text().localeCompare($right.text()); }); // Put them back in the tbody tbody.append(rows); }); 
 td, th { padding: 4px; } th { cursor: pointer; } table { border-collapse: collapse; } table, td, th { border: 1px solid #ddd; } 
 To sort the rows alphabetically by a column's contents, click its header. <table class="sortable"> <thead> <th>English</th> <th>Spanish</th> <th>Italian</th> </thead> <tbody> <tr> <td>One</td> <td>Uno</td> <td>Uno</td> </tr> <tr> <td>Two</td> <td>Dos</td> <td>Due</td> </tr> <tr> <td>Three</td> <td>Tres</td> <td>Tre</td> </tr> <tr> <td>Four</td> <td>Cuatro</td> <td>Quattro</td> </tr> </tbody> </table> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 

It can be a bit shorter, but I wanted to be clear rather than hyper-concise.

Notice that this removes the rows, sorts them, and puts them back, rather than causing all kinds of in-place DOM modifications.

Here's the React example:

 // A React "component" for the table class MyTable extends React.Component { // Initializes the component constructor(props) { super(props); this.state = { // Start out unsorted, copying the array (but reusing the entries, as `row` // properties -- we do that so we can use their original index as a key) sorted: props.data.map((row, index) => ({index, row})), sortKey: null }; } // Sort the view sort(by) { // Update state... this.setState(({sorted, sortKey}) => { if (sortKey === by) { // ...no update needed, already sorting this way return; } // Copy the array, then sort it (never change state in place) sorted = sorted.slice(); sorted.sort((left, right) => left.row[by].localeCompare(right.row[by])); // Return the state updates return {sorted, sortKey: by}; }); } // Render the component per current state render() { const {sorted} = this.state; const {headers} = this.props; return ( <table className="sortable"> <thead> {headers.map(({title, lang}) => <th key={lang} onClick={() => this.sort(lang)}>{title}</th>)} </thead> <tbody> {sorted.map(({row, index}) => <tr key={index}> {headers.map(({lang}) => <td key={lang}>{row[lang]}</td>)} </tr> )} </tbody> </table> ); } } // Mount the component ReactDOM.render( <MyTable headers={[ {title: "English", lang: "en"}, {title: "Spanish", lang: "es"}, {title: "Italian", lang: "it"} ]} data={[ {en: "One", es: "Uno", it: "Uno"}, {en: "Two", es: "Dos", it: "Due"}, {en: "Three", es: "Tres", it: "Tre"}, {en: "Four", es: "Cuatro", it: "Quattro"} ]} />, document.getElementById("root") ); 
 td, th { padding: 4px; } th { cursor: pointer; } table { border-collapse: collapse; } table, td, th { border: 1px solid #ddd; } 
 To sort the rows alphabetically by a column's contents, click its header. <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> 

That uses various ES2015+ features, such as destructuring, arrow functions, and shorthand properties; and also uses JSX syntax (which isn't a JavaScript feature; it's handled by Babel in the Snippet).

You can either sort the HTML in-place or you can bind data to the table and re-render it whenever you need to sort.

I used TJ Crowder's code for in-place sorting, but turned it into a jQuery plugin and added a bindable table. You can see both examples below.

 (function($) { $.fn.sortable = function() { this.find('thead').on('click', 'th', function(e) { var columnIndex = $(this).index(); var $tbody = $(this).closest('table').find('tbody'); var rows = $tbody.children().detach().get(); rows.sort(function(left, right) { var $left = $(left).children().eq(columnIndex); var $right = $(right).children().eq(columnIndex); return $left.text().localeCompare($right.text()); }); $tbody.append(rows); }); return this; }; $.fn.renderTable = function(data) { var fields = Object.keys(data[0]); return this.renderTableHeaders(fields).renderTableRows(fields, data); }; $.fn.renderTableHeaders = function(fields) { return this.append($.renderTableHeaders(fields)); } $.fn.renderTableRows = function(fields, data) { return this.append($.renderTableRows(fields, data)); }; $.tableFromJson = function(data) { return $('<table>').renderTable(data); }; $.renderTableHeaders = function(fields) { return $('<thead>').append($('<tr>').append(fields .map(field => $('<th>').text(field)))); }; $.renderTableRows = function(fields, data) { return $('<tbody>').append(data .map((rec, row) => $('<tr>').append(fields .map((field, col) => $('<td>').text(rec[field]))))); }; $.bindableTable = function(data, sortable) { var $table = $.tableFromJson(data).addClass('bindable'); if (sortable) { $table.dataRef = data; $table.addClass('sortable').find('thead').on('click', 'th', function(e) { var dataIndex = $(this).text(); $table.dataRef.sort(function (a, b) { var left = new String(a[dataIndex]); var right = new String(b[dataIndex]); return left.localeCompare(right); }); var fields = Object.keys($table.dataRef[0]); $table.find('tbody').replaceWith($.renderTableRows(fields, $table.dataRef)); }); } return $table; }; })(jQuery); var jsonData = [ { "id": 1, "name": "John", "age": 24, "make": "Chevrolet", "model": "Silverado", "year": 2016 }, { "id": 2, "name": "Jack", "age": 36, "make": "Toyota", "model": "Corolla", "year": 2018 }, { "id": 3, "name": "Jill", "age": 29, "make": "Ford", "model": "Escape", "year": 2015 } ]; $('body').append($('<h1>').text('HTML sort')); $.tableFromJson(jsonData).addClass('stylized sortable').sortable().appendTo('body'); $('body').append($('<h1>').text('Databinding sort')); $.bindableTable(jsonData, true).addClass('stylized').appendTo('body'); 
 body { padding: 0.25em !important; } h1 { font-weight: bold !important; margin-top: 0.75em !important; margin-bottom: 0.33em !important; } table.stylized { font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif; font-size: 12px; text-align: left; border-collapse: collapse; margin: 4px; width: 600px; } table.stylized thead th { text-transform: capitalize; font-size: 13px; color: #039; background: #b9c9fe; padding: 6px; cursor: pointer; } table.stylized tbody tr:nth-child(odd) { background: #f2f5ff; } table.stylized tbody tr:nth-child(even) { background: #e8edff; } table.stylized tbody td { border-top: 1px solid #fff; color: #669; padding: 6px; } table.stylized tbody tr:hover td { background: #d0dafd; } 
 <link href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css" rel="stylesheet"/> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 

Description

As stated, the original problem was swapping of rows. It appears the real problem was related to closures inside loops. When a header is clicked, only the last table-row swap is actually updated in DOM, and as such multiple clicks are needed to sort the table properly.

Solution

One possible solution is to use the built-in sort function, as shown below.

var table_index = {
    watched: 0,
    title: 1,
    director: 2,
    year: 3
};

var sort_index = 0;

var tbody = $("tbody").children().get();

$(document).ready(function()
{
    var table_headers = $("thead").children();
    for (var k = 0; k < table_headers.length; k++)
    {
        $("#" + table_headers[k].id).bind("click", function(e)
        {
            sort_index = table_index[e.target.id];
            switch (sort_index)
            {
            // Alphabetic sort
            case 0:
            case 1:
            case 2:
                tbody.sort(function(a, b) {
                    var l = $(a).children().eq(sort_index).text();
                    var r = $(b).children().eq(sort_index).text();
                    if (r.toLowerCase() < l.toLowerCase())
                        return 1;
                    else if (r.toLowerCase() > l.toLowerCase())
                        return -1;
                    else
                        return 0;
                });
                break;
            // Numeric sort
            case 3:
                tbody.sort(function(a, b) {
                    var l = $(a).children().eq(sort_index).text();
                    var r = $(b).children().eq(sort_index).text();
                    return l - r;
                });
                break;
            }
            $("tbody").children().detach();
            $("tbody").append(tbody);
        });
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM