Ric
Ric

Reputation: 405

knockout.js sorted observable array

I would like to have an observable array which will sort itself when an object is pushed into it (it would be even better if it would sort itself if any of the values it was using in the comparator function was changed).

Something where you could define the comparator function you want the array to sort on and then every time push was called it would add the pushed objects into the correct place in the array so the array remained sorted, like:

var sortedArray = ko.sortedObservableArray(
    function (a,b) { return b - a;},
    [1,7,4]
); // sortedArray will be [1,4,7]
sortedArray.push([5,2]); // sortedArray will now be [1,2,4,5,7]

Are there any libraries that will do this for me and if not what is the best way to go about implementing this?

Upvotes: 3

Views: 3299

Answers (2)

Ric
Ric

Reputation: 405

I ended up creating a sorted observable array by extending knockout observable array:

ko.sortedObservableArray = function (sortComparator, initialValues) {
    if (arguments.length < 2) {
        initialValues = [];
    }
    var result = ko.observableArray(initialValues);
    ko.utils.extend(result, ko.sortedObservableArray.fn);
    delete result.unshift;
    result.sort(sortComparator);
    return result;
};

ko.sortedObservableArray.fn = {
    push: function (values) {
        if (!$.isArray(values)) {
            values = [values];
        }
        var underlyingArray = this.peek();
        this.valueWillMutate();
        underlyingArray.push.apply(underlyingArray, values);
        underlyingArray.sort(this.sortComparator);
        this.valueHasMutated();
    },
    sort: function (sortComparator) {
        var underlyingArray = this.peek();
        this.valueWillMutate();
        this.sortComparator = sortComparator;
        underlyingArray.sort(this.sortComparator);
        this.valueHasMutated();
    },
    reinitialise: function (values) {
        if (!$.isArray(values)) {
            values = [values];
        }
        var underlyingArray = this.peek();
        this.valueWillMutate();
        underlyingArray.splice(0, underlyingArray.length);
        underlyingArray.push.apply(underlyingArray, values);
        underlyingArray.sort(this.sortComparator);
        this.valueHasMutated();
    },
    reverse: function () {
        var underlyingArrayClone = this.peek().slice();
        underlyingArrayClone.reverse();
        return underlyingArrayClone;
    }
};

Which can be used in the following way:

var sortedArray = ko.sortedObservableArray(
    function (a,b) { return a - b;},
    [1,7,4]
); // sortedArray will be [1,4,7]
sortedArray.push([5,2]); // sortedArray will now be [1,2,4,5,7]
sortedArray.sort(function (a,b){
    return b - a;
}); // sortedArray will now be [7,5,4,2,1]
sortedArray.push(6); // sortedArray will now be [7,6,5,4,2,1]

The only problem I have is that when reinitialising the sorted observable array with a new array in the same way you would reinitialise an observable array the sorted observable array isn't being sorted. To get around this I have added a reinitialise function on the sorted observable array:

var sortedArray = ko.sortedObservableArray(
    function (a,b) { return a - b;},
    [1,7,4]
); // sortedArray will be [1,4,7]

sortedArray([3,2,8]); // this doesn't work correctly, sortedArray will be [3,2,8]
// instead of [2,3,8]

// To get around this you can use reinitialise()
sortedArray.reinitialise([3,2,8]); // sortedArray will be [2,3,8]

Upvotes: 3

Brandon
Brandon

Reputation: 39192

Try this:

var sortedArray = ko.observableArray();
sortedArray.subscribe(function () {
    if (!sortedArray._isSorting) {
        sortedArray._isSorting = true;
        sortedArray.sort(function (a, b) { return b - a; });
        sortedArray._isSorting = false;
    }
});

You can wrap this up in a function to create new sorted observable arrays whenever you want.

Upvotes: 2

Related Questions