This is part of series of posts on building reliable, testable Ajax-enabled applications using a JavaScript MVC framework. In my first post, I discussed why this was a key technology and set up my initial server-side resources in an ASP.NET MVC application. The second post created the test framework for the application, using QUnit for the client-side code and Visual Studio for the server-side code. My last post started to provide the pay-off for all that work: integrating the client-side ViewModel’s data and methods with HTML.
The point of this application is to have the user select a customer from a dropdown list and display the information for that customer, retrieved from the server. Using Knockout as a framework for implementing the MVVM pattern, I’ve created a client-side Customer ViewModel written in JavaScript that exposes three properties (CustId, CompanyName, and City) and the functions to retrieve a Customer (getCustomer) and delete a Customer (deleteCustomer). I’ve also wired up the properties to controls on the page.
But I’ve left out one important part: integrating the code that actually retrieves the data from the server into events in the page. In this post, I’ll take care of two topics: adding a dropdownlist that displays all the customers in the Northwind database and retrieving the appropriate customer object from my server when the user selects a customer from the list. Unlike my previous posts, however, I’ll defer showing the TDD code that proves the code works to the end of the post.
Creating the DropDown List
My first step is to define the dropdown list. In my client-side ViewModel I need a property that will expose the list of Customer objects that I retrieve. I add that to the list of properties that I defined in my ViewModel again using one of Knockout’s functions. Because I’m working with multiple objects, I use Knockout’s observableArray function:
this.Customers = ko.observableArray();
Technically speaking, I probably don’t need to use observableArray here. The observableArray function gives me two-way databinding: changes to the Customers property will be reflected in the page and changes to the array in the page would be reflected in the property. Right now, I don’t intend to let the user add or remove Customers from this property so using observableArray is probably providing more functionality than I need. However, the syntax for updating “observable” properties is different from updating a standard variable: I’m using observableArray here because it makes my life easier to use Knockout’s observable functions on all of my ViewModel’s properties instead of just some of them.
Also on the client, I need a function that uses jQuery’s getJson method to retrieve the customer objects that will populate the property. I add the following function to my client-side Customer ViewModel to do that. In the function, I use my controller variable to build the URL that retrieves the list of Customer objects from the right controller. Once I’ve retrieved the array, I update my Customers property with it:
this.getCustomers = function () {
$.getJSON("/" + Controller + "/GetAllCustomers",
null,
function (custs) {
self.Customers(custs);
}
);
};
With the client-side code created, I need the server-side method in my controller that returns the JSON objects that will populate the dropdownlist. Rather than return the whole Customer object, I’ll create an anonymous object that holds just the values the dropdownlist needs—CustomerId and CompanyName:
public ActionResult GetAllCustomers()
{
using (northwndEntities ne = new northwndEntities())
{
var res = from c in ne.Customers
select new {cName = c.CompanyName, cId = c.CustomerID};
return this.Json(res.ToArray(), JsonRequestBehavior.AllowGet);
};
}
My final step in displaying these customers is to bind a dropdownlist on the page and bind it to the Customers property I’ve created on my ViewModel. That markup looks like this:
<select data-bind="options: Customers,
optionsText: 'cName',
optionsValue: 'cId',
optionsCaption: 'Select a Customer'">
</select>
I’m using several of Knockout’s bindings here to tie the list to the property:
- options: this binding is passed the name of the property on my ViewModel that holds the list of objects to be used to populate the dropdown list.
- optionsText/optionsValue: These bindings specify which property on the object are to be used for the text and value attributes in the list. The text attribute specifies the property to be displayed to the user while the value property specifies the property to be passed to any related function
- optionsCaption: Specifies the value to be displayed in the list when it’s first displayed
I don’t need the selectedOptions binding that lets you specify the list’s current selected value through a property on the ViewModel.
Retrieving the Customer Object
Now I need a client-side method to retrieve the customer object from the server. I write that method so that it accepts a customer Id and updates the ViewModel’s properties with the retrieved data:
this.getCustomer = function (custId) {
$.getJSON("/" + Controller + "/CustomerById/" + custId,
null,
function (cust) {
self.CustId(cust.CustomerID);
self.CompanyName(cust.CompanyName);
self.City(cust.City);
}
);
};
I can’t however, wire this method up to my dropdown list directly. Here’s the syntax that ties the list to a function called fetchCustomer on my ViewModel that runs as soon as the user selects an item in the list:
<select data-bind="event: {change: fetchCustomer},
options: Customers, …
I’m using Knockout’s event binding which lets me specify an event (change, in this case) to a method (fetchCustomer). Unfortunately, there’s no provision in this binding to pass a parameter to the fetchCustomer function. However, when a function is called through Knockout’s binding process, the function is passed two parameters. The second paremeter includes information about the event and through that parameter’s target property I can access the element that fired the event. From there it’s just a short step to retrieving the current value on the element that invoked the function through its value property.
This code gets the currently selected value from the dropdownlist that calls the function and passes that value to my getCustomer function:
this.fetchCustomer = function (ignore, event)
{
this.getCustomer(event.target.value);
};
Which raises the question of why have the fetchCustomer method at all? Why not call the getCustomer function from the dropdownlist binding and just extract the selected value from the second parameter? I don’t know that I have a good answer for that question. I’m could say that I’m trying to create a testable ViewModel and the more functions that I have that are tied to my View, the harder it is to test the ViewModel. However, a test for a method like fetchCustomer isn’t hard to construct—this code would do the trick:
var eventTest = { target: { value: "ALFKI"} };
cust.fetchCustomer(null, eventTest);
Still, I’m happier isolating any code that seems to me is related to the user interface into its own methods so I’ll continue with this design.
As I said at the start of this series, Learning Tree has several excellent courses in this area. In Canada, I teach both Learning Tree’s ASP.NET MVC course (which has a chapter on TDD in ASP.NET MVC) and its Design Patterns and Best Practices course (which goes into TDD in more depth). But Learning Tree also has related courses that I don’t get to teach, including ones on JavaScript and jQuery.
There’s lots more I could do here. With Knockout it’s easy to enable and disable portions of your page by binding them to properties on your ViewModel. I also haven’t tried to create or manage multiple objects on the page. However, I have convinced myself (and, hopefully, you) that there’s at least one MVC/MVVM framework out there that lets you create applications in a testable, reliable way even when your application includes client-side code.
Test Code
And here’s the code that I used to test my code. First, the test for client-side code that returns the list of customers:
test("Get All Customers",
function () {
stop();
cust = new Customer("Test");
cust.getCustomers();
setTimeout(function ()
{
equals(cust.Customers().length, 2,
"All customers not retrieved");
start();
},
3000);
}
);
Now the mock server-side method that I used to with that client-side test code:
public ActionResult GetAllCustomers()
{
List<Customer> custs = new List<Customer>();
Customer cust = Customer.CreateCustomer("PHVIS",
"PH&V Information Services", null);
cust.City = "Regina";
custs.Add(cust);
cust = Customer.CreateCustomer("Other", "Other one", null);
cust.City = "Winnipeg";
custs.Add(cust);
return this.Json(custs.ToArray(), JsonRequestBehavior.AllowGet);
}
Finally, the test code for the production version of the server-side method:
[TestMethod]
public void GetAllCustomersTest()
{
HomeController hc = new HomeController();
System.Web.Mvc.JsonResult jres =
(System.Web.Mvc.JsonResult) hc.GetAllCustomers();
dynamic res = jres.Data;
Assert.AreEqual(96, res.Length, "Unable to retrieve all customers");
}