Sorting an Observable Array in Knockout

knockout.js

knockout.js Problem Overview


I have an observable array in Knockout of person objects. I wanted to be able to sort the list of persons based on the last name. The problem is the list has a number of duplicate last names. The result is that when there are more than one last name, the first names appear as they were found. I'd like to be able to sort the array by last name and when there are more than one last name, have it also sort by first name. I'm using a text input for the user to start typing in the last name. The results are bound to a template that displays all the matches.

<input data-bind="value: filter, valueUpdate: 'afterkeydown'">

And here is my Knockout array filter code:

function Item(firstname, lastname) {
     this.firstname = ko.observable(firstname);
     this.lastname = ko.observable(lastname);
}

var playersViewModel = {
     items: ko.observableArray([]),
     filter: ko.observable("")
};
var players;

$(function() {
    playersViewModel.filteredItems = ko.computed(function() {
         var filter = this.filter().toLowerCase();
         if (!filter) {
              return this.items();
         } else {
              return ko.utils.arrayFilter(this.items(), function(item) {
                    return ko.utils.stringStartsWith(item.lastname().toLowerCase(), filter);
              });
         }
    }, playersViewModel);
    
    $.getJSON('./players.json', function(data) {
        players = data.players;
        playersViewModel.players = ko.observableArray(players);
        ko.applyBindings(playersViewModel);    
        var mappedData = ko.utils.arrayMap(players, function(item) {
             return new Item(item.firstname,item.lastname);
        });
        playersViewModel.items(mappedData);
    });    
});

For last name filtering this works fine, but I'm unable to find a way to add in sorting first name when there are duplicate last names. For example, in my array when sorted by last name I'm getting:

Joe Bailey
Jack Brown
Adam Brown
Bob Brown
Jim Byrd

I'd like the duplicate last names to have their first names sorted as well:

Joe Bailey
Adam Brown
Bob Brown
Jack Brown
Jim Byrd

knockout.js Solutions


Solution 1 - knockout.js

KnockoutJS observable arrays offer a sort function, this makes it relatively easy to sort a databound array in your UI (regardless of the natural order of the data.)

data-bind="foreach: items.sort(function (l, r) { return l.lastName() > r.lastName() ? 1 : -1 })"

You should not have to pre-sort/re-sort your source data, if you're sorting before binding (or rebinding due to a sort) then you're doing it wrong.

That said, what you're asking is to sort by two datum, last name, then first name.

Instead of "l.lastName() > r.lastName() ? 1 : -1" consider the following:

l.lastName() === r.lastName() 
    ? l.firstName() > r.firstName() ? 1 : -1
    : l.lastName() > r.lastName() ? 1 : -1

This could be more efficient, I'm sure. Basically you have three conditionals at play:

  1. Are the last names Equal?
  2. If so, compare first names and return a result.
  3. Else, compare last names and return a result.

I've scanned your code and I see no such sort function in place.

This is similar to Michael Best's answer, however, I've attempted to clarify WHERE to handle sorting (in your binding, first example) and HOW to achieve the sort you're looking for (multiple datum.)

data-bind="foreach: items().sort(function (l, r) { return (l.lastName() == r.lastName()) ? (l.firstName() > r.firstName() ? 1 : -1) : (l.lastName() > r.lastName() ? 1 : -1) })"

Naturally, this can get unwieldy as you introduce more sort vectors, such as reversals or additional datum, and so you should implement a filter function that performs the above evaluation for you:

data-bind="foreach: items().sort(my.utils.compareItems)"

Solution 2 - knockout.js

If you make sure your players.json returns the names sorted, you'll be fine. If it's loading them from a database, you need to add the first name field to your ORDER BY clause.

If you want to do the sorting in Javascript, you could do it right after you load it from the service:

players.sort(function(player1, player2) {
    return player1.lastname.localeCompare(player2.lastname) ||
        player1.firstname.localeCompare(player2.firstname);
});

Solution 3 - knockout.js

I had some issues trying to sort a observable array, I was not seeing any results in my code.

You need to sort the results you receive via ajax/getJSON request first, before you return the new items into your array.

This way the results are sorted before they are added into your observable array as new items. No need to sort them at the bindings then.

See the example below.

players(ko.utils.arrayMap(result, function (item) {
		            result.sort(function (l, r) {
		                return l.name == r.name ? 0 : (l.name < r.name ? -1 : 1);
		            });
				return new Player(item);
			}));
  1. get data using AJAX / getJSON

  2. Sort the result of that request into your array of choice

Solution 4 - knockout.js

You have to use Two sort to do this .In first sorting , sort according to persons last name And In 2nd Sorting first Search according to last name if more than two persons have same last name than sort these according to their first name at their location , using some Standard sorting Algorithm ....

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1715156View Question on Stackoverflow
Solution 1 - knockout.jsShaun WilsonView Answer on Stackoverflow
Solution 2 - knockout.jsMichael BestView Answer on Stackoverflow
Solution 3 - knockout.jsEttore RaimondiView Answer on Stackoverflow
Solution 4 - knockout.jsjohnView Answer on Stackoverflow