The Practical Client
Rendering the Page with Backbone and TypeScript
Backbone provides an elegant way to encapsulate the code necessary to get your data and HTML onto the page. Peter shows how to make it work in TypeScript.
In last month's column, I showed you how to set up a Backbone project with TypeScript (and threw in some best practices around organizing your project, structuring your code and defining the Model classes that hold your data).
This month, I'm going to show how to use a Backbone View to display the Customer objects I defined in that column. This time I'll also be showing off some useful features in TypeScript, including the ellipse (
) syntax for defining a method that accepts an indefinite number of parameters and the TypeScript equivalent of a lambda expression.
A Backbone View is tied to two things: First, either a Backbone Collection of one or more Backbone Models (in this case, my Customer classes), or to a single Backbone Model; second, a spot on your page (in this case, a div element with its id attribute set to "Customers"). The purpose of the View is to render the HTML for those Model objects into the spot on your page.
Normally, a Collection of Models is held in the View's collection property while a single Model is held in the View's model property. The spot on the page is normally held in the View's el property, specified using a jQuery query; you can also use the View's $el property which holds the jQuery object that references the spot on the page (and is often more efficient to use than the el property). While you could set the el/$el properties from within your View, the best practice is to have the host application set the View's el/$el properties at runtime to allow the application to control where the HTML generated by the View will be applied to the page.
Backbone is designed with the expectation that you'll put your code that generates the HTML in the View's render method. Once you generate your HTML, you can, from within the View, append your HTML to the element set in the el/$el properties. You can also use the View's setElement method to set the View's el/$el properties to your generated HTML.
(A side note: If you don't specify an element on the page for the el property, Backbone sets the el property a div element, unattached to any part of the page. You can override this default by setting the View's tagName property to the name of another kind of HTML tag. This means you can, within a View, generate the HTML you need without necessarily attaching the View to a spot on the page: you can instead attach your HTML to the default element in the el/$el property. After the HTML's generated, the host application can retrieve the HTML from the View's el property and attach it to some spot on the page.)
Creating the Collection
With that introduction out of the way, let's see how it works. For this column, I'll be building a <select> element with <option> elements inside it to create a dropdown list from a Collection of my Customer objects. Later columns will create more complex parts of the page.
My first step is (by extending the Backbone Collection object) to define a Backbone Collection to hold the Customer objects that will make up the dropdown list. I've called this Collection, CustomerList. To configure the Collection, I must specify the type of object held in the Collection by setting the Collection's model property (I typically do that in the Collection's constructor). TypeScript requires that I call the super function in the constructor of any class that extends another class, which means that a very simple CustomerList Collection looks like this (see last month's column for how I defined the TypeScript bb reference used in this code that points to Backbone):
export class CustomerList extends bb.Collection
{
constructor()
{
super();
this.model = Customer;
}
}
Building on the code structure I defined last month, I put this code, which defines my CustomerList, in my SalesOrderModels file, which also holds the code defining my Customer class.
Defining a View
While I could have a single View generate all the HTML for my dropdown list, I'm going to use two Views: one to generate the select element (CustomersSelectView), and another to generate the option elements (CustomersOptionView). This design results in smaller and easier-to-understand Views (it also opens up the possibility of mixing and matching select elements with option elements). This design also lets me demonstrate how Views can interact with each other.
In my CustomersSelectView, I first append a select element to whatever element the host application places in the $el property. Because the $el property holds a jQuery query, I can use the jQuery append function to add this select element to the element on the page. My code then sets an internal variable called elm to that new select element using a Backbone feature: jQuery queries scoped to the View's HTML. Here's what that code looks like:
export module CustomerViews
{
export class CustomersSelectView extends bb.View
{
private elm: JQuery;
render(): bb.View
{
this.$el.append("<select>");
this.elm = bb.$("select");
Now I loop through all the Models in the Collection passed to the CustomerSelectView in its collection property, passing each Customer Model to my CustomerOptionView which generates an option element for each Customer. I then append that option element to the select element added by the CustomerSelectView. Backbone collections have an each method (provided by Underscore) that makes it easy to process each item in a Collection. All you have to do is pass that each method an arrow expression to be executed for each item in the collection.
TypeScript arrow expressions look suspiciously like C# lambda expressions: an arrow expression consists of a set of parameters, the => characters, and a function body. The method the arrow expression is passed to is responsible for putting values in the parameters and invoking the function. Backbone's each method passes each item in the collection one at a time to the arrow expression (the each method also expects a context object to be passed as its second parameter but, for this example, I don't need the context object, so I just pass null).
The arrow expression in the following code creates a CustomerOptionView and sets the View's model property to a Customer object from the collection. The code then calls the CustomerOptionView's render method, which, after it finishes processing, returns a reference to the View the render method is part of -- in this case, the CustomerOptionView. This code expects the el property on the CustomerOptionView to contain the HTML generated by the render method and appends that HTML to the select element in the CustomerSelectView's elm variable:
var cv: CustomerOptionView;
this.collection.each(cust =>
{
cv = new CustomerOptionView();
cv.model = cust;
cv = cv.render();
this.elm.append(cv.el);
}, this);
It isn't an accident the CustomerOptionView returns a reference to the View it's part of: It's a Backbone convention, reinforced by the DefinitelyTyped definition for the render method. This convention supports chaining together calls to the View. Because of this convention I could, for instance, collapse the last two lines of my arrow expression into one line:
this.elm.append(cv.render().el);
Following that convention, my CustomerSelectView's render method finishes by returning a reference to the View of which it's a part:
return this;
}
}
Generating HTML with a Template
The CustomerOptionView uses its model property to access the Backbone Model object placed there by the CustomerSelectView. The code then reads the values from properties on the Model object using the Model's get function, passing the property name. This code retrieves the Customer's Id, FirstName and LastName property values, and uses them to build strings that will be inserted into the HTML generated by the View:
export class CustomerOptionView extends bb.View
{
render(): bb.View
{
var valueString;
valueString = this.model.get('Id');
var textString;
textString = this.model.get('LastName') + ", " + this.model.get('FirstName');
In this View I'll use a template to build my HTML. Underscore, which comes with Backbone, provides templating functionality through its template method. You pass Underscore's template method a string consisting of text, named parameters and (potentially) JavaScript to execute in generating the HTML. The named parameters and JavaScript are enclosed in the delimiters <% and %>. The template method hands you back a processor that will generate your HTML.
For this example, I've separated my code for working with the processor into three steps. First, I define a variable to hold the function returned by Underscore's template function. That variable must hold a function that accepts an unlimited number of parameters and returns a string result. To handle the unlimited number of parameters, you must use a TypeScript "rest" parameter (the equivalent of a parameter array in other languages). A rest parameter gathers up all the parameters to a method that aren't assigned to other parameters and passes those parameters as an array to the function. Rest parameters can have any name you want, but their names must begin with three periods (
). Putting it all together, my variable definition looks like this:
var templateProcessor: (...parms: any[]) => string;
I then use the Underscore template method to generate the processor. The template I pass to the method is simple, consisting of some boilerplate text and two named parameters ("valueParm" and "textParm"):
templateProcessor = _.template('<option value="<%=valueParm %>"><%= textParm %></option>');
To generate your HTML, you call the processor, passing in values to replace the named parameters in the template. The processor returns the generated HTML as a string. This example passes in strings for the template string's valueParm and textParm named parameters and grabs the resulting HTML string. The code then uses the View's setElement method to make the string available through the View's el property:
var html: string;
html = templateProcessor({
valueParm: valueString,
textParm: textString
});
this.setElement(html);
Finally, following Backbone convention, the method returns a reference to the View of which it's a part:
return this;
}
}
}
Testing the Views
My final step is to test my Views by creating some Customer objects and adding them to the CustomerList Collection. This code, placed in the SalesOrderAppStart class I defined in last month's column, does just that (last month's column also discusses the cms and cvs prefixes I use here to refer to my class definitions):
$(function()
{
var CustomersList: cms.CustomerModels.CustomerList;
CustomersList = new cms.CustomerModels.CustomerList();
var cust: cms.CustomerModels.Customer;
cust = new cms.CustomerModels.Customer(1, "Peter", "Vogel");
CustomersList.add(cust);
// ... Add some more Customers
With the Collection created, I can now create my CustomerSelectView and stuff my CustomerList Collection into the View's collection property. I also set the View's $el property to the element on the page where I want the HTML to be added:
var cv: cvs.CustomerViews.CustomersSelectView;
cv = new cvs.CustomerViews.CustomersSelectView();
cv.collection = CustomersList;
cv.$el = $("#Customers");
In a full-fledged Backbone application, the View would generate its HTML in response to an event fired by a model or its collection. For simplicity's sake in this test, I'll just call the View's render method:
cv.render();
}
The result is a dropdown list added to my page.
This is a great test, but it's not anything like a real application. Next month, I'll get closer to the real thing by retrieving data using Backbone's JSON interface and tying my Views to Events.
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/.