The Practical Client

Support Updates in a Page with TypeScript and Backbone

Peter extends his Backbone/Typescript application to support updating and deleting Customer objects. Along the way he discovers what looks like a bug in the way that Backbone and TypeScript work together.

In last month's column, "Responding to Events with TypeScript and Backbone," I added event handling to my single-page application. With those changes, a user can select a customer from a dropdown list on my page and the page will display the related customer information. The obvious next step (and the topic of this column) is allowing the user to make changes to that data.

The focus this time is on the client-side TypeScript code required to implement updates and deletes using Backbone. However, I make the actual changes to the database through an ASP.NET Web API service (it's the simplest server-side solution). You can find the code for the server-side component of this application in "Supporting Server-Side Updates" near the end of this article.

While I was writing this section of the application I ran into a problem with the Backbone support for deleting objects when using TypeScript (I suspect the problem is related to the way that I've set up my Model objects). It sure looks like a bug to me, but, of course, there's always the possibility that I'm just being stupid: More experienced Backbone gurus than me may spot my error (check the comments!). This isn't a bad thing, as it turns out: It allows me to demonstrate using jQuery from TypeScript and integrating non-Backbone code into a Backbone application.

And my usual caveat: I used NuGet to update to the latest version of everything I use in this project. If you've picked up downloads from previous columns I can't guarantee this code will integrate seamlessly with those older versions (though I'm pretty sure this code will work with any version of my project from my August 2014 column or later -- everything seems pretty stable since then). If you're using my downloads, however, your safest bet is to abandon those older versions and just use the one that comes with this article.

Setting Up the Page
I don't need a lot of changes in my UI to support updates and deletes. My page already has a div element with an id of Customers where I display my data. Within that element, I have a CustomerDetail span element where I display the selected customer's information. My only change is to add two buttons within the Customers div element that will run my code that sends update and delete requests back to the server:

<div id="Customers" >
  Select a Customer: <select id="CustomerSelect"></select>

  <h2>Customer Information</h2>
  <span id="CustomerDetail"></span>
  <input type="button" id="SaveButton" value="Save Changes" />
  <input type="button" id="DeleteButton" value="Delete" />
</div>

Extending the View for Updates
In Backbone applications, the UI is managed by a View that works with data held in a Model (in this application my Model holds customer data from the AdventureWorks database). In a View, getting data onto a page is relatively simple: Backbone allows you to data bind the properties in a Model to the HTML generated by the View. However, it's one-way data binding, so changes the user makes in the page aren't automatically passed back to the properties on your Model.

Instead of providing two-way data binding, Backbone handles retrieving data from the page through events. In the View that handles the UI, you tie an event raised by HTML elements in the UI to a method in your View. Backbone will pass that method a parameter that includes the element's current value. If you're not happy with this process you can integrate a two-way data-binding library into your toolbox, but I'm sticking with vanilla Backbone for this example.

My first step is to tie the View to the element on my page that holds my UI. To do that, I just need to pass a jQuery selector that finds my Customers div element to my View's setElement method:

export class CustomerLongView extends bb.View<cms.CustomerLong>
{
  constructor()
  {
    this.setElement($( "#Customers" ));

Still in the constructor, I now set up the events I want to respond to. I do that by passing the View's events property a list of event names/jQuery selectors and method names. For example, the following code ties the input event for the three elements in my UI that hold customer information to three methods (I named the methods firstNameUpdate, lastNameUpdate and companyNameUpdate). My TypeScript constructor must include a call to the super method so I finish the method with that:

this.events = <any> {
                "input #FirstName":   "firstNameUpdate",
                "input #LastName":    "lastNameUpdate",
                "input #CompanyName": "companyNameUpdate"};

    super();
  } 

In each of my three methods, I just need to catch the parameter passed to the method, extract the user's data from the parameter's target.value property, and update the customer object associated with the View. The TypeScript definition file for Backbone doesn't specify a type for the parameter for these event methods so I'm obligated to declare the parameter as any (assuming I datatype the parameter at all):

firstNameUpdate(event: any)
{
  this.model.set("FirstName", event.target.value);
}

Of course, this code will only work if the customer object associated with the View is being held in the View's model property. It's my responsibility to make that happen when I initially generate the View. The View generates its HTML when the View's render method is called so, in my Render method, I retrieve the first (and only) object in the Collection associated with the View and put it into the View's model property. With that change, the start of my View's render method looks like this:

render(): bb.View<cms.CustomerLong>
{
  this.model = this.collection.first()
  ...rest of view...

Updating the Server
At this point all I've accomplished in my code is to update the customer Model object in memory. I want my application to send RESTful requests to my ASP.NET Web API service when the user clicks my Save Changes button. There are three steps involved in triggering Backbone to generate and send those requests.

First, still in my View, I need to wire up methods to the click event for my Save Changes button. The following code adds that event (and another for the click event of my Delete button) to my View:

this.events = <any> {
                "input #FirstName": "firstNameUpdate",
                "input #LastName": "lastNameUpdate",
                "input #CompanyName": "companyNameUpdate",
                "click #SaveButton": "saveCustomer",
                "click #DeleteButton": "deleteCustomer"};

Second, I need to add those saveCustomer and deleteCustomer methods to my View. The saveCustomer method is relatively straightforward: I just call the save method on the Model that I put into the View's model property:

saveCustomer()
{
  this.model.save();
}

Calling the Model's save method causes the model to issue a RESTful POST request to the URL associated with the Model. So, for my third step, I tie the Model to the service that manages customer model by setting the Model's url property to point to the URL I've set up in my Web API's routings. I do that in the Model's constructor:

export class CustomerLong extends bb.Model implements ICustomerLong
{
  constructor()
  {
    this.url = "CustomerService/long/"
    super();
  }

Backbone will automatically tack my Model's Id property onto the end of the URL (if the property on your Model that uniquely identifies your object has a different name than "Id" then you can use the Model's idAttribute to specify what property to use). Backbone also includes the Model object as a JSON object in the request sent to my service.

The deleteCustomer method should have been just as simple as my saveCustomer method. All I should have had to do is call the Model's destroy property, like this:

deleteCustomer()
{
  this.model.destroy();
}

Unfortunately, I couldn't get this to work. Instead of generating a RESTful request to the service with the value of Model's Id property tacked on the end of the URL, I got a RESTful request with either the code for the Id property (or nothing at all) tacked on to the end of the URL. This isn't as much of a problem as you might suspect because, when I couldn't resolve the problem, I just wrote my own AJAX request in the method:

deleteCustomer()
{
  $.ajax( {
           type: "DELETE",
           url: this.model.url() + this.model.id(),
           contentType: "application/json"
});

And, I now have a page that allows the user to update and delete selected customers. Of course, I haven't handled adding new Customers or even updating the UI when a customer is deleted. However, before I tackle those tasks I want to look at a more interesting scenario: displaying a master/detail page that associates multiple sales orders with my customer data (that's my project for next month's column). Once I've got that working, I'll return to adding new customers and managing the UI better. Promise.

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/.

comments powered by Disqus

Featured

Subscribe on YouTube