Our website:
242 JS files.
28848 lines of code.
5-person frontend team.
Things can get messy fast.
Lightweight Model View Controller framework
var Book = Backbone.Model.extend(
defaults: {
title: 'Untitled',
author: 'Unknown',
owned: false
},
url: "/api/books",
displayString: function() {
return this.get('title') + ' By ' + this.get('author');
},
markAsOwned: function() {
this.set('owned', true);
}
});
var book = new Book({title: 'I, Robot',
author: 'Isaac Asimov'});
var BookCollection = Backbone.Collection.extend({
model: Book,
url: "/api/books"
});
var book1 = new Book({title: 'I, Robot',
author: 'Isaac Asimov'});
var book2 = new Book({title: 'Caves of Steel',
author: 'Isaac Asimov'});
var robotSeries = new BookCollection([book1, book2]);
var Book = Backbone.Model.extend(
url: "/api/books",
// ...
});
var book = new Book({id: 12});
book.fetch();
HTTP GET /api/books
{id: 12,
title: 'Robot Dreams',
author: 'Isaac Asimov',
owned: false
}
book.markAsOwned();
book.save();
HTTP PUT /api/books/12
{id: 12,
title: 'Robot Dreams',
author: 'Isaac Asimov',
owned: true
}
var BookCollection = Backbone.Collection.extend({
url: "/api/books",
// ...
});
var myBooks = new BookCollection();
myBooks.fetch();
HTTP GET /api/books
[{id: 12,
title: 'The Naked Sun',
author: 'Isaac Asimov'
}, ...
]
myBooks.create(new Book({title: 'Foundation',
author: 'Isaac Asimov'}));
HTTP POST /api/books
{title: 'Foundation', author: 'Isaac Asimov'}
var BookItemView = Backbone.View.extend({
tagName:"li",
template:_.template($('#tpl-book-list-item').html()),
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var BookListView = Backbone.View.extend({
tagName:'ul',
initialize:function () {
this.collection.bind("reset", this.render, this);
this.collection.fetch();
},
render:function (eventName) {
_.each(this.collection.models, function (book) {
var bookItemView = new BookItemView({model:book});
$(this.el).append(bookItemView.render().el);
}, this);
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes:{
"/list":"list"
},
list:function () {
this.books = new BookCollection();
this.bookListView = new BookListView({collection:this.books});
$('#sidebar').html(this.bookListView.render().el);
},
});
var app = new AppRouter();
Backbone.history.start();
Standalone jQuery:
$('body').append('<br>');
$('.coursera-listing').html('Best Course Ever');
Integrated with Backbone:
var view = BackboneView.extend({
render: function() {
this.$el.append('<br>');
this.$el.find('.coursera-listing').html('Best Course Ever');
// or shorter:
this.$('.coursera-listing').html('Best Course Ever');
}
})
Standalone Underscore:
// On collections:
_.each(coll, iterator); _.find(coll, iterator); // etc
// On arrays
_.flatten(arr), _.union(*arrs), _.uniq(arr), // etc
// On functions
_.bind(func, obj), _.throttle(func, wait) // etc
All collection functions work on BackboneCollections
but return arrays.
BackboneCollection.each(iterator);
BackboneCollection.find(iterator);
// etc
<script src="path/to/require.js" />
<script>
require.config(
baseUrl: "path/to/assets",
callback: {
require(["js/routes/main"]);
}
});
</script>
define(
["js/lib/jquery",
"js/lib/underscore",
"js/lib/backbone",],
function($, _, Backbone) {
var view = Backbone.View.extend({
render: function() {
_.each(['a', 'b', 'c'], function(letter) {
var $button = $('button').html(letter);
}
}
});
return view;
});
Coursera.router.navigate('/account/profile');
Coursera.region.open({
'pages/home/template/page',
regions: {
body: 'pages/home/account/profile'
}
})
Coursera.api.post('course/enroll', {data: {'course-id': 2}})
.done(function(data) {
alert('Success!');
})
.fail(function(xhr) {
alert(xhr.responseText);
});


Backbone.Model
↳ Topic
↳ University
↳ Course
↳ User
define(["jquery",
"backbone",
"underscore",
"js/core/coursera",
"js/lib/backbone.relational"],
function($, Backbone, _, Coursera) {
var topic = Backbone.extend({
defaults: {},
sync: function(callback) {
var that = this;
if(this.get("description") || this.get("about_the_course")) {
callback();
} else if(this.get("short_name")) {
Coursera.api.get('topic/information', {
data: {"topic-id":that.get('short_name')},
message: {"waiting": "loading course page ..."}
})
.done(function(data)
{
that.set(data);
callback();
})
.fail(function(jqXHR)
{
callback(jqXHR.status);
});
}
}
});
return topic;
});
Backbone.Collections
↳ Topics
↳ Courses
↳ Universities
define(
["backbone",
"underscore",
"js/models/topic",
"js/core/coursera",
"jquery"],
function(Backbone, _, Topic, Coursera, $) {
var Topics = Backbone.Collection.extend({
model: Topic,
comparator: function(topic) {
return topic.get('name');
},
retrieve: function(filter, callback) {
var self = this;
// hack, until we improve our apis and models
if(self.length) {
callback(null, self);
} else {
Coursera.api.get("topic/list", {
data:filter || {},
message: {waiting: "loading classes ..."}
})
.done(function(data)
{
self.add(data);
callback(null, self);
})
.fail(function()
{
callback(true, self);
});
}
},
}
});
All views extend body
or Backbone.View
pages/home/
about/
...
account/
...
catalog/
catalogBody.js
catalogBody.js.jade
catalogListing.js.jade
index.styl
course/
...
front/
...
template/
...
universities/
...
university/
var routes = {};
routes[home + "account/records"] = function() {
if (Coursera.user.get('authenticated')) {
Coursera.user.getTopics(function(topics) {
Coursera.region.open({
"pages/home/template/page": {
regions: {
body: {
"pages/home/account/courseRecords": {
id: "account-records"
}
}
}
});
});
});
} else {
this.navigate(config.dir.home, true);
}
};
// ...
Backbone.history.start({pushState: true});
Backbone.Model
↳ AdminModel
↳ TopicAdminModel
↳ CourseAdminModel
↳ UniversityAdminModel
↳ StaffAdminModel
↳ TeachingAssistantAdminModel
↳ InstructorAdminModel
↳ UniversityAdminAdminModel
Extends Backbone.Model and adds:
label
webUrl()
displayName()
fieldsets()
inline()
buttons()
instructions()
filter()
columns()
define(["backbone",
"js/core/coursera",
"pages/site-admin/models/AdminModel"
],
function(Backbone, Coursera, AdminModel) {
var model = AdminModel.extend({
displayName: function() {
return this.get('user__email');
},
fieldsets: function() {
return [
{
name: 'user',
type: 'select2search',
readonly: !this.isNew(),
extras: {
multiple: false,
search: {
url: Coursera.config.url.maestro + 'admin/search?',
param: 'email'
}
}
},
{
name: 'official_title',
type: 'text'
},
{
name: 'universities',
type: 'select2',
extras: {
multiple: true,
dataUrl: Coursera.config.url.maestro + 'admin/universities'
}
}
];
}
})
return model;
});
Backbone.View
↳ body
↳ ModelAdminPageView
↳ ModelAdminFieldsView
↳ FieldView
↳ CheckboxView
↳ DatePickerView
↳ DropdownView
↳ HiddenInputView
↳ NumberRangeView
↳ Select2SearchView
↳ TextAreaView
↳ TransloaditUploadView
↳ WysiHTMLEditorView
↳ ...
POST /instructor {...} → {...}
GET /instructor/2 → {...}
PUT /instructor/2 {...} → {...}
Backbone.Collection
↳ AdminCollection
↳ TopicsAdminCollection
↳ CoursesAdminCollection
↳ UniversitiesAdminCollection
↳ InstructorsAdminCollection
↳ TeachingAssistantsAdminCollection
↳ UniversitiesAdminsAdminCollection
Extends Backbone.Collection and adds:
label
webUrl()
comparator()
showNewButton
define(["backbone",
"js/core/coursera",
"pages/site-admin/collections/AdminCollection",
"pages/site-admin/models/CourseAdminModel"
],
function(Backbone, Coursera, AdminCollection, CourseAdminModel) {
var collection = AdminCollection.extend({
url: 'admin/courses',
webUrlLabel: 'sessions',
label: 'Sessions',
model: CourseAdminModel,
showNewButton: false,
comparator: function(model) {
return model.get('topic__name');
}
});
return collection;
});
Backbone.View
↳ body
↳ AdminDashboardView
↳ CollectionAdminPageView
↳ CollectionAdminListView
Determines what models/collections are valid URLs based on groups in cookie.
var routes = {};
routes[""] = function() {
var region = {};
region[pageTemplate] = {
regions: {
body: {
"pages/site-admin/views/AdminDashboardView": {
id: "AdminDashboard",
initialize: {
collections: getAllowedCollections()
}
}
}
}
}
Coursera.region.open(region);
};
Backbone.history.start({pushState: true, root: "/admin/"});
Powered by BackboneRelational
var item = Backbone.Model.extend({
api: Coursera.api,
partialUpdate: true,
url: "quizzes"
});
var section = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: "items",
relatedModel: Item,
collectionType: Items,
reverseRelation: {
'key': '__section',
'includeInJSON': false
}
}],
partialUpdate: true,
// ...
});
Start with a tutorial like this one, not the docs.
Port a simple app or make one from scratch, the "Backbone" way.
Figure out what's important to you:
modularity? data binding? testability? persistance?
Review your options:
Ember? Spine? AngularJS? Enyo? and many more...
Or when all else fails...
write your own.