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

Commit

Permalink
feat($goQuery): Experimental intergration of Query
Browse files Browse the repository at this point in the history
$goQuery is a service built on `key.query()`.  It accepts a key, room,
filter and options.  It returns a QueryModel. The Model has the standard
`$sync` method which keeps the models value current with that of the
query result set.

```js
	angular.module('ChatApp').controller('ChatCtrl', ['$scope', '$goQuery', function($scope, $query) {
	  var filter = { “sender”: { “$eq”: “GrandMasterLivingston” }};
	  var sort = { “$name”: “desc”};
	  var limit = 20;
	  var options = { sort: sort, limit: limit };

	  $scope.chat = $query('messages', filter, options);
	  $scope.chat.$sync();
	}]);
```

Related: #48
  • Loading branch information
Matthew Creager committed Jan 28, 2014
1 parent 0c4a910 commit eb619b2
Show file tree
Hide file tree
Showing 5 changed files with 625 additions and 1 deletion.
5 changes: 4 additions & 1 deletion component.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"main": "index.js",
"scripts": [
"index.js",
"lib/query_model.js",
"lib/query_sync.js",
"lib/safe_apply.js",
"lib/connection.js",
"lib/connection_factory.js",
Expand All @@ -28,6 +30,7 @@
"lib/model_binder.js",
"lib/bounce_protection.js",
"lib/errors.js",
"lib/util/normalize.js"
"lib/util/normalize.js",
"lib/util/args.js"
]
}
15 changes: 15 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ var connectionFactory = require('./lib/connection_factory');
var goAngularFactory = require('./lib/go_angular_factory');
var syncFactory = require('./lib/sync_factory');
var keyFactory = require('./lib/key_factory');
var QueryModel = require('./lib/query_model');
var QuerySync = require('./lib/query_sync');

/** goangular module registration */

Expand All @@ -29,12 +31,25 @@ goangular.factory('$goSync', [
syncFactory
]);

goangular.factory('$goQuerySync', [
'$parse',
'$timeout',
QuerySync
]);

goangular.factory('$goKey', [
'$goSync',
'$goConnection',
keyFactory
]);

goangular.factory('$goQuery', [
'$goQuerySync',
'$goConnection',
'$goKey',
QueryModel
]);

goangular.factory('GoAngular', [
'$q',
'$parse',
Expand Down
173 changes: 173 additions & 0 deletions lib/query_model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/* jshint browser:true */
/* global require, module */

/**
* @fileOverview
*
* This file contains the Query Factory & Model, responsible for creating and
* returning instances of the GoAngular Query Model.
*/

'use strict';

var _ = require('lodash');
var Emitter = require('emitter');
var Args = require('./util/args');

var LOCAL_EVENTS = ['ready', 'error'];
var $key;

/**
* queryFactory
* @public
* @param {Object} $sync - Responsible for synchronizing query model
* @param {Object} $conn - GoInstant connection
* @param {Function} $goKey - Uses to create GoAngular key model
* @returns {Function} option validation & instance creation
*/
module.exports = function queryFactory($sync, $conn, $goKey) {
$key = $goKey;

/**
* @public
* @param {Object} key - GoInstant key
*/
return function() {
var a = new Args([
{ key: Args.OBJECT | Args.STRING | Args.Required },
{ room: Args.STRING | Args.Optional },
{ filter: Args.OBJECT | Args.Required },
{ options: Args.OBJECT | Args.Required }
], arguments);

return new QueryModel($sync, $conn, a.key, a.room, a.filter, a.options);
};
};

function QueryModel($querySync, $connection, key, room, filter, options) {
_.bindAll(this);

// If a key is provided, use it, otherwise create one
key = (_.isObject(key) ? key : $connection.$key(key, room));

_.extend(this, {
$$querySync: $querySync,
$$connection: $connection,
$$key: key,
$$query: key.query(filter, options),
$$emitter: new Emitter(),
$$index: []
});
}

/**
* Primes our model, fetching the current result set and monitoring it for
* changes.
* @public
*/
QueryModel.prototype.$sync = function() {
var self = this;

var connected = self.$$connection.$ready();

connected.then(function() {
self.$$sync = self.$$querySync(self.$$query, self);
self.$$sync.$initialize();
});

connected.fail(function(err) {
self.$$emitter.emit('error', err);
});

return self;
};

/**
* Add a generated id with a
* @param {*} value - New value of key
* @returns {Object} promise
*/
QueryModel.prototype.$add = function(value, opts) {
opts = opts || {};

var self = this;
return this.$$connection.$ready().then(function() {
return self.$$key.add(value, opts);
});
};

/**
* Give the current key a new value
* @public
* @param {*} value - New value of key
* @returns {Object} promise
*/
QueryModel.prototype.$set = function(value, opts) {
opts = opts || {};

var self = this;
return this.$$goConnection.$ready().then(function() {
return self.$$key.set(value, opts);
});
};

/**
* Returns a new object that does not contain prefixed methods
* @public
* @returns {Object} model
*/
QueryModel.prototype.$omit = function() {
return _.omit(this, function(value, key){
return _.first(key) === '$';
});
};

/**
* Create and return a new instance of Model, with a relative key.
* @public
* @param {String} keyName - Key name
*/
QueryModel.prototype.$key = function(keyName) {
var key = this.$$key.key(keyName);

return $key(key);
};

/**
* Remove this key
* @public
* @returns {Object} promise
*/
QueryModel.prototype.$remove = function(opts) {
opts = opts || {};

var self = this;
return this.$$connection.$ready().then(function() {
return self.$$key.remove(opts);
});
};

/**
* Bind a listener to events on this key
* @public
*/
QueryModel.prototype.$on = function(eventName, listener) {
if (!_.contains(LOCAL_EVENTS, eventName)) {
return this.$$query.on(eventName, listener);
}

this.$$emitter.on(eventName, listener);
};

/**
* Remove a listener on this key
* @public
*/
QueryModel.prototype.$off = function(eventName, listener) {
if (!_.contains(LOCAL_EVENTS, eventName)) {
return this.$$query.off(eventName, listener);
}

this.$$emitter.off(eventName, listener);
};
127 changes: 127 additions & 0 deletions lib/query_sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* jshint browser:true */
/* global require, module */

/**
* @fileOverview
*
* This file contains the QuerySync class, used to create a binding between
* a query model on $scope and a GoInstant query
*/

'use strict';

var _ = require('lodash');

module.exports = function querySync($parse, $timeout) {

/**
* @public
* @param {Object} key - GoInstant key
*/
return function(query, qModel) {
return new QuerySync($parse, $timeout, query, qModel);
};
};

/**
* The Sync class is responsible for synchronizing the state of a local model,
* with that of a GoInstant key.
*
* @constructor
* @param {Object} $parse - Angular parse object
* @param {Object} $timeout - Angular timeout object
* @param {Object} query - GoInstant query object
* @param {Object} model - local object
*/
function QuerySync($parse, $timeout, query, model) {
_.bindAll(this, [
'$initialize',
'$$handleUpdate',
'$$handleRemove'
]);

_.extend(this, {
$parse: $parse,
$timeout: $timeout,
$$query: query,
$$model: model,
$$registry: {}
});
}

/**
* Creates an association between a local object and a query by
* fetching a result set and monitoring the query.
*/
QuerySync.prototype.$initialize = function() {
var self = this;
var index = self.$$model.$$index;

self.$$query.execute(function(err, results) {
if (err) {
return self.$$model.$$emitter.emit('error', err);
}

self.$$registry = {
update: self.$$handleUpdate,
add: self.$$handleUpdate,
remove: self.$$handleRemove
};

_.each(self.$$registry, function(fn, event) {
self.$$query.on(event, fn);
});

self.$timeout(function() {
_.map(results, function(result) {
index.push(result.name);
self.$$model[result.name] = result.value;
});

self.$$model.$$emitter.emit('ready');
});
});
};

/**
* When the query result set changes, update our local model object
* @private
* @param {*} result - new result set
* @param {Object} context - Information related to the key being set
*/
QuerySync.prototype.$$handleUpdate = function(result, context) {
var self = this;

var index = self.$$model.$$index;

// Update the index array with the new position of this key
var currentIndex = index.indexOf(result.name);

if (currentIndex !== -1) {
index.splice(currentIndex, 1);
}

index.splice(context.position.current, 0, result.name);

// Update the value of the model.
// The index update above will NOT trigger any changes on scope.
// Removing this will cause position to not be updated.
self.$timeout(function() {
self.$$model[result.name] = result.value;
});
};

/**
* When an item is removed from the result set, update the model
* @private
* @param {*} result - new result set
* @param {Object} context - information related to the key being set
*/
QuerySync.prototype.$$handleRemove = function(result, context) {
this.$$model.$$index.splice(context.position.previous, 1);

var self = this;
this.$timeout(function() {
delete self.$$model[result.name];
});
};
Loading

0 comments on commit eb619b2

Please sign in to comment.