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

0.5.1 release #135

Merged
merged 17 commits into from
Jan 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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