Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ It's also possible to sort by nested attibutes by separating the attribute names

<th><a href="#" data-bind="orderable: {collection: 'people', field: 'pet.name'}">Pet name</a></th>

Secondary sort is possible by passing the `thenBy` param with comma separated attribute names:

<th><a href="#" data-bind="orderable: {collection: 'people', field: 'salary', thenBy: 'firstName, age'}">Pet name</a></th>

See full examples in examples folder.

##Dependencies
Expand Down
43 changes: 43 additions & 0 deletions examples/then-by-sort.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<html>
<head>
<title>Basic Example | knockout.bindings.orderable.js </title>
<link href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.0.4/css/bootstrap.min.css" rel="stylesheet">
<link href="./css/examples.css" rel="stylesheet">
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.0/knockout-min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.4/knockout.mapping.js"></script>
<script type="text/javascript" src="../knockout.bindings.orderable.js"></script>
<script type="text/javascript">
$(function() {
vm = ko.mapping.fromJS({
people: [
{firstName: "Mike", lastName: "Smith", age: 34},
{firstName: "Ben", lastName: "Miller", age: 23},
{firstName: "Ben", lastName: "Miller", age: 18},
{firstName: "Rebecca", lastName: "Abbarno", age: 25},
{firstName: "Rebecca", lastName: "Zanc", age: 22},
{firstName: "Rebecca", lastName: "Barlic", age: 27},
]
});
ko.applyBindings(vm);
});
</script>
</head>
<body>

<table class="table">
<thead>
<th><a href="#" data-bind="orderable: {collection: 'people', field: 'firstName', thenBy: 'lastName, age'}">First Name</a></th>
<th><a href="#" data-bind="orderable: {collection: 'people', field: 'lastName'}">Last Name</a></th>
<th><a href="#" data-bind="orderable: {collection: 'people', field: 'age', defaultField: true, defaultDirection: 'desc'}">Age</a></th>
</thead>
<tbody data-bind="foreach: people">
<tr>
<td data-bind="text: firstName"></td>
<td data-bind="text: lastName"></td>
<td data-bind="text: age"></td>
</tr>
</tbody>
</table>
</body>
</html>
95 changes: 80 additions & 15 deletions knockout.bindings.orderable.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,87 @@
return o;
},

// Extracts value from a field if its a function or not.
getVal: function(object, string){
// first getProperty/field out of object
var field = ko.bindingHandlers.orderable.getProperty(object, string);
// then get the val if its a function or not.
return (typeof field === 'function') ? field() : field;
},

compare: function (left, right) {
if (typeof left === 'string' || typeof right === 'string') {
return left ? left.localeCompare(right) : 1;
}

left = left == null ? '' : left;
right = right == null ? '' : right;

//if (typeof left === 'string' || typeof right === 'string') {
// return left ? left.localeCompare(right) : 1;
//}

if (left > right)
return 1;

return left < right ? -1 : 0;
},

sort: function (viewModel, collection, field) {
//get all sort results of thenBy fields
sortThenBy: function(left, right, field, thenBy, orderDirection){
var sortResults = [];

if(!thenBy) return sortResults;

var thenByFields = thenBy.split(','); // extract fields
//console.log('sortResults', thenByFields, left, right, field);

for (var i = 0; i < thenByFields.length; i++) {

var tbField = thenByFields[i].trim();
var lv = ko.bindingHandlers.orderable.getVal(left, tbField);
var rv = ko.bindingHandlers.orderable.getVal(right, tbField);
var sort = 0;

if(orderDirection == "desc") {
sort = ko.bindingHandlers.orderable.compare(rv, lv);
} else {
sort = ko.bindingHandlers.orderable.compare(lv, rv);
}

//console.log('sortResults', lv, rv, sort);
sortResults.push(sort);
}

return sortResults;
},

sort: function (viewModel, collection, field, thenBy) {
var orderDirection = viewModel[collection].orderDirection();

//make sure we sort only once and not for every binding set on table header
if (viewModel[collection].orderField() == field) {
viewModel[collection].sort(function (left, right) {
var left_field = ko.bindingHandlers.orderable.getProperty(left, field);
var right_field = ko.bindingHandlers.orderable.getProperty(right, field);
var left_val = (typeof left_field === 'function') ? left_field() : left_field;
right_val = (typeof right_field === 'function') ? right_field() : right_field;
if (viewModel[collection].orderDirection() == "desc") {
return ko.bindingHandlers.orderable.compare(right_val, left_val);
var leftVal = ko.bindingHandlers.orderable.getVal(left, field);
var rightVal = ko.bindingHandlers.orderable.getVal(right, field);

// these will hold all fields for the thenBy fields
// evaluate all thenBy compare first
var thenByResults = ko.bindingHandlers.orderable.sortThenBy(left, right, field, thenBy, orderDirection);

var sort = 0;

if (orderDirection == "desc") {
sort = ko.bindingHandlers.orderable.compare(rightVal, leftVal);
} else {
return ko.bindingHandlers.orderable.compare(left_val, right_val);
sort = ko.bindingHandlers.orderable.compare(leftVal, rightVal);
}

// sort then by fields in same order
if(thenByResults.length > 0){
for (var i = 0; i < thenByResults.length; i++) {
sort = sort || thenByResults[i];
}
}

return sort;
});
}
},
Expand All @@ -46,21 +104,27 @@
//get provided options
var collection = valueAccessor().collection;
var field = valueAccessor().field;
var thenBy = valueAccessor().thenBy;

//add a few observables to ViewModel to track order field and direction
//add a few observables to ViewModel to track order field, direction, and then by fields
if (viewModel[collection].orderField == undefined) {
viewModel[collection].orderField = ko.observable();
}
if (viewModel[collection].orderDirection == undefined) {
viewModel[collection].orderDirection = ko.observable("asc");
}
if (viewModel[collection].orderThenByFields == undefined) {
viewModel[collection].orderThenByFields = ko.observable();
}

var defaultField = valueAccessor().defaultField;
var defaultDirection = valueAccessor().defaultDirection || "asc";
var defaultThenBy = valueAccessor().defaultThenBy || null;
if (defaultField) {
viewModel[collection].orderField(field);
viewModel[collection].orderDirection(defaultDirection);
ko.bindingHandlers.orderable.sort(viewModel, collection, field);
viewModel[collection].orderThenByFields(defaultThenBy);
ko.bindingHandlers.orderable.sort(viewModel, collection, field, thenBy);
}

//set order observables on table header click
Expand All @@ -77,14 +141,15 @@
}

viewModel[collection].orderField(field);
viewModel[collection].orderThenByFields(thenBy);
});

//order records when observables changes, so ordering can be changed programmatically
viewModel[collection].orderField.subscribe(function () {
ko.bindingHandlers.orderable.sort(viewModel, collection, field);
ko.bindingHandlers.orderable.sort(viewModel, collection, field, thenBy);
});
viewModel[collection].orderDirection.subscribe(function () {
ko.bindingHandlers.orderable.sort(viewModel, collection, field);
ko.bindingHandlers.orderable.sort(viewModel, collection, field, thenBy);
});
},

Expand Down