Skip to content
This repository has been archived by the owner on Aug 25, 2018. It is now read-only.

Commit

Permalink
Merge pull request #135 from firebase/0.5.1-release
Browse files Browse the repository at this point in the history
0.5.1 release
  • Loading branch information
davideast committed Jan 13, 2015
2 parents 09730aa + b0e4b32 commit 6eac0be
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 97 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ test/coverage/
node_modules/
bower_components/
test/coverage
.idea
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,6 @@ Any models added to the collection will be synchronized to the provided Firebase
using the Backbone binding will also receive `add`, `remove` and `changed` events on the collection
as appropriate.

**BE AWARE!** If autoSync is set to true, you do not need to call any functions that will affect _remote_ data. If you call
`fetch()` on the collection, **the library will ignore it silently**. However, if autoSync is set to false, you can use `fetch()`. This is explained above in the autoSync section.

You should add and remove your models to the collection as you normally would, (via `add()` and
`remove()`) and _remote_ data will be instantly updated. Subsequently, the same events will fire on
all your other clients immediately.
Expand Down Expand Up @@ -240,10 +237,9 @@ var todo = new Todo({
});
```

**BE AWARE!** You do not need to call any functions that will affect _remote_ data. If autoSync is enabled and you call
`save()` or `fetch()` on the model, **the library will ignore it silently**.
You do not need to call any functions that will affect _remote_ data when `autoSync` is enabled. Calling `fetch()` will simply fire the `sync` event.

If autoSync is enabled, you should modify your model as you normally would, (via `set()` and `destroy()`) and _remote_ data
If `autoSync` is enabled, you should modify your model as you normally would, (via `set()` and `destroy()`) and _remote_ data
will be instantly updated.

#### autoSync: true
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fixed - Improved asynchronous sync behavior.
feature - Added save and remove for autoSync models.
feature - Added fetch method for autoSync models.
fixed - Fixed issue with urlRoot property for models.
fixed - Fixed bug with comparator override.
175 changes: 132 additions & 43 deletions src/backbonefire.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
*/
Backbone.Firebase._determineAutoSync = function(model, options) {
var proto = Object.getPrototypeOf(model);
return _.extend(
{
return _.extend({
autoSync: proto.hasOwnProperty('autoSync') ? proto.autoSync : true
},
this,
Expand Down Expand Up @@ -194,6 +193,52 @@
return !_.isObject(value) && value !== null;
};

/**
* A naive promise-like implementation. Requires a syncPromise object.
*
* syncPromise is an object that has three properties.
* - resolve (bool) - Has the data been retreived from the server?
* - success (bool) - Was the data retrieved successfully?
* - error (Error) - If there was an error, return the error object.
*
* This function relies on the syncPromise object being resolved from an
* outside source. When the "resolve" property has been set to true,
* the "success" and "error" functions will be evaluated.
*/
Backbone.Firebase._promiseEvent = function(params) {
// setup default values
var syncPromise = params.syncPromise;
var success = params.success;
var error = params.error;
var context = params.context || this;
var complete = params.complete;

// set up an interval that checks to see if data has been synced from the server
var promiseInterval = setInterval(_.bind(function() {
// if the result has been retrieved from the server
if(syncPromise.resolve) {

// on success fire off the event
if(syncPromise.success) {
success.call(context);
}
// on error fire off the returned error
else if(syncPromise.err) {
error.call(context, syncPromise.err);
}

// fire off the provided completed event
if(complete) {
complete.call(context);
}

// the "promise" has been resolved, clear the interval
clearInterval(promiseInterval);
}
}, context));

};

/**
* Model responsible for autoSynced objects
* This model is never directly used. The Backbone.Firebase.Model will
Expand All @@ -203,11 +248,19 @@

function SyncModel() {
// Set up sync events

this._initialSync = {};
// apply remote changes locally
this.firebase.on('value', function(snap) {
this._setLocal(snap);
this._initialSync.resolve = true;
this._initialSync.success = true;
this.trigger('sync', this, null, null);
}, function(err) {
// indicate that the call has been received from the server
// and that an error has occurred
this._initialSync.resolve = true;
this._initialSync.err = err;
this.trigger('error', this, err, null);
}, this);

// apply local changes remotely
Expand All @@ -218,18 +271,20 @@
}

SyncModel.protoype = {
save: function() {
console.warn('Save called on a Firebase model with autoSync enabled, ignoring.');
},
fetch: function() {
console.warn('Save called on a Firebase model with autoSync enabled, ignoring.');
},
sync: function(method, model, options) {
if(method === 'delete') {
Backbone.Firebase.sync(method, model, options);
} else {
console.warn('Sync called on a Fireabse model with autoSync enabled, ignoring.');
}
fetch: function(options) {
Backbone.Firebase._promiseEvent({
syncPromise: this._initialSync,
context: this,
success: function() {
this.trigger('sync', this, null, options);
},
error: function(err) {
this.trigger('err', this, err, options);
},
complete: function() {
Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options);
}
});
}
};

Expand All @@ -253,14 +308,6 @@

}

OnceModel.protoype = {

sync: function(method, model, options) {
Backbone.Firebase.sync(method, model, options);
}

};

return OnceModel;
}());

Expand Down Expand Up @@ -301,7 +348,10 @@
}

},


sync: function(method, model, options) {
Backbone.Firebase.sync(method, model, options);
},

/**
* Siliently set the id of the model to the snapshot key
Expand Down Expand Up @@ -390,7 +440,7 @@
create: function(model, options) {
model.id = Backbone.Firebase._getKey(this.firebase.push());
options = _.extend({ autoSync: false }, options);
return Backbone.Collection.prototype.create.apply(this, [model, options]);
return Backbone.Collection.prototype.create.call(this, model, options);
},
/**
* Create an id from a Firebase push-id and call Backbone.add, which
Expand All @@ -400,7 +450,7 @@
add: function(model, options) {
model.id = Backbone.Firebase._getKey(this.firebase.push());
options = _.extend({ autoSync: false }, options);
return Backbone.Collection.prototype.add.apply(this, [model, options]);
return Backbone.Collection.prototype.add.call(this, model, options);
},
/**
* Proxy to Backbone.Firebase.sync
Expand Down Expand Up @@ -442,16 +492,32 @@
var SyncCollection = (function() {

function SyncCollection() {

this._initialSync = {};
// Add handlers for remote events
this.firebase.on('child_added', _.bind(this._childAdded, this));
this.firebase.on('child_moved', _.bind(this._childMoved, this));
this.firebase.on('child_changed', _.bind(this._childChanged, this));
this.firebase.on('child_removed', _.bind(this._childRemoved, this));

// Once handler to emit 'sync' event whenever data changes
this.firebase.on('value', _.bind(function() {
this.trigger('sync', this, null, null);
// Defer the listener incase the data is cached, because
// then the once call would be synchronous
_.defer(_.bind(function() {

this.firebase.once('value', function() {
// indicate that the call has been received from the server
// and the data has successfully loaded
this._initialSync.resolve = true;
this._initialSync.success = true;
this.trigger('sync', this, null, null);
}, function(err) {
// indicate that the call has been received from the server
// and that an error has occurred
this._initialSync.resolve = true;
this._initialSync.err = err;
this.trigger('error', this, err, null);
}, this);

}, this));

// Handle changes in any local models.
Expand All @@ -461,11 +527,8 @@
}

SyncCollection.protoype = {
comparator: function(model) {
return model.id;
},

add: function(models, options) {
// prepare models
var parsed = this._parseModels(models);
options = options ? _.clone(options) : {};
options.success =
Expand Down Expand Up @@ -528,6 +591,27 @@
return ret;
},

// This function does not actually fetch data from the server.
// Rather, the "sync" event is fired when data has been loaded
// from the server. Since the _initialSync property will indicate
// whether the initial load has occurred, the "sync" event can
// be fired once _initialSync has been resolved.
fetch: function(options) {
Backbone.Firebase._promiseEvent({
syncPromise: this._initialSync,
context: this,
success: function() {
this.trigger('sync', this, null, options);
},
error: function(err) {
this.trigger('err', this, err, options);
},
complete: function() {
Backbone.Firebase._onCompleteCheck(this._initialSync.err, this, options);
}
});
},

_log: function(msg) {
if (console && console.log) {
console.log(msg);
Expand All @@ -549,8 +633,8 @@
}

// call Backbone's prepareModel to apply options
model = Backbone.Collection.prototype._prepareModel.apply(
this, [model, options || {}]
model = Backbone.Collection.prototype._prepareModel.call(
this, model, options
);

if (model.toJSON && typeof model.toJSON == 'function') {
Expand All @@ -569,16 +653,17 @@

if (this._suppressEvent === true) {
this._suppressEvent = false;
Backbone.Collection.prototype.add.apply(this, [model], {silent: true});
Backbone.Collection.prototype.add.call(this, [model], {silent: true});
} else {
Backbone.Collection.prototype.add.apply(this, [model]);
Backbone.Collection.prototype.add.call(this, [model]);
}
this.get(model.id)._remoteAttributes = model;
},

_childMoved: function(snap) {
// TODO: Investigate: can this occur without the ID changing?
this._log('_childMoved called with ' + snap.val());
// TODO: child_moved is emitted when the priority for a child is changed, so it
// should update the priority of the model and maybe trigger a sort
_childMoved: function() {

},

// when a model has changed remotely find differences between the
Expand Down Expand Up @@ -620,13 +705,13 @@

if (this._suppressEvent === true) {
this._suppressEvent = false;
Backbone.Collection.prototype.remove.apply(
Backbone.Collection.prototype.remove.call(
this, [model], {silent: true}
);
} else {
// trigger sync because data has been received from the server
this.trigger('sync', this);
Backbone.Collection.prototype.remove.apply(this, [model]);
Backbone.Collection.prototype.remove.call(this, [model]);
}
},

Expand Down Expand Up @@ -750,7 +835,7 @@

var newItem = new BaseModel(attrs, opts);
newItem.autoSync = false;
newItem.firebase = self.firebase.child(newItem.id);
newItem.firebase = self.firebase.ref().child(newItem.id);
newItem.sync = Backbone.Firebase.sync;
newItem.on('change', function(model) {
var updated = Backbone.Firebase.Model.prototype._updateModel(model);
Expand All @@ -761,6 +846,10 @@

};

},

comparator: function(model) {
return model.id;
}

});
Expand Down
Loading

0 comments on commit 6eac0be

Please sign in to comment.