Skip to content

Commit

Permalink
Updates readme. Adds unsubscribe method and correctly implements the …
Browse files Browse the repository at this point in the history
…way nosub messages are handled.
  • Loading branch information
Eddie Flores committed Feb 24, 2013
1 parent 691e18b commit a10ca24
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 66 deletions.
29 changes: 26 additions & 3 deletions README.md
@@ -1,8 +1,7 @@
Meteor-DDP
==========

A promise-based Meteor DDP client for version `pre1`.
*Currently working to support DDP spec changes in Meteor 0.5.7.*
A promise-based Meteor DDP client for version `pre1` introduced in `Meteor 0.5.7`.

Dependencies
--------------------
Expand Down Expand Up @@ -38,7 +37,7 @@ createPlayer.done(function(playerId) {
});
});

// We can pipe it...
// We can pipe it... (Note: pipe is deprecated as of jQuery 1.8)
var createPlayer = ddp.call('createPlayer');
var joinGame = createPlayer.pipe(function(playerId) {
return ddp.call('joinGame', [playerId]);
Expand All @@ -65,6 +64,18 @@ ddp.subscribe('plyers', [gameId]).fail(function(err) {
});
```

* **unsubscribe(subscriptionName)** - Unsubscribes to data published on the server. Leaves local collection intact. *Returns -> Promise which resolves on successful unsubscription and fails if something went wrong in the process or subscription never existed.*

```js
var unsubPlayers = ddp.unsubscribe('players');
unsubPlayers.done(function() {
console.log("Successfully unsubscribed to players");
});
unsubPlayers.fail(function(err) {
console.log("Something went wrong, couldn't unsub players. ", err);
});
```

* **watch(collectionName, callback)** - Observe a collection and be notified whenever that collection changes via your callback. A copy of the modified document will be sent as argument to the callback. *Returns -> void*

```js
Expand All @@ -80,6 +91,18 @@ ddp.watch('players', function(changedDoc) {
});
```

* **getCollection(collectionName)** - *Returns -> An Object containing the locally stored collection.*

```js
ddp.getCollection('rooms'); // -> {id1: {document1}, id2: {document2}, ...}
```

* **getDocument(collectionName, documentId)** - *Returns -> The document with specified documentId belonging to collectionName.*

```js
ddp.getDocument('rooms', '4ec81e1b-2e16-42f4-a915-cc18ad7bdb0c') // -> {document}
```

* **close()** - Closes the WebSocket connection. *Returns -> void*

```js
Expand Down
159 changes: 96 additions & 63 deletions meteor-ddp.js
@@ -1,30 +1,14 @@
/* MeteorDdp - a client for DDP version pre1 */

MeteorDdp = function(wsUri) {
var MeteorDdp = function(wsUri) {
this.VERSIONS = ["pre1"];

this.wsUri = wsUri;
this.sock;
this.defs = {}; // { id => defObj}
this.watchers = {}; // { coll_name => [handler1, handler2, ...] }
this.collections = {}; // { coll_name => {docId => {}, docId => {}, ...}
};

MeteorDdp.prototype._handleData = function(data) {
alert("Hello, I handled your data, now what?");
};

MeteorDdp.prototype._notifyWatchers = function(collName, changedDoc, docId) {
changedDoc = JSON.parse(JSON.stringify(changedDoc)); // make a copy
changedDoc._id = docId; // id might be useful to watchers, attach it.

if(!this.watchers[collName]) {
this.watchers[collName] = [];
} else {
for(var i = 0; i < this.watchers[collName].length; i++) {
this.watchers[collName][i](changedDoc);
}
}
this.defs = {}; // { deferred_id => deferred_object }
this.subs = {}; // { pub_name => deferred_id }
this.watchers = {}; // { coll_name => [cb1, cb2, ...] }
this.collections = {}; // { coll_name => {docId => {doc}, docId => {doc}, ...} }
};

MeteorDdp.prototype._Ids = function() {
Expand Down Expand Up @@ -59,59 +43,65 @@ MeteorDdp.prototype.connect = function() {

console.log(msg);

switch(data.msg) {
case 'connected':
conn.resolve(data);
break;
case 'result':
self._resolveCall(data);
break;
case 'updated':
// TODO method call was acked
break;
case 'changed':
self._changeDoc(data);
break;
case 'added':
self._addDoc(data);
break;
case 'removed':
self._removeDoc(data);
break;
case 'ready':
self._resolveSubs(data);
break;
case 'nosub':
var error = (data.error) ? data.error : {};
self.defs[data.id].reject(error.reason || 'Subscription not found');
break;
case 'addedBefore':
// TODO implement when meteor supports ordered collections.
break;
case 'movedBefore':
// TODO
break;
switch (data.msg) {
case 'connected':
conn.resolve(data);
break;
case 'result':
self._resolveCall(data);
break;
case 'updated':
// TODO method call was acked
break;
case 'changed':
self._changeDoc(data);
break;
case 'added':
self._addDoc(data);
break;
case 'removed':
self._removeDoc(data);
break;
case 'ready':
self._resolveSubs(data);
break;
case 'nosub':
self._resolveNoSub(data);
break;
case 'addedBefore':
self._addDoc(data);
break;
case 'movedBefore':
// TODO
break;
}
};
return conn.promise();
};

MeteorDdp.prototype._resolveNoSub = function(data) {
if (data.error) {
var error = data.error;
this.defs[data.id].reject(error.reason || 'Subscription not found');
} else {
this.defs[data.id].resolve();
}
};

MeteorDdp.prototype._resolveCall = function(data) {
console.log('resolving call', data);
MeteorDdp.prototype._resolveCall = function(data) {
if (data.error) {
this.defs[data.id].reject(data.error.reason);
} else if (data.result) {
this.defs[data.id].resolve(data.result);
}
}
};

MeteorDdp.prototype._resolveSubs = function(data) {
var subIds = data.subs;
for (var i = 0; i < subIds.length; i++) {
this.defs[subIds[i]].resolve();
}
}
};

MeteorDdp.prototype._changeDoc = function(msg) {
var collName = msg.collection;
Expand All @@ -123,28 +113,55 @@ MeteorDdp.prototype._changeDoc = function(msg) {
if (fields) {
for (var k in fields) {
coll[id][k] = fields[k];
}
}
} else if (cleared) {
for (var i = 0; i < cleared.length; i++) {
var fieldName = cleared[i];
delete coll[id][fieldName];
}
}

var changedDoc = coll[id];
this._notifyWatchers(collName, changedDoc, id);
};

MeteorDdp.prototype._addDoc = function(msg) {
var collName = msg.collection;
var id = msg.id;
var id = msg.id;
if (!this.collections[collName]) {
this.collections[collName] = {};
}
/* NOTE: Ordered docs will have a 'before' field containing the id of
* the doc after it. If it is the last doc, it will be null.
*/
this.collections[collName][id] = msg.fields;

var changedDoc = this.collections[collName][id];
this._notifyWatchers(collName, changedDoc, id);
};

MeteorDdp.prototype._removeDoc = function(msg) {
var collName = msg.collection;
var id = msg.id;
var doc = this.collections[collName][id];

var docCopy = JSON.parse(JSON.stringify(doc));
docCopy.__wasDeleted = true;
delete this.collections[collName][id];
this._notifyWatchers(collName, docCopy, id);
};

MeteorDdp.prototype._notifyWatchers = function(collName, changedDoc, docId) {
changedDoc = JSON.parse(JSON.stringify(changedDoc)); // make a copy
changedDoc._id = docId; // id might be useful to watchers, attach it.

if (!this.watchers[collName]) {
this.watchers[collName] = [];
} else {
for (var i = 0; i < this.watchers[collName].length; i++) {
this.watchers[collName][i](changedDoc);
}
}
};

MeteorDdp.prototype._deferredSend = function(actionType, name, params) {
Expand All @@ -159,10 +176,11 @@ MeteorDdp.prototype._deferredSend = function(actionType, name, params) {
id: id
};

if(actionType === 'method') {
if (actionType === 'method') {
o.method = name;
} else if(actionType === 'sub') {
} else if (actionType === 'sub') {
o.name = name;
this.subs[name] = id;
}

this.send(o);
Expand All @@ -177,15 +195,30 @@ MeteorDdp.prototype.subscribe = function(pubName, params) {
return this._deferredSend('sub', pubName, params);
};

MeteorDdp.prototype.unsubscribe = function(pubName) {
this.defs[id] = new $.Deferred();
if (!this.subs[pubName]) {
this.defs[id].reject(pubName + " was never subscribed");
} else {
var id = this.subs[pubName];
var o = {
msg: 'unsub',
id: id
};
this.send(o);
}
return this.defs[id].promise();
};

MeteorDdp.prototype.watch = function(collectionName, cb) {
if(!this.watchers[collectionName]) {
if (!this.watchers[collectionName]) {
this.watchers[collectionName] = [];
}
this.watchers[collectionName].push(cb);
};

MeteorDdp.prototype.getCollection = function(collectionName) {
return this.collections[collectionName] || null;
return this.collections[collectionName] || null;
}

MeteorDdp.prototype.getDocument = function(collectionName, docId) {
Expand Down

0 comments on commit a10ca24

Please sign in to comment.