-
-
Save dmitriz/3853693 to your computer and use it in GitHub Desktop.
<!-- | |
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> |
Hi there! Could you talk me through the differences between the original implementation (or say our TodoMVC implementation) and your version? From what I can tell, you're opting for more readable variable names with a greater level of commenting.
Hi Addy!
I am very sorry I've noticed only now your request. Somehow I could not see how to activate email notifications for these comments.
The main difference of this version is that it has very short code in all parts: html, css and js.
Surely, at the price of sacrificing the look and functionality, but it is still a working app!
I've chosen only two basic functions: enter todo and delete it,
and left only the code responsible for them.
The html/css is also reduced to very minimum with explanation.
I've thought this might be helpful for a beginner to get started,
and then move on to look at the more complete but longer code in other versions.
The goal is to build a working app with minimum possible code starting from scratch.
The App can be enjoyed in http://www.maths.tcd.ie/~zaitsev/app/html/todo-backbone-minimized.html and the source found in http://www.maths.tcd.ie/~zaitsev/app/
Learning Backbone.js I found frustratingly few simple examples of true workable applications with all lines of code easily understood. Here is my attempt to hack the popular Jerome Gravel-Niquet's Backbone Todo App down to a MVA (Minimum Workable App).