Skip to content

emaphp/backbone.async

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Backbone.Async

Backbone meets Promises


###About
*Backbone.Async* is a *Backbone.js* extension that introduces a Model and a Collection class using [Promises](http://www.html5rocks.com/en/tutorials/es6/promises/ "") as a return value for its syncronization methods.
###Acknowledgement
*Backbone.Async* is based on @jsantell's [Backbone-Promised](https://github.com/jsantell/backbone-promised "").
###Installation
> Bower
bower install backbone.async --save

> Node
npm install backbone.async --save

###Polyfill
Browsers not supporting Promises should use a polyfill. This library uses [promise-polyfill](https://github.com/taylorhakes/promise-polyfill "") along with *PhantomJS* for testing.
###Backbone.Async.Model
When invoking the methods *fetch*, *save* or *destroy* a new *Promise* instance is returned. Resolve and rejection callbacks receive a single argument containing the following properties:
  • model: The model instance that invokes the synchronization method.
  • response: On success, a JSON object with the values received. On error, an XHR instance.
  • options: Options used for this request.

**Fetching a model**
var Contact = Backbone.Async.Model.extend({
    urlRoot: '/contacts'
});

var contact = new Contact({id: 1});

// Fetch by id
contact.fetch({foo: 'bar'})
.then(function(data) {
    var model = data.model,
        response = data.response,
        options = data.options;

    console.log('Model Id:', model.id);
    console.log('Response:', JSON.stringify(response);
    console.log('Options used:' JSON.stringify(options));
})
.catch(function(data) {
    if (_.isError(data)) {
        console.error(data)
    } else {
        console.log('Failed to fetch contact:', data.response.statusText);
    }
});

**Saving a model**
// Obtain model
var contact = collection.get(1);

// Change  attributes
contact.set('name', 'emaphp');

// Save changes
contact.save(null, {foo: 'bar'})
.then(function(data) {
    var model = data.model,
        response = data.response,
        options = data.options;
        
    console.log('Model with ID', model.id, 'saved');
})
.catch(function(data) {
    if (_.isError(data)) {
        console.error(data)
    } else {
        console.log('Failed to save contact:', data.response.statusText);
    }
});

**Deleting a model**
// Obtain model
var contact = collection.get(1);

// Destroy model
contact.destroy({foo: 'bar'})
.then(function(data) {
    var model = data.model,
        response = data.response,
        options = data.options;
        
    console.log('Model with ID', model.id, 'deleted');
})
.catch(function(data) {
    if (_.isError(data)) {
        console.error(data)
    } else {
        console.log('Failed to delete contact:', data.response.statusText);
    }
});

###Backbone.Async.Collection
When invoking the methods *fetch*, *create*, *update* or *delete* a new *Promise* instance is returned. Resolve and rejection callbacks receive a single argument containing the following properties:
  • model / collection: The model / collection instance that invokes the synchronization method.
  • response: On success, a JSON object with the values received. On error, an XHR instance.
  • options: Options used for this request.

The *update* and *delete* methods are just wrappers for *Async.Model::save* and *Async.Model::destroy* respectively. Callbacks for *fetch* receive a collection instance, while *create*, *update* and *delete* callbacks receive the corresponding model.
**Fetching a collection** ```javascript var Contacts = Backbone.Async.Collection.extend({ url: '/contacts' });

var contacts = new Contacts();

// Fetch a collection contacts.fetch({foo: 'bar'}) .then(function(data) { var collection = data.collection, response = data.response, options = data.options;

console.log('Collection has a total of', collection.length, 'models');
console.log('Response:', JSON.stringify(response);
console.log('Options used:' JSON.stringify(options));

}) .catch(function(data) { if (_.isError(data)) { console.error(data); } else { console.log('Failed to fetch collection:', data.response.statusText); } });


<br/>
**Creating models**

```javascript
// Values to save
var values = {title: 'Hi', message: 'Hello World'};

// Collection class
var Notes = Backbone.Async.Collection.extend({
    url: '/notes'
});

var notes = new Notes();

// Create new model
notes.create(values, {
    foo: 'bar',
    wait: true
})
.then(function (data) {
    var model = data.model,
        response = data.response,
        options = data.options;
        
    console.log('Model ID:', model.id);
    console.log('Response:', JSON.stringify(response));
    console.log('Options used:'. JSON.stringify(options));
})
.catch(function (data) {
    if (_.isError(data)) {
        console.error(data);
    } else {
        console.log('Failed to create model:', data.response.statusText);
    }
});

**Updating models** ```javascript // Get contact by ID contact = contacts.get(1);

// Update attributes contact.set('name', 'emaphp');

// Send request contacts.update(contact, { foo: 'bar' }) .then(function (data) { var model = data.model, response = data.response, options = data.options;

console.log('Contact has been updated!');

}) .catch(function (data) { if (_.isError(data)) { console.error(data); } else { console.log('Failed to create model:', data.response.statusText); } });


<br/>
**Deleting models**
```javascript
// Get contact by ID
contact = contacts.get(1);

contacts.delete(contact, {
    foo: 'bar'
})
.then(function (data) {
    var model = data.model,
        response = data.response,
        options = data.options;
    
    console.log('Model with ID', model.id, 'has been deleted');
})
.catch(function (data) {
    if (_.isError(data)) {
        console.error(data);
    } else {
        console.log('Failed to delete model:', data.response.statusText);
    }
});

###Events
*Backbone.Async* adds additional before/after events when synchronizing a Model or Collection.
#####Async Model events
```javascript var Contact = Backbone.Async.Contact.extend({ urlRoot: '/contacts' });

var contact = new Contact({id: 1});

contact.on('before:fetch', function(model, options) { console.log('Contact is being fetched...'); });

// Fetch contact contact.fetch() .then(function(data) { console.log('Contact fetched correctly'); }) .catch(function(data) { console.log('Something went wrong'); });


<br/>
**before:fetch**
> Arguments: *Async.Model* ***model***, *object* ***options***

<br/>
**after:fetch**
> Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***

<br/>
**before:save**
> Arguments: *Async.Model* ***model***, *object* ***attributes***, *object* ***options***

<br/>
**after:save**
> Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***

<br/>
**before:destroy**
> Arguments: *Async.Model* ***model***, *object* ***options***

<br/>
**after:destroy**
> Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***

<br/>
#####Async Collection events

<br/>
```javascript
var Contacts = Backbone.Async.Collection.extend({
    url: '/contacts'
});

var contacts = new Contacts();

contacts.on('after:fetch', function(collection, response, options, success) {
    if (!success) {
        console.log('Error:', _.isError(collection) ? collection : response.statusText);
    }
});

// Fetch contacts
contacts.fetch()
.then(function(data) {
    //render collection
})
.catch(function(data) {
    console.log('Something went wrong');
});

**before:fetch** > Arguments: *Async.Collection* ***collection***, *object* ***options***
**after:fetch** > Arguments: *Async.Collection* ***collection***, *object* ***response***, *object* options, *boolean* ***success***
**before:create** > Arguments: *Async.Collection* ***collection***, *object* ***attributes***, *object* ***options***
**after:create** > Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***
**before:update** > Arguments: *Async.Model* ***model***, *object* ***options***
**after:update** > Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***
**before:delete** > Arguments: *Async.Model* ***model***, *object* ***options***
**after:delete** > Arguments: *Async.Model* ***model***, *object* ***response***, *object* ***options***, *boolean* ***success***
#####Event callbacks order
Event callbacks are evaluated in the following order:
  • before:event
  • success / error
  • after:event
  • complete
  • then / catch

Wrapper methods like *Async.Collection::update* and *Async.Collection::delete* behave as expected. For example, when removing a model through *Async.Collection::delete* callbacks are invoked in the following order:
  • before:delete
  • before:destroy (Model)
  • success / error
  • after:destroy (Model)
  • after:delete
  • complete
  • then / catch

###Backbone.Async.Store
The *Async.Store* class provides an additional wrapper for Model and Collection classes.
var Note = Backbone.Async.Model.extend({
    urlRoot: '/notes'
});

var Notes = Backbone.Async.Collection.extend({
    url: '/notes',
    model: Note
});

var NotesStore = Backbone.Async.Store.extend({
    modelClass: Note,
    collectionClass: Notes
});

var store = new NotesStore();

**Fetch model by ID** ```javascript // Fetchs a model by its ID store.fetchById(1, {foo: 'var'}) .then(function(data) { // 'fetchById' returns a resolved promise if the model was alreay loaded var note = store.get(1); //storage.collection.get(1)
// The 'response' property will only be available when a request is made
if (!data.response) {
    console.log('No request was made');
}

}) .catch(function(data) { if (_.isError(data)) { console.error(data); } else { console.log('Error:', data.response.statusText); } });


<br/>
**Fetch collection**

```javascript
// Fetch all models
store.fetchAll({foo: 'var'})
.then(function(data) {
    storage.loaded; //returns true
    
    // Get collection
    var collection = store.collection; //or data.collection
    
    // Count models
    console.log('Models loaded:', store.length());
})
.catch(function(data) {
    if (_.isError(data)) {
        console.error(data);
    } else {
        console.log(data.response.statusText);
    }
});

Addtional proxy methods: *reset*, *get*, *add*, *remove*, *shift*, *pop*, *push*, *unshift*, *where*, *findWhere*, *toJSON*, *sort*, *create*, *update*, *delete*.
###License

This library is distributed under the terms of the MIT license.