Dugan Chen's Homepage

Various Things

Backbone.js and RESTful web services

One of the more interesting things about Backbone.js is its integration with RESTful web services.

Let’s say I have the following web services:

URL Verb Description
/entities GET fetch entities
/entities POST create entity
/entities/id PUT modify entity
/entities/id DELETE delete entity

And that each service takes JSON objects or arrays of objects, where each object has the following properties (with sample values):

{
    id: 1 // not needed for POST
    name: 'my name'
}

You can see that this is exactly what Backbone.js expects.

I knocked the service up in Django, then wrote the following front-end app to demonstrate it. Notice the use of jQuery templates.

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>Backbone.js REST Support Demo</title>
	</head>
	<body>
		<p><input id="new-name" type="text"><button id="add">Add</button></p>
		<div id="entities"></div>
	</body>
	<script id="entity-template" type="x-jquery-tmpl">
		<input type="text" value="${name}">
		<button>Delete</button>
	</script>
	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
	<script src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
	<script src="{{ STATIC_URL }}underscore-min.js"></script>
	<script src="{{ STATIC_URL }}backbone-min.js"></script>
	<script src="{{ STATIC_URL }}rest.js"></script>
</html>
/*global Backbone, $, _ */

var Entities = {};

Entities.Collection = Backbone.Collection.extend({
	url: '/entities'
});

Entities.Views = {};

Entities.Views.Edit = Backbone.View.extend({
	change: function () {
		this.model.set({
			name: $(this.el).children('input').val()
		});
		this.model.save();
	},
	destroy: function () {
		var el = this.el;
		this.model.destroy({
			success: function () {
				$(el).remove();
			}
		});
	},
	events: {
		'click button': 'destroy',
		'change input': 'change'
	},
	initialize: function () {
		_(this).bindAll('change', 'destroy', 'render');
	},
	render: function () {
		$('#entity-template').tmpl(this.model.toJSON()).appendTo(this.el);
		this.delegateEvents();
	}
});

Entities.Views.List = Backbone.View.extend({
	append: function (model) {
		var p = $('<p>').appendTo('#entities'),
			view = new Entities.Views.Edit({
				model: model,
				el: p[0]
			});
		view.render();	
	},
	initialize: function () {
		_(this).bindAll('append', 'render');
		this.collection.bind('refresh', this.render);
		this.collection.bind('add', this.append);
	},
	render: function () {
		$('#entities').empty();
		this.collection.each(function (model) {
			this.append(model);
		}, this);
	}
});

$(function () {
	var collection = new Entities.Collection(),
		view = new Entities.Views.List({
			collection: collection
		});

	collection.fetch();
	
	$('#add').click(function () {
		var model = new Backbone.Model({
			name: $('#new-name').val()
		});
		collection.add(model);
		model.save();
	});
});