Skip to content

Instantly share code, notes, and snippets.

@dmitriz
Last active August 29, 2016 14:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dmitriz/4645896 to your computer and use it in GitHub Desktop.
Save dmitriz/4645896 to your computer and use it in GitHub Desktop.
Backbone App: Beginner Tutorial
<!--
Web Application with Backbone: BEGINNER TUTORIAL
Backbone.js is a very popular and light(weight) JavaScript library
for building Web Applications.
However, learning it I found frustratingly few simple examples of
true workable applications with all lines of code explained.
Here is my attempt to hack the popular Jerome Gravel-Niquet's Backbone Todo App
down to a MVA (Minimum Workable App), and turn it into
a Beginner Tutorial by adding long
and tedious explanations for every single line of Code.
When not broken, this App should offer Entry Field for your Item,
where you can enter any Text (but please be careful with other characters).
Pressing ENTER will add the Item (if not empty) to the List.
Then hovering your mouse over a List Entry,
you see a little gray cross image at the very right,
which you can click to instantly delete the Item for good.
Just before deleting as your mouse hovers over that image ready to click,
it turns black (i.e. the image, not your mouse, hopefully)
to give you the last chance to save the Item from its coming death.
This is it, as far as the unsofisticated functionality goes.
Yet remarkably, this involves quite a few useful things Backbone can offer
to sweeten your life when attempting to write something more useful.
So the primary goal of this Tutorial is to explain all those little features
and hopefully help the reader to get a quick start,
even if she is only vaguely familiar with all this JavaScript stuff.
Since I've stripped this App to its absolute bare nacked minimum,
including whatever styles are used for decoration purposes,
this is probably the ugliest looking App you have ever seen.
And just to relieve the reader's pain, she is welcome to add any of her
favorite styles to make the App 'not so hard on the eyes'.
Unfortunately, every beautiful page seems to have very 'unbeautiful'
CSS code underneath, which would leave no hope to keep this tutorial
(reasonably) short keeping all lines ferociously explained.
You can copy the file either with 'Download Gist' or the '<>' Icon "View Raw",
and view it with your Browser, where you see links to download 4 missing files.
Once those are in the same directory, the App should become functional.
I could provide many links to the terms discussed.
However, the links are known to become outdated or die/break at moments of viewing.
Thus it will better serve the reader simply to copy-paste them
into her favorite Search Engine.
Practice. Yes, you can only learn through practice!
Which is why I included little paragraphs with homework.
To do it, you need to access JavaScript Console provided
either by Firebug Extension for Firefox,
or natively in the View/Developer menu in Chrome, also available for other Browsers.
Have Fun!
-->
<!DOCTYPE html> <!-- The (reasonably) new short HTML5 tag -->
<html>
<head>
<title>Item Management App with Backbone</title>
<!-- It is recommended to declare the charset even for English Web sites -->
<meta charset='UTF-8'/>
<style>
/* To reduce the code to bare minimum we style only elements showing the Items,
the only styling needed for the App to work correctly.
These elements all appear inside the ordered list 'ol' with id='todo-list'.
Every Item will be placed in its own 'li' element
as child of <ol id='todo-list'>.
Every 'li' element will contain a 'span' with class='todo-destroy'
used to display a part of the image 'destroy.png'.
The whole HTML structure here fits into the simple scheme:
ol.#todo-list > li > span.todo-destroy in CSS style
or as HTML:
<ol id="todo-list">
<li>
<div class='todo-content'><%= content %></div>
<span class='todo-destroy'></span>
</li>
<li>
...
</li>
...
</ol>
*/
/* Newbie note. Even though 'style' tag can appear anywhere,
it is recommended to place it inside the 'head',
so it will be active before the 'body' gets processed.
Otherwise the browser would show the unstyled elements first
and reapply the new style to them after */
/* styling Elements <li> that are children of <ol id="todo-list"> */
#todo-list li {
/* needed to allow positioning its children element relative to itself*/
position: relative;
border-bottom: 1px solid gray; /* puts a gray line */
/* adds space 0.2 the size of letter 'm' at the top and bottom of the text */
padding: .2em 0;
}
/* styling Elements <span class='todo-destroy'>
that are children of <ol id="todo-list">.
These Elements will contain the 'destroy.png' image,
appearing at the end of each line upon mouse hovering over it */
#todo-list .todo-destroy {
/* we don't display the image initially,
only upon mouse hovering -- to be declared below */
display: none;
/* absolute positioning inside relative positioning
allows to use the 2 positioning declarations below */
position: absolute;
/* space between the right edges of this element and its parent */
right: 3px;
/* space between the top edges of this element and its parent */
top: 3px;
/* prescribing the width of the element corresponding
to the horizontal size of the image,
otherwise the image would repeat since declared as background */
width: 20px;
/* prescribing the hight of the element, showing the upper half (gray) of the image */
height: 20px;
/* declaring the image as background, should be in the same directory */
background: url(destroy.png);
}
/* Practice. Change the numbers above and see the effect in the browser */
/* declarations for the container with 'destroy.png'
as shown upon mouse hovering over the parent 'li' element */
#todo-list li:hover .todo-destroy {
/* now the element is displaying showing the upper half (gray) of the image */
display: block;
}
/* declarations for the same container when mouse hovers over it */
#todo-list .todo-destroy:hover {
/* shifts the image 20 pixels up to display its lower half (black) */
background-position: 0 -20px;
}
</style>
<!-- Loading external JavaScript libraries/frameworks in the right order,
they have to be in the same directory
and have to be loaded before our main JavaScript code.
Minified versions can be used for faster loading and less bandwidth -->
<!-- if you are reading that far, you've certainly heard about jQuery -->
<script src='jquery.js'></script>
<!-- lightweight utility library used by and to be loaded before backbone.js -->
<script src='underscore.js'></script>
<script src='backbone.js'></script> <!-- Backbone framework -->
<script> // The main JavaScript Code
/* We need to decide where to place our JavaScript Code.
Since it refers to HTML elements of our document,
we need to make sure the latter are loaded before.
One way is to place the Code at the end of the 'body'.
However, if there are large images or videos taking longer to load,
our Code will have to wait leaving our page
without functionality during the waiting time.
A better solution is to place it in the 'head' but inside
'$(function(){ ... })' or '$().ready(function() { ... })'
or '$(document).ready(function() { ... }'.
This uses the jQuery utility '.ready()' which executes the function enclosed
only after the HTML of the 'body' (the DOM) is fully loaded.
That means our JavaScript will wait for all needed HTML elements to be present
but will not wait for images or videos to load. */
$(function() {
/* We shall need to reference the RETURN key.
It is a good practice to store it in a variable at the beginning of the Code,
rather than 'hard-coding' it down inside the Code,
so we can easily change it later if needed. */
/* Per convention, constant variables whose values
are not changed are written using capitals.
We save the key code of the RETURN key,
which is pressed when the Item needs to be stored */
var RETURN_KEYCODE = 13;
/* Newbie note. Allways use 'var' to declare your variables. */
/* We store Items as Backbone Models in a Backbone Collection.
A Model is the simplest piece of data
with a bunch of useful methods coming from Backbone.
The data are stored as 'attributes'.
Since our Item is a string, we only need one attribute for each Model.
We need a list of Items,
so it is convenient to organized them in a Backbone Collection,
which is basically an array of Models again,
with a bunch of useful methods coming from Backbone.
As we add our Items dynamically, we start with empty Collection.
(Note the convention that instances of Models, Collections and Views
begin with small letters.) */
/* We intentionally remove 'var',
so the Variable here will be available in the Console for practicing */
/* var */ itemCollection = new Backbone.Collection();
/* Practice. Type 'itemCollection' in your Console. Your should get an object.
Then inspect its properties. These are set by Backbone. */
/* Next we need Views to display our Items, one View per Item.
The Views will be similar,
so there is no need to define Methods (Properties pointing to Functions)
for each View separately.
Instead Methods will be defined on the Prototype.
In a nutshell, Prototype is a property of any Function 'F()'
that can be used to store properties
to be inherited by any intance defined via 'new F()'.
However you don't need to worry about Prototypes
as Backbone does it for your behind the scene.
All you need to do is to Extend the plain Backbone.View constructor Function
with your custom Properties and Methods.
Then every time we need an instance say of a View,
we get it using the 'new' operator with our constructor:
view = new ItemView(options);
*/
/* var */ ItemView = Backbone.View.extend({
/* Every View creates an Element where it will be attached,
referenced 'view.el' where 'view' is any Backbone view.
Note that as long as this Element is not yet attached to our DOM,
it is not visible in the Browser. */
// The 'tagName' property specifies the HTML tag name of that Element.
tagName: 'li',
/* Next we prepare and cache Template to display our Items.
Our Template will be stored in the HTML 'script' Element
<script id='item-template'> at the end of the file,
see the long description below preceeding it.
Using jQuery we extract Template's HTML content as $('#item-template').html()
and then pass it to Underscore's _.template() utility which creates a Function.
We reference this Function as 'itemTemplate'.
Whenever we need HTML code displaying our Item,
we simply evaluate this Function
with data object {content: 'our content goes here'} passed to it as argument.
For instance,
itemTemplate({content: 'My Text'})
produces the code:
<div class='todo-content'> My Text </div>
<span class='todo-destroy'></span> */
/* Practice. In your Console type:
myHTML = _.template($('#item-template').html()).
You will get a Function. You can inspect its source by typing 'myHTML.source',
this property is provided by Underscore for convenience.
Now pass any value of 'content' to this function. For example, type:
myHTML({content: 'HAHAHA!'})
You will see the resulting HTML with
'HAHAHA!' inserted in place of '<%= content %>'.
Now use our cached Function 'itemTemplate' to achieve the same result.
You need to create an instance of ItemView by typing
'myView = new ItemView()' or 'myView = new ItemView',
then our Function will be accessed as 'myView.itemTemplate()' */
//compiling template function with Underscore's _.template()
itemTemplate: _.template($('#item-template').html()),
/* We next want to destroy our Item whenever User clicks on the 'destroy.png' image
appearing at the end of the line displaying each Item,
i.e. on the element <span class='todo-destroy'>
that we included in Template.
Backbone provides us with a simple way of doing it.
Capture the 'click' Event and assign to it Event Handler Function
that will do the needed work for us.
This assignment can be done via Backbone's 'events' Property.
The latter is an object, whose keys are our DOM (Document Object Model) Events
(i.e. Events arising from User's interaction with the Document)
and whose values are their Handling Functions.
We only have one Event 'click .todo-destroy',
to which we assign Function 'clear' to be defined later.
You should not forget to define that Function or an error will be thrown.
So every time User clicks on a DOM Element captured by CSS selector '.todo-destroy'
(within the scope of the View),
the Handling Function 'clear' is executed.
'Withing the scope of the View' means that our View
only reacts to Events of its Element 'el' and its 'children'.
Even though there are many Elements of class='todo-destroy' in our Document,
one for each Item, only one will be used for each View.
Note that these are DOM Events
that should not be confused with Backbone's Built-in Events.
See 'delegateEvents' in Backbone's Documentation for more details. */
events: {
/* clicking any element with class="todo-destroy"
leads to executing this.clear() function */
'click .todo-destroy': 'clear'
},
/* Practice. In your Console use ItemView constructor to instantiate new view:
myView = new ItemView()
Check events property:
myView.events
*/
/* In our 'events' property above we introduced the Function 'clear' to be executed
when Event 'click .todo-destroy' is triggered,
i.e. when the User clicks on the Element with class='todo-destroy'.
We need to define this Function */
clear: function() {
// Backbone's Method: destroys Item's Model and removes it from Collection
this.model.destroy();
// Backbone's Method: removes this View and its Element 'el' from the DOM
this.remove();
}
/* In Backbone every View has 'render()' Property (or Method)
that is originally void and is meant to be overridden.
It is up to the programmer what this method does but typically it creates HTML
shown by the View and refreshes it when needed.
Note that if the View Element 'el' is not placed into our Document yet,
it is not yet visible in Browser. */
render: function() {
this.$el.html(this.itemTemplate(this.model.toJSON()));
/* In our render() Method we use the standard Backbone's construction
to update our View's Element.
First note the usage of JavaScript's 'this' keyword,
which is called Context and refers to the object, whose Method we are executing.
Since 'render()' is our View's method, similar to the above Method 'itemTemplate()',
its Context 'this' is the View itself.
Note that here we are still defining here Constructor
(from which our Views will inherit), not a View itself.
That means, 'this' will be set at the execution time
to be the current View at that time.
For each Item (and its Backbone Model) we shall create its own View responsible
to display that Model and to process its Events.
For each View 'myView', we shall store the corresponding Model as 'myView.model'.
This way every View "knows" where its Model is.
However, it is a good practice to keep the Model "in the dark" about its View,
i.e. not to put any referce on the Model to its View.
The reason is that Model's are considered bare data
that may be accessed by several Views.
The data should not "know" anything about their presentation,
which is what View's business is.
Thus 'this.model' will refer to the Model corresponding to the current View 'this'.
Next we use Backbone's Method 'toJSON()' available for every Model,
which is somewhat confusing (see below) and,
unfortunately, not very well explained in Backbone's Documentation.
For better understanding, let us play with Backbone Models:
Practice. In your Console create a Model:
person = new Backbone.Model()
Then set some attributes:
person.set('name', 'Bob')
person.set('age', 101)
Now inspect properties of 'person'.
You see the reference 'person.attributes' that shows the attributes we just set.
Note that the attributes are not set on 'person' directly.
That is, 'person.name' and 'person.age' are undefined.
This is to keep them separate from Backbone's own properties
that are available for every model.
For instance, we are going to use 'toJSON()' as method of our Model,
which would be lost if we set it to something else.
That is why one should avoid setting properties directly
on Models (or Collections, Views etc)
but use the 'set()' Method above.
Note that one could use 'person.attributes.age = 101' instead,
which is not recommended however,
as one e.g. can accidentally overwrite the whole 'person.attributes' hash that way.
To get the value of an attribute, we use another Method,
unsurprisingly called 'get()'.
Practice. In your Console:
person.get('name')
person.get('age')
Now let us use toJSON():
person.toJSON()
You should get an Object with keys 'name' and 'age'
and corresponding values 'Bob' and 101.
According to Backbone's Documentation, 'model.toJSON()'
returns a copy of model's attributes.
It is however not a JSON (JavaScript Object Notation) string,
which is a way to represent Objects in a string (i.e. to serialize them).
(Note there is also native JavaScript global object 'JSON'
containing Methods 'strigify()' and 'parse()'.)
To get a JSON string representing Model's attributes, you can use
JSON.stringify(person.attributes) or JSON.stringify(person.toJSON()),
however, the best and simplest way is
JSON.stringify(person).
What happens here, is the native JavaScript function 'JSON.stringify()' checks
whether its argument (in our case 'person')
has Method 'toJSON()' defined for it and executes it if yes.
Since 'person' is a Backbone Model, it is provided with 'toJSON()' Method.
*/
/* We don't need however to serialize our Model's attributes.
Instead we pass the attribute Object as parameter to
our previously defined Function 'this.itemTemplate()', discussed above,
which returns the compiled HTML Code obtained from our Template.
Finally, set the HTML obtained as the content of the View Element 'this.el'.
This is done with jQuery.
For our convenience, Backbone caches the needed jQuery Object as 'this.$el'.
It only remains to set the HTML content via the Method 'html()',
which is available for any jQuery Object.
Practice. Assuming you set 'myView = new ItemView()' in your Console,
compile its Template with your favorite HTML string, e.g.
myHtml = myView.itemTemplate({content: "<h3>HA!</h3>"}).
Then use jQuery to replace the content of any HTML Element with 'myHtml', e.g.
$('h1').html(myHtml).
Alternatively one could use the verbose JavaScript native functions
'getElementsByTagName' and 'innerHTML':
document.getElementsByTagName('h1')[0].innerHTML = myHtml.
*/
/* It is recommended to end the definition of 'render()'
by returning the Context, i.e. the View.
This way we can use chain constructions like 'myView.render().el' etc. */
return this; // returns the current view for chaining
}
/* Practice. Let us test the defined 'render()' Method.
We need to pass to 'myView' a model with 'content' attribute:
myView.model = new Backbone.Model({content: "HA!"}).
Execute 'myView.render()'. It should output our View with its 'el' property reset.
Inspect 'myView.el.innerHTML' */
}); // end of ItemView
/* Everything we did so far was related to a single Item.
However, we need to generate the list of all Items.
For this we define another View extension: */
// Again remove 'var' to access it for practice
/* var */ CollectionView = Backbone.View.extend({
/* This time we shall only create one instance of this View.
Therefore it will be convenient to set its Element directly
by passing its CSS Selector to 'el': */
el: '#todoapp',
/* Practice. In your Console instantiate another copy of CollectionView:
cv1 = new CollectionView
Its Element is automatically attached to DOM. Inspect this:
cv1.el.innerHTML
*/
/* We next cache (save) our Input and Output Elements (wrapped as jQuery Objects).
This way we don't need to run jQuery's search for this Element again,
which has considerable performance advantage.
*/
// caching jQuery Object for the Input form
input: $('#new-todo'),
// caching jQuery Object for the Item list's 'ol' Element
output: $('#todo-list'),
/* Practice. In your Console check these properties for the View 'cv1' defined above
and for 'cv' set by the Code:
cv.input
cv.output
*/
/* We need to watch for another Event, namely for User to press RETURN.
This is taken care by the 'events' Property, as discussed above. */
events: {
// binding 'press key' event with 'createOnEnter' Method
'keypress #new-todo': 'createOnEnter',
},
// when executing here, last pressed character is passed as 'character'
createOnEnter: function(key) {
/* Note that we put 'key' as our Function's argument.
This is because the 'keypress' Event passess the key pressed
as argument to its Event handling Function */
// breaking execution unless RETURN was pressed
if (key.keyCode != RETURN_KEYCODE) return;
/* We use jQuery's 'val()' Function to retrieve the current value of our 'input' field
and check it for being empty */
// breaking execution if the <input> field is empty
if (!this.input.val()) return;
/* Now we know that RETURN was pressed and 'val()' is not an empty string.
This means we want to register a new Item, i.e. to create a new Model.
It is conveniently done by using Backbone's 'add()' method
provided for every Backbone Collection.
Here we use it to add a new Model with attribute 'content'
set to the value of our input field.
*/
// creating new Item in itemCollection
itemCollection.add({content: this.input.val()});
/* Practice. Try to add a new Model manually in your Console:
itemCollection.add({content: 'HAHAHA!!!'})
The Document should be automatically updated showing the new Model.
This is because of the Event Listener defined below.
*/
/* Note that here, for simplicity,
we insert whatever the User entered in the 'input' field
directly into our Model without any validation.
For instance, try to enter '<script>' as new Item to see the Error in your Console.
Needless to say, one should never accept any data
without validation in real life Applications.
E.g. if Item name should only contain letters and numbers,
one can remove all other characters using Regular Expressions:
this.input.val().replace(/\W+/g,'')
*/
/* Once the new Item is Entered, we want to clear the 'input' field for new Items: */
this.input.val('');
}, // end of 'createOnEnter'
/* For every Backbone Model, Collection, and View, we can define
'initialize()' Method, which is automatically executed each time the Model,
Collection or View is instantiated (created).
In case of CollectionView that we are defining now,
this Function is only executed once at the beginning.*/
initialize: function() { // runs upon instantiating the view
/* For User's convenience, we set focus to our 'input' Element using jQuery: */
this.input.focus(); // jquery focus method to set focus
/* Now comes one of the most important Backbone's features --
refreshing the View upon Collection changes.
This is done using Backbone's new Event Listener Method 'listenTo()',
which is available for every Backbone Model, Collection, or View.
Here our View 'this' listens to our main Collection 'itemCollection',
more specifically to its 'add' Event.
The latter is triggered whenever a Model is added to it.
When this happens, 'this.addOne' Function is executed.
What is the last 'this' for? It is the Context of 'addOne()'.
That means, if 'addOne()' executes as result of this Event Listener,
its 'this' Object will be the same as 'this' for the current View,
i.e. the View itself.
(In old Backbone tutorials one can see
Underscore's utility '_.bindAll' used for this purpose.
It is not needed here anymore, since 'this' can be simply passed as 4th argument.)
Note that 'listenTo()' is only defined in Backbone 0.9.9 and above.
*/
this.listenTo(itemCollection, 'add', this.addOne, this);
}, // end of 'initialize'
/* Finally we need to define 'addOne()'.
Since it is triggered by the 'add' Backbone Built-in Event,
it is passed the arguments 'model', 'collection', 'options'
(see Backbone's Documentation)
Here we only use the first argument and call it 'item'.
Thus, inside our Function, 'item' points to the Model added to 'itemCollection'. */
/* adding single Item by creating itemView for it,
and appending its element to the `<ul>` */
addOne: function(item) {
/* We have just added new Model 'item' representing our Item.
However, our Document has not been updated as of yet.
In order to show the new Item,
we create a new View using the constructor 'ItemView' defined before,
to which we pass our new Model as 'model' property.
This 'model' will then be used inside 'itemTemplate' */
// create View for the new Model
var itemView = new ItemView({model: item});
/* The View is created but we still need to run its 'render()' Method
and to add its 'el' to the Document.
Remember that our 'render()' returns the View itself?
That feature is used now, where we "chain" 'el' after applying 'render()'
and finally place 'el' at the top of our cached 'output' Element.
Instead we could run 'itemView.render()' and then
use 'itemView.el' in another Code line but chaining saved us one line. */
// placing the View Element with jQuery at the top of the 'output' Element
this.output.prepend(itemView.render().el);
/* Instead of 'prepend()' we could use jQuery's 'append()'
to place the new Element at the bottom.
Practice. In your Console use the view 'cv' defined
by the Code to prepend/append your favorite HTML strings:
cv.output.prepend('<h1>Ha</h1>')
cv.output.append('<h1>Ha</h1>')
*/
} // end of 'addOne'
}); // end of 'CollectionView'
/* It remains to start the App by instantiating a CollectionView,
which is saved as 'cv' to make it accessible at your Console for practicing: */
cv = new CollectionView(); //
}); // end of '$(function(){})'
</script>
</head>
<body>
<h1>Item Management with <a href="http://backbonejs.org/">Backbone</a>
</h1>
<h2>Hacked from the <a href="http://jgn.me/">Jerome Gravel-Niquet's</a>
<a href="http://documentcloud.github.com/backbone/docs/todos.html">
Backbone todo application</a>
</h2>
<p>
It this App is 'dead'/unresponsive, likely you did not put the files
<a href="http://code.jquery.com/jquery.js">jquery.js</a>,
<a href="http://underscorejs.org/underscore.js">underscore.js</a>,
<a href="http://backbonejs.org/backbone.js">backbone.js</a> and
<a href="http://www.maths.tcd.ie/~zaitsev/app/destroy.png">destroy.png</a>
in the same directory.
</p>
<!-- The main App container -->
<div id='todoapp'>
<!-- Input field for entering Item name -->
<input id='new-todo' placeholder='New Entry?' type='text' />
<!-- Ordered List Container, where Items will show up -->
<ol id='todo-list'></ol>
</div>
<!-- To display each Item, we need a piece of HTML code.
This code is the same except for the name of the Item,
which we store in the variable 'content'.
A convenient way of handling this is to use a Template.
We use the Underscore.js template engine.
According to its syntax, we wrap our variable 'content' as <%= content %>,
so the template engine will evaluate it
and replace that piece with the value of 'content'.
Alternatively we could use <%- content %> (or <%-content%>)
to escape the value of 'content'.
One can also write any piece of JavaScript bewteen <% and %> that will be executed.
See the Documentation of Underscore.js for more details -->
<!-- It is convenient to place our template
inside a 'script' tag with attribute type='text/template',
to make sure it won't be executed by JavaScript.
Placing templates at the end of the 'body'
will not block loading any preceeding Element,
but it will still be available for our JavaScript Code enclosed
with the '$(document).ready()' utility, see above -->
<!-- Item View Template, will be compiled with '<%= content %>'
replaced by the value of variable 'content' -->
<script type='text/template' id='item-template'>
<div class='todo-content'><%= content %></div>
<span class='todo-destroy'></span>
</script>
</body>
</html>
@akashkakkar
Copy link

Got this error when tried the example
"Uncaught SyntaxError: Unexpected identifier " backbone-app-tutorial.html:362

@SeaBassTian
Copy link

Okay, this looks great. Had to test it out before I got involved. I discovered that there is a comma missing from line 352 after the closing parenthesis. Also you have to specify which version of Jquery your'e using!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment