Practical TypeScript

Supporting Updates and Deletes with Backbone and TypeScript

Peter finishes off his series on creating single-page applications by showing how jQuery integrates with Backbone to support updates and deletes (also: how to convert a string to a number in TypeScript).

This column finishes off a series I began in May covering the various issues in creating a single page master/detail application in Backbone using TypeScript. Here, I'll show how to integrate update and delete functionality into the page.

In the sample application I've been using, a user selects a customer from a dropdown list to retrieve a customer object from the server (in Backbone-speak these objects are referred to as Models and are held in Backbone Collections at the browser). Each Customer object has an Orders collection that holds all the SalesOrderHeaders associated with that customer (I'm using the AdventureWorks database for this case study). I've already showed how to update or delete the single customer object. This column shows how to update or delete the SalesOrderHeaders.

In last month's column, "Integrating JavaScript with TypeScript (and Backbone and Knockout)," I showed how Backbone can work with other libraries by leveraging the Knockout data binding support (that column also showed how to integrate native JavaScript into your TypeScript application). This column, however, backs out those additional libraries to provide a "pure Backbone" solution. In this column, I've also ignored some more advanced patterns available in Backbone to support nested collections -- if you were building a production application you have more sophisticated options than I show here … but that would take longer to explain. Besides, going with the simpler solution lets me demonstrate using jQuery from TypeScript in Backbone.

HTML for Supporting Updates
In my page, the form that my users interact with is enclosed inside a div element called Customers. The CustomerDetail span element inside this div element holds the table that displays the customer's salesorder headers:

<div id="Customers">
  <h2>Customer Information</h2>
  <span id="CustomerDetail"></span>

My first step in supporting update/delete functionality is to add two buttons within the Customers element for the user to trigger processing:
  <input type="button" id="DeleteOrders" value="Delete Orders" />
  <input type="button" id="UpdateOrders" value="Update Orders" />
</div>

My next step is to enhance the template that generates the table I use to display salesorder headers (shown in Listing 1).

Listing 1: A Template for Displaying SalesOrderHeaders
<script type="text/template" id="salesOrdertemplate">
  <tr>
    <td> 
      <input type="checkbox" id="selected" /> 
    </td>
    <td> 
      <div id="salesOrderId"><%= SalesOrderID %></div> 
    </td>
    <td>
      <input type="text" name="Status" id="status" value="<%= Status %>" /></td>
    <td><%= OrderDate %> </td>
  </tr>
</script>

My template will generate a table with a row for each SalesOrderHeader Model. Each template row has a checkbox (with an id of selected) that allows users to check the row to flag it for processing. The row's second column displays the SalesOrderId, which uniquely identifies the SalesOrderHeader displayed in the row. I've enclosed that SalesOrderId in a div element with an id of salesOrderId so I can retrieve the value when I need it. The third column has an input element for the SalesOrderHeader Status property, which is the only property I'm going to allow the user to update (I've given it an id of status). The final column displays the SalesOrderHeader OrderDate property and, because I don't need to update or retrieve it, I haven't enclosed in any element other than the cell's td tag.

Processing Deletes
In my client-side code, I have a Backbone view called CustomerLongView that's responsible for displaying the template. In the constructor for that View, I associate the View with the div element that encloses my customer form. Also in the constructor, I tie click events for the DeleteOrders and UpdateOrders buttons to methods I've called deleteOrders and updateOrders. Finally, the constructor includes a call to the super method because that's required in the constructor of any TypeScript class that extends another class:

export class CustomerLongView extends bb.View<cms.CustomerLong>
{
  constructor()
  {
    this.setElement($("#Customers"), true);
    this.events = <any> 
      {
        "click #DeleteOrders": "deleteOrders",
        "click #UpdateOrders": "updateOrders"
      };
    super();
}

Backbone will pass my deleteOrders method a parameter that includes information about the element that fired the event. However, I'm not interested in the button that fired the event, but instead, in what rows in the table the user has checked. To find those rows, I take advantage of the Backbone support for jQuery. For example, any jQuery statements I execute from the View are scoped to the element I've tied the View to in my constructor, which simplifies my jQuery selectors.

Using jQuery, my first step is to retrieve all of the table rows that have a checked-off checkbox, using the jQuery checked selector. Once jQuery has found all of those checkboxes, I use the jQuery each function to process every selected element. The each function calls a function of my own, passing my function two parameters: the position of the element in the collection that jQuery found and the element itself. Here's what that code looks like:

deleteOrders()
{
  var id: number;
  $("#selected:checked").each((rpos: number, elm: HTMLElement) => {

As I process each element, I first retrieve the tr element of which the checkbox is inside, using the jQuery closest function. Once I've found the tr element, I use the jQuery find function to retrieve the salesOrderId element inside the tr element (that gives me the unique identifier for the SalesOrderHeader). Because the salesOrderId is held inside a div element, I use the jQuery text function to retrieve the salesorder Id.

I have a problem, though. The jQuery text function returns a string, but the SalesOrderId property on my SalesOrderHeader class is a number. I could use one of the built-in TypeScript methods to convert the text function's output to a number, but TypeScript has a simpler solution: the unary + operator. All I have to do is prefix my jQuery statement with a plus sign to convert a string to a number:

var id: number;
id = +$(elm).closest("tr").find("#salesOrderId").text();

Now that I have the value for the SalesOrderHeaderId, I can issue a jQuery AJAX call to the ASP.NET Web API service I've written to process the request:

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

The URL I've designed for my service has three parts to it. The first part ("CustomerService") ties this URL to the correct route at my service (see "Supporting Deletes on the Service" for more on the server-side processing). I use the second part of the URL ("Orders") to control what processing I'll do at the service. The third part of the URL holds the SalesOrderId of the salesorder to process.

My final step is to remove that order from the customer Model of which it's a part. First, I declare an array of SalesOrderHeaders to hold the contents of the customer's Orders property. (In an earlier column, I defined an interface for the SalesOrderHeaders class called ISalesOrderHeader.) I've also stashed the current customer Model in the View's model property. This lets me use use the model property's get method, passing the name of the Orders property, to retrieve the array of salesorder headers for this customer:

var sohs: cms.ISalesOrderHeader[];
sohs = this.model.get("Orders");

Now I need to find the position of the SalesOrderHeader in the array. Looping through the array looking for a match lets me find that position:

var pos: number
for (var i = 0; i < sohs.length; i++)
{
  if (sohs[i].SalesOrderID === id)
  {
    pos = i;
  }
}

Once I've found the position of SalesOrderHeader in the array, I can use the JavaScript splice method to remove the single order at that position. With that done, I use the model property's set method to update the Customer's Orders property with the updated array:

  sohs.splice(pos, 1);
  this.model.set("Orders", sohs);
});

Finally, after processing all the checked rows, I call my View's render method to redisplay the page:

this.render();

Supporting Deletes on the Service
In my ASP.NET Web API service, to support my AJAX call when deleting SalesOrderHeader, I first need to establish a route in the service's Global.asax file that will decode the URL I use. My route specifies all URLs beginning with CustomerService be routed to a controller called Customer. I assign the name type to the second part of the URL and the name Id to the third part of the URL:

protected void Application_Start(object sender, EventArgs e)
{
  GlobalConfiguration.Configure(config =>
  {
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
      name: "CustomerApi",
      routeTemplate: "CustomerService/{type}/{id}",
      defaults: new { controller = "Customer", type = "short", id = 
      RouteParameter.Optional });

In the constructor for my CustomerController class, I instantiate my DbContext object, which is working with a version of the AdventureWorks database:

public class CustomerController : ApiController
{
  private AdventureWorksLTEntities ade;
  public CustomerController()
  {
    Database.SetInitializer<AdventureWorksLTEntities>(null);
    ade = new AdventureWorksLTEntities();
  }

To handle an AJAX DELETE call in my controller, I just need to add a method called Delete to my controller. I've set up my service to handle updates to both Customers and SalesOrderHeaders and I signal which processing I want using the type parameter in my URL. To catch the type parameters (and the id parameter holding the SalesOrderHeaderId), I just need to declare parameters in my Delete method with matching names. To delete the specified SalesOrderHeader, I retrieve it from the database, remove it from the SalesOrderHeaders collection, and then save my changes, as shown in Listing 2.

Listing 2: Deleting the Specified SalesOrderHeader
public void Delete(int? id, string type)
{
  switch (type)
  {
    case "Orders":

      SalesOrderHeader sohExisting = (from so in ade.SalesOrderHeaders
                                      where so.SalesOrderID == Id
                                      select so).SingleOrDefault();
      ade.SalesOrderHeaders.Remove(sohExisting);
      ade.SaveChanges();
   }
   return;
}   

Processing Updates
With the delete processing, I did virtually all of the work myself. With the updates, I can let Backbone do some of the heavy lifting. Much of my updateOrders method looks like my deleteOrders method, but there are several differences. First, I retrieve all of the tr elements to get all of the SalesOrderHeader rows from my table:

$( "tr" ).each( ( pos, elm ) =>
{

Second, because I'm working with the table rows (rather than with a checkbox inside the table row), the jQuery selector for finding the SalesOrderId in the row is a little simpler. I use a selector based on the element's id to retrieve the status value from the row:

id = +$( elm ).find( "#salesOrderId" ).text();
status = $( elm ).find( "#status" ).val();

My next step is to find the matching SalesOrderHeader in the customer's Orders collection. However, this time I update its Status property rather than removing the SalesOrderHeader from the collection:

sohs = this.model.get( "Orders" );
for ( var i = 0; i < sohs.length; i++ )
{
  if ( sohs[i].SalesOrderID === id )
  {
    pos = i;
  }
}
soh.Status = status;
);

Finally, instead of issuing the call to my server directly, I let Backbone take care of it by calling the model's save method. The save method will use the URL I assigned to the model's Backbone Collection to make a RESTful call to my ASP.NET Web API service:

this.model.save();

Supporting Updates on the Server
To support the Backbone save method (which uses HTTP POST calls to send updates back to the server), I need to add a method called Post to my controller. Backbone will send, as a parameter to the method, a JSON version of its CustomerLong Model. To catch that parameter, I just need a server-side object with properties whose names match the names on the client-side CustomerLong Model. I've called that server-side object CustomerLong:

public void Post(CustomerLong cust)
{

My server-side object that corresponds to my client-side SalesOrderHeader object is called SalesOrderHeaderDTO (remember: it's the property names that count and my server-side SalesOrderHeaderDTO and client-side SalesOrderHeader classes share property names). In my Post method, I roll through all the SalesOrderHeaderDTO objects in the CustomerLong Orders collection. For each SalesOrderHeaderDTO, I retrieve the corresponding SalesOrderHeader from the database and update its Status property:

SalesOrderHeader soh;
foreach (SalesOrderHeaderDTO sohDTO in cust.Orders)
{
  soh = ade.SalesOrderHeaders.Find(sohDTO.SalesOrderID);
  soh.Status = (byte) sohDTO.Status;                    
}

After processing all of the SalesOrderHeaderDTOs in the CustomerLong Orders collection, I call my DbContext object's SaveChanges method to update my database:

ade.SaveChanges();

There you have it: A single-page application that lets a user select a customer to display both the customer information and a list of salesorder headers. The download for this article includes the code to update both the customer and the salesorder headers.

Now, for Something Completely Different …
This column finishes my look at using Backbone with TypeScript. For the next few columns, rather than show how to work with a JavaScript framework as I've done in the last set of columns, I'm going to examine some features of the TypeScript language. However, later on in the year, I'll show how to use TypeScript with another popular JavaScript framework: Angular.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.