Philosophy

Ideally each "module" of the application would be self-contained. This means that a module/view for widget A should know how to retrieve, add, edit, and delete widget A (assuming that is what the module is tasked with). It should be able to be instantiated without significant support from other modules

General

A well-constructed View Model follows the same pattern as WPF. Properties are located at the top of the module, functions come at the end. Knockout has other "types" of object, like "computed’, and "subscribe". These should come between the properties and the functions. So the preferred order will be 1) Properties, 2) Computed, 3) Subscribe, 4) Functions.

Views

Application views are generally comprised of multiple partial views. For widget A there will general be an "Index" page which may contain multiple partial views. Each partial view is responsible for a specific part of the widget. There may be a "list" view, and a "detail" view. For objects that have a significant hierarchy there may be multiple views that allow the user to drill down the hierarchy.

As we can see here the Vehicle Models Index page has three partial views associated with it, one for the Brands (Manufacturers), one for the Models (associated with a Brand), and one for the Detail/Edit (of a specific vehicle) NOTE: depending on the application architecture the partial views may be dynamically loaded on demand instead of "hard coded" as shown here.

View Models

Each application view (think Index) will be bound to a Main or Shell view model. This view model typically contains property place holders for each Partial page.

We can see here that the Vehicle Model "Main" VM has three property holders for each of the partial views associated with a vehicle. Additionally, depending on the requirements of each view there may be a property holder for a "select list" so that we can load a select list once and reuse is multiple times (i.e. if the detail vm always uses the same select list then load it once, not each time the detail vm is instantiated).

Figure 1: Application Layout

Looking at line 26 of CODE 1 we see where the "MainVM" is instantiated and bound to the view. We also see the "Load" function is called. CODE 2 line 16 shows the "Load" function, which is responsible for loading the "brands" view model script, then instantiating and loading it into the Main property holder for brands. Looking back up at CODE 1, line 6 we can see that the brands partial view model is bound to the brands property. Initially this is null so the view does not show. However once the property has a value the view is then displayed (how it is displayed is driven by the styles defined for the application).

Figure 2: Main View Model

List Partial Views

The exact structure of a partial view will vary greatly based on the overall application theme and styling. Additionally they will vary based on the specific requirements of the information being displayed. In general though all partial views will have a set of actions, and an area bound to detail information.

Figure 3: List Partial View

CODE 3 shows what a partial view that is bound to a list may look like. Note that the overall binding of this view is set in the Index view, so all binding shown here are relative to the "list view model". The Knockout view model bound to this view support filtering, so we can see it has a search box (line 22) and the list is bound to a "sortedFilterdItems" (line 30).

List ViewModel

A typical list view model is shown in CODE 4. The current preferred pattern for list view models is pass any filtering criteria to the Load method. Previously the pattern was to pass the filter criteria to the constructor and store it in a property. This is no longer the preferred pattern.

It may be tempting for some to emulate the C# pattern of passing in an object (as the CODE Framework "request/response" pattern follows) but since JavaScript is a dynamic language it really doesn’t care if you add/remove properties to its constructor. Additionally most view model constructors should receive a relatively few number of parameters (it would be unusual for a constructor to require more than three, 99% will only require one). Additionally parameters vs objects will give some intellisense to the developer as to what parameter is required. A general guideline is to use parameters until you reach 6 or so. After that it is acceptable to use an object.

Figure 4:List View Model

There are several notable features of this example. Line 24 defines an "items" array to hold the list of items the view model will be bound to. For convenience the property name "items" is a standard to simply support of some stock filtering code (shown at the bottom of the view model). If your list view model does not require filtering, or you are accomplishing filtering outside of the Knockout view model then a more descriptive property name like Models or ModelsList could be used.

Line 25 and 26 illustrate how we can support the selection of an item in the list. Where "SelectedItem" is simply a property to store the selected item, line 26 shows a typical function that happens when an item in the list is clicked (see CODE 3, line 31 for the view binding). First we check to see if this is item is different from the previously selected item, if so we perform some functions. Exactly what happens may vary, but typically you will stuff the currently selected item in to the property, the set the "Selected" property of the item (see discussion on the List Item View Model below), then load a detail view model with the selected item’s identifier.

Jumping down to line 75 we see a couple things in the Load method. First it accepts any required parameters (not as an object). The top part shows how we can wire up "fake" data to the view model, allowing the UI to develop independently from the service layer.

ts.requre is a EPS developed helper method that loads external assets like HTML and JavaScript files. Part if its functionality is that it keeps track of previously loaded assets and only makes a data call once. Upon completion of the loading it returns to the second parameter (callback) and performs additional operations. The exact name and availability of the "require" function will vary based on your application JavaScript framework.

This fake data can also serve as a pattern for the service layer contracts to follow. Eventually the "fake" data will be removed and the comment out service call will be enabled, making any adjustments necessary during the service contract development process. Similar to the ts.require functions, the ts.status and ts.serviceCall are application helper methods. Their names and parameters may vary based on the EPS JavaScript framework employed on your project.

List Item View Model

The list item view model represents a single item in a list. This it typically very light weight and has few properties. This is the model to hold the service call response <items>. As we see CODE 5 a list item view model will always accept a single object parameter to its constructor (Note we are using object here, not parameters). Additionally it will not have a "Load" method like a list or detail view model. It may also have one or more "computed" properties (much like a C# View Model read only properties) which extend the display capabilities of the list item. It would be very uncommon for an "item" view model to contain functions.

Other common features of a list item view model are a "Selected" property. This is typically set by the parent list view model, and when combined with the "Css" property allows the UI to visually indicate what list item was selected by the user.

Figure 5: List Item View Model

Detail Partial View

In CODE 6 we can see an example of a partial view to display the detail of an item. In most cases the "detail" view will have the edit capability. Note that the view is bound to the "detail" view model (see CODE 6). This means that the command bindings (like those on line 6, 15, 16, 17) are to the "detail" view model, and on line 25 we indicate the individual fields are bound to the "detail item" view model.

Starting on line 31 we see a typical select list binding. There are several items of note here:

  • optionsCaption – text shown if nothing is selected (i.e. "Select One…"). This does not count as a selection so when the "caption" is displayed the select list has no value
  • options – the KO observable array holding the selections available
  • optionsText – the property name of the "options" item to display to the user
  • optionsValue – the property name of the "options" item to use for value binding
  • value – the property to bind selected value to.

In this case the ‘VehicleBrandSelectList’ is a KO observable array consisting of multiple items with (at minimum) two properties, ‘Text’ and ‘Value’. So its JSON equivalent would be {Text:"Option1", Value: 123}

You may have notice the @DbRes.T("abc","xyz") several places in the view examples. This is a localization framework to display text in a localized language. While localization is a discussion unto itself, this particular framework "Westwind Globalization" has a unique feature of being data driven, and the ability to have the localized resources available to both Server Side (MVC) views, and Clilent Side (JavaScript)

Figure 6: Detail Item View

Detail View Model

The detail view model shown in CODE 7 shows how a typical "detail" view model may be laid out. One of the first properties, VehicleModel is a property which will hold the "item", basically the property bag that corresponds to the record being displayed/edited (see CODE 8).

We also see several functions related to CRUD actions (assuming this is the goal of the detail view model) Note: the "AddSize" and "RemoveSize" are for a child list contained on the detail. The "add" for a new detail record is generally contained on the parent view model. In other words, a view model cannot add itself, it can only add new items of a list it contains.

The "Save" method shows how a "edit" view model may serialize the "item" view model to the JSON data payload to ben posted to the service method. This serialization is accomplished using the Knockout ‘toJSON’ utility.

Figure 7: Detail View Model

Detail Item View Model

The item view model is typically in the same file as the "detail" view model (shown in CODE 7). There may be cases where the item object is used for multiple view models. This is an acceptable practice, in this case the item view model definition may be in a central place. You NEVER want to have two view models with the same name defined in multiple places. If both were loaded then the result would be a combined view model with some properties/functions added and others re-defined.

We can see that the "detail item" view model is much like the "list item" view model used when binding a list of objects to a list. The major difference between the two is that the item will represent the complete record, where the list item is a sub set. This example is a bit unusual in that in addition to properties it also has a list of items associated with it. This is acceptable, but can complicate both view model and view construction.

Figure 8: Item View Model

Knockout

Currently the preferred client side binding framework is Knockout.js (http://knockoutjs.com ). It is strongly recommended that every developer follow the tutorials located at http://learn.knockoutjs.com . They will provide you with a good foundation of how to develop client side applications with Knockout. There is also a list of "live examples" (http://knockoutjs.com/examples ) that cover many of the common tasks you might come across.

The single biggest issues with Knockout is the () . Specifically how to know when to use it and when not to use it. The code self.Id and self.Id() mean different things, but in certain situations each is valid.

Let’s re-enforce something you probably already know. The data payload coming back from a service call is JavaScript Object Notation (JSON). As such the () has no meaning and would never be used when dealing with JSON. So you would never write code like response.Customer() or response.Customer.Id() The proper way to access the properties would be response.Customer or response.Customer.Id (based on a JSON data object like {Customer: {Id:123, Name: "Jack"} assigned to a JavaScript variable response ) So access values of a JSON object do NOT use the ().

Knockout objects are a completely different animal. The secret behind the scenes is that KO uses JavaScript functions to accomplish its observable nature. So when you create a property self.Id = ko.observable() you are in reality creating a JavaScript function that represents a value. As such attempting to assign a value to your property is different from standard JavaScript variable assignment. Consider the following lines:

1self.Name = ko.observable();
2self.Name = "Jack"; <-- WRONG assignment
3self.Name("Jack"); <-- Correct assignment4console.log(self.Name); <-- Displays the raw JavaScript function
5console.log(self.Name()); <-- Displays the value of the property

Line two would actually redefine the property to be a string with the value "Jack". Line 3 shows the proper way to assign a KO property. This is passing in the new value "Jack" to the function that represents the observable property "Name". This allows the KO observable magic to kick in and update any bound elements.

Another easy to make mistake is when manipulating KO arrays.

1self.Customers = ko.observableArray([]);
2self.Customers().push(new customer({Id:123,Name:"Jack"}); <-- WRONG
3self.Customers.push(new Customer({Id:123,Name:"Jack"}); <-- Correct

We know by now we access the value of a KO property using the (), so it’s tempting to call the standard JavaScript array functions (like push, pop, splice, etc) on the value of the observable array. In fact this will work, and if you look at the collection after line two you would see the expected result. However doing it this way bypasses the KO observable magic. Only when calling the KO specific version of the standard JavaScript functions will the bindings be updated.