Update: this feature is now available as part of the ko.plus library available on GitHub and NuGet!
In an earlier post I outlined a basic implementation of a command pattern using Knockout that tracks the progress of an asynchronous operation using the jQuery.Deferred
object.
Having used it for a couple of weeks I found that there were a few things that could do with improving, so here we go with Command v2!.
No more .execute()
The first version of the command would wrap the operation in an object, meaning that whenever you wanted to bind to or just run the action you had to call a .execute
method on the new object:
//old code
var viewModel = {
doSomething: new Command(function() {
//invoke async operation
});
};
//invoke the command
viewModel.doSomething.execute();
Whilst using the command I kept forgetting to add the .execute
(particularly to bindings) and this quickly became annoying, so I changed the constructor function to return the execution function instead of a new object in a similar manner to ko.observable
.
Note: having previously been returning this
I had to ditch the traditional constructor for a factory method that is a bit more in line with the Knockout syntax.
ko.command = Utils.command = function (options) {
//...
//execute function (and return object
var _execute = function () {
//...
},
//public properties now attached to _execute instead of this
_execute.isRunning = _isRunning;
_execute.done = _done;
_execute.fail = _fail;
_execute.always = _always;
return _execute;
};
After these changes the invocation of the command is much simpler:
var viewModel = {
doSomething: ko.command(function() {
//invoke async operation
});
};
//invoke the command
viewModel.doSomething();
//get the status of the operation
viewModel.doSomething.isRunning();
//attach a success handler
viewModel.doSomething.done(function() {
alert("Finished");
});
That’s much cleaner, and the helper observables and functions such as isRunning
and the callback methods still function as before.
Support for canExecute
The command pattern implementation in .NET (the original inspiration for writing this) includes a CanExecute
method that determines whether or not the command is currently in a suitable state to execute. For example, if the command is going to submit a form, the CanExecute
implementation might check that the form is valid prior to submission.
For my JavaScript command I wanted to be able to specify a custom function to determine whether or not the command can execute, as well as always preventing execution whilst the command is running asynchronously. To do this I made use of Knockout’s computed observables to implement a canExecute
property:
ko.command = Utils.command = function (options) {
//factory method to create a $.Deferred that is already completed
_instantDeferred = function(resolve, returnValue) {
//
},
//execute function
var _execute = function () {
//check if we are able to execute
if (!_canExecute()) {
//dont attach any global handlers
return _instantDeferred(false).promise();
}
//...
},
//dummy to allow us to force a re-evaluation of the computed _canExecute observable
_forceRefreshCanExecute = ko.observable(),
//canExecute flag observable
_canExecute = ko.computed(function() {
_forceRefreshCanExecute(); //get and ignore the value
//can execute if we're not running, and either there's no canExecute
//function specified or it returns true
return !_isRunning() &&
(typeof options.canExecute === "undefined" ||
options.canExecute.call(_execute));
}, _execute),
//invalidate canExecute
_canExecuteHasMutated = function() {
_forceRefreshCanExecute.notifySubscribers();
};
//public properties
_execute.canExecute = _canExecute;
_execute.canExecuteHasMutated = _canExecuteHasMutated;
return _execute;
};
Things of interest here:
- If the
_execute
method cannot execute, it returns a$.Deferred
object that has already been rejected so that any calling class that attaches a done/fail handler will not throw exceptions and can at least sensibly react - We are creating a dummy observable (
_forceRefreshCanExecute
) that is used by thecanExecuteHasMutated
function to force the computedcanExecute
observable to refresh (as described here)
Synchronous Execution
The first version relied on the action returning a promise
object representing the status of the background operation, but after implementing the canExecute
support I started to see a use case for commands that do not do anything in the background and simply execute synchronously.
var ExampleViewModel = function(externalValidationDefinition) {
var _self = this;
this.listOfItems = ko.observableArray();
this.clearList = ko.command({
action: function() {
_self.listOfItems.removeAll();
},
canExecute: function() {
return _self.listOfItems().length > 0;
}
});
};
To accommodate this type of usage I have updated the behaviour of the command so that when the action returns anything other than a promise
it wraps that result in an immediately-completed jQuery.Deferred
instance and then executes any registered handlers.
Other Minor Changes
errorMessage
Removed
I realised that the error handling in version 1 was far too dependent on the signature of the error handler - what works for a failed AJAX request will not work for a failed HTML5 SQL request etc. - so I elected to remove the built-in error message recording and leave that down to the user.
Better Error Handling
As part of the support for synchronous code I also added improved error handling so that any error thrown directly from the action will be passed to the fail
handlers.
Source
As ever, the code and unit tests are available on GitHub so help yourselves.