Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 6e38dad

Browse files
committed
Fixes #501 - remove $firebase object
Renamed $extendFactory to $extend (they are not factories anymore) Added doSet() and doRemove() to $firebaseUtils (ported from old $firebase code) Upgraded MockFirebase dependency
1 parent a0674e6 commit 6e38dad

File tree

13 files changed

+757
-1511
lines changed

13 files changed

+757
-1511
lines changed

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@
3535
},
3636
"devDependencies": {
3737
"angular-mocks": "~1.3.11",
38-
"mockfirebase": "~0.8.0"
38+
"mockfirebase": "~0.10.1"
3939
}
4040
}

src/FirebaseArray.js

Lines changed: 140 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,25 @@
77
*
88
* Internally, the $firebase object depends on this class to provide 5 $$ methods, which it invokes
99
* to notify the array whenever a change has been made at the server:
10-
* $$added - called whenever a child_added event occurs
11-
* $$updated - called whenever a child_changed event occurs
12-
* $$moved - called whenever a child_moved event occurs
13-
* $$removed - called whenever a child_removed event occurs
10+
* $$added - called whenever a child_added event occurs, returns the new record, or null to cancel
11+
* $$updated - called whenever a child_changed event occurs, returns true if updates were applied
12+
* $$moved - called whenever a child_moved event occurs, returns true if move should be applied
13+
* $$removed - called whenever a child_removed event occurs, returns true if remove should be applied
1414
* $$error - called when listeners are canceled due to a security error
1515
* $$process - called immediately after $$added/$$updated/$$moved/$$removed
16-
* to splice/manipulate the array and invokes $$notify
16+
* (assuming that these methods do not abort by returning false or null)
17+
* to splice/manipulate the array and invoke $$notify
1718
*
1819
* Additionally, these methods may be of interest to devs extending this class:
1920
* $$notify - triggers notifications to any $watch listeners, called by $$process
2021
* $$getKey - determines how to look up a record's key (returns $id by default)
2122
*
22-
* Instead of directly modifying this class, one should generally use the $extendFactory
23-
* method to add or change how methods behave. $extendFactory modifies the prototype of
23+
* Instead of directly modifying this class, one should generally use the $extend
24+
* method to add or change how methods behave. $extend modifies the prototype of
2425
* the array class by returning a clone of $FirebaseArray.
2526
*
2627
* <pre><code>
27-
* var NewFactory = $FirebaseArray.$extendFactory({
28+
* var ExtendedArray = $FirebaseArray.$extend({
2829
* // add a new method to the prototype
2930
* foo: function() { return 'bar'; },
3031
*
@@ -38,31 +39,29 @@
3839
* return this.$getRecord(snap.key()).update(snap);
3940
* }
4041
* });
41-
* </code></pre>
4242
*
43-
* And then the new factory can be passed as an argument:
44-
* <code>$firebase( firebaseRef, {arrayFactory: NewFactory}).$asArray();</code>
43+
* var list = new ExtendedArray(ref);
44+
* </code></pre>
4545
*/
4646
angular.module('firebase').factory('$FirebaseArray', ["$log", "$firebaseUtils",
4747
function($log, $firebaseUtils) {
4848
/**
4949
* This constructor should probably never be called manually. It is used internally by
5050
* <code>$firebase.$asArray()</code>.
5151
*
52-
* @param $firebase
53-
* @param {Function} destroyFn invoking this will cancel all event listeners and stop
54-
* notifications from being delivered to $$added, $$updated, $$moved, and $$removed
55-
* @param readyPromise resolved when the initial data downloaded from Firebase
52+
* @param {Firebase} ref
5653
* @returns {Array}
5754
* @constructor
5855
*/
59-
function FirebaseArray($firebase, destroyFn, readyPromise) {
56+
function FirebaseArray(ref) {
6057
var self = this;
6158
this._observers = [];
6259
this.$list = [];
63-
this._inst = $firebase;
64-
this._promise = readyPromise;
65-
this._destroyFn = destroyFn;
60+
this._ref = ref;
61+
this._sync = new ArraySyncManager(this);
62+
63+
$firebaseUtils.assertValidRef(ref, 'Must pass a valid Firebase reference ' +
64+
'to $FirebaseArray (not a string or URL)');
6665

6766
// indexCache is a weak hashmap (a lazy list) of keys to array indices,
6867
// items are not guaranteed to stay up to date in this list (since the data
@@ -80,6 +79,8 @@
8079
self.$list[key] = fn.bind(self);
8180
});
8281

82+
this._sync.init(this.$list);
83+
8384
return this.$list;
8485
}
8586

@@ -101,7 +102,12 @@
101102
*/
102103
$add: function(data) {
103104
this._assertNotDestroyed('$add');
104-
return this.$inst().$push($firebaseUtils.toJSON(data));
105+
var def = $firebaseUtils.defer();
106+
var ref = this.$ref().ref().push();
107+
ref.set($firebaseUtils.toJSON(data), $firebaseUtils.makeNodeResolver(def));
108+
return def.promise.then(function() {
109+
return ref;
110+
});
105111
},
106112

107113
/**
@@ -124,14 +130,15 @@
124130
var item = self._resolveItem(indexOrItem);
125131
var key = self.$keyAt(item);
126132
if( key !== null ) {
127-
return self.$inst().$set(key, $firebaseUtils.toJSON(item))
128-
.then(function(ref) {
129-
self.$$notify('child_changed', key);
130-
return ref;
131-
});
133+
var ref = self.$ref().ref().child(key);
134+
var data = $firebaseUtils.toJSON(item);
135+
return $firebaseUtils.doSet(ref, data).then(function() {
136+
self.$$notify('child_changed', key);
137+
return ref;
138+
});
132139
}
133140
else {
134-
return $firebaseUtils.reject('Invalid record; could determine its key: '+indexOrItem);
141+
return $firebaseUtils.reject('Invalid record; could determine key for '+indexOrItem);
135142
}
136143
},
137144

@@ -153,10 +160,13 @@
153160
this._assertNotDestroyed('$remove');
154161
var key = this.$keyAt(indexOrItem);
155162
if( key !== null ) {
156-
return this.$inst().$remove(key);
163+
var ref = this.$ref().ref().child(key);
164+
return $firebaseUtils.doRemove(ref).then(function() {
165+
return ref;
166+
});
157167
}
158168
else {
159-
return $firebaseUtils.reject('Invalid record; could not find key: '+indexOrItem);
169+
return $firebaseUtils.reject('Invalid record; could not determine key for '+indexOrItem);
160170
}
161171
},
162172

@@ -208,7 +218,7 @@
208218
* @returns a promise
209219
*/
210220
$loaded: function(resolve, reject) {
211-
var promise = this._promise;
221+
var promise = this._sync.ready();
212222
if( arguments.length ) {
213223
// allow this method to be called just like .then
214224
// by passing any arguments on to .then
@@ -218,9 +228,9 @@
218228
},
219229

220230
/**
221-
* @returns the original $firebase object used to create this object.
231+
* @returns {Firebase} the original Firebase ref used to create this object.
222232
*/
223-
$inst: function() { return this._inst; },
233+
$ref: function() { return this._ref; },
224234

225235
/**
226236
* Listeners passed into this method are notified whenever a new change (add, updated,
@@ -257,9 +267,9 @@
257267
$destroy: function(err) {
258268
if( !this._isDestroyed ) {
259269
this._isDestroyed = true;
270+
this._sync.destroy(err);
260271
this.$list.length = 0;
261-
$log.debug('destroy called for FirebaseArray: '+this.$inst().$ref().toString());
262-
this._destroyFn(err);
272+
$log.debug('destroy called for FirebaseArray: '+this.$ref().ref().toString());
263273
}
264274
},
265275

@@ -282,6 +292,7 @@
282292
* @param {object} snap a Firebase snapshot
283293
* @param {string} prevChild
284294
* @return {object} the record to be inserted into the array
295+
* @protected
285296
*/
286297
$$added: function(snap/*, prevChild*/) {
287298
// check to make sure record does not exist
@@ -540,14 +551,109 @@
540551
* @param {Object} [methods] a list of functions to add onto the prototype
541552
* @returns {Function} a new factory suitable for use with $firebase
542553
*/
543-
FirebaseArray.$extendFactory = function(ChildClass, methods) {
554+
FirebaseArray.$extend = function(ChildClass, methods) {
544555
if( arguments.length === 1 && angular.isObject(ChildClass) ) {
545556
methods = ChildClass;
546557
ChildClass = function() { return FirebaseArray.apply(this, arguments); };
547558
}
548559
return $firebaseUtils.inherit(ChildClass, FirebaseArray, methods);
549560
};
550561

562+
function ArraySyncManager(firebaseArray) {
563+
function destroy(err) {
564+
if( !sync.isDestroyed ) {
565+
sync.isDestroyed = true;
566+
var ref = firebaseArray.$ref();
567+
ref.off('child_added', created);
568+
ref.off('child_moved', moved);
569+
ref.off('child_changed', updated);
570+
ref.off('child_removed', removed);
571+
firebaseArray = null;
572+
resolve(err||'destroyed');
573+
}
574+
}
575+
576+
function init($list) {
577+
var ref = firebaseArray.$ref();
578+
579+
// listen for changes at the Firebase instance
580+
ref.on('child_added', created, error);
581+
ref.on('child_moved', moved, error);
582+
ref.on('child_changed', updated, error);
583+
ref.on('child_removed', removed, error);
584+
585+
// determine when initial load is completed
586+
ref.once('value', function(snap) {
587+
if (angular.isArray(snap.val())) {
588+
$log.warn('Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information.');
589+
}
590+
591+
resolve(null, $list);
592+
}, resolve);
593+
}
594+
595+
// call resolve(), do not call this directly
596+
function _resolveFn(err, result) {
597+
if( !isResolved ) {
598+
isResolved = true;
599+
if( err ) { def.reject(err); }
600+
else { def.resolve(result); }
601+
}
602+
}
603+
604+
var def = $firebaseUtils.defer();
605+
var batch = $firebaseUtils.batch();
606+
var created = batch(function(snap, prevChild) {
607+
var rec = firebaseArray.$$added(snap, prevChild);
608+
if( rec ) {
609+
firebaseArray.$$process('child_added', rec, prevChild);
610+
}
611+
});
612+
var updated = batch(function(snap) {
613+
var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
614+
if( rec ) {
615+
var changed = firebaseArray.$$updated(snap);
616+
if( changed ) {
617+
firebaseArray.$$process('child_changed', rec);
618+
}
619+
}
620+
});
621+
var moved = batch(function(snap, prevChild) {
622+
var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
623+
if( rec ) {
624+
var confirmed = firebaseArray.$$moved(snap, prevChild);
625+
if( confirmed ) {
626+
firebaseArray.$$process('child_moved', rec, prevChild);
627+
}
628+
}
629+
});
630+
var removed = batch(function(snap) {
631+
var rec = firebaseArray.$getRecord($firebaseUtils.getKey(snap));
632+
if( rec ) {
633+
var confirmed = firebaseArray.$$removed(snap);
634+
if( confirmed ) {
635+
firebaseArray.$$process('child_removed', rec);
636+
}
637+
}
638+
});
639+
640+
var isResolved = false;
641+
var error = batch(function(err) {
642+
_resolveFn(err);
643+
firebaseArray.$$error(err);
644+
});
645+
var resolve = batch(_resolveFn);
646+
647+
var sync = {
648+
destroy: destroy,
649+
isDestroyed: false,
650+
init: init,
651+
ready: function() { return def.promise; }
652+
};
653+
654+
return sync;
655+
}
656+
551657
return FirebaseArray;
552658
}
553659
]);

0 commit comments

Comments
 (0)