quilligana
quilligana

Reputation: 251

Get $firebaseArray from GeoQuery

I'm using firebase with a combination of GeoFire, AngularFire and the regular JavaScript API.

In my application, a GeoQuery returns a list of object keys by subscribing to the .on("key_entered") event. I can then get the objects with the .once() query method. However, what I really need to do is have a synchronised array which contains the objects who's keys are returned by the GeoQuery. I also need to limit/order these by a their createdAt timestamp so as not to download every single object. As new objects hit my .on("key_entered") event, they should be added to the array.

After quite a lot of researching I cannot figure out how I can do this. Is it possible? If not, what would be the best way for me to approach this issue.

Upvotes: 0

Views: 257

Answers (2)

alexseik
alexseik

Reputation: 61

One possible solution but it is not tested:

/*global angular,Firebase,GeoFire,_*/
(function() {
  'use strict';

  angular.module('app').provider('GeofireService', function GeofireServiceProvider() {
    var firebaseRef = "";

    this.setUrl = function(firebaseUrl) {
      firebaseRef = firebaseUrl;
    };

    this.$get = [
      '$rootScope',
      '$firebaseUtils',
      function($rootScope, $firebaseUtils) {

        function GeofireService() {
          var self = this;
          this._firebase = new Firebase(firebaseRef);
          this._itemsRef = this._firebase.child('items');
          this._geoRef = this._firebase.child('_geofire');
          this._geofire = new GeoFire(this._geoRef);
          this._list = [];
          this._observers = [];
          this._sync = new QueryArraySync(this);

          $firebaseUtils.getPublicMethods(self, function(fn, key) {
            self._list[key] = fn.bind(self);
          });

          this._sync.init(this._list);

          return this._list;
        }

        GeofireService.prototype = {
          add: function(item) {
            var itemID = this._itemsRef.push(item);
            var thatItem = item;
            thatItem.id = itemID.key();
            var geofirePromise = this._geofire.set(
              thatItem.id, [thatItem.coord.latitude, thatItem.coord.longitude]);
            return geofirePromise.then(function() {
              return thatItem;
            });
          },
          //Update does not update geofire location, only the item
          update: function(id, item) {
            var defer = $firebaseUtils.defer();
            var itemRef = this._itemsRef.child(id);
            //TODO test if undefined or item does not exist
            itemRef.update(item, function(err) {
              if (err) {
                //TODO noooooooooooo
                defer.reject(err);
              }
              defer.resolve(id);
            });
            return defer.promise;
          },
          getById: function(id) {
            var defer = $firebaseUtils.defer();
            var itemRef = this._itemsRef.child(id);
            itemRef.on('value', function(snap) {
              defer.resolve(snap.val());
            });
            return defer.promise;
          },
          getAll: function() {
            var defer = $firebaseUtils.defer();
            this._itemsRef.on('value', function(snapshot) {
              defer.resolve(snapshot.val());
            });
            return defer.promise;
          },
          distance: function(loc1, loc2) {
            return GeoFire.distance(loc1, loc2);
          },
          updateQuery: function(queryCriteria) {
            //TODO test if sync is destroyed
            this._query = this._sync.updateQuery(queryCriteria);
          },
          stopCallbacks: function() {
            _.each(this._observers, function(callback) {
              callback.cancel();
            });
            this._observers = [];
          },
          on: function(eventType, callback) {
            if (this._query !== undefined) {
              var cb = this._query.on(eventType, callback);
              this._observers.push(cb);
              return cb;
            }
          }
        };

        function QueryArraySync(geofireArray) {
          function init(list) {
            _list = list;
          }

          //TODO remove if not used
          function destroy() {
            if (!sync.isDestroyed) {
              sync.isDestroyed = true;
              _.each(_callbackRegister, function(cb) {
                cb.cancel();
              });
            }
          }

          function onKeyEntered(key, location, distance) {
            return geofireArray.getById(key)
              .then(function(data) {
                var index = _.findIndex(_list, function(item) {
                  return item.id === key;
                });
                if (index !== -1) {
                  return;
                }
                data.location = location;
                data.distance = distance;
                _list.push(data);
              });
          }

          function onKeyExited(key) {
            var index = _.findIndex(_list, function(item) {
              return item.id === key;
            });
            if (index !== -1) {
              _list.splice(index, 1);
            }
          }

          function updateQuery(queryCriteria) {
            if (_query !== null) {
              _query.updateCriteria(queryCriteria);
            } else {
              _query = geofireArray._geofire.query(queryCriteria);
              _callbackRegister.push(_query.on('key_entered', onKeyEntered));
              _callbackRegister.push(_query.on('key_exited', onKeyExited));
            }
            return _query;
          }

          var _list;
          var _query = null;
          var _callbackRegister = [];

          var sync = {
            init: init,
            destroy: destroy,
            isDestroyed: false,
            updateQuery: updateQuery
          };

          return sync;
        }

        return new GeofireService();
      }
    ];
  });
})();

With this solution, we can create items with _geofire positions, update them, retrieve all of items and maintain an array of items which are within the query you set up with updateQuery method.

Idea inspired from Frank answer!

Upvotes: 0

Frank van Puffelen
Frank van Puffelen

Reputation: 598728

Yes, that is possible.

You'll have to build a class similar to $firebaseArray logic on top of the key_ events ($firebaseArray uses child_ events).

since the AngularFire project is available on Github, this would be a good place to start: https://github.com/firebase/angularfire/blob/master/src/FirebaseArray.js

Upvotes: 1

Related Questions