Their purpose is to hold all the attributes of a logical piece of information.
.set({"attr name": value});
.get("attr name");
save()
will call validate() if it is present and then sync to the database if it passes validation.destroy()
will delete the model from the database and fire the "destroy" event.Computed Values: Backbone really wants all of the attributes persisted to the database. If you have a read-only, computed attribute, then make it a function of the model. To pass the computed value to a view, you can merge the attributes with the function. For example, _.extend({'duration': this.model.computedField()}, this.model.toJSON())
Validation: Validation can happen in a few locations:
The concept of optimistic changes to a collection makes Backbone validation problematic. If validation fails, the view is already updated with bad data, so you will then need to back-out those changes if the “invalid” event gets fired. This seems like a lot of back and forth and possibility for conflicts.It is probably best to perform validatation right before the save, which is the default behavior of Backbone (vs. manually validating during a set({key:value},{validate:true})
).
Collections are a list of models. You can fill a collection using fetch or reset.
Most of the time the models will get loaded from the database via Collection.fetch() or some other means, but it is also possible to simply work with transient data that exists only in memory. Simply shove models into the collection when you create it. Ex:
var myCollection = new App.MyCollection([ {id: 1, name: 'alice'}, {id: 2, name: 'bob'}, {id: 3, name: 'craig'} ]);
Are responsible for DOM display and interaction. Data will be passed in. Keyboard and mouse events are tied to that DOM, and listeners are created to respond to other actions in the app.
Your goal is to have views automatically update themselves when a model is created, updated, or destroyed. This is achieved by listening to events that get fired by the model attached to the view. For example:
app.MyView = Backbone.View.extend({ initialize: function() { //this.model is set when the view is instantiated (it is passed in). this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); }, ...
render()
The job of render is to shove some HTML into the view’s el
(or $el
) property and then return the entire view. If you specify the el when you create the view, then you can render the view by simply calling view.render()
. If you left el blank, then you will need to do: $('#yourContainer').html(view.render().el);
Most of your event hooks ups are going to happen inside a view (and most inside your main AppView).
Use the .on() syntax when binding a callback to this object's events.
Use the this.listenTo('otherObject', event, callback) syntax when working with a different object.
Model | Collection | Description |
---|---|---|
invalid | invalid | A model’s validation failed on the server |
change | change | A model's attributes have changed |
destroy | destroy | A model has been removed from the server |
add | A model was added to collection | |
remove | A model has been removed from collection | |
update | Any number of models have been added or removed from the collection | |
reset | Collection was cleared out (any probably rebuilt) | |
sort | Has been resorted | |
request | request | Started a request to the server |
sync | sync | |
error | error | Request to server failed |
There is a fair amount of documentation for Backbone, a lot of it complete crap. I wouldn’t call these resources great, but they are best I’ve come across so far:
This tutorial from Christophe Coenraets is perfect if you want to learn how to use Backbone with a PHP/MySQL backend using the Slim Framework for PHP.
and building my own REST interface with help from this:
These are quite good, too.
Here are more lists of resources:
RESTful Web Services article from IBM DeveloperWorks.
Obviously, you need your database up and running. The MAMP on OSX connection is:
function getConnection() { $dbhost="localhost"; $dbuser="root"; $dbpass="root"; $dbname="PxPro"; //Your database name here $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); return $dbh; }
The above function is actually located in Slim’s index.php
page. Which is to say, make sure the correct connection info is specified in Slim’s index.php
page.
Backbone makes the assumption that every table has a primary key column called "id". You can tell it to use another similar type of column (auto-increment) with idAttribute.
Put the Slim directory inside folder api
. That same folder will contain Slim’s index.php
file and a .htaccess file. The .htaccess file is a critical bit of magic that allows Slim to work. It tells Apache to rewrite the URL to Slim’s index.php
if the file requested is not found (which, of course, is why those files had better not exist!).
When setting the url and urlRoot properties, keep in mind that it is a relative address to the api
folder from your application's HTML page. Also, include the name of the Slim route (in this case, /airplane
). That same route must exist in Slim’s index.php
file and there should not be an actual directory/file called airplane
inside the api
folder because Apache's rewrite rule states “if no file with that name exists, then call Slim’s index.php
”. This is what we want; Slim’s index.php
should be processing all data requests.
App.Plane = Backbone.Model.extend({ urlRoot : "../api/airplane" }); App.Planes = Backbone.Collection.extend({ model : App.Plane, url : "../api/airplane" });
Another way to think of this is, urlRoot and url are mapping data access functions to a physical location on the web server. If we were NOT using Slim (say you are writing your own REST interface from scratch) then you very well may have actual files located there. e.g. http://myDomain.com/api/airplane/index.php
These strings can look identical, therefore it is important (and confusing!) to understand that they are completely separate and serve different purposes.
Backbone Routes are requested from the web server as a URL to your webpage (say, http://myDomain.com/index.html) plus a fragment identifier (say, #/plane/15).
http://myDomain.com/index.html#/plane/15
In Backbone, you would define the above route as:
var AppRouter = Backbone.Router.extend({ routes:{ "plane/:id" : function(id) { //read a specific airplane } } });
Don't make this more complicated than it deserves - all you are doing is requesting your index page. Tthe page is loaded by your browser, your Javascript code starts running, and it will hit the line:
var app = new AppRouter();
which then inspects the fragment identifier (#/model/15), and finally kicks off the function().
The reason I am using Slim in this project is because it's little trick of magic is to execute a PHP function you tell it to, depending on the URL requested by your users (or more likely, Backbone). For example, if Backbone requests from the webserver http://myDomain.com/api/airplane/15
, Apache will, of course try to find the default file (index.html) at that location. Namely, http://myDomain.com/api/airplane/15/index.html
. This file does not, and should not exist. Why? Because we are tricking Apache into responding with Slim’s index.php
file through the use of Apache's RewriteEngine.
So, we are requesting (via the HTTP GET method) http://myDomain.com/api/airplane/15
. Instead, http://myDomain.com/api/index.php
is called. That file contains and executes:
$app = new Slim();
$app->get('/airplane/:id', 'getSpecificAirplane');
$app->run();
function getSpecificAirplane($id) {
$sql = "select * FROM modelAirplanes Where planeId=$id ORDER BY name";
try {
$db = getConnection();
$stmt = $db->query($sql);
$rst = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo json_encode($rst);
} catch(PDOException $e) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
}
When PHP hits $app->run();
it will inspect the URL that was actually called, try to find a match (line two above), and then call the appropriate function (getSpecificAirplane in this case).
Views | ||
Properties | ||
el | The DOM object that is the target of the view. Can be passed as initialization option. | |
$el | Automatic jQuery reference to el | |
events | A map of listeners to functions. Ex: events:{'keypress #myTextbox':'saveOnEnter'} |
|
model | Can be passed as initialization option. | |
collection | Can be passed as initialization option. | |
Methods | ||
en | ||
Collection | ||
create | Convenience to create a new instance of a model within a collection. Equivalent to instantiating a model with a hash of attributes, saving the model to the server, and adding the model to the set after being successfully created. |
Views listen to their model’s “change” event, then re-render themselves.
Views listen to changes in their editable fields, and then call the save method on the model. i.e.
app.MyView = Backbone.View.extend({ initialize: function(){ this.model.on('change', this.render, this); }, events: { 'click #saveBtn' : 'saveRec' }, saveRec: function(){ var value = this.input.val().trim(); if(value) { this.model.save({title: value}); } } });
In the background, the save() function delegates to Backbone.sync, which is the component in charge of making RESTful requests, using the jQuery function $.ajax() by default. Because REST style architecture is involved, each Create, Read, Update, or Delete (CRUD) action is associated with a different type of HTTP request (POST, GET, PUT, DELETE). The first time the model object is saved, a POST request is used and an identifier ID is created. For subsequent attempts to send the object to the server, a PUT request is used.
When a model needs to be retrieved from the server, a Read action is requested and an Ajax GET request is used. This type of request uses the fetch() method. To determine the location on the server where the model data is pushed or pulled from: