Backbone.js Tutorial - Getting Started with Backbone

Backbone.js Tutorial: Getting Started with Backbone www.korenlc.com /backbone-js-tutorial-getting-started-with-backbone/ Koren Leslie Cohen Backbone.j...

8 downloads 285 Views 438KB Size

Backbone.js Tutorial: Getting Started with Backbone www.korenlc.com /backbone-js-tutorial-getting-started-with-backbone/ Koren Leslie Cohen

Backbone.js is a lightweight framework designed to give “structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connect[ions to] existing API[s] over a RESTful JSON interface.” Basically, it helps organize your spaghetti. Unlike a traditional MVC which has three components, Backbone has six main components: (1) Models – store data; (2) Views – display model data; (3) Collections – group models, like a JavaScript array, (4) Events – allow custom events; (5) Routers – create navigation; and (6) Sync – maps Backbone to server side databases. Because of this, Backbone is sometimes referred to as an MV* framework. Technically, Backbone is a library and, because of this, it is highly customizable. Its main goal is to generate modular code, meaning it breaks down your code into modules which are independent but work together. If at any point in this tutorial you want further clarification, I suggest referring to the Backbone documentation or the complete code for this tutorial on GitHub. There is also a helpful book Developing Backbone.js Applications available online. If you’d like additional information on the benefits of Backbone, read this. A final note: In a standard application, you would probably want to break your code into several .js files and save them in the appropriate directories for models, views, collections and routers. However, for the purpose of this tutorial, I have included everything in application.js (and later in namespaced-application.js) so you can easily follow along. Installing Backbone.js

Getting started with Backbone.js is fairly straightforward. Backbone relies on three JavaScript libraries: (1) Backbone, (2) Underscore and (3) jQuery. To get started with Backbone, simply download these three libraries (minified versions are available) and add the scripts to your index.html file as follows: Backbone.js

1 2 3 4 5 6 7 8 9 10 11 12

<meta charset="UTF-8"> Backbone.js Tutorial <script src="js/jQuery.js"> <script src="js/underscore.js"> <script src="js/backbone.js">

In the alternative, you can reference these libraries as follows: Backbone.js 1 2 3 4 5 6 7 8 9 10 11 12

<meta charset="UTF-8"> Backbone.js Tutorial <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> <script src="http://documentcloud.github.com/underscore/underscore-min.js"> <script src="http://documentcloud.github.com/backbone/backbone-min.js">

Either way, you want to make sure the files are loaded in the following order: (1) jQuery, (2) Underscore, (3) Backbone. Otherwise, your application may not function as expected. To make sure Backbone is functioning properly, load the index.html page in Chrome, open the console, and begin typing Backbone. If Backbone is working, this should autocomplete and you should have access to the following:

Models

According to the Backbone documentation, “Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.” In a standard JavaScript file, you may create an Animal class as follows: Objects & Prototypes JavaScript 1 2 3 4 5 6 7 8 9 10 11

var Animal = function(){ this.name = name; this.color = color; this.sound = sound; }; Animal.prototype.sleep = function(){ alert(this.name + ' is sleeping.'); }; var dog = new Animal({name: 'Fido', color: 'black', sound: 'woof'});

In Backbone, your model would look slightly different: Backbone Model

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

var Animal = Backbone.Model.extend({ defaults: { name: 'Fido', color: 'black', sound: 'woof' }, validate: function(attrs, options){ if (!attrs.name){ alert('Your animal must have a name!'); } if (attrs.name.length < 2){ alert('Your animal\'s name must have more than one letter!'); } }, sleep: function(){ alert(this.get('name') + ' is sleeping.'); } });

The above code defines the Animal model and sets default values. This code will go in your application.js file (you may name this file whatever you prefer, but make sure to reference the file in index.html). In addition to defining a class and setting default values, the above model sets a validate function and a sleep function (more on validate later). Now, if you refresh index.html in your browser, you should be able to create a new Animal with default values:

Accessing attributes is slightly different in Backbone. Instead of simply calling dog.name, you must use the .get function: dog.get('name'). Likewise, you would use .set to set attributes: dog.set('name', 'Biscuit) or: dog.set({name: 'Biscuit', color: 'brown', sound: 'arf'}) or var dog = new Animal({name: 'Biscuit', color: 'brown', sound: 'arf'}). You can easily access all attributes of an object by calling .toJSON as follows:

It’s worthwhile to review the validate section of the Backbone documentation. A few things to remember: (1) validate is triggered only by .set or .save, and (2) if you want validate to be triggered with .set , you need to include {validate: true}: dog.set('name','a',{validate: true}) For more information on specific error handling, see the Backbone documentation re validate and validationError. Views

Backbone views aren’t traditional views like you’d see in a framework such as Rails. In fact, they don’t determine anything about your HTML or CSS. According to the Backbone documentation, “[t]he general idea is to organize your interface into logical views, backed by models, each of which can be updated independently when the model changes, without having to redraw the page. Instead of digging into a JSON object, looking up an element in the DOM, and updating the HTML by hand, you can bind your view’s render function to the model’s "change" event — and now everywhere that model data is displayed in the UI, it is always immediately up to date.” To create an Animal view, you’ll want to first create a custom view class and decide how an Animal will be represented. You may want to override the render function, and specify declarative events, tagName (the HTML element tag), className or id. The default tagName is div, so if you wanted your Animal to be represented as an li, you could add the following to application.js: Backbone Views 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

var AnimalView = Backbone.View.extend({ tagName: 'li', // defaults to div if not specified className: 'animal', // optional, can also set multiple like 'animal dog' id: 'dogs', // also optional events: { 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, initialize: function() { this.render(); // render is an optional function that defines the logic for rendering a template }, render: function() { this.$el.html(this.model.get('name') + ' is ' + this.model.get('color') + ' and says ' + this.model.get('sound')); } });

The initialize function watches for changes in a model and executes code as soon as a new model instance is created. As part of the initialize function, you could include a simple console notification which would let you know when a model has been changed (such as through the use of .set): Backbone Change

1 2 3 4 5

initialize: function() { this.on('change', function(){ console.log('Something has changed'); }); },

The above code watches for all changes. However, if you modified 'change' to 'change:color', it would only watch for color changes. If you head to your console now, you should be able to create a view and call .el and .$el (jQuery) on that view, which will show you the view for that object:

el is a reference to a DOM element. All views must have el and use el to compose their element’s content and insert it into the DOM all at once, making it faster for the browser to render. Upon completion of the above, the following HTML should be rendered by the browser: Although this works, maintaining this code will be difficult once your views are more complex. To help with this, you may want to render a template. Templates

Defining a template function on your views can be helpful when rendering your view by allowing convenient access to instance data. Templates are not a function provided directly by Backbone, but you can define a template using Underscore. Although Underscore is a Backbone dependency, you may want to use a templating engine such as Mustache or Handlebars.js. (I wrote a separate Handlebars.js Tutorial that may be helpful.) There are internal templates and external templates, and each is as it sounds. Template syntax may look like the following: Backbone Template 1 2 3

var AnimalView = Backbone.View.extend({ template: _.template(...) });

Using the above example, your application.js view code will look like this once you include an inline template:

Backbone Template 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

var AnimalView = Backbone.View.extend({ tagName: 'li', // defaults to div if not specified className: 'animal', // optional, can also set multiple like 'animal dog' id: 'dogs', // also optional events: { 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, newTemplate: _.template('<%= name %> is <%= color %> and says <%= sound %>'), // inline template initialize: function() { this.render(); // render is an optional function that defines the logic for rendering a template }, render: function() { // the below line represents the code prior to adding the template // this.$el.html(this.model.get('name') + ' is ' + this.model.get('color') + ' and says ' + this.model.get('sound')); this.$el.html(this.newTemplate(this.model.toJSON())); // calls the template } });

If you check your console, you will see the same code was rendered from the inline template that was previously included in the render function:

You can also use external templates directly in your HTML: Backbone Template

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

<meta charset="UTF-8"> Backbone.js Tutorial <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> <script src="http://documentcloud.github.com/underscore/underscore-min.js"> <script src="http://documentcloud.github.com/backbone/backbone-min.js"> <script id="dogTemplate" type="text/template"> <%= name %> is <%= color %> and says <%= sound %> <script src="js/application.js">

Once you include the external template, your application.js view code should look like this: Backbone Template 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

var AnimalView = Backbone.View.extend({ tagName: 'li', // defaults to div if not specified className: 'animal', // optional, can also set multiple like 'animal dog' id: 'dogs', // also optional events: { 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, // newTemplate: _.template('<%= name %> is <%= color %> and says <%= sound %>'), // inline template newTemplate: _.template($('#dogTemplate').html()), // external template initialize: function() { this.render(); // render is an optional function that defines the logic for rendering a template }, render: function() { // the below line represents the code prior to adding the template // this.$el.html(this.model.get('name') + ' is ' + this.model.get('color') + ' and says ' + this.model.get('sound')); this.$el.html(this.newTemplate(this.model.toJSON())); // calls the template } });

If you head to your console, you should be able to render the same HTML from the external template that you have done twice so far:

Collections

Collections in Backbone are ordered sets of models. You can bind "change" events so you’re notified when any model in the collection has been modified, listen for "add" and "remove" events, fetch the collection from the server and access various Underscore methods. Events triggered on a model will also be triggered on the collection, allowing you to listen for changes to the attributes of any model in a collection. Collections are used when you want to define multiple instances (i.e., instead of defining 10 dogs or 10 animals individually, you can use a collection). The following code can be added to your application.js file: Backbone Collection 1 2 3

var AnimalCollection = Backbone.Collection.extend({ model: Animal });

New models can be added to AnimalCollection individually or as a group. To add models individually, you can add the following to application.js: Backbone Collection 1 2 3 4 5 6 7 8 9

// adding individual models to collection var chihuahua = new Animal({name: 'Sugar', color: 'black', sound: 'woof'}); var chihuahuaView = new AnimalView({model: chihuahua}); var animalCollection = new AnimalCollection(); // only need to create the collection once animalCollection.add(chihuahua); var pug = new Animal({name: 'Gizmo', color: 'tan', sound: 'woof'}); var pugView = new AnimalView({model: pug}); animalCollection.add(pug); // can now directly add to animalCollection

If you go to your console, you should see your entire collection:

Adding models individually isn’t very efficient though. If you’d like to add multiple models, you can do so by adding the following to application.js: Backbone Collection 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// adding multiple models to collection var animalCollection = new AnimalCollection([ { name: 'Sugar', color: 'black', sound: 'woof' }, { name: 'Gizmo', color: 'tan', sound: 'woof' }, { name: 'Biscuit', color: 'brown', sound: 'arf' } ]);

As you can see in your console, your collection now contains the above three models:

Collection Views

Thus far, we have AnimalView for individual Animal models, but we don’t have a view for AnimalCollection that compiles all Animal model views. To create a collection view, add the following to your application.js file: Backbone Collection View 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// View for all animals (collection) var AnimalsView = Backbone.View.extend({ // calling this AnimalsView to distinguish as the view for the collection tagName: 'ul', initialize: function(){ this.collection; }, render: function(){ this.collection.each(function(Animal){ var animalView = new AnimalView({model: Animal}); $(document.body).append(animalView.el); }); } }); // creates view for collection and renders collection var animalsView = new AnimalsView({collection: animalCollection}); animalsView.render();

If you refresh your browser now, the entire collection you added above will be rendered: Namespacing

Namespacing helps avoid the potential collision of global variables (declared with var), thereby reducing the likelihood that your code may break. I have prepared a separate JavaScript file, namespaced-application.js, which is now referenced in index.html instead of application.js (and included on GitHub for your reference). Your full namespaced JavaScript file should look like this, while still producing the same output as above: Backbone Namespace 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// defines the namespace window.Tutorial = { // top level namespace is declared on the window Models: {}, Collections: {}, Views: {} }; // Animal model Tutorial.Models.Animal = Backbone.Model.extend({ defaults: { name: 'Fido', color: 'black', sound: 'woof' }, validate: function(attrs, options){ if ( !attrs.name ){ alert('Your animal must have a name!'); }

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

if ( attrs.name.length < 2 ){ alert('Your animal\'s name must have more than one letter!'); } }, sleep: function(){ alert(this.get('name') + ' is sleeping.'); } }); // Animal view Tutorial.Views.Animal = Backbone.View.extend({ tagName: 'li', // defaults to div if not specified className: 'animal', // optional, can also set multiple like 'animal dog' id: 'dogs', // also optional events: { 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, // newTemplate: _.template('<%= name %> is <%= color %> and says <%= sound %>'), // inline template newTemplate: _.template($('#dogTemplate').html()), // external template initialize: function() { this.render(); // render is an optional function that defines the logic for rendering a template }, render: function() { // the below line represents the code prior to adding the template // this.$el.html(this.model.get('name') + ' is ' + this.model.get('color') + ' and says ' + this.model.get('sound')); this.$el.html(this.newTemplate(this.model.toJSON())); // calls the template } }); // Animal collection Tutorial.Collections.Animal = Backbone.Collection.extend({ model: Tutorial.Models.Animal }); // adding individual models to collection var chihuahua = new Tutorial.Models.Animal({name: 'Sugar', color: 'black', sound: 'woof'}); var chihuahuaView = new Tutorial.Views.Animal({model: chihuahua}); var animalCollection = new Tutorial.Collections.Animal(); // only need to create the collection once animalCollection.add(chihuahua); var pug = new Tutorial.Models.Animal({name: 'Gizmo', color: 'tan', sound: 'woof'}); var pugView = new Tutorial.Views.Animal({model: pug}); animalCollection.add(pug); // can now directly add to animalCollection // adding multiple models to collection (this will override the above Tutorial.Collections.Animal) var animalCollection = new Tutorial.Collections.Animal([ { name: 'Sugar', color: 'black', sound: 'woof' }, { name: 'Gizmo', color: 'tan', sound: 'woof' },

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

{ name: 'Biscuit', color: 'brown', sound: 'arf' } ]); // View for all animals (collection) Tutorial.Views.Animals = Backbone.View.extend({ // plural to distinguish as the view for the collection tagName: 'ul', initialize: function(){ this.collection; }, render: function(){ this.collection.each(function(Animal){ var animalView = new Tutorial.Views.Animal({model: Animal}); $(document.body).append(animalView.el); }); } }); // creates view for collection and renders collection var animalsView = new Tutorial.Views.Animals({collection: animalCollection}); animalsView.render();

For more information, see the namespacing chapter in Developing Backbone.js Applications. Events

As you recall, three .click events were included in your Animal view (see above). These events can now be defined. If you start with the first event, alertTest, you should be able to test your click functionality by clicking on each model in the view after adding the following code: Backbone Events 1 2 3 4 5 6 7 8

events: { 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, alertTest: function(){ alert('Backbone click event works'); },

You can now add buttons or bind the click event to some other HTML element with the .edit and .delete classes. Prior to doing so, you want to comment out the alertTest event and function above, otherwise when you click to edit or delete, you’ll continue to receive the test alert. The external template in index.html will now contain two buttons: Backbone Buttons

1 2 3 4 5 6

<script id="dogTemplate" type="text/template"> <%= name %> is <%= color %> and says <%= sound %>

Your browser should render the following: Now, you have to include the editAnimal and deleteAnimal functions, as well as the modify the initialize function and add any helper functions, such as remove: Backbone DOM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

// Animal view Tutorial.Views.Animal = Backbone.View.extend({ tagName: 'li', // defaults to div if not specified className: 'animal', // optional, can also set multiple like 'animal dog' id: 'dogs', // also optional events: { // 'click': 'alertTest', 'click .edit': 'editAnimal', 'click .delete': 'deleteAnimal' }, // alertTest: function(){ // alert('Backbone click event works'); // }, editAnimal: function(){ var newAnimal = prompt("New animal name:", this.model.get('name')); // prompts for new name if (!newAnimal)return; // no change if user hits cancel this.model.set('name', newAnimal); // sets new name to model }, deleteAnimal: function(){ this.model.destroy(); // deletes the model when delete button clicked }, // newTemplate: _.template('<%= name %> is <%= color %> and says <%= sound %>'), // inline template newTemplate: _.template($('#dogTemplate').html()), // external template initialize: function(){ this.render(); // render is an optional function that defines the logic for rendering a template this.model.on('change', this.render, this); // calls render function once name changed this.model.on('destroy', this.remove, this); // calls remove function once model deleted }, remove: function(){ this.$el.remove(); // removes the HTML element from view when delete button clicked/model deleted }, render: function(){ // the below line represents the code prior to adding the template // this.$el.html(this.model.get('name') + ' is ' + this.model.get('color') + ' and says ' + this.model.get('sound')); this.$el.html(this.newTemplate(this.model.toJSON())); // calls the template } });

Some of this code is best understood by reviewing the events section of the Backbone documentation, which

explains things like 'change' and 'destroy'. You could also create a simple form for adding new Animal models. Routing

Backbone routing is useful when dealing with hashtags (typically routed as anchors on a page). Routers interpret anything after a #, so the links in your application should contain a # (i.e., http://tutorial.com/#backbone). Backbone uses two routing styles – :params (match specific URL components, i.e. http://tutorial.com/:route) and splats (match a variety of URL components, i.e. http://tutorial.com/*default). If you’re a Rails developer, this should seem familiar. However, the routing system in Backbone probably closer resembles Sinatra than Rails. First, add the Router namespace: Backbone Router 1 2 3 4 5 6 7

// defines the namespace window.Tutorial = { // top level namespace is declared on the window Models: {}, Collections: {}, Views: {}, Router: {} };

Next, define your routes with the Backbone router (typical applications will only include one router, and would probably be included in a separate routes.js file), and start your Backbone history: Backbone Router 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

// Backbone router Tutorial.Router = Backbone.Router.extend({ routes: { // sets the routes '': 'index', // http://tutorial.com 'edit/:id': 'edit' // http://tutorial.com/#edit/7 }, // the same as we did for click events, we now define function for each route index: function(){ console.log('index route'); }, edit: function(id){ console.log('edit route with id: ' + id); } }); var newRouter = new Tutorial.Router; Backbone.history.start(); // start Backbone history

If you enter the above routes and watch your console, you should see the following:

Backbone.history handles hashchange events or pushState in your application. For a better idea of how Backbone routes are used in an actual application, and why they’re important, check out this helpful Code Project article. For additional information, it may be worthwhile to read the Backbone documentation on routing and history, or this Backbone tutorial on routing.

Koren Leslie Cohen Product manager at Dollar Shave Club in Los Angeles; former engineer at J.Crew / Madewell in New York City; recovering trial lawyer.

Subscribe

© Copyright 2013 - 2019 AZDOC.PL All rights reserved.