TypeScript : Using Backbone.Marionette and REST WebAPI (Part 1)

This article ( Part 1 and Part 2) aims to walk the reader through setting up a Backbone.Marionette SPA application with Visual Studio 2013, and in particular, write Marionette JavaScript using TypeScript.  Generally, writing JavaScript applications relies on RESTful web services, so this blog will also aim to show how to accomplish this with ASP.NET WebAPI data controllers.

Backbone.Marionette was designed to simplify large scale JavaScript applications.  After a mate of mine ( three votes, two votes, two votes one. ) loaned me a book on Marionette, I decided to give it a whirl.

Being the unit-test junkie that I am, I also aim to show how to build JasmineJs unit-tests to test your REST services, and also show how to generate and use nested JSON objects.  Nested JSON data fits hand in glove with Backbone Models and TypeScript.

Update : Many thanks to Alastair who pointed out some typos. These have now been fixed.

Firstly, a few links:

Backbone.Marionette.js: A gentle introduction is an excellent book by David Sulc, that I used to gently introduce me to Marionette. 

Marionettejs.com is the official Marionette site – downloads and documentation.

MarionetteJS 1.4.1 on nuget is the nuGet repository by benb1in.

marionette.TypeScript.DefinitelyTyped is the nuGet repository for TypeScript definitions by Jason Jarrett

DefinitelyTyped/marionette holds the TypeScript definition .d.ts files for Marionette, by sventschui.

Twitter.Bootstrap is the nuGet repository for Bootstrap 3.0.1.1 by Jakob Thornton and Mark Otto

bootstrap.TypeScript.DefinitelyTyped is the nuGet repository for TypeScript definitions of boostrap by Jason Jarrett

As always, the full source code for this article can be found on github: blorkfish/typescript-marionette

I have broken this article up over two parts, as we will be covering quite a few techniques and sample code – but fear not, it is quite fast-paced with tangible results every step of the way.  As an overview, we will accomplish the following:

  • Setting up a Visual Studio 2013 project.
  • Install required nuGet packages.
  • Creating the ASP.NET HomeController and View.
  • Including required javascript libraries in our Index.cshtml.
  • Creating a Marionette.Application.
  • Adding a Marionette Region
  • Referencing the Region in the Application
  • Creating a Marionette.View
  • Using bootstrap to create a clickable NavBar Button
  • Using Backbone Models to drive NavBar buttons.
  • Creating a Backbone.Collection to hold multiple Models
  • Creating a Marionette.CompositeView to render collections
  • Rendering Model Properties in templates
  • Using Marionette Events.
    At the end of Part 1, our awesome application will look like this : and use events to notify our App when a navbar button is clicked:

image

image

In Part 2 of this article we will cover the following:

    • Creating an ASP.NET WebAPI Data Controller.
    • Unit testing the WebAPI Data Controller in C#
    • Modifying the WebAPI Data Controller to return a collection of nested C# POCO objects.
    • Defining TypeScript Backbone.Model classes to match our nested class structure.
    • Writing Jasmine unit tests for our Backbone Collection.
    • Creating a Marionette.CompositeView to render data in a bootstrap table.
    • Using a Marionette.CompositeView as an ItemView
    • Rendering nested Backbone.Collections
    • Using CompositeView properties to generate html.
    • Using bootstrap styles in a Marionette.CompositeView
    When we are complete with this tutorial, we will have generated a nested Json structure as follows: UserList –> User –> RoundScores –> RoundScore.
    We will then render it as follows:

image

 

Setting up a Visual Studio 2013 project.

Firstly, let’s create a new Visual Studio project.  Make sure that you select an ASP.NET MVC 4 Web Application.  This will allow for the addition of WebAPI data controllers.

image

Personally, I prefer creating an Empty web application – as Visual Studio makes it very easy to add Controllers and Views later.  Change the View Engine to Razor.

image

Next, let’s switch to the .NET framework 4.5.1.  Right-click on the project and choose Properties.  Under Application, change the Target framework to .NET Framework 4.5.1:

image

Install required nuGet packages.

Next, install the following nuGet packages.  Click on TOOLS | Library Package Manager | Package Manager Console, and type the following:

Install-Package Backbone.Marionette

Install-Package marionette.TypeScript.DefinitelyTyped

Install-Package jasmine-js

Install-Package jasmine.TypeScript.DefinitelyTyped

Install-Package Twitter.Bootstrap

Install-Package bootstrap.TypeScript.DefinitelyTyped

Install-Package jasmine-jquery.TypeScript.DefinitelyTyped

Install-Package json2

Install-Package Newtonsoft.Json

Install-Package Microsoft.AspNet.WebApi

Install-Package underscore.TypeScript.DefinitelyTyped

NOTE : Compiling the project now will generate about 120 compile errors, similar to the following:

Build: Duplicate identifier ‘abort’. Additional locations

To fix this, navigate to the Scripts / typings / jasmine directory, and delete the jasmine-1.3.d.ts file from the project.

Creating the ASP.NET Home Controller and View.

Next, we will need to create a Controller and View to serve up a simple web page.

From Solution Explorer, right-click on the Controllers directory, and select Add | Controller.  Call it HomeController, and use the Empty MVC Controler Template:

image

Now create a Home directory under Views – and then right-click on the Home directory, and select Add | View .  Name the View “Index” as below:

image

Our solution explorer should look as follows:

image

Modify the Index.cshtml with some Hello world text as follows – just to check that we can hit this view:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <h1>Hello Home Controller</h1>
    </div>
</body>
</html>

Running the application now ( hit F5 ) should successfully run the HomeController, and serve our Index.cshtml page:

image

Including required javascript libraries in our Index.cshtml.

Our next step is to reference some of the javascript libaries that we downloaded via nuGet in order for Marionette to work correctly.  Note that nuGet has placed the downloaded javascript libaries in the folder /Scripts – and the downloaded TypeScript definition files in /Scripts/typings.

Also, when running with Internet Explorer, we will need to ensure that IE uses the correct engine when parsing both the DOM and JavaScript.  This is accomplished by adding a meta tag to the page to force IE to use the latest JavaScript engine (by default, IE will revert to the IE 8 engine).

Modify the Index.cshtml page to include the following javascript libaries – and add the meta tag for IE as follows:

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Index</title>

    <link rel="stylesheet" href="../../Content/bootstrap.css" type="text/css" />

    <script language="javascript" type="text/javascript" src="../../Scripts/jquery-1.9.1.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/json2.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/underscore.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/backbone.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/backbone.marionette.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/bootstrap.js"></script>
    
</head>
<body>
    <div>
        <h1>Hello Home Controller</h1>
    </div>
</body>
</html>

Creating a Marionette.Application

All Marionette SPAs start with a Marionette.Application.  The Marionette.Application has a number of responsibilities, including responding to application-wide events, and defining broad html “regions” that relate to specific application controllers and views.

To create a Marionette Application, simply create a class that extends from Marionette.Application.

This is one of the greatest advantages of using Backbone and Marionette with TypeScript.  The TypeScript extends keyword does exactly what you would expect it to do – it extends the definition of an existing class.  Just what you would expect from an Object Oriented language.  Unfortunately, most of the popular JavaScript libraries are not built this way, and use configuration settings to drive object behaviour.  Coming from a strongly typed background, I personally find it easier to understand and work with Backbone and Marionette because of this.

I plan to write a blog fairly soon comparing how Backbone, ExtJs, AngularJs and Marionette shape up as compatible libraries when using TypeScript and Visual Studio as your main development tools.  In the  meantime, though, Marionette wins the race hands-down (IMHO).

So back to the task at hand.  I prefer to keep all TypeScript code in a separate directory, named /tscode. Go ahead and create this directory, and then create a new TypeScript file in the /tscode directory named MarionetteApp.ts:

Add | New Item | Visual C# | Web | TypeScript File:

/// <reference path="../Scripts/typings/jquery/jquery.d.ts"/>
/// <reference path="../Scripts/typings/underscore/underscore.d.ts"/>
/// <reference path="../Scripts/typings/backbone/backbone.d.ts"/>
/// <reference path="../Scripts/typings/marionette/marionette.d.ts"/>

class MarionetteApp extends Marionette.Application {
    constructor() {
        super();
        this.on("initialize:after", this.initializeAfter);
    }
    initializeAfter() {
        alert("initializeAfter called");
    }
}

Now, lets include this file in the Index.cshtml.  Note that to start-up a Marionette application, we need to instantiate an instance of our Marionette.Application, and call the start() function:

Modify your index.cshtml file – firstly to include the MarionettApp.js file in the <head> element, and then with a script down the bottom to instatiate the application, and call start() :

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Index</title>

    <link rel="stylesheet" href="../../Content/bootstrap.css" type="text/css" />

    <script language="javascript" type="text/javascript" src="../../Scripts/jquery-1.9.1.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/json2.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/underscore.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/backbone.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/backbone.marionette.js"></script>
    <script language="javascript" type="text/javascript" src="../../Scripts/bootstrap.js"></script>
    
    <script language="javascript" type="text/javascript" src="../../tscode/MarionetteApp.js"></script>
</head>
    <body>
        <div>
            <h1>Hello Home Controller</h1>
        </div>

        <script type="text/javascript">
            var marionetteApp = new MarionetteApp();
            marionetteApp.start();
        </script>
    </body>
</html>

Running the web application now should call the alert() function :

image

Adding a Marionette Region

As mentioned before, one of the responsibilities of  a Marionette Application is to create and manage regions.  Think of a region as a broad section of your html that Marionette will inject DOM elements into.  These regions can be shown and hidden – and can even transition with JQuery animations.  To create a Region, simply add a div to your html page, and specify an id.   As we have already installed bootstrap with nuGet, let’s use it to create a navbar panel with a region in it. 

Modify your Index.cshtml as follows:

 <body>
        
        <div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="row">
                    <div class="col-lg-6">
                        <div >TypeScript Marionette</div>
                    </div>
                    <div class="col-lg-6">
                        <div id="navbarRegion" >
                            <p>navbarRegion</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>        
        
        <div class="row">
            <div class="col-lg-12">
                <h1>Hello Home Controller</h1>    
            </div>
        </div>        

        <script type="text/javascript">
            var marionetteApp = new MarionetteApp();
            marionetteApp.start();
        </script>
    </body>

Note that we are starting to use some bootstrap styles here – creating a navbar, containers and rows.

Unfortunately, running the app now will produce a dark navbar, with the navbar text very difficult to read, as well as overlapping our Hello Home Controller Text:

image

To fix this, create an app.css file in the /Content directory, with the following styles:

body { 
   padding-top : 60px; 
 } 

.app-navbar-text { 
   Font-family : Verdana,Arial,Sans-serif; 
   Font-size : 25px; 
   Font-style : Normal; 
   color : White; 
   padding-top : 5px; 
 } 

Now include this app.css in the Index.cshtml file:

    <link rel="stylesheet" href="../../Content/bootstrap.css" type="text/css" />
    <link rel="stylesheet" href="../../Content/app.css" type="text/css" />

And finally apply the app-navbar-text style to the divs in the navbar:

        <div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="row">
                    <div class="col-lg-6">
                        <div class="app-navbar-text">TypeScript Marionette</div>
                    </div>
                    <div class="col-lg-6">
                        <div id="navbarRegion" class="app-navbar-text">
                            <p>navbarRegion</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>        

Your page should now at least be readable:

image

Referencing the Region in the Application

To reference an html div as a Region in Marionette, we simply need to call the Marionette.Application function addRegions().  As this function is at the Application level, the easiest way to do this in TypeScript is to use a class property to store the Region as follows (note that the alert is now commented ) :

class MarionetteApp extends Marionette.Application {
    navbarRegion: Marionette.Region;
    constructor() {
        super();
        this.on("initialize:after", this.initializeAfter);
        this.addRegions({ navbarRegion: "#navbarRegion" });
    }
    initializeAfter() {
        //alert("initializeAfter called");
    }
}

We will use this region as a content placeholder to render our Views a bit later on.

Creating a Marionette.View

In order to render something within the region, we will need to create a Marionette.ItemView.

In much the same way as we provided a <div> as the html template for a Marionette.Region, we provide a <script> block with a type of “text/template” to a Marionette.ItemView to use as an html template.  Modify your index.cshtml file to include the following:

        <script type="text/template" id="navBarItemViewTemplate">
            <p>NavBar View Template</p>
        </script>

To create a Marionette.ItemView in TypeScript, simply derive a class from Marionette.ItemView.  To do this, create a views directory under /tscode, and add a new TypeScript file named NavBarItemView.ts.

image

The code for NavBarItemView.ts is shown below.  Note that the reference paths at the top of this file will need to change slightly in order to correctly reference .d.ts files, as we are now two directories up from the base directory.

In the constructor we can see that the code is initializing the options parameter before it passes it to the base class.    To use the template that we created above, simply assign the template property of the options parameter to reference our <script> by id. :

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts"/>
/// <reference path="../../Scripts/typings/underscore/underscore.d.ts"/>
/// <reference path="../../Scripts/typings/backbone/backbone.d.ts"/>
/// <reference path="../../Scripts/typings/marionette/marionette.d.ts"/> 

class NavBarItemView extends Marionette.ItemView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarItemViewTemplate";
        super(options);
    }
}

Now modify your index.cshtml file to include the generated NavBarItemView.js file:

    <script language="javascript" type="text/javascript" src="../../tscode/MarionetteApp.js"></script>
    <script language="javascript" type="text/javascript" src="../../tscode/views/NavBarItemView.js"></script>
</head>

Lastly, modify the MarionetteApp to call show() on this view:

class MarionetteApp extends Marionette.Application {
    navbarRegion: Marionette.Region;
    constructor() {
        super();
        this.on("initialize:after", this.initializeAfter);
        this.addRegions({ navbarRegion: "#navbarRegion" });
    }
    initializeAfter() {
        //alert("initializeAfter called");
        this.navbarRegion.show(new NavBarItemView());
    }
}

If we run the application now ( hit F5 ) – we should see the “navBar region” text in the page replaced by the html we  specified in navBarItemViewTemplate :

image

Using bootstrap to create a clickable NavBar Button.

Our NavBarItemView is all well and good, but let’s do something useful with it. 

Most navigation bars are used to provide site-wide functionality – such as login / logout – so let’s update our NavBarItemView to show a boostrap button, and make it clickable.

To render our NavBarItemView as a bootstrap button, our template simply needs to have a css class assigned to it.  Modify the constructor of NavBarItemView to set the options.classname as follows:

class NavBarItemView extends Marionette.ItemView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarItemViewTemplate";
        options.className = "btn btn-primary";
        super(options);
    }
}

Running the app now will assign the correct classnames to our template rendering it as a bootstrap button:

image

In order to make the button clickable, simply set the options.events property.  We will need to specify a function to call when the click event is fired.  This function can be called anything.  In the sample below, I’ve called it onClickEvent():

class NavItemBarView extends Marionette.ItemView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarItemViewTemplate";
        options.className = "btn btn-primary";
        options.events = { "click": this.onClickEvent };
        super(options);
    }
    onClickEvent() {
        alert('NavBarItemView clicked');
    }
}

Run the app now, click on the navbar button, and verify that the event is fired correctly:

image

How simple was that ?

At this point it would be nice to render a couple of buttons in the navbar – but really this should be data-driven and not require too many changes to our ItemView.

Enter Backbone models and collections.

Let’s define a Backbone.Model to hold two properties for our buttons: Name and Id.  Then let’s create a collection of these models to drive rendering of multiple navbar buttons.

Using Backbone Models to drive NavBar buttons:

I’ve blogged previously about using strongly typed Backbone models with TypeScript. ( Can’t believe that was a year ago already ! ).   So if you would like a refresher then go an have a look.  Basically, we will be using ES5 syntax for model properties.  Go ahead and create a models directory under /tscode, and add a TypeScript file called NavBarButtonModel.ts.  Create a class that extends from Backbone.Model, called NavBarButtonModel.  Note that we also create a TypeScript interface definition for this model – which will help us when we start working with nested JSON and nested Backbone models and collections.

The constructor takes the INavBarButtonModel as input, and then sets each of the properties in the for loop.  This simple technique of using ES5 syntax and the constructor ensures that the model is synched with the underlying Backbone get and set functions, as well as giving us full type safety.

The source for NavBarButtonModel is as follows:

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts"/>
/// <reference path="../../Scripts/typings/underscore/underscore.d.ts"/>
/// <reference path="../../Scripts/typings/backbone/backbone.d.ts"/>
/// <reference path="../../Scripts/typings/marionette/marionette.d.ts"/> 

interface INavBarButtonModel {
    Name?: string;
    Id?: number;
}

class NavBarButtonModel extends Backbone.Model implements INavBarButtonModel {
    get Name(): string { return this.get('Name'); }
    set Name(value: string) { this.set('Name', value); }

    get Id(): number { return this.get('Id'); }
    set Id(value: number) { this.set('Id', value); }

    constructor(input: INavBarButtonModel) {
        super();
        for (var key in input) {
            if (key) { this[key] = input[key]; }

        }
    }
}

Creating a Backbone.Collection to hold multiple Models.

Now that we have a Backbone.Model defined, lets define a Backbone.Collection to hold multiple buttons.  Create a NavBarButtonCollection.ts file in the models directory.  To define a Backbone.Collection, all we need to do is extend from Backbone.Collection, and set our model property to the name of the model class as follows.  Note that we have added a reference path at the top of the file to point to our model’s TypeScript file:

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts"/>
/// <reference path="../../Scripts/typings/underscore/underscore.d.ts"/>
/// <reference path="../../Scripts/typings/backbone/backbone.d.ts"/>
/// <reference path="../../Scripts/typings/marionette/marionette.d.ts"/> 
/// <reference path="./NavBarButtonModel.ts" />

class NavBarButtonCollection extends Backbone.Collection {
    constructor(options?: any) {
        super(options);
        this.model = NavBarButtonModel;
    }
}

This collection will eventually be populated by JSON retrieved from a WebAPI service. But for now we can create a new collection as in the code below.  Note that this code is just a sample of how to create a collection – we will include it in the MarionetteApp.ts file a little later.:

        var navBarButtonCollection: NavBarButtonCollection = new NavBarButtonCollection(
            [
                { Name: "Home", Id: 1 },
                { Name: "About", Id: 2 },
                { Name: "Contact Us", Id: 3 }
            ]);

  But once we have a collection of NavBarButtonModels, we will need a new View to render the collection.

Creating a Marionette.CompositeView to render collections.

We now have most of the building blocks in place in order to render this collection on our page.  The final piece is a view that will take the NavBarButtonCollection as unput, and then instantiate a NavBarItemView for each model found in the collection.

Let’s create a new Marionette.CompositeView for this purpose.    As usual, we first need to create a <script> region in our Index.cshtml file – which will serve as the html template for our composite view.  Modify the Index.cshtml file to add a template with the id of navBarCollectionViewTemplate .  For the moment, we will simply define the template, but leave it blank as follows:

        <script type="text/template" id="navBarCollectionViewTemplate">
        </script>

Next, create a new TypeScript file under /tscode/views named NavBarCollectionView.ts.  This class will extend from Marionette.CompositeView.   As usual, specify the name of the html template in the options.template property as below:

Now we need to set the the itemView property to the class name of the view that is responsible for rendering an item – which in our case is NavBarItemView:

/// <reference path="../../Scripts/typings/jquery/jquery.d.ts"/>
/// <reference path="../../Scripts/typings/underscore/underscore.d.ts"/>
/// <reference path="../../Scripts/typings/backbone/backbone.d.ts"/>
/// <reference path="../../Scripts/typings/marionette/marionette.d.ts"/> 
/// <reference path="./NavBarItemView.ts"/> 

class NavBarCollectionView extends Marionette.CompositeView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarCollectionViewTemplate";
        super(options);
        this.itemView = NavBarItemView;
    }
}

Next, we will need to include the new .js files in our Index.html <head> section:

    <script language="javascript" type="text/javascript" src="../../tscode/MarionetteApp.js"></script>

    <script language="javascript" type="text/javascript" src="../../tscode/views/NavBarItemView.js"></script>
    <script language="javascript" type="text/javascript" src="../../tscode/views/NavBarCollectionView.js"></script>
    
    <script language="javascript" type="text/javascript" src="../../tscode/models/NavBarButtonCollection.js"></script>
    <script language="javascript" type="text/javascript" src="../../tscode/models/NavBarButtonModel.js"></script>

Finally, we need to create an instance of the collection and pass it to the NavBarCollectionView.  Modify the MarionetteApp.ts file as shown below. 

Note that we create a NavBarButtonCollection by simply passing an array of objects – very similar to what raw JSON would look like.  Also, we create a NavBarCollectionView and pass it the collection in another JSON style object – by setting the collection property to the newly created NavBarButtonCollection:

class MarionetteApp extends Marionette.Application {
    navbarRegion: Marionette.Region;
    constructor() {
        super();
        this.on("initialize:after", this.initializeAfter);
        this.addRegions({ navbarRegion: "#navbarRegion" });
    }
    initializeAfter() {
        var navBarButtonCollection: NavBarButtonCollection = new NavBarButtonCollection(
            [
                { Name: "Home", Id: 1 },
                { Name: "About", Id: 2 },
                { Name: "Contact Us", Id: 3 }
            ]);

        this.navbarRegion.show(new NavBarCollectionView({ collection: navBarButtonCollection }));
    }
}

Firing up the app now should show us three buttons in our navbar – although it’s not quite what we envisaged.

image

Rendering Model Properties in templates.

What we really want here is to modify our ItemView template to display the Name value of the NavBarButtonModel instead of NavBarItem View Template.

Thankfully, it’s a piece of cake.

Modify the <script type="text/template" id="navBarItemViewTemplate"> tag in Index.cshtml as follows:

        <script type="text/template" id="navBarItemViewTemplate">
            <%= Name %>
        </script>

Running the app now will render the button names based on the model properties:

image

Finally, lets modify our click event in NavBarItemView to read the Id from the NavBarButtonModel:  You may notice that the call to get the Id property from the model is NOT using ES5 syntax.  As far as I understand, this is because Backbone is using the base Backbone.Model class internally, and therefore relies on the base get(‘attribute’) functions.

class NavBarItemView extends Marionette.ItemView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarItemViewTemplate";
        options.className = "btn btn-primary";
        options.events = { "click": "onClickEvent" };
        super(options);
    }
    onClickEvent() {
        alert('NavBarItemView clicked with id :' + this.model.get('Id'));
    }
}

Clicking on any one of the buttons will now show a message with the model’s Id.

image

Triggering a Marionette event.

For the final exercise of Part 1, let’s use Marionette events to notify the MarionetteApp when someone clicks on a Navbar menu item.

Modify the onClickEvent() of the NavBarItemView to call Marionette’s trigger() function.  When triggering an event, we will need an event name (which can be anything), and we can also attach any data we need to the event.  In the code below, we are attaching the Model’s Id to the event.

class NavBarItemView extends Marionette.ItemView {
    constructor(options?: any) {
        if (!options)
            options = {};
        options.template = "#navBarItemViewTemplate";
        options.className = "btn btn-primary";
        options.events = { "click": "onClickEvent" };
        super(options);
    }
    onClickEvent() {
        this.trigger("navbar:clicked", this.model.get('Id'));
    }
}

Listening to a Marionette.Event

To handle this even, we will make some changes to the MarionetteApp.  Basically, just call the

on(‘eventname’,callback) method on the view, and provide a function callback (this.navBarButtonClicked).  Note too that the listening eventName is slightly different to the trigger eventName: itemview:navbar:clicked (handler) as opposed to just navbar:clicked ( trigger ).  This is because Marionette automatically attaches itemview: to any event that is fired by an ItemView.

Also note the signature of the callback method: we have two parameters – itemView and buttonId.  Again, Marionette always sends a handle to the originating ItemView as the first parameter to an event handler.  The second parameter therefore is our Model’s Id.

class MarionetteApp extends Marionette.Application {
    navbarRegion: Marionette.Region;
    constructor() {
        super();
        this.on("initialize:after", this.initializeAfter);
        this.addRegions({ navbarRegion: "#navbarRegion" });
    }
    initializeAfter() {
        var navBarButtonCollection: NavBarButtonCollection = new NavBarButtonCollection(
            [
                { Name: "Home", Id: 1 },
                { Name: "About", Id: 2 },
                { Name: "Contact Us", Id: 3 }
            ]);

        var navBarView = new NavBarCollectionView({ collection: navBarButtonCollection });

        navBarView.on("itemview:navbar:clicked", this.navBarButtonClicked);

        this.navbarRegion.show(navBarView);
    }

    navBarButtonClicked(itemView: Marionette.ItemView, buttonId: number) {
        alert('Marionette.App handled NavBarItemView clicked with id :' + buttonId);
    }

}

Running our App now – and clicking on a NavBarButton will then fire a Marionette event, which is then handled by the Marionette.App itself:

image

Well, that’s it for Part 1 of this article.

As mentioned, Part 2 of this article we will cover the following:

  • Creating an ASP.NET WebAPI Data Controller.
  • Unit testing the DataController in C#
  • Updating the NavBarButtonCollection to use our DataController.
  • Unit testing the NavBarButtonCollection using Jasmine.
  • Creating a Marionette.CompositeView to render data in a bootstrap table.

Have fun,

blorkfish.

TypeScript strongly typed Backbone Models

TypeScript and Backbone are a great fit in terms of writing simple, Object-Oriented code using JavaScript.  The simplicity of Backbone, coupled with the TypeScript generated closure syntax allow one to simply use the TypeScript extends keyword to derive from any of Backbone’s base classes – and start implementing functionality immediately.  This is a very natural way to write Object-Oriented JavaScript:

class ListItem extends Backbone.Model {

}

The problem with Backbone Models

Unfortunately, Backbone uses object attributes to store Model properties, and these need to be set in order for the Backbone model to work correctly.  The following code shows how to set Backbone Model properties:

class ListItem extends Backbone.Model {
    constructor() {
        super();
        this.set('Id', '2');
    }
}

The nature of the set and get functions of Backbone, however do not have any inherent type-safety.  These model properties are also not exposed as first-class object properties, and must always be accessed via the setter and getter functions.  A more natural way of expressing Backbone Models would be as follows:

class ListItem extends Backbone.Model {
    Id: number;
    Name: string;
    constructor() {
        super();
        this.Id = 2;
        this.Name = "ModelName";
    }
}

Using ES5 getter and setter syntax

The above effect can be achieve by using ES5 getter and setter syntax.  By defining a get and set function for each property, and then in turn calling the Backbone get and set functions, we can keep our Backbone Model in-synch with our TypeScript properties.

Note that you will need to switch your TypeScript project properties to compile to ES5 in order for the following code to work:

class ListItem extends Backbone.Model {
    get Id(): number {
        return this.get('Id');
    }
    set Id(value: number) {
        this.set('Id', value);
    }
    set Name(value: string) {
        this.set('Name', value);
    }
    get Name(): string {
        return this.get('Name');
    }

    constructor() {
        super();
        this.Id = 1;
        this.Name = "ModelName";
    }
}

Model Type-safety

We can further improve our model’s type-safety by using an interface – both for the implements keyword ( forcing our class to implement getters and setters for each interface property – and also for the object constructor.

Consider the following code:

interface IListItem {
    Id: number;
    Name: string;
    Description: string;
}

class ListItem extends Backbone.Model implements IListItem {
    get Id(): number        { return this.get('Id'); }
    set Id(value: number)   { this.set('Id', value); }
    set Name(value: string) { this.set('Name', value); }
    get Name(): string      { return this.get('Name'); }

    constructor(input: IListItem) {
        super();
        this.Id = input.Id;
        this.Name = input.Name;
    }
}

Note that we have defined an interface ( IListItem ), and forced the ListItem object to implement the interface.  We have also added the interface to the constructor, further enhancing our type-safety.

This ensures that changes to the interface will generate compile-time errors if the object does not have corresponding getter and setter functions, and also ensures that any object passed via the constructor will have all properties defined.

Simplifying the constructor

For a Backbone model with many properties, the constructor can further be simplified by looping through the properties of the input parameter as follows:

interface IListItem {
    Id: number;
    Name: string;
}

class ListItem extends Backbone.Model implements IListItem {
    get Id(): number { return this.get('Id'); }
    set Id(value: number) { this.set('Id', value); }
    set Name(value: string) { this.set('Name', value); }
    get Name(): string { return this.get('Name'); }

    constructor(input: IListItem) {
        super();
        for (var key in input) {
            if (key) {
                this[key] = input[key];
            }
        }
    }
}

A Jasmine Unit test

The above code will allow for both standard get and set Backbone syntax, as well as type-safe TypeScript syntax as shown in the following unit test:

describe("SampleApp : tests : models : ListItem_tests.ts ", () => {
    it("can construct a ListItem model", () => {
        var listItem = new ListItem(
            {
                Id: 1,
                Name: "TestName",
            });
        expect(listItem.get("Id")).toEqual(1);
        expect(listItem.get("Name")).toEqual("TestName");

        expect(listItem.Id).toEqual(1);

        listItem.Id = 5;
        expect(listItem.get("Id")).toEqual(5);

        listItem.set("Id", 20);
        expect(listItem.Id).toEqual(20);
    });

});

Note how a List Item is constructed with a standard JavaScript object definition.  Also, both listItem.Id or listItem.set(‘Id’, 20) syntax can be used to interact with a type-safe TypeScript Backbone model.

Have fun,

blorkfish.

Note : This blog post was as a result of a question posted on stackoverflow.com