Handling ‘this’ in ko.command

Update: this feature is now available as part of the ko.plus library available on GitHub and NuGet!


The problem of context – the this value – in JavaScript is one that seems to keep causing problems.  Languages with similar syntax (C#, Java) do not allow the developer to alter the value of this and so people don’t always expect that it can change.

JavaScript likes to be different though.

I don’t want to get into too much detail on how this behaves – there are already more than enough articles out there that cover the topic to a greater depth than I could (e.g. Scope in Javascript); instead, this is a post about how I have worked around the issue in my ko.command library.

Problematic Command Context

Take the following simple example usage of the command library.

function ViewModel() {
    this.value = ko.observable(123);
    this.increment = ko.command(function() {
        this.value(this.value()+1);
    });
}

var viewModel = new ViewModel();
viewModel.increment();
//viewModel.value() => 124

The increment command adds 1 to the value of an observable property on the same view model; everything is working so far, but what if we move the implementation of the command action onto the prototype?

function ViewModel() {
    this.value = ko.observable(123);
    this.increment = ko.command(this._increment);
}

ViewModel.prototype._increment = function() {
    this.value(this.value()+1);
}

We might expect this to behave in the same way as before, but now when we call increment we get an error that, after a little investigation, we can see is because the value of this within the _increment function is not set to the view model.

Now It (mostly) Just Works

This behaviour was actually down to a design decision in the earlier version of the library to set the context of the various callbacks to the command itself; only recently has this started to cause problems that have prompted me to fix it.

The updated behaviour (available for download from Github) now endeavours to “just work” wherever possible.  This means that in the scenario above there are no code changes required to use the prototype implementation.

To be specific, the command action will always be invoked in the context from which the command was called which should (in most cases) behave in a way that seems to make sense.

There are some specific scenarios where a little more work is needed though.

CanExecute Function

The canExecute property on any ko.command instance is currently implemented as a computed observable, and as explained in the Knockout documentation, computed observables can be a little tricky when dealing with the context.

The behaviour does make sense when you consider the cause: computed observables will be re-evaluated whenever a dependent observable changes, so there is no way for them to (automatically) guarantee the context in which it will be invoked.

It is, however, possible to explicitly specify a context for a computed observable, so ko.command has been extended to mimic this implementation:

function ViewModel() {
    this.value = ko.observable(123);
    this.increment = ko.command({
        context: this,
        action: this._increment,
        canExecute: this.somethingThatReliesOnScope
    });
}

In this example the value of this when running the canExecute function will always be set to the current instance of ViewModel.

Note: explicitly setting the context in this way will also override the default behaviour when invoking commands, so use it carefully!

Asynchronously-Invoked Callbacks

The callbacks to an asynchronous command can be attached using the done/fail/always functions and are invoked once the promise returned by the action has completed.

function ViewModel() {
    this.value = ko.observable(123);
    this.incrementAsync = ko.command(function() {
        var promise = $.Deferred();
        promise.resolve();
        return promise.promise();
    }).done(this._increment);
}

But in what context will they be executed?

This scenario is a little more complicated because the promise implementation itself is able to specify the context in which callbacks should be invoked (in jQuery this is achieved using resolveWith).  In this case we have 3 choices:

  1. Do nothing.  Leave the context for the callback as whatever is set by the promise
  2. Replace the promise context with the context from the command
  3. Replace the promise context, but only if it has been explicitly specified.

For the time being I decided to leave the behaviour unchanged as it feels like changing it – forcing the context back to the command context – would be breaking the expected behaviour of whoever has explicitly (or implicitly) set the context of the callback.

Advertisements

Server-Side Paged Lists in Knockout

After my last post on client-side paging using Knockout I had a few people comment that it would be useful if something reusable could handle server-side paging. It’s not quite what I needed but it sounded useful so I thought I’d see what I could come up with…

Strategising the Server

Server-side paging poses a slightly different problem to paging on the client-side as it requires (obviously) some cooperation from the server. Given that the server might be implemented in any of a hundred different ways I decided to use a strategy pattern to actually load the data:

var ViewModel = function() {
    this.pagedList = ko.pagedList({
        loadPage: function(pageIndex, pageSize) {
            //load data here
        }
    });
};

The requirements of the strategy are:

  1. Accept a pageIndex and pageSize to be loaded
  2. Return a jQuery.Deferred instance (such as that returned by $.ajax)
  3. Resolve the deferred object with a value that contains an array rows property

Assuming that the server response needs no modification (i.e. already has an array rows property) then a paged list can be created as below:

var ViewModel = function() {
    this.pagedList = ko.pagedList({
        loadPage: function(pageIndex, pageSize) {
            return $.getJson("/getPage?index=" + pageIndex + "&size=" + pageSize);
        }
    });
};

Getting the Total Number of Rows

When writing this I was keen to avoid enforcing anything on the server implementation that wasn’t absolutely necessary, and one of the things I decided wasn’t needed was the total number of available rows. As the client side component cannot always know how many total rows are available, I decided that by default I would simply set the totalRows property to -1 and the pagedList will function perfectly without ever knowing how many rows are actually available – with the following limitations:

  • pageCount and totalRows will always return -1
  • nextPage will never prevent the user from moving to the next page, even if that page will be empty

That being said, I also wanted to support those scenarios where the total number of rows is available, so totalRows can be set in one of two ways:

Manually Set totalRows

As with any Knockout observable property the totalRows value can be set externally, and the pagedList will use whatever value is set to calculate pageCount and prevent moving beyond the final page using nextPage.

var vm = new ViewModel();
vm.pagedList.totalRows(100); //specify 100 rows total

Include totalRows in Server Response

The minimum requirement of the data returned from the page loading strategy is that it requires an array rows property, but if it also includes an integer totalRows value then this will automatically set the observable value on the pagedList.

this.pagedList = ko.pagedList({
    loadPage: function(pageIndex, pageSize) {
        var defer = $.Deferred();

        //this is an example of the expected response object from the server
        defer.resolve({
            rows: [1, 2, 3],
            totalRows: 100 //specify the total number of rows in the response
        });

        return defer;
    }
});

In this way we can either specify the total number of rows on the client side, on the server side, or not at all – and still use the pagedList in the same way.

Pre-Loading the First page

When the page containing the pagedList is being generated on the server it is preferable that the first page of data already be loaded, rather than waiting on an unnecessary post back to the server to populate the list.

With that scenario in mind, I added the ability to specify the first page of data as another option on the constructor.

this.pagedList = ko.pagedList({
    loadPage: function(pageIndex, pageSize) {
        //...
    },
    firstPage: {
        rows: [1,2,3],
        totalRows: 100
    }
});

If this option is specified then the pagedList will not attempt to load the first page of data and will instead render the data specified in the option.

Note: this is also another mechanism by which the totalRows property can be pre-populated on the client side.

Mapping Data to ViewModels

In most cases the server will be returning something more complicated than the numeric values in the examples above, and in those cases it is common to want to wrap the returned object in a view model of it’s own.

To support this functionality I included a map constructor parameter that will be invoked on each of the rows returned from the page load strategy.

this.pagedList = ko.pagedList({
    //...
    map: function(item) {
        return ko.mapping.fromJS(item);
    }
});

Note: this example makes use of the Knockout mapping plugin to generate a view model.

Bindable Properties

Much of the behaviour is similar to the pagedObservableArray as it was described in my original post, with the most significant difference being that the returned object can be used directly – there is no need to bind to a page property as there was before. It also exposes the following properties:

  • pageSize – An observable instance containing the number of items per page
  • pageIndex – An observable instance containing the zero-based current page index
  • totalRows – An observable instance containing the total number of rows that are assumed to be available (or -1 if this has not been determined)
  • pageCount – A computed observable that returns the number of pages of data available (or -1 if totalRows is unknown)
  • previousPage – A command that moves to the previous page, if possible
  • nextPage – A command that moves to the next page, if possible
  • loadPage – A command that loads the page data at the index specified as the first parameter. The loadPage.isRunning observable should be used for all loading notifications

One point to note is that this implementation does not expose an observableArray containing the data; instead it exposes a regular observable with a value that is an array. This may seem a little strange but I felt that the additional functions and properties on the observable array do not really make sense when talking about a collection of server-specified data.

Source Code & Samples

The source code and unit tests are available on GitHub, and a working sample is available on jsFiddle.

If anyone else has any other suggestions or requests just let me know in the comments!

Forcing Computed Observables to Refresh in Knockout

Computed Observables in Knockout are a useful way of calculating values based on other observables:

var ExampleViewModel = function() {
    this.name = ko.observable();
    this.isNameValid = ko.computed(function() {
        return this.name().length > 0;
    });
};​

One of the great features here is that when the name property changes the Knockout framework will automatically re-evaluate the computed observable and notify anything that is bound to that value. This works by automatically subscribing to the change notifications from the name observable when the value if first acquired.

Unfortunately this does not work when the value of the computed observable is dependent on a non-observable value:

var ExampleViewModel = function(externalValidationDefinition) {
    this.name = ko.observable();
    this.isNameValid = ko.computed(function() {
        return this.name().length > externalValidationDefinition.minLength;
    });
};

var validationDefinition = {
    minLength: 5;        
};

var vm = new ExampleViewModel(validationDefinition);

//change the validation definition...
validationDefinition.minLength = 10;
//...but the computed observable is not re-evaluated

In this situation we need to manually force the re-evaluation of the computed observable, but there is no native way of doing so.

To get around this we can add a dummy observable to the view model, then retrieve and ignore its value from within the computed observable. This will cause the Knockout framework to re-evaluate the computed observable whenever the dummy observable changes:

var ExampleViewModel = function(externalValidationDefinition) {
    var _dummyObservable = ko.observable();

    this.name = ko.observable();
    this.isNameValid = ko.computed(function() {
        _dummyObservable(); //retrieve and ignore the value
        return this.name().length > externalValidationDefinition.minLength;
    });
};

Now we can invalidate the computed observable just by invalidating the value of the dummy one:

var ExampleViewModel = function(externalValidationDefinition) {
    var _dummyObservable = ko.observable();

    this.name = ko.observable();
    this.isNameValid = ko.computed(function() {
        _dummyObservable(); //retrieve and ignore the value
        return this.name().length > externalValidationDefinition.minLength;
    });
    this.invalidateIsNameValid = function() {
        _dummyObservable.notifySubscribers(); //fake a change notification
    };
};

var validationDefinition = {
    minLength: 5;
};

var vm = new ExampleViewModel(validationDefinition);

//change the validation definition...
validationDefinition.minLength = 10;
//...and manually invalidate
vm.invalidateIsNameValid();​

Now we can manually force the computed observable to be updated when we know that an external factor has changed. If I get the time I may convert this into an extender or helper for reusability later…