Using Backbone.js to create MVC applications on Tizen
PUBLISHED
Introduction
No complex application can be built without using the MVC (Model View Controller) architecture. Small web applications can be made by combining a code from all three layers in one file but even that is not a good practice. In this article we will use BACKBONE.JS library to build a search application based on a Google Custom Search API. We will discuss how the developers can manage application data, display templates and connect different layers. First, we will discuss how Backbone.js manages model, view and controller layers and later we will investigate implementation details of the sample application.
Model
Model is used to keep application data separate from its presentation. With Backbone.js we can validate any value set on the object, compute properties’ values based on other object’s properties, convert properties’ values before applying the new values and restrict access to some of them. First of all, let’s see how to create a model definition:
var User = Backbone.Model.extend({ /* Constructor */ initialize: function () { }, /* Methods */ getFullName: function () { return this.get('name') +' '+ this.get('surname'); }, /* Default attributes' values */ defaults: { name: 'John', surname: 'Smith' } });
As seen in the code above, to create the model definition using ‘Backbone.Model.extend()’ function which extends the default model definition. We don’t have to declare attributes. They can be added dynamically to created object. However, you can define default attributes’ values as shown above. We set default ‘name’ and ‘surname’ values if not provided. We’ve also defined a custom method ‘getFullName()’ and a constructor ‘initialize()’. To instantiate object of given model (class) we use standard JavaScript ‘new’ identifier.
var john = new User({surname: 'Johnson'});
We pass two objects as constructor’s arguments. The first one is the object with attributes’ definition and the second one is the object with properties. You can read more about it in Backbone.js documentation. Both constructors’ arguments are optional.
Setting and getting object’s attributes
Every object has several methods to manipulate its attributes. First of all we can check if object has given attribute by calling the ‘has()’ function and passing sought attribute name as its parameter.
user.has('name');
To set and get attribute we invoke functions with corresponding names. When setting, as the first parameter we pass argument name and as the second its value. When getting attribute’s value we only pass its name to the ‘get()’ function.
user.set('name', 'John'); user.get('name'); // 'John'
- escape() – returns escaped version of an attribute’s value. It’s important to escape every attribute having HTML code to prevent XSS attacks,
- clear() – removes all the object’s attributes,
- unset() – removes attribute with the name given as a parameter.
Inheritance
The object Backbone.Model has an ‘extend()’ function which is inherited by every object extended from the Backbone.Model object. Thanks to that we can achieve multilevel inheritance.
var Fruit = Backbone.Model.extend({ someMethod: function() {} }); var Apple = Fruit.extend({ /* Override method */ someMethod: function() {} });
When overriding a function from the parent model (class) there is no simple way to invoke the parent’s function. You have to explicitly call it from the parent. It’s shown in the code below.
var Apple = Fruit.extend({ /* Override method */ someMethod: function() { /* Call overridden function from the parent */ Fruit.prototype.someMethod.apply(this, arguments); } });
To read more about the inheritance and the ‘extend()’ function go to this website.
Saving, removing and retrieving objects from the server
The most important part of a REST application is objects’ exchange between client and server. When server provides REST API, the client should have access to server’s resources, by calling a prepared URL. This URL could look like ‘user/5’ which refers to user object with id 5, stored on the server. The Backbone.js makes a HTTP request with method corresponding to the operation that it has to do: GET method when retrieving data, POST when updating or creating and DELETE when removing. The default URL pattern looks like this: ‘[collection.url]/[id]’, however you can override the ‘url’ property of the model definition. This property can be a string or a function returning a string.
To save an object on the server just use the ‘save()’ method on given object. It can take as the first argument list of attributes that have to be stored. As the second argument we can pass some options like success or error callbacks. If the object exists on the server we just update its attributes.
To remove an object from the server use the ‘destroy()’ method. It sends a DELETE HTTP request. We can also pass options as the arguments with success and error callbacks.
To retrieve data from the server we use the ‘fetch()’ method. It gets data and puts it in our model’s object. As we said before, you don’t have to specify model’s attributes. When fetching data each attribute that doesn’t exists in our model will be created.
For more information you can go to the documentation.
View
The Backbone’s view doesn’t specify a template system you should use. It just provides some conventions that help manage a process of rendering the view by binding events and object changes and passing object’s data to the template. To create the view use a ‘Backbone.View.extend()’ function to which we pass an object specifying some view’s properties.
Every view is correlated with some DOM element that is stored in an ‘el’ property of the view. If no element is specified, a default ‘div’ element will be created but it’s your job to insert it into the DOM tree. If you specify some properties like ‘className’, ‘tagName’, ‘id’ and ‘attributes’ then all of them will be taken into account when creating the DOM element.
var MyView = Backbone.View.extend({ tagName: 'div', el: '#element', /* ... */ });
In the ‘initialize()’ function you can do some initialization on the view object. The most common usage is calling a ‘listenTo()’ function that binds object’s changes with the view.
initialize: function() { /* Listen to changes of the model and if any change occure call view's render function. */ this.listenTo(this.model, 'change', this.render); }
The ‘render()’ function is the place where you do the entire template rendering. You have to get a template, sometimes compile it, and pass object’s data to it. Having the HTML code of the view you can put it inside the ‘el’ element. Every ‘view’ has also a special ‘$el’ property - an ‘el’ element wrapped with a jQuery object. I will show an example code of the ‘render()’ function when describing the sample application.
Events delegation
Another thing worth mentioning is the events delegation. You can bind mouse or keyboard events to template elements and invoke some functions that can change the template itself or do any other action. An ‘events’ property is the place where you define your events delegation. Events are defined in the following format.
{'event selector': 'callback'}
The events delegation uses jQuery ‘on()’ function to match selector and bind function to an event. The ‘callback’ string is the name of the function within the ‘view’ object that will be called when the event happens.
Using the ‘listenTo()’ function with the events delegation automates much of your application actions. On the one side, you can automatically refresh the template whenever the model changes, on the other side you can change the model in response to user’s actions. All of this is done automatically and all the code is structured in accordance to the MVC model, making the code readable and easy to change.
Controller
Actually, the events delegation should be part of the controller layer in the MVC model but sometimes it’s good to break that rule and keep it close to the view layer. However, you could move the definition of the events delegation to another file. When creating the controller you can use the ‘delegateEvents()’ function of the view object, to pass the events definition to the view. The example code could look like this:
var Controller = (function() { var _home = function () {}; var _contact = function () {}; var _exit = function () { /* Delegate events. */ MyView.delegateEvents({ 'click .exit': function () { /* Close Tizen application when element with 'exit' class was clicked. */ tizen.application.getCurrentApplication.exit(); } }); }; /* Return public module interface. */ return { home : _home, contact : _contact, exit : _exit }; }());
The MVC model has many variations and it’s up to you how you organize your code. However, it’s good to follow some common patterns.
Routing
Routing matches website URL with the content or page to display. When we have many records in database, each of them having a unique ID, we would like to access them by writing the URL address like this: ‘/record/ID’. Sometimes it’s desirable to access some application pages by URL, for example:
- About – ‘/about’
- Home – ‘/’
- Send – ‘/send’
Of course, we can achieve page changing and records presentation without using routing but when we want to deploy our application not only to Tizen but also to the web it’s the recommended way because such application are SEO (Search Engine Optimization) friendly.
In the Backbone.js there is a special object to manage routing - ‘Backbone.Router’. We have to create our own router, define routes and functions to invoke when given route matches the URL.
var Router = Backbone.Router.extend({ routes: { '': 'home', 'record/:id': 'record' /* Route with ID parameter. */ }, home: function () { Controller.home(); }, /* Parameter are passed as function's arguments. */ record: function(id) { } });
Each route has a corresponding function that will be invoked when the URL matches that route. Later we can pass handling that action to the Controller’s functions or handle it inside the Router object if there is not too much code.
Routes can have parameters. Parameters are preceded by a colon and passed to the function as its arguments. We can also match URLs by using regular expressions. You can find a detailed description in the official documentation.
Sample application
The sample application named ‘Search’ is an "Internet browser" using Google Custom Search API. It is a simple application that has a query field and a search button. It displays search results with ten rows (items) on a single page. Navigation appears at the bottom of the query results if search query returns more than ten items. In the navigation we have ‘Previous page’ and ‘Next page’ links, which allows navigating through pages. I’ve stylized application to look similar to Google search.
Before getting into the code, let’s have a quick look at Google Custom Search API. There is a limit of 100 queries per day (above this limit the service is paid). When you run the application, it will not work because you have to configure it. Here is a description of getting a data for the configuration.
Setting up Custom Search API
Here I’ll give a short explanation of setting up the Custom Search API. For more details you have to read Google Custom Search documentation and investigate Google APIs Console. We will need two things: the Custom Search identifier and the API key.
- First of all you have to create a Google account.
- Next, log in to the Custom Search.
- You have to enter the search website to create a Custom Search. You will be able to remove it later and just search for all the sites indexed by Google, so you can enter here any domain you want.
Choose also the name and language. - Now go to the list of all your browsers and edit the one you’ve just created. Remove the website you’ve entered in the last step and choose to search the whole network instead of the given website. Change also the language to ‘All languages’ if desired.
- Next, you have to copy your search identifier by clicking button with this name. You will be using it in your API.
- Now, go to the Google APIs Console and create a new project and activate Custom Search API option. Accept terms and you’re almost ready.
- Last thing you will need is the API key which can be found on API Access page of Google APIs Console.
Configuration and initialization
The Google Custom Search API makes call to the Google server, so we have to give our application access to the given URLs. We make it by providing the domain name in the config.xml file.
<access origin="https://www.googleapis.com" subdomains="true"/>
The application entry point is ‘app.js’ file where all the initialization takes place. The API key and Custom Search identifier are stored in the ‘_config’ variable, and are available through the public interface ‘app.config.KEY’ and ‘app.config.CX’.
_config = { KEY: 'PASTE_THE_KEY_HERE', CX: 'PASTE_THE_CX_HERE' };
Inside the ‘_initialize’ function initialization of model, view and controller takes place. We will discuss it in details when investigating MVC layers of the application.
Dependencies
The Backbone.js can be downloaded from the official website in both production and development versions. In order to run Backbone you also have to include an Underscore.js which provides around 80 functions that extend basic JavaScript functionalities. It helps in managing arrays, objects and collections. You can read more about that on the official website. You also have to include a jQuery library.
The sample application uses Backbone.js in version 1.0.0, Underscore.js 1.4.4 and jQuery 2.0.0.
<script type="text/javascript" src="js/libs/jquery-2.0.0.js"></script> <script type="text/javascript" src="js/libs/underscore-1.4.4.js"></script> <script type="text/javascript" src="js/libs/backbone-1.0.0.js"></script>
Model
I’ve decided to divide application in two parts, the first one is the query and the second one is search results. I’ve created corresponding models’ definitions.
The ‘SearchQuery’ model consists of only one property which is ‘query’.
var SearchQuery = Backbone.Model.extend({ defaults: { query: null } });
The ‘SearchResult’ model is the heart of the application. It’s responsible for requesting data from Google server. It has two properties ‘query’ and ‘start’. The first one just stores the search query and the second one indicates from which item we want to start searching. Of course, it’s used to make pagination.
We have overridden the ‘fetch()’ method to retrieve data only if the query was set. It reduces the amount of queries made to the server. If no query was set, we just return nothing. The line, where parent’s ‘fetch()’ method is invoked, is worth discussing. In the Backbone.js, there is no function facilitating calling parent methods, so we have to call it explicitly as it was discussed previously in this article in the Inheritance section.
/* Override the fetch method to retrieve data only if the query was set. */ fetch: function(options) { if (this.has('query')) { /* Call parent method. */ return Backbone.Model.prototype.fetch.call(this, options); } return; }
As I mentioned earlier, any model has the ‘url’ property that identifies the resource. It can be string or can be dynamically generated by function. We use API Key, Custom Search identifier, query and start properties here to create URL. The created query string follows this pattern: ‘https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM_SEARCH_ID}&q={QUERY}&start={START}.
url: function () { var url, params; params = { key: app.config.KEY, cx: app.config.CX }; if (this.has('query')) { params.q = this.get('query'); } if (this.has('start')) { params.start = this.get('start'); } url = 'https://www.googleapis.com/customsearch/v1'; url += '?'+ jQuery.param(params); return url; }
View
Each model has a corresponding view: SearchQueryView for SearchQuery model and SearchResultView for SearchResult model. I used Handlebars template system for rendering the view. You can download it from the official website. You have to include it in the HEAD section of the ‘index.html’ file.
Handlebars is a very simple semantic template system compatible with Mustache template system. I’ve placed templates definitions inside the HEAD section in the ‘index.html’ file. To do it I had to wrap them with SCRIPT tags.
<script id="search-query-template" type="text/x-handlebars-template"> <header>Search Application<span class="close">x</span></header> <form action="#" class="search-query"> <div class="search-input-padding"><input id="query" name="query" type="text" value="{{query}}" autocomplete="off" /></div><input id="search" type="submit" value="Search" /> </form> </script> <script id="search-result-template" type="text/x-handlebars-template"> <ul class="search-result"> {{#if searchInformation}} <li class="result-stats">Around {{searchInformation.formattedTotalResults}} results ({{searchInformation.formattedSearchTime}} s)</li> {{/if}} {{#if items}} {{#each items}} <li class="search-item"> <h3><a href="{{link}}">{{{htmlTitle}}}</a></h3> <div class="url">{{{htmlFormattedUrl}}}</div> <div class="snippet">{{{htmlSnippet}}}</div> </li> {{/each}} {{/if}} <li class="pagination"> <ul> {{#if queries.previousPage}} <li><a href="#query/{{queries.request.0.searchTerms}}/start/{{queries.previousPage.0.startIndex}}">Previous page</a></li> {{/if}} {{#if queries.nextPage}} <li><a href="#query/{{queries.request.0.searchTerms}}/start/{{queries.nextPage.0.startIndex}}">Next page</a></li> {{/if}} </ul> </li> </ul> </script>
As you can see, each SCRIPT tag has its ID attribute to identify it while rendering. The ‘type’ attribute is set to ‘text/x-handlebars-template ‘. Handlebars use expressions inside double curly braces. We can use many predefined expressions or create your own. In the template in the sample application I used only three of them. You can read more about creating templates and how expressions work in the official Handlebars documentation. In the sample application templates are included directly in the index.html file but they could be precompiled and included as an external JavaScript file. You can read more about that here.
The ‘el’ property of the ‘SearchQueryView’ was set to ‘#search-query-holder’ which is a selector for one of the two DIVs inside the BODY element. The second DIV has id ‘#search-result-holder’ which of course holds rendered template with search results.
In the ‘events’ property we defined two actions. One closes application when user clicks ‘X’ in the application’s header and seconds one is submitting from.
events: { 'click .close': 'close', 'submit form': 'submit' },
When the form is submitted then the ‘submit()’ function is executed. It gets a search query from the form and composes a new URL address and sets it.
submit: function (e) { var query; e.preventDefault(); query = this.$el.find('#query').val(); if (query) { document.location = '#query/' + query; } else { document.location = '#'; } }
The ‘initialize()’ function does only one thing. It listens to model changes. In this situation model is the instance of ‘SearchQuery’ class, which was defined inside the ‘app.js’ file and passed, as the first parameter, to the ‘SearchQueryView’ constructor.
/* js/view/SearchQueryView.js */ initialize: function () { this.listenTo(this.model, 'change', this.render); } /* js/app.js */ /* Create objects. */ app.searchQuery = new SearchQuery(); app.searchResult = new SearchResult(); /* Create view. */ app.searchQueryView = new SearchQueryView({model: app.searchQuery}); app.searchResultView = new SearchResultView({model: app.searchResult});
The last function of the ‘SearchQueryView’ is ‘render()’ which gets template’s code, compiles it, passes model’s attribute to it and renders it. The rendered template is then put inside previously mentioned DIV element with the ‘#seach-query-holder’ id attribute.
render: function () { var source; if (!this.template) { this.template = Handlebars.compile($('#search-query-template').html()); } this.$el.html(this.template(this.model.attributes)); }
Now we will investigate the ‘SearchResultView’. It’s much simpler than previously discussed. It also has an ‘el’ property defined to proper DIV element inside the BODY element. It also listens to model’s changes but this time the model object is an instance of the SearchResult. The template rendering is almost the same; the only difference is the template code we take for rendering.
var SearchResultView = Backbone.View.extend({ tagName: 'div', el: '#search-result-holder', initialize: function () { this.listenTo(this.model, 'change', this.render); }, render: function () { var source; if (!this.template) { this.template = Handlebars.compile($('#search-result-template').html()); } this.$el.html(this.template(this.model.attributes)); } });
Controller
I didn’t use controller in the full meaning of this word in the sample application. The code is very simple and most of the logic is close to the model and view. However, we need a routing system so I created a Router object. It defines three routes. One for the main empty application screen, one for the screen with search results and the last one when user decides to navigate through the search result pages.
routes: { '': 'search', 'query/:query': 'search', 'query/:query/start/:start': 'search' }
Each route executes the same ‘search()’ function. The first thing this function does is clearing the ‘app.searchResult’ object which causes clearing the search results from the previous search. Next, we set properties of ‘app.searchQuery’ and ‘app.searchResult’ objects depending on the variables passed by the router to the ‘search()’ function. The last thing is fetching search data from the Google server by calling the ‘fetch()’ method.
search: function (query, start) { app.searchResult.clear(); if (query !== undefined) { app.searchResult.set('query', query); app.searchQuery.set('query', query); } if (start !== undefined) { app.searchResult.set('start', start); } app.searchResult.fetch(); }
In order to make routing work we have to initialize the router. We do it in the ‘_initialize()’ function in the ‘app’ module.
/* Initialize router. */ app.router = new Router(); Backbone.history.start();
The last thing to make everything work is to render empty templates at the application start. Without it nothing would appear on the screen.
app.searchQueryView.render(); app.searchResultView.render();
Summary
I hope that this simple introduction to the Backbone helped you understand how it works and now you will be able to create more dynamic applications. The Backbone framework is a good base for creating Tizen Web Applications. It follows the REST pattern and MVC model and can reduce the time of creating application.