Skip to content

Commit

Permalink
Merge pull request FirebaseExtended#414 from firebase/release_0.8.1
Browse files Browse the repository at this point in the history
Release 0.8.1
  • Loading branch information
katowulf committed Aug 28, 2014
2 parents bae2156 + 3ad0426 commit 10b6a57
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 45 deletions.
2 changes: 2 additions & 0 deletions src/FirebaseArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@
}
rec.$id = snap.name();
rec.$priority = snap.getPriority();
$firebaseUtils.applyDefaults(rec, this.$$defaults);

// add it to array and send notifications
this._process('child_added', rec, prevChild);
Expand Down Expand Up @@ -295,6 +296,7 @@
if( angular.isObject(rec) ) {
// apply changes to the record
var changed = $firebaseUtils.updateRec(rec, snap);
$firebaseUtils.applyDefaults(rec, this.$$defaults);
if( changed ) {
this._process('child_changed', rec);
}
Expand Down
65 changes: 41 additions & 24 deletions src/FirebaseObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,26 @@
* @constructor
*/
function FirebaseObject($firebase, destroyFn, readyPromise) {
var self = this;

// These are private config props and functions used internally
// they are collected here to reduce clutter on the prototype
// and instance signatures.
self.$$conf = {
// IDE does not understand defineProperty so declare traditionally
// to avoid lots of IDE warnings about invalid properties
this.$$conf = {
promise: readyPromise,
inst: $firebase,
bound: null,
destroyFn: destroyFn,
listeners: [],
/**
* Updates any bound scope variables and notifies listeners registered
* with $watch any time there is a change to data
*/
notify: function() {
if( self.$$conf.bound ) {
self.$$conf.bound.update();
}
// be sure to do this after setting up data and init state
angular.forEach(self.$$conf.listeners, function (parts) {
parts[0].call(parts[1], {event: 'value', key: self.$id});
});
}
listeners: []
};

self.$id = $firebase.$ref().ref().name();
self.$priority = null;
// this bit of magic makes $$conf non-enumerable and non-configurable
// and non-writable (its properties are still writable but the ref cannot be replaced)
Object.defineProperty(this, '$$conf', {
value: this.$$conf
});

this.$id = $firebase.$ref().ref().name();
this.$priority = null;

$firebaseUtils.applyDefaults(this, this.$$defaults);
}

FirebaseObject.prototype = {
Expand All @@ -74,7 +66,7 @@
* @returns a promise which will resolve after the save is completed.
*/
$save: function () {
var notify = this.$$conf.notify;
var notify = this.$$notify.bind(this);
return this.$inst().$set($firebaseUtils.toJSON(this))
.then(function(ref) {
notify();
Expand Down Expand Up @@ -222,10 +214,11 @@
$$updated: function (snap) {
// applies new data to this object
var changed = $firebaseUtils.updateRec(this, snap);
$firebaseUtils.applyDefaults(this, this.$$defaults);
if( changed ) {
// notifies $watch listeners and
// updates $scope if bound to a variable
this.$$conf.notify();
this.$$notify();
}
},

Expand All @@ -239,6 +232,30 @@
$log.error(err);
// frees memory and cancels any remaining listeners
this.$destroy(err);
},

/**
* Updates any bound scope variables and notifies listeners registered
* with $watch any time there is a change to data
*/
$$notify: function() {
var self = this;
if( self.$$conf.bound ) {
self.$$conf.bound.update();
}
// be sure to do this after setting up data and init state
angular.forEach(self.$$conf.listeners, function (parts) {
parts[0].call(parts[1], {event: 'value', key: self.$id});
});
},

/**
* Overrides how Angular.forEach iterates records on this object so that only
* fields stored in Firebase are part of the iteration. To include meta fields like
* $id and $priority in the iteration, utilize for(key in obj) instead.
*/
forEach: function(iterator, context) {
return $firebaseUtils.each(this, iterator, context);
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
throw new Error('config.arrayFactory must be a valid function');
}
if (!angular.isFunction(cnf.objectFactory)) {
throw new Error('config.arrayFactory must be a valid function');
throw new Error('config.objectFactory must be a valid function');
}
}
};
Expand Down
35 changes: 24 additions & 11 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,17 +224,20 @@
angular.extend(rec, data);
rec.$priority = snap.getPriority();

if( angular.isObject(rec.$$defaults) ) {
angular.forEach(rec.$$defaults, function(v,k) {
return !angular.equals(oldData, rec) ||
oldData.$value !== rec.$value ||
oldData.$priority !== rec.$priority;
},

applyDefaults: function(rec, defaults) {
if( angular.isObject(defaults) ) {
angular.forEach(defaults, function(v,k) {
if( !rec.hasOwnProperty(k) ) {
rec[k] = v;
}
});
}

return !angular.equals(oldData, rec) ||
oldData.$value !== rec.$value ||
oldData.$priority !== rec.$priority;
return rec;
},

dataKeys: function(obj) {
Expand All @@ -246,12 +249,22 @@
},

each: function(obj, iterator, context) {
angular.forEach(obj, function(v,k) {
var c = k.charAt(0);
if( c !== '_' && c !== '$' && c !== '.' ) {
iterator.call(context, v, k, obj);
if(angular.isObject(obj)) {
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
var c = k.charAt(0);
if( c !== '_' && c !== '$' && c !== '.' ) {
iterator.call(context, obj[k], k, obj);
}
}
}
});
}
else if(angular.isArray(obj)) {
for(var i = 0, len = obj.length; i < len; i++) {
iterator.call(context, obj[i], i, obj);
}
}
return obj;
},

/**
Expand Down
27 changes: 25 additions & 2 deletions tests/unit/FirebaseArray.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,15 @@ describe('$FirebaseArray', function () {
arr.$$added(testutils.snap($utils.toJSON(arr[pos]), 'a'));
expect(spy).not.toHaveBeenCalled();
});

it('should apply $$defaults if they exist', function() {
var arr = stubArray(STUB_DATA, $FirebaseArray.$extendFactory({
$$defaults: {aString: 'not_applied', foo: 'foo'}
}));
var rec = arr.$getRecord('a');
expect(rec.aString).toBe(STUB_DATA.a.aString);
expect(rec.foo).toBe('foo');
});
});

describe('$$updated', function() {
Expand Down Expand Up @@ -530,6 +539,19 @@ describe('$FirebaseArray', function () {
arr.$$updated(testutils.snap($utils.toJSON(arr[pos]), 'a'), null);
expect(spy).not.toHaveBeenCalled();
});

it('should apply $$defaults if they exist', function() {
var arr = stubArray(STUB_DATA, $FirebaseArray.$extendFactory({
$$defaults: {aString: 'not_applied', foo: 'foo'}
}));
var rec = arr.$getRecord('a');
expect(rec.aString).toBe(STUB_DATA.a.aString);
expect(rec.foo).toBe('foo');
delete rec.foo;
arr.$$updated(testutils.snap($utils.toJSON(rec), 'a'));
expect(rec.aString).toBe(STUB_DATA.a.aString);
expect(rec.foo).toBe('foo');
});
});

describe('$$moved', function() {
Expand Down Expand Up @@ -700,13 +722,14 @@ describe('$FirebaseArray', function () {
return fb;
}

function stubArray(initialData) {
function stubArray(initialData, Factory) {
if( !Factory ) { Factory = $FirebaseArray; }
var readyFuture = $utils.defer();
var destroySpy = jasmine.createSpy('destroy').and.callFake(function(err) {
readyFuture.reject(err||'destroyed');
});
var fb = stubFb();
var arr = new $FirebaseArray(fb, destroySpy, readyFuture.promise);
var arr = new Factory(fb, destroySpy, readyFuture.promise);
if( initialData ) {
var prev = null;
for (var key in initialData) {
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/FirebaseObject.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ describe('$FirebaseObject', function() {
flushAll();
expect(obj).toEqual(jasmine.objectContaining({foo: 'bar'}));
});

it('should apply $$defaults if they exist', function() {
var F = $FirebaseObject.$extendFactory({
$$defaults: {aNum: 0, aStr: 'foo', aBool: false}
});
var obj = new F($fb, noop, $utils.resolve());
expect(obj).toEqual(jasmine.objectContaining({aNum: 0, aStr: 'foo', aBool: false}));
})
});

describe('$save', function () {
Expand Down Expand Up @@ -440,6 +448,16 @@ describe('$FirebaseObject', function() {
obj.$$updated(fakeSnap(null, true));
expect(obj.$priority).toBe(true);
});

it('should apply $$defaults if they exist', function() {
var F = $FirebaseObject.$extendFactory({
$$defaults: {baz: 'baz', aString: 'bravo'}
});
var obj = new F($fb, noop, $utils.resolve());
obj.$$updated(fakeSnap(FIXTURE_DATA));
expect(obj.aString).toBe(FIXTURE_DATA.aString);
expect(obj.baz).toBe('baz');
});
});

function flushAll() {
Expand Down
27 changes: 20 additions & 7 deletions tests/unit/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,33 @@ describe('$firebaseUtils', function () {
expect($utils.updateRec(rec, testutils.snap({foo: 'bar'}, 'foo'))).toBe(false);
});

it('should add $$defaults if they exist', function() {
var rec = { foo: 'bar' };
rec.$$defaults = { baz: 'not_applied', bar: 'foo' };
$utils.updateRec(rec, testutils.snap({baz: 'bar'}));
expect(rec).toEqual(jasmine.objectContaining({bar: 'foo', baz: 'bar'}));
});

it('should apply changes to record', function() {
var rec = {foo: 'bar', bar: 'foo', $id: 'foo', $priority: null};
$utils.updateRec(rec, testutils.snap({bar: 'baz', baz: 'foo'}));
expect(rec).toEqual({bar: 'baz', baz: 'foo', $id: 'foo', $priority: null})
});
});

describe('#applyDefaults', function() {
it('should return rec', function() {
var rec = {foo: 'bar'};
expect($utils.applyDefaults(rec), {bar: 'baz'}).toBe(rec);
});

it('should do nothing if no defaults exist', function() {
var rec = {foo: 'bar'};
$utils.applyDefaults(rec, null);
expect(rec).toEqual({foo: 'bar'});
});

it('should add $$defaults if they exist', function() {
var rec = {foo: 'foo', bar: 'bar', $id: 'foo', $priority: null};
var defaults = { baz: 'baz', bar: 'not_applied' };
$utils.applyDefaults(rec, defaults);
expect(rec).toEqual({foo: 'foo', bar: 'bar', $id: 'foo', $priority: null, baz: 'baz'});
});
});

describe('#toJSON', function() {
it('should use toJSON if it exists', function() {
var json = {json: true};
Expand Down

0 comments on commit 10b6a57

Please sign in to comment.