Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Now it’s time to bring all the pieces together and get the frontend running. What do we need to make our life easier? Right, a JavaScript MVC framework... and again, you’ve plenty of choices, e.g. Gordon Hempton compares several frameworks. Personally, I like Backbone.JS a lot: it has some quite nice features and, what I consider especially important – a great, active community. If you’re already familiar with MVC, you should read section "How does Backbone relate to traditional MVC" to get an idea how Backbone.JS differs. In addition, you can learn about the naming conventions. You need this knowledge to continue. Otherwise, I recommend that you make yourself familiar with the MVC pattern first.

First, let’s download the necessary libraries and get the folder structure in place. Besides Backbone.JS (v0.9.2), you need to download Underscore.JS (v1.3.3; Backbone.JS depends on it) and json2.js. Anything else we’ve already downloaded before. After extracting, renaming and copying the files (from the step 1 and step 2), your folder structure should look as follows:

step03_backbone/ 

├── assets/

│   ├── css/

│   │   ├── bootstrap-2.1.0.css 

│   │   ├── bootstrap-2.1.0.min.css 

│   │   ├── bootstrap-responsive-2.1.0.css 

│   │   └── bootstrap-responsive-2.1.0.min.css 

│   ├── img/ 

│   │   ├── glyphicons-halflings-white.png 

│   │   └── glyphicons-halflings.png 

│   └── js/ 

│       ├── backbone-0.9.2.js       

│       ├── bootstrap-2.1.0.js 

│       ├── bootstrap-2.1.0.min.js 

│       ├── jquery-1.7.2.js 

│       ├── json2.js 

│      ├── sinon-1.4.2.js 

│       ├── sinon-ie-1.4.2.js 

│       └── underscore-1.3.3.js 

├── index.html 

└── js/     

    ├── app.js

    └── fake-server.js

Besides copying fake-server.js from step 1 to js/, you should also create a file called app.js in the same folder. All JavaScript coding will be added to this file. Finally, we create a file called index.html and define a simple project skeleton, which is a single page application (as in step 2, I provide the intermediate steps: for this step, see index-01.html),

<html>
  <head>
    <title>Session supplements</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="Lars Karg, Twitter: @larskarg">
    <!-- Le styles -->
    <link href="assets/css/bootstrap-2.1.0.css" rel="stylesheet">
    <link href="assets/css/bootstrap-responsive-2.1.0.css" rel="stylesheet">
    <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
      <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
  </head>
  <body>
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="assets/js/jquery-1.7.2.js"></script>
    <script src="assets/js/bootstrap-2.1.0.js"></script>
    <script src="assets/js/underscore-1.3.3.js"></script>
    <script src="assets/js/json2.js"></script>
    <script src="assets/js/backbone-0.9.2.js"></script>
    <script src="assets/js/handlebars-1.0.0.beta.6.js"></script>
    <!-- Files needed for fake-server.js -->
    <script src="assets/js/sinon-1.4.2.js"></script>
    <script src="assets/js/sinon-ie-1.4.2.js"></script>
    <script src="js/fake-server.js"></script>
    <script src="js/app-01.js"></script>
  </body>
</html>

and add the following lines to app.js (Delete it when you’re done; see app-01.js😞

// jquery ready function is execute after the DOM is ready
$(document).ready(function() {
    // if the DOM is ready, see if all is set up correctly
    // by calling our fake-server
    $.ajax({
        url: '/api/v1/content',
        success: function(result) {
            // if we're successful, we append SUCCESS to the DOM
            $('body').append('<h3>SUCCESS: All seems to be set up correctly!</h3>' + '<br />' + JSON.stringify(result));
        },
        error: function() {
            // else ERROR
            $('body').append('<h3>ERROR: Something seems to be set up wrongly!</h3>');
        }
    });
});

Open index.html in the browser and see if it works:

Now, it’s time to come back to the application, and to talk about its models and collections. Our specification suggests that there are two kinds of services: one for the content and one for the dashboard. As also specified, the application loads the whole content, i.e. the content of all the chapters, when it is first started. Consequently, we need a model representing each chapter (called ContentModel) and a collection storing all models (called ContentCollection). In addition, the application pulls every 30 seconds the latest dashboard, suggesting that we also need a model for representing the dashboard (called DashboardModel). Since we do not store the dashboard history, we do not need a collection for the dashboards.

As you’ve already learned in step 1, I’m a big fan of BDD. Jim Newbery wrote an excellent tutorial on testing Backbone.JS applications with Jasmine.JS and Sinon.JS. Since I don’t want to repeat him, it’s best you go through his tutorial before continuing. It also gives you some more background on BBD and explains many techniques I use for testing our application. In particular, I write test specs for the ContentCollection and the DashboardModel to ensure that they are retrieved correctly from the server.

So let’s set it up and write the test specification. Copy folder test/, delete fake-server-spec.js, create file app-spec.js and update SpecRunner.html. The folder structure should now look-like as follows:

step03_backbone/ 

├── assets/ 

│   ├── css/ 

│   │   ├── bootstrap-2.1.0.css 

│   │   ├── bootstrap-2.1.0.min.css 

│   │   ├── bootstrap-responsive-2.1.0.css 

│   │   └── bootstrap-responsive-2.1.0.min.css 

│   ├── img/ 

│   │   ├── glyphicons-halflings-white.png 

│   │   └── glyphicons-halflings.png 

│   └── js/ 

│       ├── backbone-0.9.2.js       

│       ├── bootstrap-2.1.0.js 

│       ├── bootstrap-2.1.0.min.js 

│       ├── jquery-1.7.2.js 

│       ├── json2.js 

│       ├── sinon-1.4.2.js 

│       ├── sinon-ie-1.4.2.js 

│       └── underscore-1.3.3.js 

├── index.html 

├── js/ 

│   ├── app.js 

│   └── fake-server.js   

└── test/     

     ├── assets/     

     │   ├── css/     

     │   │   └── jasmine-1.2.0.css     

     │   ├── img/     

     │   │   └── jasmine_favicon.png     

     │   └── js/     

     │       ├── jasmine-1.2.0.js     

     │       └── jasmine-html-1.2.0.js     

     ├── MIT.LICENSE       

     ├── spec/     

     │   └── app-spec.js     

     └── SpecRunner.html

SpecRunner.html should look as follows after updating it:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Spec Runner</title>
  <link rel="shortcut icon" type="image/png" href="assets/img/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="assets/css/jasmine-1.2.0.css">
  <script type="text/javascript" src="assets/js/jasmine-1.2.0.js"></script>
  <script type="text/javascript" src="assets/js/jasmine-html-1.2.0.js"></script>
  <script type="text/javascript" src="../assets/js/sinon-1.4.2.js"></script>
  <script type="text/javascript" src="../assets/js/sinon-ie-1.4.2.js"></script>
  <!-- include libs needed to run the app... -->
  <script type="text/javascript" src="../assets/js/jquery-1.7.2.js"></script>
  <script type="text/javascript" src="../assets/js/underscore-1.3.3.js"></script>
  <script type="text/javascript" src="../assets/js/json2.js"></script>
  <script type="text/javascript" src="../assets/js/backbone-0.9.2.js"></script>
  <!-- already tested in step 1 -->
  <script type="text/javascript" src="../js/fake-server.js"></script>
  <!-- include source files here... -->
  <script type="text/javascript" src="../js/app.js"></script>
  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/app-spec.js"></script>
  <script type="text/javascript">
    (function() {
      var jasmineEnv = jasmine.getEnv();
      jasmineEnv.updateInterval = 1000;
      var htmlReporter = new jasmine.HtmlReporter();
      jasmineEnv.addReporter(htmlReporter);
      jasmineEnv.specFilter = function(spec) {
        return htmlReporter.specFilter(spec);
      };
      var currentWindowOnload = window.onload;
      window.onload = function() {
        if (currentWindowOnload) {
          currentWindowOnload();
        }
        execJasmine();
      };
      function execJasmine() {
        jasmineEnv.execute();
      }
    })();
  </script>
</head>
<body>
</body>
</html>

Now, it’s time to write the test specification:

describe("App Session Supplements", function() {
    describe("Models and Collections", function() {
        beforeEach(function() {
            this.contentCollection = new ContentCollection();
            this.dashboardModel = new DashboardModel();
        });
        describe("when instantiated", function() {
            it("should ensure that ContentCollection url is set", function() {
                expect(this.contentCollection.url).toEqual("/api/v1/content");
            });
            it("should ensure that ContentCollection model is set", function() {
                expect(this.contentCollection.model).toBeDefined();
                expect(this.contentCollection.model).not.toBeNull();
            });
            it("should ensure that DashboardModel urlRoot is set", function() {
                expect(this.dashboardModel.urlRoot).toEqual("/api/v1/dashboard");
            });
            it("should ensure that DashboardModel id is set", function() {
                expect(this.dashboardModel.id).toEqual("latest");
            });
        });
        // We don't test for the semantics -> see fake-server-spec.js
        describe("when fetching models and collections", function() {
            beforeEach(function () {
                $.ajaxSetup({ async: false });
            });
            // Clean up afterwards and set back to default behavior
            afterEach(function () {
                $.ajaxSetup({ async: true });
            });
            it("should make AJAX call to content service", function() {
                var status = "error";
                this.contentCollection.fetch({
                    success: function(model, response) {
                        status = "success";
                    }});
                expect(status).toEqual("success");
            });
            it("should make AJAX call to dashboard service", function() {
                var status = "error";
                this.dashboardModel.fetch({
                    success: function(model, response) {
                        status = "success";
                    }});
                expect(status).toEqual("success");
            });
        });
    });
});

After the most essential test cases are in place, let’s implement the two models and the collection. Implementing ContentModel and ContentCollection is straight forward and a standard case for Backbone.JS. The DashboardModel, however, is a more complicated case since it represents a nested data structure. There are several ways (see Backbone.JS FAQ) to implement nested data structures. Since our model is very simple and must be updated in one patch (i.e., lazy loading doesn’t make sense) and it’s a read-only scenario, I decided to go for the most simple implementation possible: don’t handle the nested data structure in any special manner, just have it in mind when accessing it from the views.

Enough background information, open app.js and replace its content with the following lines of code (see again index-02.html and app-02.js) to specify the models and collections:

/*
MODELS AND COLLECTIONS
================================================== */
var ContentModel = Backbone.Model.extend({
});
var ContentCollection = Backbone.Collection.extend({
    url: "/api/v1/content",
    model: ContentModel
});
var DashboardModel = Backbone.Model.extend({
    urlRoot: "/api/v1/dashboard",
    id: "latest"    // We are only interested in the latest dashboard
});

That was very easy. But wait, let’s see if it really works by adding some additional test coding to app.js:

// jquery ready function is execute after the DOM is ready
$(document).ready(function() {
    var contentCollection = new ContentCollection();
contentCollection.fetch({
        error: function(model, response) {
$('body').append('<h3>SUCCESS - ContentCollection: ' + response);
        },
success: function(model, response) {
$('body').append('<h3>ERROR - ContentCollection: ' + response);
        }});
    var dashboardModel = new DashboardModel();
dashboardModel.fetch({
        error: function(model, response) {
         $('body').append('<h3>SUCCESS - DashboardModel: ' + response);
                 },
success: function(model, response) {
$('body').append('<h3>ERROR - DashboardModel: ' + response);
     }});
});

Open index.html in the browser and you see the following screen if you’re successful:

You can see that it is very fast and straightforward (if you’ve understood how Backbone.JS works) to define models and collections – just a few lines of code; service integration done.

I would usually start thinking about my application router right about now. However, since it’s such a simple application and I do not have enough time (18 minutes, you remember), I decided to skip defining an application router. Maybe you’d like to implement it and send me a merge request?

Great, just one step left: defining the views. So let’s do it… but wait, why don’t I specify my test cases first? Simple, I usually do not specify test cases for my views. If you’re interested in testing your views, read Jim Newbery’s excellent tutorial. He covers it in detail. Why don’t I specify tests for my views? Test cases are important to me, especially for the service and model layer (as in our case). Testing them ensures that the frontend and backend work smoothly together. Views are another topic… I prefer manual testing without coded test cases. What is your opinion on it? Use manual testing? What is your best practice? Let me know.

Figure 1. Building blocks and view mapping.

Figure 1 shows how you map the building blocks from step 2 to views. Several view layouts are possible. I chose a very simple one and went for an AppView containing one sub-view: DashboardWidget (I usually call my sub-views widgets since they can only live in another view.). I split the dashboard building block into a widget, since it’s updated every 30 seconds and should take care of all the necessary actions itself.

Let’s define the AppView and the DashboardWidget. Open app.js and add the following defines for the view and the widget (app-03.js😞

var AppView = Backbone.View.extend({
// empty
});
var AppView = Backbone.View.extend({
// emtpy
});

Next, we reuse the responsive mockup from step 2. Copy file style.js and folder img/ into the step 3 folder structure as follows:

step03_backbone/ 

├── assets/ 

│   ├── css/ 

│   │   ├── bootstrap-2.1.0.css 

│   │   ├── bootstrap-2.1.0.min.css 

│   │   ├── bootstrap-responsive-2.1.0.css 

│   │   └── bootstrap-responsive-2.1.0.min.css 

│   ├── img/ 

│   │   ├── glyphicons-halflings-white.png 

│   │   └── glyphicons-halflings.png 

│   └── js/ 

│       ├── backbone-0.9.2.js       

│       ├── bootstrap-2.1.0.js 

│       ├── bootstrap-2.1.0.min.js 

│       ├── jquery-1.7.2.js 

│       ├── json2.js 

│       ├── sinon-1.4.2.js 

│       ├── sinon-ie-1.4.2.js 

│       └── underscore-1.3.3.js 

├── img/ 

│   ├── icon_about.png 

│   ├── icon_book.png 

│   ├── icon_home.png 

│   └── icon_twitter.png 

├── index.html 

├── js/ 

│   ├── app.js 

│   ├── fake-server.js 

│   └── styles.js   

└── test/     

    ├── assets/     

    │   ├── css/     

    │   │   └── jasmine-1.2.0.css     

    │   ├── img/     

    │   │   └── jasmine_favicon.png     

    │   └── js/     

    │       ├── jasmine-1.2.0.js     

    │       └── jasmine-html-1.2.0.js     

    ├── MIT.LICENSE       

    ├── spec/     

    │   └── app-spec.js     

    └── SpecRunner.html

We merge the content from mockup.html with index.html. See index-03.html for the result.

Now open app.js and delete all between (app-03.js)

// jquery ready function is execute after the DOM is ready
$(document).ready(function() {
// Now empty
});

If you now open index.html and mockup.html in the browser, both should look the same.

Next, we spend some time thinking about the view/widget rendering. Indeed, we could embed the html code directly into the view and/or use DOM manipulations. Well, its maintainability is very low. So, why don’t we use a template library? In particular, I like handlebars.js. It’s a very powerful, but also very easy to use template library. Just quickly scan the webpage and you see how easy it is to use it. So let’s download the handlebars.js, add it to the assets/ folder and include it in index.html. In addition, we define two templates: (1) one for the AppView (appview-tpl) containing most of the content from the index.html and one for the DashboardWidget (dashboard-tpl). The folder structure should now look as follows:

step03_backbone/  

├── assets/  

│   ├── css/  

│   │   ├── bootstrap-2.1.0.css  

│   │   ├── bootstrap-2.1.0.min.css  

│   │   ├── bootstrap-responsive-2.1.0.css  

│   │   └── bootstrap-responsive-2.1.0.min.css  

│   ├── img/  

│   │   ├── glyphicons-halflings-white.png  

│   │   └── glyphicons-halflings.png  

│   └── js/  

│       ├── backbone-0.9.2.js       

│       ├── bootstrap-2.1.0.js  

│       ├── bootstrap-2.1.0.min.js  

│       ├── handlebars-1.0.0.beta.6.js  

│       ├── jquery-1.7.2.js  

│       ├── json2.js  

│       ├── sinon-1.4.2.js  

│       ├── sinon-ie-1.4.2.js  

│       └── underscore-1.3.3.js  

├── img/  

│   ├── icon_about.png  

│   ├── icon_book.png  

│   ├── icon_home.png  

│   └── icon_twitter.png  

├── index.html  

├── js/  

│   ├── app.js  

│   ├── fake-server.js  

│   └── styles.js   

└── test/      

     ├── assets/      

     │   ├── css/      

     │   │   └── jasmine-1.2.0.css      

     │   ├── img/      

     │   │   └── jasmine_favicon.png      

     │   └── js/      

     │       ├── jasmine-1.2.0.js      

     │       └── jasmine-html-1.2.0.js      

     ├── MIT.LICENSE        

     ├── spec/      

     │   └── app-spec.js      

     └── SpecRunner.html

and index.html as you see it in index-04.html.

After this little preparation, we can work on the AppView specified in app.js. First, we define Backbone.JS’ el-element (see Backbine.JS documentation):

var AppView = Backbone.View.extend({
      el: $("body"),
});

Next, we specify the expected events. At the moment, AppView only listens to events triggered by the menu:

var AppView = Backbone.View.extend({

// Events definition
    events: {
        "click a[href=#home]":      "handleNav",
        "click a[href=#chapter1]": "handleNav",
        "click a[href=#chapter2]": "handleNav",
        "click a[href=#chapter3]": "handleNav",
        "click a[href=#chapter4]": "handleNav",
        "click a[href=#chapter5]": "handleNav",
        "click a[href=#chapter6]":  "handleNav",
        "click a[href=#about]":     "handleNav"
    },

    handleNav: function(e) {
        hash = $(e.target).attr("href");

        $(".navbar li").removeClass("active");
        $('.navbar li a[href$="' + hash + '"]').parent().addClass("active");
}
});

and the initialize and render method:

var AppView = Backbone.View.extend({

initialize: function() {
      #explain#
_.bindAll(this, "render");
// Let's render the AppView when it's created
this.render();
},
render: function() {
        // Load template
        var source = $("#appview-tpl").html();
        // Compile template
        var tpl = Handlebars.compile(source);
        // Render with empty data
        var html = tpl({});
        // Since we append to body, we have to clean up our stuff very carefully and make sure that we don't delete our script tags
        // Select all direct childs of this.el which are not a script
        $(this.el.tagName + " > *:not(script)").remove();
$(this.el).append(html);
},
});

Finally, add

// jquery ready function is execute after the DOM is ready
$(document).ready(function() {
      var appView = new AppView();
});

and open index.html (index-05-html) in the browser… and it works… somehow, one thing: have you realized that the chapter color does not change? Let’s fix this by removing style.js and adding one line to AppView:

var AppView = Backbone.View.extend({

    handleNav: function(e) {
        hash = $(e.target).attr("href");
$(".navbar li").removeClass("active");
$('.navbar li a[href$="' + hash + '"]').parent().addClass("active");

// Ensure that the title changes color as well
$("h1.header-title").css("color", $("li.active a").css("background-color"));
}
});

Now it looks good. Next, let’s integrate the dynamic content. First, we update index.html and add some variables to the template (see index-06.html😞

    <header>
      <div class="container">
        <img class="header-img" src="img/{{icon}}"></img>
        <div class="header-inner">
          <h1 class="header-title">{{title}}</h1>
          <p class="header-subtitle">{{sub_title}}</p>
        </div>
      </div>
    </header>
    <div class="container">
      {{{content}}}
    </div>

You might wonder what the difference is between {{}} and {{{}}}. "Handlebars HTML-escapes values returned by a {{expression}}. If you don't want Handlebars to escape a value, use the triple-stash." Next, we link the view with the model (see again index-06.html and app-06.js). We update the initialize function, and create the necessary collection and the necessary model. In addition, we attach the setCurModel function (see below) that updates the current model based on the selected menu entry, and the render function to the event reset and change event triggered by the collection/model. The events indicate that the model has been updated and that it must be re-rendered.

initialize: function() {
_.bindAll(this, "render");
        // Create the content collection and model
        this.collection  = new ContentCollection();
        this.model = new ContentModel();
// http://stackoverflow.com/questions/5681246/backbone-js-rendering-view
        // Triggers current model update (stored in model) if collection is reset -> this triggers rendering
        // var self = this;
        this.collection.on("reset", function() { this.setcurModel("#home"); }, this);
        this.model.on("change", function() { this.render(); }, this)
        // Views take care of their data and since we have only one view, we do it this way
        this.collection.fetch();
    },

    setcurModel: function(id) {
        this.model.clear({silent: true});
        this.model.set(this.collection.get(id));
    }

Furthermore, we update handleNav, so that the right model is set based on the selected menu entry:

handleNav: function(e) {
        // Get hash tag, i.e. #chapter1, #chapter2, ...
        hash = $(e.target).attr("href");
// Update the model
this.setcurModel(hash);
        // Change the selected menu item
        $(".navbar li").removeClass("active");
        $('.navbar li a[href$="' + hash + '"]').parent().addClass("active"); // <- does this work in all browsers?
        // Ensure that the title changes color as well
$("h1.header-title").css("color", $("li.active a").css("background-color"));
    },

and the render function by attaching the model:

render: function() {
        // Load template
        var source = $("#appview-tpl").html();
        // Compile template
        var tpl = Handlebars.compile(source);
// Assign data and render
var html = tpl(this.model.toJSON());
        // Since we append to body, we have to clean up our stuff very carefully and make sure that we don't delete our script tags
        // Select all direct childs of this.el which are not a script
        $(this.el.tagName + " > *:not(script)").remove();
        $(this.el).append(html);
},

That’s it, we’re done with AppView. It’s time to work on the final part: the DashboardWidget. First, we update the template (in index.html) and add the necessary variables, see index.html.

Next, we write the necessary JavaScript code (app.js). As for AppView, we implement the two functions initialize and render:

    initialize: function() {
        _.bindAll(this, "render");
        // Create model
        this.model = new DashboardModel();
        // Render the model every time the model has changed
        this.model.on("change", function() { this.render(); }, this);
// Start data pulling, the widget is self-updating the content
this.startPullingModel();
    },
    render: function() {
        // Get template
        var source = $("#dashboardwidget-tpl").html();
        // Compile template
        var tpl = Handlebars.compile(source);
        // Render widget only if there is data
        if (this.model.has("tweet_count")) {
            var html = tpl(this.model.toJSON());
            $(this.id).html(html);
        }
        else {
            // Render dummy data or a loading screen...
        }
    },

The only real difference is that we call function startPullingModel in the initialize function. As its name already suggests, the function frequently pulls (in our case every 30 seconds) the dashboard model from the server:

startPullingModel: function() {
        // Initial pulling if model is empty
        if (!this.model.has("tweet_count")) {
            this.model.fetch({
                error: function(model, response) {
                    console.log("ERROR");
                },
                success: function(model, response) {
console.log("SUCCESS");
                }});
        }
        // Fetch model every 30 seconds
        var self = this; // maintain context
        window.setInterval(function() {
            self.model.fetch({
                error: function(model, response) {
console.log("ERROR");
                },
                success: function(model, response) {
                    console.log("SUCCESS");
                }});
        }, 30000);
    }

Finally, we must update the AppView. We add one line of code to create the widget and one for rendering the widget every time the AppView is rendered:

initialize: function() {

             _.bindAll(this, "render");

        // Create the content collection and model

this.collection  = new ContentCollection();

this.model = new ContentModel();

        // Create widget

        // Have in mind: The widget is self refreshing and takes care of the model itself

        this.dashboardWidget = new DashboardWidget({id: "#dashboard"});

        // http://stackoverflow.com/questions/5681246/backbone-js-rendering-view

        // Triggers current model update (stored in model) if collection is reset -> this triggers rendering

        // var self = this;

this.collection.on("reset", function() { this.setcurModel("#home"); }, this);

this.model.on("change", function() { this.render(); }, this)

        // Views take care of their data and since we have only one view, we do it this way

this.collection.fetch();

    },

    render: function() {

        // Load template

        var source = $("#appview-tpl").html();

        // Compile template

        var tpl = Handlebars.compile(source);

        // Assign data and render

        var html = tpl(this.model.toJSON());

        // Since we append to body, we have to clean up our stuff very carefully and make sure that we don't delete our script tags

        // Select all direct childs of this.el which are not a script

$(this.el.tagName + " > *:not(script)").remove();

$(this.el).append(html);

        this.dashboardWidget.render();    // Re-render dashboard after AppView is rendered

    },

We are done. Our application frontend is up and running. Now, it’s time to dig into the backend part. Enjoy Matthias’ blog on it.

2 Comments