Building Backbone js apps with Backlift: Full Tutorial

By Cole Krumbholz

This is a beginning Backbone.js tutorial that will cover:

  • Setting up a bare-bones Backbone.js website
  • Creating a Backbone.js View, and
  • Using Handlebars templates to render dynamic content

During the tutorial we'll build a simple app that displays a randomly generated poem. You won't need any prior knowledge of Backbone.js to complete the exercises, but you should be familiar with Javascript, HTML and CSS, and know a bit about jQuery. We'll be using Backlift.com to host the app so you won't need to know a server framework like Rails, Django or Node.

This is the first tutorial of a series that covers the basics of Backbone.js, including Views, Models, Routers and working with a back-end. In the future we'll post follow up tutorials that discuss how to build a more complex Backbone.js app: a social photo gallery inspired by Pinterest.

If any questions about Backlift come up while you're working through this tutorial, please check out the Backlift docs.

Why Backbone?

This tutorial is meant to be a hands-on lab, not a lecture. So rather than getting into a lengthy discussion about the merits of Backbone.js, I'll simply provide a few links.

This Quora discussion contains a few good reasons for using Backbone.js. Also the answer to this Stack Overflow question provides a concise explanation of Backbone and MVC, though it's a touch out of date.

For more in-depth discussion about Javascript MV* check out this Smashing Magazine article. It provides a decent introduction to Javascript MVC and offers a comparative analysis of the different frameworks.

Once you're convinced that Backbone.js is worth learning, this tutorial is a good place to start.

Why Backlift?

Many tutorials assume that you'll be working with a server framework like Ruby on Rails or Node.js. These back-end frameworks require an entirely separate knowledge set. If you're a student or designer that is approaching web development from the front-end, you might want to tackle these concepts one at a time. Even if you're a full-stack developer who's familiar with a back-end stack, you may not want to install a different stack just to play around with Backbone.js.

Backlift is a back-end service that's made to work "out of the box" with Javascript MV* libraries like Backbone.js. It handles the grunt work required to set up a server and exposes an API for data persistence, validation and user authentication. Also, Backlift integrates with Dropbox and compiles your assets on the back-end. This means you can quickly launch a Backbone.js website and start playing around without installing anything on your computer. (Other than Dropbox of course)

If you're still unsure about learning Backbone with Backlift, just try the next two steps that demonstrate how easily you can get a Backbone.js website up and running.

Enough preamble, let's get started!

The setup

We'll start with a bare-bones project that contains Backbone.js and its dependencies. Backlift makes this part easy, just click the link below to download the project into your Dropbox. The ony prerequisite is that you have a Dropbox account and have the client installed on your computer.

When you click the "create" button, Backlift will begin downloading the project into your Dropbox folder. Once Backlift has finished creating the app, you can click the "view app" link to visit the app's public URL.

You have just published a Backbone.js website, congratulations!

Modifying your website with Backlift and Dropbox

Let's make a change to ensure that Backlift is working correctly, and to demonstrate the Backlift-Dropbox workflow. Navigate to your new app's folder within your Dropbox. It should be located in your Dropbox folder under Dropbox/Apps/Backlift/barebones. Once you've found it, open up the index.html file with a text editor and find the line that says:

<h1> Hello World! </h1>

Now replace it with:

<p>
  I postpone death by living, <br>
  by suffering, by error, by risking, <br> 
  by giving, by losing.
</p>
<small>Anais Nin</small>

Once you save the file, if the barebones website is still open in your browser, it should refresh in a few seconds. You don't need to tab over to refresh the page manually, it will update itself as soon as Backlift detects the changes.

Anatomy of the barebones Backbone.js app

Let's take a look at the other files in the project we just downloaded. The barebones folder in your Dropbox should contain the following files:

.
├── README.md
├── config.yml
├── index.html
├── libraries
│   ├── backbone-0.9.10.js
│   ├── backlift-reloader.js
│   ├── jquery-1.8.3.js
│   └── underscore-1.4.4.js
└── style.css

All the Backbone.js specific files are contained in the libraries folder. Backbone depends on jQuery and Underscore, so those scripts have been included along with backbone.js itself.

Two of the files are specific to Backlift. The backlift-reloader.js file is responsible for refreshing the website automatically when signaled by Backlift. The other Backlift specific file is config.yml which we will discuss in a moment. Otherwise this project earns its name: there's really not much to it.

Linking scripts and stylesheets

Backlift can help keep track of your scripts and stylesheets during development. If you take a look at the index.html you won't see any <link> or <script> tags. Instead, javascript and css files are linked automatically via the and symbols. These are server template variables-- their contents will be filled in by Backlift when the page is rendered.

<!-- /index.html -->    

<!DOCTYPE html>
<html>
  <head>
    
    
  </head>
  <body>

    ... 

  </body>
</html>

The contents of the server variables are determined by the scripts and styles properties within the config.yml file. A tag for each file matching one of the patterns will be added, in order, to index.html.

scripts:
- /libraries/backlift-reloader.js
- /libraries/jquery*.js
- /libraries/underscore*.js
- /libraries/backbone*.js
- /**/*.js

The order of the list is important because it ensures that Backbone's dependencies will be loaded first. Note that you can use a single wildcard * to match a part of a filename, and a double wildcard ** to match any path.

You can also add external URLs to scripts or styles list like so:

scripts:        
- //cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js
- /libraries/underscore*.js
...

You can prefix the resource with the protocol (http:// or https://), or use a double slash (//) which will match the protocol of your website. All Backlift websites are served via HTTP over SSL (using the https:// prefix).

Adding a Backbone View

We've got Backbone.js setup so let's put it to use. We'll start by rendering our poem using a Backbone View.

Backbone Views are a way to separate presentation logic from your application's "business logic." This separation of concerns makes it easier to modify how your website looks without effecting how it works. Backbone Views also offer shortcuts for creating dynamic user interfaces. For example the Backbone.View events property provides a convenient way to trigger behaviors as a result of user actions, like mouse click events. See Backbone.View.delegateEvents.

To add a View, create a new file called main.js at the root of the project folder, and add this code to define a PoemView:

// main.js

var poem = "<p>"+
  "  I postpone death by living, <br>"+
  "  by suffering, by error, by risking, <br>"+
  "  by giving, by losing."+
  "</p>"+
  "<small>Anais Nin</small>"

// Create the poem view class
var PoemView = Backbone.View.extend({
  render: function() {
    this.$el.html(poem);
    return this;
  }
});

// Add a poem view to the DOM
$(function() {
  var poemView = new PoemView();
  $("body").append(poemView.render().el);  
});

In the code above we've created a PoemView class that inherits from Backbone.View. PoemView has a single method, render(), that generates the HTML for our Anais Nin poem.

Every Backbone View has an el property that refers to the View's node in the DOM. It's the responsibility of the render() method to set the el property's html, thus filling in the View's content.

Note: $el and el both refer to the DOM node, but $el refers to the node wrapped in a jQuery object, and thus has access to jQuery methods such as html()

In the PoemView.render() function above, we set el to a string variable, poem that contains the HTML. If this were a real application, we'd probably want to create the HTML from a template using data from a database or API. We'll get to that later.

At the bottom of main.js we define an anonymous function that will be invoked when the page is finished loading. This function creates an instance of the PoemView class and uses jQuery to add it to the body of the page. Because render() returns this we're able to render the poem and get the resulting DOM node at once with poemView.render().el. This is a common Backbone convention.

Note: It's convenient for me to talk about View "classes" and "instances," because these concepts from classical object-oriented programming describe how Backbone Views are commonly used. However, Javascript is not a classical object oriented language, and the analogy doesn't describe how inheritance is implemented in Javascript. For more information about the Javascript inheritance model, check out this article.

Since we're using Backbone to render our view, we can now remove the poem's HTML from index.html. Please edit index.html so that it looks like this:

<!DOCTYPE html>
<html>
  <head>
     
    
  </head>
  <body>
  </body>
</html>

So far in this example we've taken a simple HTML document, cut out the content, pasted it into a Backbone View and rendered it back into the page. It may seem like unnecessary work just to render some static HTML, but in the process we've demonstrated the simplest use of a Backbone View.

In the next section we'll start to reap the benefits of Backbone as we add dynamic content to our website.

Rendering dynamic content with templates

Backbone excels at creating dynamic interfaces that change as the user interacts with the page. As a simple demonstration we'll modify our website to display a randomly generated poem that changes when clicked.

As a first step, we're going to move the poem's HTML from a string variable in main.js to a separate template. To accomplish this we'll use the Handlebars.js template library. To speed things up, I've created a new project that includes Handlebars.js, and the new template file. Download it by clicking the link below:

A few things have changed in this version of the poem app: The libraries folder now contains Handlebars.js. Handlebars has also been added to config.yml. Finally, there's a new app folder which contains the main.js script, style.css and a new poem.handlebars template:

.
├── README.md
├── app
│   ├── main.js
│   ├── poem.handlebars
│   └── style.css
├── config.yml
├── index.html
└── libraries

If you open app/poem.handlebars, you'll notice that it's just the HTML snippet containing the poem. Let's modify the template so that we can randomize the poem. Change poem.handlebars so that it matches this code:

<p>
  I postpone death by , <br>
  by , by , by , <br> 
  by , by .
</p>
<small></small>

Handlebars lets us specify template expressions using double curly brackets: . Now when we render this template, we can pass in values for the author and verb expressions in order to create a dynamic poem.

What's that crazy verb.[] syntax? That's the handlebars' "segment-literal notation" which can be used to pick an item from an array. You can read more about Handlebars expressions here.

At this point if you look at the poem website, you will find a blank page. If you open up your browser's Developer Console, you'll find an error. In Chrome I see the following:

Uncaught TypeError: Cannot read property 'verbs' of undefined 

Obviously we need to tell the template what values to substitute for the expressions we defined above.

Open up the `app/main.js' and create a random list of verbs by adding this code at the top:

var verbList = _.shuffle(["working", "loving", "singing", 
                          "screaming", "eating", "drinking", 
                          "fishing", "smoking", "chillin'"]);

Feel free to add a few verbs of your own. Now change the PoemView.render() method so that it looks like this:

  render: function() {
    var params = { 
        verbs: verbList,
        author: "Randy T. Robot"
    };
    this.$el.html(Handlebars.templates.poem(params));
    return this;
  }

In the new render() method we create a params object with verbs and author properties to match the Handlebars template we created earlier. We pass those parameters to the Handlebars.templates.poem function, which will render the template into an HTML string using our data. Finally we use the results to set the contents of our view's el.

Where did Handlebars.templates.poem() come from? One of the ways that Backlift speeds up development is by automatically compiling templates and scripts on the back-end. Backlift knows what compilers to use based on the file extension of the source file. Any .handlebars file is compiled into a Javascript function by the Handlebars command line tool and added to the Handlebars.templates variable. If you weren't using Backlift, you could compile these templates yourself by installing the Handlebars npm package and having it monitor your files for changes.

As a final touch, lets add an event handler that randomizes the poem when it's clicked. Edit your PoemView definition so that it look like this:

var PoemView = Backbone.View.extend({
  render: function() {
    var params = { 
        verbs: verbList,
        author: "Randy T. Robot",
    };
    this.$el.html(Handlebars.templates.poem(params));
    return this;
  },

  // --- new stuff ---
  events: {
    "click": "randomize"
  },
  randomize: function() {
    verbList = _.shuffle(verbList);
    this.render();
  }
});

We've added an events object that will dispatch click events to the randomize() function. Now when we view the poem website, we should see a randomly generated poem. Clicking the poem will generate a new random poem. This is awesome.

Conclusion

This wraps up part one of our tutorial on basic Backbone.js. In this tutorial we've created a simple website that displays a randomly generated poem using a Backbone View, and a Handlebars template. In case you want to check your work, you can download the final product here:

Stay tuned for Part two of this tutorial in which we'll introduce Backbone Models and fetching data from a server. In addition, we'll start a more involved project: a Pinterest inspired photo gallery. Until then, happy Backboning.

Do you want to create your own Dropbox download buttons? Anyone can build templates that are deployable via Backlift. Just create a github repository of your own, and then include the github repo in a special Backlift URL like this: https://www.backlift.com/backlift/dropbox/create?template=<github-URL>&appname=<default-app-name>. When users click on this link, they'll be directed to a "create app" page that will let them download the project to their Dropbox.

 

This is part two of the Building Backbone.js with Backlift tutorial series. This part will cover:

  • Creating Backbone.js Models and Collections
  • Fetching collections from a backend API
  • Saving data to a backend using a form

During this tutorial we'll create a Pinterest inspired gallery website that showcases photos from Dropbox and allows anyone to edit the captions. We'll be building on the concepts covered in Part 1 of this tutorial which covered setting up a Backbone.js project on Backlift, creating Backbone Views and using Handlebars templates. Again we'll be using Backlift.com to host the website and provide a database for storing and retrieving metadata about the photos in our gallery.

While following this tutorial it's good to have a browser window open to the Backbone.js docs so you can dig deeper into backbone as needed. Similarly, if questions about Backlift come up while you're working through this tutorial, please check out the Backlift docs.

Setting up the project

Let's start with a rough sketch of our gallery using just static HTML and CSS. Then as we work through the tutorial, we'll swap in Backbone.js Models, Views and Templates to create a dynamic website. Download the static version into your Dropbox by clicking the link below:

Here's what you should see in your Dropbox/Apps/Backlift/gallery folder:

.
├── README.md
├── app/
│ ├── main.js
│ ├── satinweave.png
│ └── style.css
├── config.yml
├── index.html
├── libraries/
├── photos/
└── thumbnail.jpg

The project folder structure is similar to the poem app from Part 1 with the addition of a photos folder that contains lots of cat pictures. index.html contains a static list of the photos:

      <h1> My Gallery </h1>
<div id="gallery">
<div class="photo"><img src="/photos/cat1.jpg"><p>A cat!</p></div>
<div class="photo"><img src="/photos/cat2.jpg"><p>A cat!</p></div>
<div class="photo"><img src="/photos/cat3.jpg"><p>A cat!</p></div>
<!-- ... -->
</div>

This gallery website looks cute but right now it's static. If we want to add new photos, we have to change the HTML by hand. Wouldn't it be nice if we could add a photo to the photos folder and see it in the gallery automatically? Let's make it so!

Using the Backlift table of contents API

In order to update our gallery automatically we need a data source that tells us what files are in the photos folder. Luckily Backlift provides a table of contents (TOC) API. This API reports the files in a path specified in the URL. For example, to get a listing of files in the photos folder, we just send a GET request to /backlift/toc/photos. You can test this out by navigating to your gallery website's URL, opening the developer console, and typing:

$.get("/backlift/toc/photos")

The result should be an object with a responseText property containing a JSON document that looks something like this:

[{"url": "/photos/cat1.jpg", 
"file": "cat1.jpg",
"modified": "2013-02-21T12:51:09Z",
"created": "2013-02-21T12:51:09Z"},
{"url": "/photos/cat2.jpg",
"file": "cat2.jpg",
"modified": "2013-02-21T12:51:09Z",
"created": "2013-02-21T12:51:09Z"},
{"url": "/photos/cat3.jpg",
"file": "cat3.jpg",
"modified": "2013-02-21T12:51:09Z",
"created": "2013-02-21T12:51:09Z"},
// ...
]

We can use wildcards in the URL to search for certain files. For example, if we only want a list of .jpg files in the photos folder, we can send a GET request to /backlift/toc/photos/*.jpg.

In the following steps we will use the data from the TOC API to populate a Backbone Collection.

Fetching Data with a Backbone Collection

In a Backbone app, Models are the "things" that your app manipulates, and Collections are groups of things. In our case we want to manipulate a Collection of photos, so each photo will be a Model. By using Models and Collections we can avoid putting data manipulation logic into our Views. Plus, Models and Collections provide convenience methods for working with a backend, and can automatically signal Backbone Views when the data changes. The bulk of the Backbone docs are devoted to Models and Collections.

To create a Photos collection edit your apps/main.js file so that it looks like this:

var Photos = Backbone.Collection.extend({
url: "/backlift/toc/photos"
});

function main() {
var photos = new Photos();
photos.fetch({update: true});
photos.on("add", function(photo) {
console.log("fetched "+photo.get('file'));
});
}

// run main() on page load
$(main);

In the above code we first create a new Photos Collection with a url that refers to the Backlift TOC API. Then we define a main() function that will be called on page load. This function first creates a new photos object that inherits from the Photos Collection and then uses it to fetch() a list of photos.

About Collection urls: Think of the url as a connection to a server that the Collection is "plugged into." Calling fetch() on the Collection will use this connection to retrieve data. Alternatively, if you are familiar with REST, you can think of the url as a resource. Any fetch(), save(), create() or destroy() operations on the Collection or its Models are translated into GET, PUT, POST and DELETE requests to that resource.

Where's the Model? Since we haven't explicitly specified a Backbone Model for our Photos class, Backbone will use the default Backbone.Model. Often the default Model's functionality is sufficient.

The last statement in main() sets up an event listener to notify us each time a new photo is added:

  photos.on("add", function(photo) {
console.log("fetched "+photo.get('file'));
});

We need this statement in order to see the results of the fetch() call. Since fetch() is asynchronous, we use the event listener above to schedule a function to be executed when fetch() receives each photo. The add event is one of several events that can be triggered when Models or Collections change asynchronously.

At this point navigate to your gallery website and open the developer console. You should find a listing of each photo in the photos folder like this:

fetched cat1.jpg                main.js:9
fetched cat2.jpg main.js:9
fetched cat3.jpg main.js:9
fetched cat4.jpg main.js:9
fetched cat5.jpg main.js:9
fetched cat6.jpg main.js:9
fetched notcat.jpg main.js:9

If you delete a photo, the page will refresh and the new list will reflect the missing photo. However, since our HTML is still static, the website will show a broken image link. Let's fix that!

Refresher on Views and Templates

In order to correctly display the images in our photos folder, let's create a GalleryView to render each photo using a Handlebars template. This is largely an application of the views and templates concepts that we covered in part 1. First create a new template file in the app folder named photo.handlebars containing the following code:

<div class="photo">
<img src="">
<p>A cat!</p>
</div>

Then, below the Photos Collection definition in app/main.js create a GalleryView like so:

var GalleryView = Backbone.View.extend({
initialize: function() {
this.collection.on("add", this.renderPhoto, this);
},
renderPhoto: function(photo) {
var params = {
photo: photo.toJSON()
};
this.$el.append(Handlebars.templates.photo(params));
return this;
}
});

In the initialize function above we've added a listener that responds to add events by calling renderPhoto. The renderPhoto function is passed the new photo model which it first converts into a simple javascript object using photo.toJSON(). Then this object is passed to the photo template to render an HTML snippet that is appended to the el node.

Now change the main function in app/main.js to look like this:

function main() {
var photos = new Photos();
var gallery = new GalleryView({
collection: photos,
el: $("#gallery")
});
photos.fetch({update: true});
}

The main function begins by creating a Photos collection and passing it to a new GalleryView. We also set the GelleryView's el attribute to the #gallery node where our photos will be appended. Finally we call fetch to retrieve the list of photos from the Backlift TOC API. Since the GalleryView.initialize method sets up a listener, we no longer need to do that in main.

What's that {update: true} stuff? Backbone gives you a lot of control over how the fetch() method behaves. By default, fetch() simply resets the collection with the new data from the server, triggering only the reset event. In our case, since we want the add event to be triggered for each new object, we need to set the update flag. See more about customizing fetch() behavior here.

As a final step, we can remove the list of static photos from the gallery div in index.html. This is the same node that we set to our GalleryView's el. Our index.html <body> tag should now look like this:

  <body>
<div class="container">
<h1> My Gallery </h1>
<div id="gallery">
<!-- GalleryView will fill in this div -->
</div>
</div>
</body>

Our gallery website should now only contain the images from our Dropbox. If we copy or delete photos from the photos folder in Dropbox, we should see the gallery website update accordingly.

Errors while we work While performing the steps above, our gallery website may have refreshed several times. Until our work was complete, these refreshes may have left the website in various error states. This is to be expected, and useful, since the errors can help us remember what still needs to be done.

Adding a Form to the Photo Template

Currently our photos all have the same caption, "A cat!". However, some of the photos are of non-cats. So, for the last half of this tutorial, we'll make it possible for anyone to edit a photo's caption to provide a more appropriate description.

Let's start by downloading an update to the gallery application to make sure we're on the same page.

In this update I've done a bit of reorganization. I've split the main.js file into three files, main.js, collections.js and views.js. This makes our code easier to maintain, as opposed to stuffing everything into one javascript file. Your new app folder should look like this:

app
├── collections.js
├── main.js
├── photo.handlebars
├── satinweave.png
├── style.css
└── views.js

As our project grows, we need some way to keep our variables from cluttering up the global namespace. So in this update I've added the App object and defined all functions and classes as properties of this object. If you look at line 1 in app/main.js you'll find a line like this at the top:

App = this.App || {};

This imports the App object if it's defined, otherwise it creates a new App object. Once the object is imported, I can attach functions and classes to it. Then I can refer to those functions and classes in other files where the App object is imported. This technique is a good alternative to defining variables on the top-level window object since that object can quickly become overcrowded with properties that could be accidentally overwritten.

Now let's get to work on making the captions editable. We're going to create a text input form at the bottom of each photo. The default value for the input will be "A cat!" however the value can be changed by website visitors to provide a more descriptive caption.

Open up the app/photo.handlebars file and edit it so that it looks like this:

<div class="photo">
<img src="">
<form class="form-inline" id="">
<input type="text" placeholder="A cat!" value="">
</form>
</div>

Above we've replaced the <p> with a form containing a single input. Since the list of photos we receive from the TOC API doesn't contain caption data, we've added a separate caption.text expression to the template. Our next step is to create the captions, and pass the data to this template.

But before that, if you open up the gallery website, you can now click on the caption and type in a new one. However if you reload the page, all your caption data is gone. That's because we still need to save the form data back to the Backlift server.

Creating the Captions Collection

In order to save the form data to the Backlift server we must first create a separate captions Collection. In app/collections.js add a new Collection called Captions like so:

App.Captions = Backbone.Collection.extend({
url: "/backliftapp/captions",
forFile: function(file) {
return this.find(function(item) {
return item.get('file') == file;
});
}
});

The Captions collection lets us store a caption for each file in our Photos collection. It includes the forFile() helper function to let us match captions to photos. This function calls the Caption collection's find method to iterate through each caption looking for the one whose file property matches the file argument that we passed in. A typical use case for this method would be, when rendering a particular photo, to first find the caption that matches the photo's file property.

We could have just placed the logic for iterating through the Captions collection directly in the renderPhoto function of GalleryView. However, if the captions collection were to change we'd need to hunt down this code and make sure it still works. Keeping this code within the Captions collection allows Captions to be modular-- contained in one place and easily modified.

Note: We use the special url prefix "/backliftapp" to store and retreive custom app data. Backlift data persistence API endpoints take the form /backliftapp/<collection> where <collection> can be any collection name we want. We don't need to tell Backlift in advance that we're creating a new collection-- as soon as we try to fetch or store data with a persistence API endpoint, Backlift will create the collection. For more information about the persistence API, see here.

Now, in app\main.js we need to create an instance of the new Captions collection and hand it off to the GalleryView. In the past we used a listener to reload the view asynchronously when the photo data was fetched. However, now we have two collections to load. If we use another listener, we may end up rendering the gallery twice. Instead we'll define a function that takes a list of collections, fetches each one from the server, and waits for all of them to load before continuing.

Before the main() function in app\main.js add this new function:

function withCollections(collections, fn) {
_.each(collections, function(col) {
col.fetch({
success: function() {
col.fetched = true;
var done = _.every(collections, function(item) {
return item.fetched == true;
});
if (done) {
fn.call();
}
}
})
});
}

withCollections() fetches a list of collections, and when all fetch operations have succeeded, it calls a function. Now we can re-write main() to look like this:

function main() {
App.photos = new App.Photos();
App.captions = new App.Captions();

App.gallery = new App.GalleryView({
collection: App.photos,
captions: App.captions,
el: "#gallery"
});

withCollections([App.photos, App.captions], function() {
App.gallery.render();
});
}

Here we create the two Collections and pass them to the GalleryView, similar to the way it was done before. Then we use withCollections() to load the two collections, wait until they're ready, and then render the gallery.

Note: In App.gallery above, we assign App.photos to a generic collections property, and App.captions to a specific captions property. Why not use captions and photos properties? This is because Backbone.Views recognize the collections property and assign it to the top-level collections property on the view. So later in a render method we can access it through this.collection. For the captions, since Backbone.View doesn't know about the captions property we used in construction, we must use a more convoluted way to find that property in the render method: this.options.captions.

Finally we need to update the GalleryView class to use the new caption data. Open app/views.js and edit the GalleryView class to look like this:

App.GalleryView = Backbone.View.extend({
render: function() {
var self = this;
self.$el.empty();
self.collection.each(function(photo) {
self.renderPhoto(photo);
});
return self;
},
renderPhoto: function(photo) {
var caption = this.options.captions.forFile(photo.get('file'));
var params = {
photo: photo.toJSON(),
caption: caption ? caption.toJSON() : ""
};
this.$el.append(Handlebars.templates.photo(params));
return this;
}
});

In the new GalleryView we've removed the initialize() method since we no longer need to set up an event listener. Instead we've created a render() function that first clears the view's el and then iterates over all the photos in the collection and calls renderPhoto() for each.

renderPhoto() is mostly the same as before except for two changes. First, the function now starts by finding the appropriate caption from the captions collection, using the forFile() helper method. Secondly, it adds the caption to the params object that's used to render the photo template.

To test to see if our Views and Collections are wired up correctly, open up your browser's developer console and type in the following:

App.captions.add({file:'cat6.jpg', text:'A space cat?'}); App.gallery.render()

This should cause the gallery to re-render, displaying a caption for our outer-space cat.

A note about pre-fetching: In a production app, it would not be smart to send several AJAX requests on page load like we're doing in this example. It causes the render time of the page to be quite slow. Instead it's better to embed the data in the HTML rendered by the server, so that all the information necessary to display the page is available immediately. Backlift has a prefetch mechanism that can be used to embed json content from the server into the page on load. I'll cover its use in a future tutorial.

Final Step: Saving Form Data

The last thing we need to do is save the captions to the Backlift server when a user makes a change. Let's add an event handler to our GalleryView that responds to change events on the form inputs. Add the following properties to GalleryView, starting with events:

App.GalleryView = Backbone.View.extend({

...

},
events: {
"change input": "captionChange",
"submit form": "formSubmit",
},
captionChange: function(ev) {
var data = {
file: $(ev.target).parent().attr("id"),
text: $(ev.target).val()
};
var caption = this.options.captions.forFile(data.file);
if (caption) {
caption.save(data);
} else {
this.options.captions.create(data);
}
},
formSubmit: function(ev) {
ev.preventDefault();
$(ev.target).find("input").blur();
}
});

The captionChange() function first extracts data from the form. It then checks to see if a caption already exists for this file. If so it saves the data to that caption, otherwise it creates a new caption. The save() and create() methods are how Backbone.js sends data to the server.

The formSubmit() function is only there to make sure the page doesn't attempt to reload when the user submits the form by pressing Return.

At this point, check out the gallery website. You should now be able to update your photo captions, and when you reload the page, your changes should remain. You can also share this page with your friends, and let them comment on photos as well!

Conclusion

In this tutorial we covered a lot of information. Specifically we built an entire Pinterest-like gallery using Backlift to fetch photos and save captions. The final version can be downloaded here:

This gallery app is pretty cool, but it's not done yet. In the third and final part of this tutorial we'll expand on the gallery website by allowing users to comment on photos. This final part will get much more into some of the functionality that Backlift provides to create user accounts and set data permissions. Stay tuned!

If you have any feedback on this tutorial, please send it to cole (at) backlift.com or post it in the comments section. You can follow me at @colevscode. Happy hacking!

You can follow me at @colevscode

See More Posts About:

JavaScript


Posted by Cole Krumbholz

LinkedIn Website