Practical .NET
Test Driving a JavaScript MVC Framework
Peter looks at Knockout, one of the MVC environments for writing client-side JavaScript, and wonders if we're on the wrong path.
The typical interweaving of code and presentation logic in JavaScript and jQuery code ignores the MVC paradigm, which segregates logic to your Model and Controller, leaving a View that's so brain-dead simple that nothing complicated can go wrong with it. It's difficult to do test-driven-development (TDD) with typical JavaScript code unless you start imposing a lot of "good programming" practices.
What I want is an MVC infrastructure to use in JavaScript development. There are several potential candidates, including MVC, JavaScript, Knockout, Spine and Backbone. In this column, I'm going to look at Knockout (primarily because it's the easiest of the frameworks to explain), which implements the MVVM model. Knockout just requires you to add a single library to your application and it's ready to use.
Creating the ViewModel
I'll start with the ViewModel object, which holds the data and the logic handle all the work of bridging from the Model (a set of WCF services) and the View (an HTML page). This object contains the properties to be displayed and updated in the user interface.
In this example, I've defined a Customer object with two properties. I'll set up my ViewModel to accept a Customer Id and use that to retrieve the data to initialize the properties from my server-side services (I've omitted that code here). I also define a variable called self to hold a simple internal reference to my ViewModel:
var Customer = function (custId) {
//...get data from service...
this.CustId = ko.observable(custId);
this.City = ko.observable("Goderich");
var self = this;
In defining my " ViewModel's properties, I've used Knockout's observable function. Later, I'm going to bind the properties in my ViewModel object to elements in my page. By using the observable function, I get bi-directional databinding: changes to the City and Name properties will automatically update the elements, and changes the user makes to the elements will automatically update these properties. In addition exposing a single scalar value as I've shown here, Knockout supports exposing arrays and computed values.
Since the ViewModel handles all the communication between the View and the Model (my page and my services), I also put my code to move data back to the services here. I've omitted the code to call the services here:
this.updateCustomer = function () {
this.Action = "update";
this.UpdateService();
};
this.addCustomer = function () {
this.Action = "add";
this.UpdateService(ko.toJSON(self));
};
this.UpdateService = function (data) {
var jsonSelf = ko.toJSON(self);
//…call service, passing JSON version of ViewModel
};
this.deleteCustomer = function () {
//...call service, passing this.CustomerId...
};
Knockout's toJSON function knows how to work with Knockout's observable functions to retrieve the current values for the ViewModel's properties and create a simple JSON object. I couldn't get the toJSON function to work with a direct reference to the JavaScript, which is why I set up the self variable earlier.
All that's left in my JavaScript code is to turn on Knockout and tell it about my ViewModel using Knockout's applyBindings function. If I do that when my page initializes, and at the same time retrieve the initial customer to display, the code would look like this:
$(function () {
var cust = new Customer("A123");
ko.applyBindings(cust);
}
Now, in my HTML, I can bind elements to the properties and functions that I've built into my ViewModel. Binding the CustId and City properties of my ViewModel to the value attributes on text boxes looks like this:
<p>Id: <input type="text" data-bind="text: CustId" /> </p>
<p>City: <input type="text" data-bind="text: City" /> </p>
And I can also bind the functions on my ViewModel to events fired by my elements. In this case, I'm binding to click events on buttons:
<button type="button" data-bind='click: updateCustomer'>Update Customer</button>
<button type="button" data-bind='click: deleteCustomer'>Delete Customer</button>
<button type="button" data-bind='click: addCustomer'>Add Customer</button>
Knockout is remarkably agnostic about how I choose to retrieve my data and what I do in my functions. I'm also ignoring much of Knockout's functionality: I could, for instance, use Knockout's css binding to have a property on my ViewModel that controls the display of my elements.
Leveraging TDD
I can now test my ViewModel functions under TDD without having to create the page that it will be used in. I can prove, for instance, that my Customer object correctly populates the ViewModel with the data for the customer Id passed to it. I could also test, under TDD, to make sure properties driving style are also being set correctly. It would be easy to create a test that would set the data-related properties on the ViewModel and then check that the style-related properties controlling it are set correctly. It even looks easy to integrate client-side validation into this framework in a testable way.
I can't, of course, test my data binding; but then, there's no logic involved there: either I type the databinding expressions correctly or I don't. I refer to these kinds of problems as "blunders" rather than "bugs" because they don't involve inserting any logical smarts into my View.
But there's much that this framework doesn't address that I want to do with JavaScript and jQuery. For instance, much of my JavaScript code will seek out various elements on a page in order to perform some operation on them. I can't, in a TDD environment, prove that my code will find all of the correct elements and perform the operation correctly (at least, not without creating that page). It's not clear to me that I can duplicate all of that functionality using Knockout; and even if I did, that I would want to give up doing it with jQuery.
But I don't have to give up jQuery or its functionality: I'll do some of my client-side development with Knockout and TDD, and some outside of Knockout with jQuery. But I really want an MVC infrastructure that will do it all. Perhaps it's going to turn out that jQuery itself is wrong-headed, and if we want true MVC development in our clients, we need a new model altogether. Maybe we need to start over again.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.