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 would like to both display the contents of a Model and a Collection of models in one view. For example, we may wish to display a model representing some calculations we performed on the collection
A simple example is an a list of Orders.
namespace ExampleCompViewNS
{
export class OrderItem extends Backbone.Model
{
SetValues(value: { id: string, title: string, price: string, qty: string }, options?: any): void
{
this.set(value, options); // Set all the attributes in one go using Backbone
}
}
export class OrdersCollection extends Backbone.Collection<OrderItem>
{
constructor(orders: OrderItem[] | Object[])
{
super(orders, {
model: OrdersCollection.CreateModel
});
}
private static CreateModel(attrs?: any, options?: any): OrderItem
{
let model: OrderItem = new OrderItem();
model.SetValues(attrs, options);
return model;
}
GetTotal(): number
{
return this.reduce(
function (total, value)
{
return total + value.get("price") * value.get("qty")
}, 0);
}
}
}
Having defined the Collection to display in the CompositeView, we also need define a Model class to display in the CompositeView to display the result of the calculations made.
namespace ExampleCompViewNS
{
export class OrderTotal extends OrderItem
{
constructor(subtotal: number = 0, tax: number = 0, total: number = 0)
{
super({
subtotal: subtotal,
tax: tax,
total: total
});
}
set SubTotal(value: number) { this.set('subtotal', value); }
get SubTotal(): number { return this.get('subtotal'); }
set Tax(value: number) { this.set('tax', value); }
get Tax(): number { return this.get('tax'); }
set Total(value: number) { this.set('total', value); }
get Total(): number { return this.get('total'); }
}
}
Next we define the ItemViews we should display for each Model in the Collection i.e. OrderItem, and the CompositeView class itself. The EmptyOrderGridView is displayed if the collection is empty.
namespace ExampleCompViewNS
{
export class OrderGridRowView extends Backbone.Marionette.ItemView<OrderItem>
{
constructor(options: Backbone.Marionette.ItemViewOptions<OrderItem>)
{
super({
model: options.model,
template: "#orderGridRow",
tagName: "tr",
});
}
}
export class EmptyOrderGridView extends Backbone.Marionette.ItemView<OrderItem>
{
constructor()
{
super({
template: "#emptyOrder"
});
}
}
// We'll display both a Collection as a <table> and the order totals
// within a <div> tag. To do this we provide a 'model' and a 'collection'
// If you need to do things to modify your HTML. Well, in that scenario,
// you should use the onRender() function, which is a very helpful method
// that will allow you to manipulate the DOM just after your render method
// was called.
//
// The events are:
// "before:render:template" / onBeforeRenderTemplate - before the model has been rendered
// "render:template" / onRenderTemplate - after the model has been rendered
// "before:render:collection" / onBeforeRenderCollection - before the collection of models is rendered
// "render:collection" / onRenderCollection - after the collection of models has been rendered
// "before:render" / onBeforeRender - before anything has been rendered
// "render" / onRender - after everything has been rendered
export class OrderListView extends Backbone.Marionette.CompositeView<OrderItem, OrderGridRowView>
{
constructor(total: OrderTotal, orders: OrdersCollection, el: any)
{
super({
model: total,
collection: orders,
el: el,
template: "#orderGrid",
childView: OrderGridRowView, // Render model from collection
emptyView: EmptyOrderGridView, // Render when no models in collection
childViewContainer: "tbody" // Selector..where to display child views
});
}
get Model(): OrderTotal { return <OrderTotal>this.model; }
get Collection(): OrdersCollection { return <OrdersCollection>this.collection; }
// Override
onBeforeRender(): void
{
// Calculate values for the 'model' before rendering the view
let subtotal = this.Collection.GetTotal();
let tax = subtotal * .08;
let total = subtotal + tax;
this.Model.SubTotal = subtotal;
this.Model.Tax = tax;
this.Model.Total = total;
}
// Override
onRender(): void
{
// Called to modify HTML generated when rendering view
// If the collection isn't empty, display the head and foot of the table.
if (this.Collection.length > 0)
{
this.$('thead').removeClass('hide'); // Unhide the table and div tag
this.$('#totalSummary').removeClass('hide');
}
}
}
}
You use this CompositeView like this:
class ExampleCompViewPage
{
PageLoad(): void
{
let orders: ExampleCompViewNS.OrdersCollection = new ExampleCompViewNS.OrdersCollection([
{ id: "1", title: "title 1", price: "1", qty: "1" },
{ id: "2", title: "title 2", price: "2", qty: "2" },
{ id: "3", title: "title 3", price: "3", qty: "5" },
{ id: "4", title: "title 4", price: "1", qty: "3" },
{ id: "5", title: "title 5", price: "2", qty: "1" },
{ id: "6", title: "title 6", price: "3", qty: "1" },
{ id: "7", title: "title 7", price: "1", qty: "2" },
{ id: "8", title: "title 8", price: "2", qty: "1" },
{ id: "9", title: "title 9", price: "3", qty: "1" }
]);
let total: ExampleCompViewNS.OrderTotal = new ExampleCompViewNS.OrderTotal();
// When we create the view we specify the Backbone.Collection and the
// Backbone.Model that should be displayed
let orderGridView: ExampleCompViewNS.OrderListView = new ExampleCompViewNS.OrderListView(total, orders, "#ordersGridList");
orderGridView.render();
}
}
$(function ()
{
let page: ExampleCompViewPage = new ExampleCompViewPage();
page.PageLoad();
});
The underscore.js templates look like this:
<script id="orderGrid" type="text/html">
<table>
<thead class="hide">
<tr>
<th>id</th>
<th>Title</th>
<th>Price</th>
<th>Quantity</th>
<th></th>
</tr>
</thead>
<tbody></tbody> <--- Child views are displayed in here
</table>
<br />
<div id="totalSummary" class="hide">
<%= subtotal%><br />
<%= tax%><br />
<%= total%><br />
</div>
</script>
<script id="orderGridRow" type="text/html">
<td> <%= id%></td>
<td> <%= title%></td>
<td> <%= price%></td>
<td> <%= qty%></td>
<td>
<button class="btn btn-primary" type="button">Remove</button>
</td>
</script>
<script id="emptyOrder" type="text/html">
<div>
<h3>Thanks for buying with us!</h3>
<a href="#" class="thumbnail">
<img data-src="holder.js/300x200" alt="300x200" style="width: 300px; height: 200px;" src="../images/freeShipping.jpg" />
</a>
</div>
</script>
Build an application JavaScript application for a bookstore. The book details should include:
Continuing the previous exercise, assume we wish to alllow a customer to add books to a shopping cart and when the cart is viewed they should see all the books they have added. Beneath, the books the the total cost of all the books and display the total, including and excluding VAT should also be displayed. Furthermore, alongside the detals for each book, the customer should be given the oppurtunity to increase the quantity required or delete the book from the cart. We are building a non-functional prototype, so the edit and delete don't need to do anything.