Reuse skills and frameworks in JavaScript apps

As mentioned in the previous post in the collaboration series with Arian from Zilverstede, web developers can reuse skills and frameworks to create native Windows 8 apps in JavaScript. Last time we dove into jQuery specifically, but this time we want to elaborate a bit on how to structure your app in a more MVC manner. We’re going to be using Backbone.js to structure our app in an MVC manner and Handlebars to build semantic templates in our app. As in the previous post, this post will highlight key things in the sample solution that can be downloaded at the end of the post.

Including references

One of the important things to understand when using frameworks or any script files in JavaScript apps for that matter, is that the app is going to be installed on the end-user’s machine, rather than viewed over the web. Traditional techniques to bundle files or defer loading that work very well on the web, may prove to be counterproductive in Windows 8 JavaScript apps. There’s a great article on MSDN explaining how to reduce your app’s loading time. The most important thing to note here is bytecode caching. Bytecode caching is a mechanism that’s used in JavaScript apps to create bytecode for each JavaScript file once, so subsequent loads of that JavaScript file will read from cache. To be able to utilize this mechanism, you must reference all JavaScript files statically in the root of your HTML start page.

In our case, this is going to be the default.html page from the Blank template, which will have the following HEAD tag:

<head>
    <meta charset="utf-8" />
    <title>Backbone</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

    <!-- Backbone references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="js/jquery-2.0.0.js"></script>
    <script src="js/underscore-min.js"></script>
    <script src="js/backbone-min.js"></script>
    <script src="js/handlebars-1.0.rc.1.min.js"></script>
    <script src="js/sync.js"></script>
    <script src="/js/default.js"></script>
    <script src="js/router.js"></script>
    <script src="js/models/task.js"></script>
    <script src="js/collections/tasks.js"></script>
    <script src="js/views/home.js"></script>
    <script src="js/views/details.js"></script>
</head>

As you can see, we reference everything we want to use in our app, to leverage bytecode caching. To further optimize our app’s initialization sequence, we can defer loading certain files to first use by using the defer attribute:

<script src="js/views/details.js" defer="defer"></script>

Integrating Handlebars

Assuming you have a working knowledge of Handlebars and Backbone.js, we have defined a few templates in the default.html page to specify our title area, item template and detailpage template. These templates use Handlebars to create a semantic template that can be easily read and understood:

<script id="item-template" type="text/x-handlebars-template">
    <div class="item">
        <img class="item-image" src="{{backgroundImage}}" />
        <div class="item-overlay">
            <h4 class="item-title">{{title}}</h4>
            <h6 class="item-subtitle win-type-ellipsis">{{subtitle}}</h6>
        </div>
    </div>
</script>

We define a script of type text/x-handlebars-template, where we can easily use Handlebar’s markup to specify where our background image, title and subtitle go. These placeholders get replaced with the actual values at runtime by calling the Handlebars.compile function:

Handlebars.compile($("#item-template").html());

In the render function of our home.js file we use the WinJS ListView control and specify our data source, which will use the Handlebars template to fill the correct values according to the object’s properties:

render: function ()
{
    this.$el.append(this.template());
        
    var listView = new WinJS.UI.ListView(this.$("#tasks").get(0), {
        itemTemplate: this.itemTemplateFunction,
        itemDataSource: new WinJS.Binding.List(this.collection.toJSON()).dataSource,
        oniteminvoked: this.itemInvoked,
        selectionMode: "none"
    });

    return this;
}

Integrating Backbone.js

To complete our default.html page, we’ll add two placeholders that will hold the two views we have in our application: home and details.

<div id="home" class="fragment"></div>
<div id="details" class="fragment"></div>

Next, we’ll define a Task model by creating a models folder and a task.js file inside. Our task doesn’t have a specific implementation, so specifying it’s a model will suffice for now:

"use strict";

ToDo.Models.Task = Backbone.Model.extend({
});

We’ll also need a list of tasks to use in our app, so we’ll create a collections folder with a tasks.js file inside. Our tasks collection will contain the following JavaScript:

"use strict";

ToDo.Collections.Tasks = Backbone.Collection.extend({
    model: ToDo.Models.Task,
});

Now we’ll move to the default.js file and specify some namespaces and initialize Backbone.js in the app.onactivated handler:

var router = new ToDo.Router();
Backbone.history.start();

Backbone uses a router to determine which view needs to be loaded, so our router.js file will contain the definition of the different routes to be able to navigate to […]/details/1 to see item number 1’s details page for instance:

ToDo.Router = Backbone.Router.extend({
    tasks: null,
    
    routes: {
        "home": "home",
        "details/:id": "details"
    }

Last, but not least, we override Backbone.js’s sync function to create a static set of data to use in our sample app in sync.js:

Backbone.sync = function (method, model, options)
{
[...]
}

Navigation

Now that we have an app that displays data, we also want to be able to switch between the two views we have. To do so, we use Backbone.js’s history.navigate function to navigate to the details page when an item is invoked in home.js:

itemInvoked: function(e)
{
    return e.detail.itemPromise.then(function (currentItem)
    {
        Backbone.history.navigate("details/" + currentItem.data.id, { replace: true, trigger: true });
    });
}

Similarly, we use the same method in details.js to handle the back button’s click event:

backInvoked: function()
{
    Backbone.history.navigate("home", { trigger: true });
}

Downloads

Backbone.js sample - Backbone.zip (89 downloads)

One thought on “Reuse skills and frameworks in JavaScript apps

  1. Pingback: Reuse skills and frameworks in JavaScript apps - Rajen's Technical Tidbits - Site Home - MSDN Blogs

Leave a Reply