window
objectl.~~SLIDESHOW~~
Contact Hour 17: To be discussed on Wednesday 5th March, 2013.
Lecturer: Dr Chris P. Jobling.
An introduction to Backbone based on the Free Udemy course Learn Backbone.js and StackMob by Sidney Maestre with a contribution from Christophe Coenraets.
You can watch the video below in which Sid goes through much the same example at a 2013 San Francisco HTML5 Developer's Meetup. (published by Marakana.tv).
<html> <iframe width=“560” height=“315” src=“http://www.youtube.com/embed/jM8KE_Fa6JI” frameborder=“0” allowfullscreen></iframe> </html>
Please review the Pearltree used in the previous session for some background to MVC and Client-side JavaScript MVC Frameworks.
For convenience, all examples, jsFiddles and source code are linked to backbonejs-hands-on/index.html (web/backbonejs-hands-on/index.html
) on the latest update to the eg-259-vm repository.
(function(){ var foo = bar; })();
This pattern is used to prevent any code/variables/functions declared inside the body leaking out into the global namespace.
Even so, if you omit the var
:
(function(){ foo = bar; })();
The variable foo would become a global variable1)
(function($){ var foo = bar; })(jQuery);
This uses a “closure” to ensure that the jQuery
variable is available inside the scope of the anonymous self-calling function as $
. This will be true even if $
was redefined after loading this function.
var app = (function($){ var foo = "bar"; return { name : 'module pattern', setFoo: function(name) { foo = name; }, getFoo: function() { return foo; } }; })(jQuery);
See jsFiddle and 00-module/index.html
Ensures that methods and properties are name-spaced (e.g. app.name
, app.setFoo
, app.getFoo
) to avoid collisions and also that only properties and methods that are made available outside the module. Any dependencies, e.g. in this case jQuery, are passed in as arguments to the self-calling anonymous function.
var app = app || {}; app.model = app.model || {}; app.routers = app.routers || {}; app.collections = app.collections || {}; ... app.model.Project = Backbone.Model.extend({}); app.model.ProjectSelection = Backbone.Model.extend({}); app.routers.Application = Backbone.Router.extend({}); app.routers.Projects = Backbone.Collection.extend({}); app.collections.ProjectSelection = Backbone.Collection.extend({});
This is not used in this example project, but is recommended by Addy Osmani in his forthcoming O'Reilly book Developing Backbone.js Applications (Source code and copy, in various e-book formats, on GitHub at addyosmani/backbone-fundamentals). It is a pattern worth considering for your coursework as well.
In server-side MVC applications, the controller typically responds to URLs and coodinates the retrieval of model data from databases and pssing them on to HTML templates which are then passed on to the client.
In REST-based applications, the application is much more controller and model-based. Data is typically passed to the browser for rendering as a view. Often, these days, as JSON objects.
<!-- Script libraries --> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://static.stackmob.com/js/json2-min.js"></script> <script src="http://static.stackmob.com/js/underscore-1.3.3-min.js"></script> <script src="http://static.stackmob.com/js/backbone-0.9.2-min.js"></script>
(function ($) { Project = Backbone.Model.extend({}); firstProject = new Project({ title: 'Project 1' }); })(jQuery);
Project = Backbone.Model.extend({ defaults: { title: "Project 1" }, updateTitle: function(newTitle) { this.set("title", newTitle); } }); firstProject = new Project(); console.log(firstProject.toJSON());
firstProject.on('change:title', function () { console.log("you've updated the title"); console.log(this.get('title')); });
Project = Backbone.Model.extend(); Projects = Backbone.Collection.extend({ Model: Project, url: "#" });
URL is “RESTful endpoint” which backbone uses to retrive collections.
projects = new Projects([{ title: "Project 1" }, { title: "Project 2" }]); console.log(projects.toJSON());
projects.each(function (project) { console.log(project.get('title')); });
each
is a method provided by the underscore library. It loops through a collection, returning each element which is then passed on to a function.
: projects.on('add remove', function (event) { console.log("you've changed the collection"); }); var firstProject = new Project({ name: 'Project 1' }); projects.add(firstProject);
In this example, a change event is triggered if an item is added to the collection, or if an item is removed from the collection. In Backbone, wiews can subscribe to such events and redraw the collection should any item change.
(function ($) { HomeView = Backbone.View.extend({}); })(jQuery);
A view (in HTML) is a rectangular area on the page that observes models and collections and updates themselves when the models change. In JavaScript MVC they can also be the target of user interactions and can generate events themselves.
(function ($) { HomeView = Backbone.View.extend({ render: function () { this.$el.append("<h1>My Projects App</h1>"); return this; } }); })(jQuery);
Render is a function that is used to build the DOM that represents the HTML in the view. Note $el
is the jQuery representation of the views element el
(which by default is a div).
$(document).ready(function () { projectApp = new HomeView(); });
Here we call the view, defined as in the previous slide, on the $(document).ready
event that we saw in the session on jQuery.
function ($) { HomeView = Backbone.View.extend({ el: 'body', initialize: function () {...}, render: function () {...} }); })(jQuery); $(document).ready(function () { projectApp = new HomeView(); });
By defining the el
property to be the body element, the elements contained in the view will automatically be added to the document when it is loaded in the browser. By making the rendered DOM elements the result of processing an HTML template, quite complex user interface components can be built-up from smaller parts. Note that the el property will often be set to a div with an id, as in el: “#contents”
.
HomeView = Backbone.View.extend({...}); ListView = Backbone.View.extend({ tagName: 'ul', initialize: function () {...}, render: function () { this.$el.empty(); this.$el.append("<li>Hello</li>"); this.$el.append("<li>Goodbye</li>"); return this; } });
Here, we add a ListView
, based on an unordered-list, for the display of list items.
HomeView = Backbone.View.extend({ el: 'body', initialize: function () { this.render(); }, render: function () { this.$el.empty(); this.$el.append("<h1>My Project App</h1>"); this.listView = new ListView(); this.$el.append(this.listView.render().el); return this; } });
Here the home view has been extended and is being used to render the list view inside its own render method. You could render a whole tree of HTML elements in the same way.
_.template
.A brief list would include:
Use Google to find out more.
In the HTML:
<!-- undescore template ... compiles body then interpolates value at call time --> <script type="text/template" id="item-container"> <li><%= value %></li> </script>
Here we use a script with type of text/template
. The browser will ignore this, as the script is not JavaScript, but the browser will still build a script DOM element and Backbone can retrieve its content later by grabbing the content of the $(#item-container')
element. Ember.js has a very similar approach, albeit with a different templating engine.
The template syntax is very simple: anything between <%= … %>
is considered to be an expression to be output. The content can be any JavaScript statement that returns a value. Another tag pair <% … %>
can be used to encapsulate any JavaScript code, e.g. a loop, that doesn't output a value. Te syntax is very similar to the syntax used in server-side templating languages like PHP!
ListView = Backbone.View.extend({ : initialize: function () { this.template = _.template($('#item-container').html()); }, render: function () { this.$el.empty(); this.$el.append("<li>Hello</li>"); : this.$el.append(this.template({value: "Hello Backbone"})); return this; } });
Here the HTML from the script tag with id item-container
is passed to the template function
_.template
where it is “compiled” into a DOM object witha place-holder for a value. Later, when the render function appends the template to the DOM, the value is passed as an argument to the template and it will then be output into the document.
This simple idea is remarkably powerful and is used in many popular server- and client-side web development frameworks.
render: function () { var el = this.$el, template = this.template; el.empty(); projects.each(function (project) { el.append(template(project.toJSON())); }); return this; }
Here we use a similar template, this time outputting HTML view prepresenting each project in the collection.
Here we have a more complex example using three templates:
Here is the code: 05-router-01
AppRouter = Backbone.Router.extend({ routes: { "": "home", "add": "add", "close": "home", }, home: function () { console.log('home'); new HomeView(); }, add: function () { console.log('add'); new AddView(); } });
The purpose of the router is to define some “URL“s that the client will use to navigate around the application. These are simply strings that map a URL to a method. In this example, they map the client-side URLs #
, #add
and #close
to the methods home
, add
and home
respectively. These methods in turn will create, dynamically, a home view and an add view which will respectively display the list of projects and allow a project to be added.
$(document).ready(function () { projectApp = new AppRouter(); Backbone.history.start(); });
Here we create a new object representing the whole application as an AppRouter
, which itself is defined as an instance of Backbone.Router
. We also call Backbone.history.start();
which enables the browser to record the client-side state as URLs that can be retrieved as bookmarks or using the back button.
In the second router example 05-router-02 we add two event listeners to the AddView
so that both the add button and pressing enter in the text field will cause an event that will trigger the invocation of the add
method that will eventually cause a new project to be added to the collection.
In the final iteration 05-router-03, we add the new project to the project collection and also register an event listener on the ListView
so that when a new project is added, a change event is fired and the whole list is redrawn.
UpdateView
which supports the editing feature and the list items now include an id in the URL that allows a particular project to be selected for editing.Backbone.sync
object to allow data to be read from an array of objects in memory rather than a RESTful api (which is it's default behaviour).We probably didn't get through all this, but please do take some time to explore!
Part 3: Server-Side Programming
window
objectl.