From d89e1ecd77c72d112721f02aceb33e976459d5bc Mon Sep 17 00:00:00 2001 From: Christoph Herbst Date: Sat, 30 Nov 2013 00:02:38 +0100 Subject: [PATCH 1/3] client: handle Meteors users collection specially --- client.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client.js b/client.js index 6d72d03..0de1dd4 100644 --- a/client.js +++ b/client.js @@ -6,7 +6,10 @@ function() { if (self.collections[name]) { return self.collections[name]; } else { - self.collections[name] = new Meteor.Collection(name); + if (name === "users") + self.collections[name] = Meteor.users; + else + self.collections[name] = new Meteor.Collection(name); return self.collections[name]; } } From 62b5516cdc8c9f43e4a85de3413737de8c1831b7 Mon Sep 17 00:00:00 2001 From: Christoph Herbst Date: Sat, 30 Nov 2013 00:11:35 +0100 Subject: [PATCH 2/3] client: add automatic populates/cleanup cursers --- client.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/client.js b/client.js index 0de1dd4..efd71e4 100644 --- a/client.js +++ b/client.js @@ -45,51 +45,110 @@ function($rootScope, MeteorCollections, $meteorObject) { setTimeout($rootScope.apply, 0); } }, 100); - function CollectionFactory(collection) { + function CollectionFactory(collection, populates) { var collection = MeteorCollections.getCollection(collection); - var value = []; + var values = []; + var value = {}; + var handle; + var populateHandles = {}; + + populates = populates || []; + populates = [].concat(populates); function Collection(value) { value = {}; } + function stopPopulates(document, populate) { + var id = document._id; + if (populateHandles[populate] && populateHandles[populate][id]) { + populateHandles[populate][id].stop(); + populateHandles[populate][id] = undefined; + } + } + + function stopAllPopulates(document) { + _.each(populates, function(populate){ + stopPopulates(document, populate); + }); + } + + function populate(document){ + _.each(_.intersection(populates, _.keys(document)), function(join){ + var id = document._id; + var jColl = join[0].toUpperCase() + join.slice(1) + 's'; + + jColl = MeteorCollections.getCollection(jColl); + + if (!populateHandles[join]) + populateHandles[join] = {}; + populateHandles[join][id] = jColl.find({'_id' : document[join]}).observe({ + "added" : function(jdocument) { + document[join] = jdocument; + $rootScope.apply(); + }, + "changed" : function(jdocument) { + document[join] = jdocument; + $rootScope.apply(); + }, + "removed" : function() { + document[join] = undefined; + stopPopulates(document, join); + $rootScope.apply(); + }, + }); + }); + } Collection.observe = function(cursor, array) { - cursor.observe({ + if (handle) { + handle.stop(); + handle = undefined; + } + + handle = cursor.observe({ "addedAt" : function(document, atIndex, before) { - //console.log(document); + stopAllPopulates(document); + populate(document); if (!array) { - value = new $meteorObject(collection, document); + angular.extend(value, document); } if (array) { - value[atIndex] = new $meteorObject(collection, document); + values[atIndex] = new $meteorObject(collection, document); } $rootScope.apply(); }, "changedAt" : function(newDocument, oldDocument, atIndex) { - value[atIndex] = new $meteorObject(collection, newDocument); + stopAllPopulates(newDocument); + populate(newDocument); + if (!array) { + angular.extend(value, newDocument); + } else { + values[atIndex] = new $meteorObject(collection, newDocument); + } $rootScope.apply(); }, "removedAt" : function(oldDocument, atIndex) { - - value.splice(atIndex, 1); + stopAllPopulates(oldDocument); + values.splice(atIndex, 1); $rootScope.apply(); } - }) + }); } Collection.find = function(selector, options, callback) { - value = this instanceof Collection ? this : []; this.observe(collection.find(selector, options), true); - return value; + return values; } Collection.findOne = function(selector, options, callback) { - value = this instanceof Collection ? this : {}; value = new $meteorObject(collection,collection.find(selector,options).fetch()[0]); this.observe(collection.find(selector, options), false); return value; } + Collection.count = function(selector) { + return collection.find(selector).fetch().length; + } Collection.insert = function(values) { values = angular.copy(values); cleanupAngularObject(values); @@ -99,6 +158,10 @@ function($rootScope, MeteorCollections, $meteorObject) { updateValues = angular.copy(updateValues); cleanupAngularObject(updateValues); delete updateValues._id; + + _.each(_.intersection(populates, _.keys(updateValues)), function(populate){ + updateValues[populate] = updateValues[populate]._id; + }); return collection.update(selector, { $set : updateValues }); From 20f7215d05ab33185b3aca3202780f3895609260 Mon Sep 17 00:00:00 2001 From: Christoph Herbst Date: Thu, 2 Jan 2014 16:47:33 +0100 Subject: [PATCH 3/3] Improve populates/Support mongodb $unset --- client.js | 140 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 41 deletions(-) diff --git a/client.js b/client.js index efd71e4..3f66353 100644 --- a/client.js +++ b/client.js @@ -46,24 +46,45 @@ function($rootScope, MeteorCollections, $meteorObject) { } }, 100); function CollectionFactory(collection, populates) { - var collection = MeteorCollections.getCollection(collection); - var values = []; - var value = {}; - var handle; - var populateHandles = {}; + var collection = MeteorCollections.getCollection(collection), + values = [], + value = {}, + handle, + populateHandles = {}, + populatedDocuments = {}, + populateCollections = {}; + populates = populates || []; populates = [].concat(populates); + populates = _.map(populates, function(populate){ + if (_.isObject(populate)){ + var property = _.chain(populate).keys().first().value(); + populateCollections[property] = populate[property]; + return property; + } else { + populateCollections[populate] = + populate[0].toUpperCase() + populate.slice(1) + 's'; + return populate; + } + }); + function Collection(value) { value = {}; } function stopPopulates(document, populate) { - var id = document._id; - if (populateHandles[populate] && populateHandles[populate][id]) { - populateHandles[populate][id].stop(); - populateHandles[populate][id] = undefined; + var id = document[populate]; + + if (populatedDocuments[populate] && populatedDocuments[populate][id]) { + delete populatedDocuments[populate][id][document._id]; + + if (_.isEmpty(populatedDocuments[populate][id]) && + populateHandles[populate][id]) { + populateHandles[populate][id].stop(); + populateHandles[populate][id] = undefined; + } } } @@ -73,33 +94,68 @@ function($rootScope, MeteorCollections, $meteorObject) { }); } + function updatePopulatedDocuments(join, id, jdocument){ + _.each(populatedDocuments[join][id], function(document){ + document[join] = jdocument; + if (value) + angular.extend(value, document); + }); + $rootScope.apply(); + } + function populate(document){ _.each(_.intersection(populates, _.keys(document)), function(join){ - var id = document._id; - var jColl = join[0].toUpperCase() + join.slice(1) + 's'; + var id = document[join]; + var jColl = populateCollections[join]; jColl = MeteorCollections.getCollection(jColl); if (!populateHandles[join]) populateHandles[join] = {}; - populateHandles[join][id] = jColl.find({'_id' : document[join]}).observe({ - "added" : function(jdocument) { - document[join] = jdocument; - $rootScope.apply(); - }, - "changed" : function(jdocument) { - document[join] = jdocument; - $rootScope.apply(); - }, - "removed" : function() { - document[join] = undefined; - stopPopulates(document, join); - $rootScope.apply(); - }, - }); + if (!populatedDocuments[join]) + populatedDocuments[join] = {}; + + if (!populatedDocuments[join][id]) + populatedDocuments[join][id] = {}; + + populatedDocuments[join][id][document._id] = document; + + if (!populateHandles[join][id]) { + populateHandles[join][id] = jColl.find({'_id' : id}).observe({ + "added" : function(jdocument) { + updatePopulatedDocuments(join, id, jdocument); + }, + "changed" : function(jdocument) { + updatePopulatedDocuments(join, id, jdocument); + }, + "removed" : function() { + updatePopulatedDocuments(join, id, undefined); + }, + }); + } else { + document[join] = jColl.findOne(id); + } + }); } + + function depopulate(document) { + _.each(_.intersection(populates, _.keys(document)), function(populate){ + if (document[populate] && document[populate]._id) + document[populate] = document[populate]._id; + }); + } + + function getDeletedKeys(obj){ + var keys = {}; + _.each(obj, function(value, key){ + if (value === undefined) + keys[key] = 1; + }); + return _.isEmpty(keys)?undefined:keys; + } + Collection.observe = function(cursor, array) { if (handle) { handle.stop(); @@ -121,12 +177,12 @@ function($rootScope, MeteorCollections, $meteorObject) { }, "changedAt" : function(newDocument, oldDocument, atIndex) { - stopAllPopulates(newDocument); + stopAllPopulates(oldDocument); populate(newDocument); if (!array) { angular.extend(value, newDocument); } else { - values[atIndex] = new $meteorObject(collection, newDocument); + values[atIndex] = new $meteorObject(collection, newDocument); } $rootScope.apply(); }, @@ -138,33 +194,35 @@ function($rootScope, MeteorCollections, $meteorObject) { }); } Collection.find = function(selector, options, callback) { + value = undefined; this.observe(collection.find(selector, options), true); return values; } Collection.findOne = function(selector, options, callback) { - value = new $meteorObject(collection,collection.find(selector,options).fetch()[0]); + value = new $meteorObject(collection, collection.find(selector,options).fetch()[0]); this.observe(collection.find(selector, options), false); return value; } - Collection.count = function(selector) { - return collection.find(selector).fetch().length; - } Collection.insert = function(values) { values = angular.copy(values); - cleanupAngularObject(values); + cleanupAngularObject(values); + depopulate(values); return collection.insert(values); } Collection.update = function(selector, updateValues) { updateValues = angular.copy(updateValues); - cleanupAngularObject(updateValues); - delete updateValues._id; + cleanupAngularObject(updateValues); + delete updateValues._id; - _.each(_.intersection(populates, _.keys(updateValues)), function(populate){ - updateValues[populate] = updateValues[populate]._id; - }); - return collection.update(selector, { + depopulate(updateValues); + var modifier = { $set : updateValues - }); + }; + var deletedKeys = getDeletedKeys(updateValues); + + if (deletedKeys) + modifier['$unset'] = deletedKeys; + return collection.update(selector, modifier); } Collection.remove = function(selector) { return collection.remove(selector); @@ -173,7 +231,7 @@ function($rootScope, MeteorCollections, $meteorObject) { } return CollectionFactory; -}]); +}]); /** Removes AngularJS transient properties from Object tree */ cleanupAngularObject = function(value) {