You will need copies of the following files:
PLEASE NOTE: You must add a reference to the .d.ts file at the top of the typescript file
⁄⁄⁄ <path="typings/backbone.marionette/index.d.ts" ⁄>
We need to create a way of displaying the contents of the Backbone models and collections. A "view" is like a window or area of the webpage into which we want to display the contents.
A simple usage of a view is to display a single Backbone model. We must begin by create a new class that inherits from Backbone.Marionette.ItemView. But before we can do this, we will need a model to display in this view. Assume we start with the following Model.
namespace ExampleItemViewNS
{
export class SampleModel extends Backbone.Model
{
constructor(value1: string = "a", value2: string = "b")
{
super({
value1: value1,
value2: value2
});
}
get Value1(): string { return this.get('value1'); }
set Value1(value: string) { this.set('value1', value); }
get Value2(): string { return this.get('value2'); }
set Value2(value: string) { this.set('value2', value); }
}
}
To display this model, we now need to create an ItemView and specify the model type (class) that will be display by it. This is done by using generics that allow us to use one class declaration that can serve as a template for a range of different classes. In short, you specify the ItemView will store a SampleModel using the syntax <SampleModel>. The constructor for the ItemView class looks like this:
constructor(options?: ItemViewOptions<TModel>);
The options here are limited to those specified by the ItemViewOptions, although when you create your own class you can also inherit from ItemViewOptions to add new options for your class. Here is how we create our new ItemView class:
namespace ExampleItemViewNS
{
.... SampleModel declared here ....
// You can't use Marionette.View directly, so we use ItemView instead
// which extends Marionette.View.
export class SampleView extends Backbone.Marionette.ItemView<SampleModel>
{
constructor(model: SampleModel, el: any)
{
super({
model: model,
el: el,
template: '#sample-template' // The name of the template (in the html file)
});
}
onBeforeDestroy(): void
{
window.alert("View is closing");
}
}
}
Our new constructor takes two arguments, model and el. The "model" is the Backbone Model that we want to display. While the "el" arguments represents the HTML location where we want the view to be displayed, and can be specified as either a jQuery selector string e.g. "#someDivId", a jQuery object or a HTML DOM object. We have to pass them into the constructor because these are values that we want to pass in the ItemViewOptions in the parent ItemView constructor.
To display it use the method render() and to close it use the method "destroy()". Here's an example of how we might use this view:
class ExampleItemViewPage
{
PageLoad(): void
{
let sampleModel: ExampleItemViewNS.SampleModel = new ExampleItemViewNS.SampleModel();
let sampleView: ExampleItemViewNS.SampleView = new ExampleItemViewNS.SampleView(sampleModel, '#container'); // Display in <div> tag with id="container"
/**********************************************************************
We should really display Views in Regions (see later examples)
**********************************************************************/
sampleView.render(); // Call the render() method
sampleView.destroy(); // Close it
}
}
$(function ()
{
let page: ExampleItemViewPage = new ExampleItemViewPage();
page.PageLoad();
});
In the ItemView class we used a "template", by default Backbone uses the underscore library template engine, so the templat is written in "underscore" syntax. A template, takes the contents of a JS object and maps it into tags. We add the template to our HTML page. The "sample-template" looks like this:
<script type="text/html" id="sample-template">
<p>This is an ItemView</p>
<%= value1 %>
<br />
<%= value2 %>
<br />
</script>
In the template, the <=attributeName> displays the value of the attribute contained in the Model. In our case, the SampleModel has two attributes 'value1' and 'value2'.
An alternative approach to using the ItemView class is to allow the view to render into a HTML element it creates and then attach the view manually to the page yourself. We start by creating an new ItemView class to demonstrate this idea. In this case, the constructor doesn't require the el argument because the view will render into the HTML tag it creates. It looks like this:
namespace ExampleItemViewNS
{
/****************************************************/
/* Example using tagName and className for ItemView */
/****************************************************/
export class SampleView2 extends Backbone.Marionette.ItemView<SampleModel>
{
constructor(model: SampleModel) // No el argument required
{
// Note: tagName and className will be ignored if el is specified
super({
model: model,
template: '#sample-template', // The name of the template (in html file)
tagName: 'div', // View creates a new <div> tag itself
className: 'redbox'
});
}
}
}
Here the tagName contains the name of the HTML tag the view should create for us, and the className is the CSS class name that we want to associate with it.
Using this class is similar to the previous code, but we have to attach the view to a HTML element manually after we render it. It looks like this:
class ExampleItemViewPage
{
PageLoad(): void
{
let sampleModel: ExampleItemViewNS.SampleModel = new ExampleItemViewNS.SampleModel();
let sampleView: ExampleItemViewNS.SampleView2 = new ExampleItemViewNS.SampleView2(sampleModel); // No "el" specified
sampleView2.render();
$('#container2').append(sampleView2.el); // Attach the view manually to <div> tag with id="container2"
}
}
$(function ()
{
let page: ExampleItemViewPage = new ExampleItemViewPage();
page.PageLoad();
});
We used a Collection class to contain a set of Model instances. We clearly want to be able to display a collection as well as a single instance of a Model. We can actually, still use an ItemView to do this. As simple Collection class looks like this.
namespace ExampleItemViewNS
{
export class SampleModelCollection extends Backbone.Collection<SampleModel>
{
constructor(models: SampleModel[]) // Actually, we don't even need to create this constructor
{
super(models);
}
}
}
We use an ItemView class in exactly the same way as we did before, with one difference, we pass a collection to it rather than a Model. It looks like this:
namespace ExampleItemViewNS
{
export class ModelListView extends Backbone.Marionette.ItemView<SampleModel>
{
constructor(collection: SampleModelCollection, el: any)
{
super({
collection: collection, // Provide a Collection instead of Model
el: el,
template: "#books-template"
})
}
}
}
The books-template looks like this:
<script id="books-template" type="text/html">
<!-- Use the template to iterate through the items -->
<ul>
<% _.each(items, function(item){ %>
<li><a href="book/<%= item.id %>"><%= item.name %></a></li>
<% }); %>
</ul>
</script>
This tempate uses the underscore.js each function to loop through all the items in the collection.
We use the ModelListView like this:
class ExampleItemViewPage
{
PageLoad(): void
{
let modelCollection: Example5NS.BookCollection = new ExampleItemViewNS.SampleModelCollection([
new SampleModel("1", "One"),
new SampleModel("2", "Two"),
new SampleModel("3", "Three")
]);
// You can also add a model using create(), and the collection
// will create the new BookModel() for you and do a save().
// However, it will crash here because it want's a "url"
// to which it can do a REST "POST".
// modelCollection.create({ id: undefined, value1: "4", value2:"Four" });
let view: ExampleItemViewNS.ModelListView = new ExampleItemViewNS.ModelListView(modelCollection, "#container");
view.render();
}
}
$(function ()
{
let page: ExampleItemViewPage = new ExampleItemViewPage();
page.PageLoad();
});
If we want to be really swish, we may want to display a collection of models but render each model as its own ItemView. The CollectionView class allows us to do just that. Lets say we want to display each model in an unnumbered list, where the list element is created in an ItemView (a bit simplistic I know). I've used the underscore .extend() function to merge the options "tagName" and "template" into the childViewOptions that are passed into the constructor. First we create an ItemView class like this:
namespace ExampleItemViewNS
{
export class SampleModelView2 extends Backbone.Marionette.ItemView<SampleModel>
{
constructor(childViewOptions: Backbone.Marionette.ItemViewOptions<SampleModel>)
{
super(_.extend({
tagName: 'li',
template: "#categoryTemplate" // Marionette.js option
}, childViewOptions));
}
}
export class SampleModelCollectionView extends Backbone.Marionette.CollectionView<SampleModel, SampleModelView2>
{
constructor(models: SampleModelCollection)
{
super({
collection: models,
tagName: 'ul',
className: 'unstyled',
childView: SampleModelView2
});
}
}
}
We use it like this:
class ExampleItemViewPage
{
PageLoad(): void
{
let modelCollection: Example5NS.BookCollection = new ExampleItemViewNS.SampleModelCollection([
new SampleModel("1", "One"),
new SampleModel("2", "Two"),
new SampleModel("3", "Three")
]);
let modelsView: ExampleItemViewNS.SampleModelCollectionView = new ExampleItemViewNS.SampleModelCollectionView(modelCollection);
modelsView.render();
$("#container1").append(modelsView.el);
}
}
$(function ()
{
let page: ExampleItemViewPage = new ExampleItemViewPage();
page.PageLoad();
});
Build an application JavaScript application for a bookstore. The book details should include:
First create a single model and display its contents first into a div tag already on the web page and secondly into span tag.
Create at least 5 books and display the books details. Display all 5 books as a rows in a HTML table.
Repeat the same exercise but display all the books using a CollectionView.
You can also use a slightly nicer approach by using CollectionViewExt which I have produced. Ask if you are interested.