Hack Backbone.js
Server-side powered web sites

SPA

SPA Frameworks


@author: Jeremy Ashkenas (Coffeescript, Underscore.js, Docco)
@website: http://backbonejs.org/
@annotation: http://backbonejs.org/docs/backbone.html
Backbone.js gives 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 connects it all to your existing API over a RESTful JSON interface.
FAST facts
- Core components: Model, View, Collection, Router
- Event-driven communication between Views and Models
- Support for RESTful interfaces out of the box, so Models can be easily tied to a backend
- Prototypes are instantiated with the new keyword, which some developers prefer
- Underscore’s micro-templating is available by default

Simple Survey App




View


Welcome View

Router



APP Initialisation
Page source

Initialisation Script

DEMO
Model



Collection



var QUESTIONS = [{
message: 'HTML is ... ?',
type: 'radio',
choices: ['Fun', 'Sexually Transmitted Disease', 'HTML']
}, {
message: 'Who helps decide the outcome of football games',
type: 'radio',
choices: ['God', 'Score', 'Luck']
}, {
message: 'Stormy weather "affects" Cloud Computing',
type: 'radio',
choices: ['sure', 'no', 'seldom']
}, {
message: 'Sun goes around the Earth',
type: 'radio',
choices: ['no', 'it does not', 'yes']
}, {
message: 'Choose Prime Numbers',
type: 'checkbox',
choices: [2, 23, 71, 131, 157, 7, 59, 83]
}];
Sample questions
Single Question View
var QuestionView = Backbone.View.extend({
tagName: 'li',
className: 'question well',
questionTpl: $('#question-tpl').html(),
events: {
'click input': 'complete'
},
initialize: function (question) {
this.model = question;
this.model.attributes.cid = this.model.cid;
this.render();
},
render: function () {
var tpl = _.template(this.questionTpl);
this.$el.html(tpl(this.model.toJSON()));
return this;
},
complete: function (e) {
}
});
<!-- ### Templates ### -->
<!-- Question -->
<script id="question-tpl" type="text/template">
<div class="complete-mark"></div>
<h4><%= message %></h4>
<ul>
<% _.each(choices, function (item, i) {
if (type === 'radio') { %>
<li><label for="<%= cid %>-<%= i %>">
<input type="radio" name="<%= cid %>"
value="<%= item %>"
id="<%= cid %>-<%= i %>">
<div><%= item %></div>
</label></li>
<% } else { %>
<li><label for="<%= cid %>-<%= i %>">
<input type="checkbox" name="<%= cid %>-<%= i %>"
value="<%= item %>"
id="<%= cid %>-<%= i %>">
<div><%= item %></div>
</label></li>
<% }
}); %>
</ul>
</script>Questions List View
var ListView = Backbone.View.extend({
tagName: 'ul',
className: 'questions',
initialize: function (questions) {
// create new collection of questions.
// turn questions into question model instance
this.collection = new Questions(questions);
// render view
this.render();
},
render: function () {
var $docFragment = $(document.createDocumentFragment());
_.each(this.collection.models, (function (model) {
$docFragment.append(this.renderQuestion(model));
}).bind(this));
// append document frament to View element (e.g. to {tagName: 'li'})
this.$el.append($docFragment);
return this;
},
renderQuestion: function (model) {
var questionView = new QuestionView(model);
return questionView.el;
}
});
Survey View
var SurveyView = Backbone.View.extend({
id: 'survey-view',
events: {
'click input': 'activateButton'
},
initialize: function () {
this.render();
},
render: function () {
var listView = new ListView(QUESTIONS),
surveyView = this.$el.html(listView.el),
$button = $('<a id="submit" disabled="disabled"\
class="btn btn-success btn-lg"\
href="#thankyou">Submit</a>');
$('#main-view').html(surveyView);
this.$el.append($button);
return this;
},
activateButton: function (e) {
var $questions = $('.question'),
$completedQuestions = $('.question.completed');
if ($questions.length === $completedQuestions.length) {
$('#submit').removeAttr('disabled');
} else {
$('#submit').attr('disabled', 'disabled');
}
}
});
Thankyou View
var ThankyouView = Backbone.View.extend({
el: '#main-view',
initialize: function () {
this.render();
},
template: _.template($('#thankyou-tpl').html()),
render: function () {
this.$el.html(this.template());
return this;
}
});
<!-- Thankyou View -->
<script id="thankyou-tpl" type="text/template">
<div id="thankyou-view" class="well">
<h1>Thank You!</h1>
</div>
</script>thankyou.js
in index.html
Final Router
var SurveyRouter = Backbone.Router.extend({
routes: {
'': 'viewWelcome',
'survey': 'viewSurvey',
'thankyou': 'viewThankyou'
},
viewWelcome: function () {
return new WelcomeView();
},
viewSurvey: function () {
return new SurveyView();
},
viewThankyou: function () {
return new ThankyouView();
}
});
DEMO

Where to start hacking?
Define the objectives
- Change View html
- Listen to site events
- Listen to Model/View changes
Investigate client's SPA structure
- Find global variables
- window.Backbone !
- Try to find the global application variable (e.g `window.APP`)
(e.g. https://www.leovegas.com/ has `window.APP`)
Investigate client's SPA structure
- Find global variables
- window.Backbone !
- Try to find the global application variable (e.g `window.APP`)
(e.g. https://www.leovegas.com/ has `window.APP`)
- Find out where the html templates are defined and if they are visible on the global level
Investigate client's SPA structure
- Find global variables
- window.Backbone !
- Try to find the global application variable (e.g `window.APP`)
(e.g. https://www.leovegas.com/ has `window.APP`)
- Find out where the html templates are defined and if they are visible on the global level
- Find out whether the Model attributes are accessible and can be changed
Investigate client's SPA structure
- Look for global events
Such as `Backbone._events`, `APP._events` or something similar.
If some are there you can then listen for them unsing Backbone default functionality - Backbone.on, Backbone.off or APP.on, APP.off, and so on.
Investigate client's SPA structure
- Look for global events
Such as `Backbone._events`, `APP._events` or something similar.
If some are there you can then listen for them unsing Backbone default functionality - Backbone.on, Backbone.off or APP.on, APP.off, and so on.
- Check if it is possible to listen to Backbone history changes
- Load target site
- Execute javascript code below in DevTools console
- Interact with the site to get another view rendered
- If console.log appears - you can use this listener for something more interesting replacing log with your code :)
Backbone.history.on('route', function () {
console.log('Route is changed');
});Decorator pattern
initialFunction = (function (fn) {
return function () {
// your code here
return fn.apply(this, arguments);
};
})(initialFunction);- Override safe!
- Return the initial function!
Example:
var defaultGreeting = function (greeting) {
console.log('Hey!');
console.log(greeting);
};
defaultGreeting = (function (fn) {
return function () {
var newGreeting = arguments[0] + ' Nice to hack you!';
return fn.apply(this, [newGreeting]);
};
})(defaultGreeting);
defaultGreeting('You are awesome!');.extend
- This method is used to create new Routers/Models/Views objects
- Overriding `.extend` method gives you possibility to change properties/methods of the object before it will be created by Backbone
- Note: Fires only once and can be overridden only right after Backbone is loaded and app isn't initialised yet
Example:
Backbone.View.extend = (function (fn) {
return function () {
var view = arguments[0];
if (view.el && view.el === '#main-view') {
// override template
view.template = _.template('<div id="welcome-view" class="well">\
<h1>Changed by Ninjas!</h1>\
<a id="button-start-survey" class="btn btn-success btn-lg" href="#survey">Start Survey</a>\
</div>');
// add new method
view.newMethod = function () {
alert('New Method added by Ninjas!');
};
// call new method on initialize
view.initialize = (function (fn) {
return function () {
view.newMethod();
return fn.apply(this, arguments);
};
})(view.initialize);
return fn.apply(this, [view]);
} else {
return fn.apply(this, arguments);
}
};
})(Backbone.View.extend);DEMO

.initialize
You can override `.initialize` method (e.g. `Backbone.View.prototype.initialize`) in order to make your changes before View/Model is being initialized.
NOTE: It can be done only for the Models/Views that implementation has no `initialize` method re-definition (remains empty)
Example:
Backbone.View.prototype.initialize = (function (fn) {
return function () {
// you awesome javascript
return fn.apply(this, arguments);
};
})(Backbone.View.prototype.initialize);Listen to `Backbone.history`
Every time when the route is changed you can listen to `route` event of `Backbone.history` object
NOTE: not every application written in Backbone will support this listener
Example:
Backbone.history.on('route', function () {
// awesome javascript goes here
});Listen to `hashchange` event
You can listen to `hashchange` event and react accordingly when the `window.location.hash` is changing
Example:
$(window).bind('hashchange', function() {
// do something nice
});Questions?

Thank you for listening !
Hack Backbone.js
By workslon
Hack Backbone.js
Intermal Maxymiser presentation made in order to show how we can deal with websites that are using Backbone.js framework
- 891