Getting friendly with jQuery and Knockout.js
It's new project time! More on that in a future post no doubt, but in brief it's an ASP.NET MVC web application making use of jQuery and Knockout.js. I've dabbled with Javascript in the past, but still definitely on the start of that learning curve.
Enter Knockout.js
I initially started with jQuery to do simple things like hiding/showing elements depending on choices in the interface and for making AJAX calls; jQueryUI for things like dialogs and split buttons.
What was bugging me, was that I had a fair bit of code related to setting up default values in input fields and toggling visibility - it felt a bit low level. I took a look at Knockout.js and as I'm a fan of the Silverlight MVVM framework Caliburn.Micro, could obviously see the parallels.
Knockout.js is already NuGet packaged so it's trivial to include in your project. As a simple example, I wanted to bind a couple of fields and hide one depending on the model value:
Starting with the HTML:
<div>
Distance: <span data-bind="visible: !enterManualDistance(), text: distance"></span>
<input type="text" data-bind="visible: enterManualDistance, value: distance, valueUpdate: 'afterkeydown'" id="distance-manual"/>
</div>
<div>
Time:
<input type="text" data-bind="value: time, valueUpdate: 'afterkeydown'" id="time"/>
</div>
The data-bind attributes are the Knockout.js bits. And onto the Javascript:
<script src="@Url.Content("~/Scripts/knockout-2.0.0.js")" type="text/javascript"></script>
<script type="text/javascript">
$(function() {
var Model = function() {
this.enterManualDistance = ko.observable(false);
this.distance = ko.observable("0");
this.time = ko.observable("");
}
ko.applyBindings(new Model());
});
</script>
The last line, ko.applyBindings takes the model and updates the DOM with the model values. And when the model is updated - for example by the server - the DOM elements are updated automatically, or correspondingly if the user updates the value in the browser, then the model is updated. This is particularly useful when using computed properties.
Dependent values and AJAX
I wanted another field on the UI - the average - which was the result of an AJAX call, that should update when either the distance or time fields are updated. Here's the HTML:
<div>
Average: <span data-bind="text: average"></span>
</div>
OK, onto the JavaScript - showing a simple computed property for now:
var Model = function() {
// snip - other properties as before
this.average = ko.computed(function() {
return "the result of the ajax call taking: dist=" + this.distance() + " and time=" + this.time();
}, this);
So whenever distance and time are updated, the average is updated accordingly. Excellent! However, the function isn’t a simply string concatenation - I want to call an AJAX method. But the jQuery post method takes a callback which receives the result - so I can't simply return the value from the computed function. This is where I think it could be better. My workaround was to have two properties: one computed and one regular observable:
var self = this;
this.average = ko.observable();
this.computeAverage = ko.computed(function() {
$.post('@Url.Action("action", "controller")', { distance: self.distance(), time: self.time() },
function(jsonResult) {
self.average(jsonResult.Avg);
}
}, this).extend({ throttle: 250 });
This does the trick. I added on the throttle extension to avoid unnecessary AJAX calls in quick succession as the user is typing in new values.
I'd be happy to hear of a way to simplify the above, but nevertheless, am really liking Knockout.js and mixing in jQuery is no problem at all.
