Knockout.js Table Sorting

This table shows ascending/descending by Multiple column headers

by CodeRenaissance

HTML

<script src="http://cloud.github.com/downloads/coderenaissance/knockout.mapping/knockout-1.3.0beta.js"></script>
<script src="http://cloud.github.com/downloads/coderenaissance/knockout.mapping/knockout.mapping.js"></script>
<div id="container">
    <table>
        <thead>
            <tr>
                <td data-bind="click:$root.actions.sortCol1"> col1 </td>
                <td data-bind="click:$root.actions.sortCol2"> col2 </td>
                <td data-bind="click:$root.actions.sortCol3"> col3 </td>
            </tr>
        </thead>
        <tbody data-bind="foreach:items">
            <tr>
                <td><span data-bind="text:$data.c1"/></td>
                <td><span data-bind="text:$data.c2"/></td>
                <td><span data-bind="text:$data.c3"/></td>
            </tr>
        </tbody>
    </table>
</div>

CSS

td{
    padding: 2px 10px;   
}

thead td{
    cursor: pointer;
    background:#ccc;   
}

thead td:hover{
    cursor: pointer;
    background:#eee;  
}

thead .ascending, thead .ascending:hover{
    background:red;
    color:black;
}


thead .descending, thead .descending:hover{
    background:blue;
    color: white
}

JavaScript

var SortManager = function(observableArrayOfObjects) {
    var ascendingTracker = {};
    var lastColumnSorted = "";
    var fnSort = function(columnName, isNumeric, target) {
        var isAscending, selected, unselected, repeatedColumnSort;

        repeatedColumnSort = (columnName == lastColumnSorted);
        lastColumnSorted = columnName;

        isAscending = ascendingTracker[columnName] = (repeatedColumnSort && ascendingTracker[columnName] ? false : true);

        selected = $(target);
        unselected = selected.siblings();

        selected.toggleClass('ascending', isAscending).toggleClass('descending', !isAscending);
        unselected.removeClass('ascending').removeClass('descending');

        observableArrayOfObjects.sort(

        function doNumericSort(left, right) {
            left = isNumeric ? (parseFloat(ko.utils.unwrapObservable(left[columnName])) || 0) : ko.utils.unwrapObservable(left[columnName]);
            right = isNumeric ? (parseFloat(ko.utils.unwrapObservable(right[columnName])) || 0) : ko.utils.unwrapObservable(right[columnName]);
            return (left === right ? 0 : (left < right ? -1 : 1)) * (isAscending ? 1 : -1);
        });
    };
    return {
        numericSort: function doNumericSort(columnName, target) {
            fnSort(columnName, true, target);
        },
        textSort: function doTextSort(columnName, target) {
            fnSort(columnName, false, target);
        }
    };
};

var model= {//this code is injected into the page on the server or retrieved via ajax
    items: [
        { c1: "d", c2: "210", c3: "1005"},
        { c1: "z", c2: "1", c3: "205"},
        { c1: "a", c2: "20", c3: "2005"}
    ]
};

var viewModel = ko.mapping.fromJS(model);//create a knockout viewModel

//add actions to your model    
viewModel.actions = {
    getSortManager: function() {
        return this.itemSortManager = this.itemSortManager || new SortManager(viewModel.items);
    },
    sortCol1: function(e) {
       ...