SventoryMang
SventoryMang

Reputation: 10479

Using knockout extenders to return data not tied to the underlying observable?

I am trying to add default sorting to an observable array. However, I don't want it to apply to the underlying observable. Just return the array as sorted without affecting the extended target.

I have this extender:

ko.extenders.defaultSort = function (target, sortExpression) {

var expression = sortExpression;
//it is an observable, the underlying value is an array, and it's not a computed observable
if (ko.isObservable(target) && Array.isArray(target()) && ko.isComputed(target) == false) {

    //returns the sorted array and sorts the target,
    //do not want this behavior, just want to return the underlying array sorted
    target.sorted = function () {
        var arrayValue = target();
        return arrayValue.sort(expression);
    };
}

}

I can take this model:

var testModel = ko.validatedObservable({
        Confirmations: ko.observableArray([
            {
                Id: 5,
                IsOwner: ko.observable(false),
                User: ko.observable("Johnny"),
                AvailableLabs: ko.observableArray([
                    {
                        Key: "guid", Value: "Something"
                    },
                    {
                        Key: "guid2", Value: "Something2"
                    },
                    {
                        Key: "guid3", Value: "Something3"
                    }
                ])
            },
            {
                Id: 2,
                IsOwner: ko.observable(true),
                User: ko.observable("Same"),
                AvailableLabs: ko.observableArray([
                    {
                        Key: "guid31", Value: "this is cool"
                    },
                    {
                        Key: "guid15", Value: "another item"
                    }
                ])
            }
        ]).extend({
            defaultSort: function(a, b){
                if(a.Id < b.Id) { return -1;}
                if(a.Id > b.Id) { return 1; }
                return 0;
            }
        })
    });

If I load the page and view testModel().Confirmations() normally. I see the array with the ID 5 record being first.

Then if I type:

testModel().Confirmations.sorted()

It returns an array with the ID 2 record first, so it has been sorted now, great! But when I once again type testModel().Confirmations(), the ID 2 record is now first. I don't want the behavior. I just want to return a separately ordered array. I thought by using target() to first get the underlying value, then return that, it wouldn't affect the observable, but that appears to be wrong. How can I do that?

Note, I cannot use ko.toJS or ko.toJSON because I need to preserve any other nested observables and their extended properties for further processing after binding.

Upvotes: 0

Views: 47

Answers (1)

SventoryMang
SventoryMang

Reputation: 10479

I was able to figure this out, you need to slice the value as a new array first, which knockout provides a helper function to do so straight off the observable:

target.sorted = function () {
     return target.slice().sort(expression);
}

The sort is also a knockout wrapped method of the regular sort. Which I found documentation of here:

https://knockoutjs.com/documentation/observableArrays.html

They also did exactly what I was trying to do, have a .sorted method which returns a copy of the array sorted and doesn't not affect the original. But upon reading this:

https://github.com/knockout/knockout/issues/2501

.sorted is only in v3.5+, which I don't have.

Upvotes: 0

Related Questions