Skip to content

Instantly share code, notes, and snippets.

@crcn
Created July 2, 2011 17:23
Show Gist options
  • Save crcn/1061377 to your computer and use it in GitHub Desktop.
Save crcn/1061377 to your computer and use it in GitHub Desktop.
require('../../core/object');
require('../../core/pretty');
require('../../core/events');
require('../../core/pubsub');
require('../../core/bindable');
require('../../core/settings');
require('../../core/str');
require('../cookies');
require('../image');
var Spice = { apps: { } };
/**
* this is just a hell of a lot cleaner. Strings are a lot like magic numbers. They should be in one place, and controlled by a constant
*/
Spice.Events = {
CONNECT: 'connect',
RESULT: 'result',
ACCOUNT: 'account'
}
/**
* returns the host
*/
Spice.getHost = function(appName, protocol)
{
return (protocol || 'http:')+ '//'+appName+'.spice.io/';
}
/**
* opens a centered window. used for connect
*/
Spice.openWindow = function(url, title, width, height)
{
window.open(url, title,"width="+width+",height="+height+",left="+(screen.width/2-width/2)+",top="+(screen.height/2-height/2));
}
/**
* authorized the parent. happens after redirect
*/
Spice.authorizeParent = function()
{
if(!window.opener) return;
var hash = window.location.href.slice(window.location.href.indexOf('?')+1).split('&'),
data = {};
for(var i = hash.length; i--;)
{
var val = hash[i].split('=');
data[val[0]] = val[1];
}
window.opener.Spice.authorizedConnect(data);
window.close();
}
/**
* transforms a collection of data to another data type
*/
Spice.transformCollection = function(app, collectionOrItem, clazz)
{
if(collectionOrItem instanceof Array)
{
var transformed = [];
for(var i = collectionOrItem.length; i--;)
{
transformed.push(Spice.transformCollection(app, collectionOrItem[i], clazz));
}
return transformed;
}
return new clazz(app, collectionOrItem);
}
Spice.transformCollectionPartial = function(clazz)
{
return function(app, collectionOrItem)
{
return Spice.transformCollection(app, collectionOrItem, clazz)
}
}
Spice.authorizeParent();
/*if(this._app.profile()) params.accessToken = this._app.profile().accessToken;
this._requests[method].push({
uri: uri,
data: params,
callback: callback
})
if(this._requestTimeout) clearTimeout(this._requestTimeout);
this._requestTimeout = setTimeout(this.getMethod('_onRequestTimeout'), 500);*/
Spice.ServiceHandler = sk.EventEmitter.extend({
/**
*/
'override __construct': function(app)
{
this._app = app;
this._super();
this._requests = {POST:[],GET:[]};
},
/**
* makes a request to the spice.io server
*/
'_request': function(uri, params, method, callback, factory)
{
new Spice.Request(this._app, uri, params, method, callback, factory);
},
/**
*/
'_get': function(uri, params, callback, factory)
{
if(!callback)
{
callback = params;
params = undefined;
}
return this._request(uri, params, 'GET', callback, factory);
},
/**
*/
'_post': function(uri, params, callback, factory)
{
if(!callback)
{
callback = params;
params = undefined;
}
return this._request(uri, params, 'POST', callback, factory);
}
});
Spice.BindedData = sk.EventEmitter.extend({
'override __construct': function(app, data)
{
this._super();
this._app = app;
this._data = data;
Bindable.apply(this).proxy.subscribe('propertyChange', this.getMethod('onPropertyChange'));
this._ready = true;
},
'onPropertyChange': function(data)
{
//abstract...
}
});
Spice.GroupMetaData = Structr({
'override __construct': function(group, name, value)
{
this._super();
this._group = group;
this._name = name;
this.value(value);
},
'name': function()
{
return this._name;
},
'explicit value': {
get: function()
{
return this._value;
},
set: function(value)
{
this._value = value;
if(!this._group._ready) return;
clearTimeout(this._timeout);
this._timeout = setTimeout(this.getMethod('_onTimeout'), 500);
}
},
'_onTimeout': function()
{
var data = {};
data[this._name] = this.value();
this._group._save( {metadata: JSON.stringify(data) });
}
});
Spice.GroupFeed = Spice.BindedData.extend({
'override __construct': function(group, data)
{
this._group = group;
this.value(data.url || data.q);
this.category(data.categories);
this.service(data.services);
this.action(data.action);
this.id(data.id);
this._super();
},
'explicit bindable id': 'r',
'explicit bindable value': 1,
'explicit bindable category': 1,
'explicit bindable service': 1,
'explicit bindable action': 1,
'remove': function()
{
this._group.removeFeed(this);
}
});
Spice.Group = Spice.BindedData.extend({
/**
*/
'override __construct': function(app, data, parent)
{
if(!data) data = {};
this._id = data._id;
this._janitor = new sk.Janitor();
this.name(data.name);
this.parentId(data.parentId);
this.type(data.type);
this.publicName(data.publicName);
this.createdAt(new Date(data.createdAt));
this.parent(parent);
this._metadata = {};
if(data.metadata)
{
for(var property in data.metadata)
{
this.metadata(property, data.metadata[property]);
}
}
var feeds = [];
if(data.feeds)
{
for(var i = data.feeds.length; i--;)
{
feeds.push(new Spice.GroupFeed(this, data.feeds[i]));
}
}
this._feeds = Bindable.apply(feeds);
this._super(app);
},
'feeds': function()
{
return this._feeds;
},
/**
* returns, or sets metadata for group
*/
'metadata': function(name, value)
{
var md = this._metadata[name];
if(!md)
{
md = this._metadata[name] = new Spice.GroupMetaData(this, name, value)
}
else
if(value)
{
md.value(value);
}
return md;
},
/**
* the parent group
*/
'explicit parent': 1,
/**
*/
'explicit type': 1,
/**
* the name of the group
*/
'explicit bindable name': 1,
/**
* the parent id of THIS group
*/
'explicit bindable parentId': 1,
/**
* the public name for the group - e.g: xxx.spice.io/new/footballFans
*/
'explicit bindable publicName': 1,
/**
* when this group was created
*/
'explicit bindable createdAt': 1,
/**
* adds current feeds. strings to evaluate, or json objects
*/
'addFeeds': function(feedsAr)
{
var self = this;
this._app.api.watchFeeds(this._id, JSON.stringify(feedsAr instanceof Array ? feedsAr : [feedaAr]), function(feeds)
{
var newFeeds = [];
for(var i = feeds.length; i--;)
{
newFeeds.push(new Spice.GroupFeed(self, feeds[i]));
}
self.feeds().push.apply(self.feeds(), newFeeds);
})
},
/**
* removes a feed
*/
'removeFeed': function(feed)
{
var i = this.feeds().indexOf(feed);
if(i > -1)
{
this.feeds().splice(i, 1);
}
this._app.api.unwatchFeeds(this._id, [feed.id()], function(){});
},
/**
* returns child groups
*/
'children': function()
{
if(this._children) return this._children;
var children = this._children = sk.Bindable.apply([]),
self = this;
this._app.api.getGroups(this._id ? { parent: this._id } : { type: 'scene' }, function(){}, function(app, rawGroups)
{
var groups = [];
for(var i = rawGroups.length; i--;)
{
var group = new Spice.Group(this._app, rawGroups[i], self);
self.addDisposable(group);
groups.push(group);
}
self.children().push.apply(children, groups);
return self.children();
});
return children;
},
/**
* adds a child group
*/
'addChild': function(params)
{
if(this._id) params.parent = this._id;
var self = this;
this._app.api.saveGroup(params, function(group)
{
if(self._children) self.children().push(new Spice.Group(self._app, group, self));
});
},
/**
* removes a child group
*/
'removeChild': function(child)
{
if(this._children)
{
var i = this.children().indexOf(child);
if(i > -1) this.children().splice(i,1);
}
this._app.api.removeGroup(child._id, function(){});
},
/**
* removes THIS group from the parent
*/
'remove': function()
{
if(!this.parent())
{
console.warn('The parent does not exist for group');
return;
}
this.parent().removeChild(this);
},
/**
* when ANY properties change, we need to save it to the server
*/
'onPropertyChange': function(data)
{
var toSave = {};
toSave[data.name] = data.value;
this._save(toSave);
},
/**
* saves data then sends it up
*/
'_save': function(params)
{
if(!this._ready) return;
params._id = this._id;
var logStr = 'Saving Group: ';
for(var prop in params)
{
logStr += prop + '=' + params[prop] + ', ';
}
console.log(logStr);
this._app.api.saveGroup(params, function(){})
},
'addDisposable': function(value)
{
this._janitor.addDisposable(value);
},
'dispose': function()
{
console.log('Disposing Group: '+ this._id);
this._janitor.dispose();
}
});
Spice.Group.fromObject = Spice.transformCollectionPartial(Spice.Group);
Spice.Account = Spice.BindedData.extend({
/**
*/
'override __construct': function(profile, data)
{
this.id(data._id || data.accountId);
if(data.joined) this.joined(new Date(data.joined));
if(data.lastAccessed) this.lastAccessed(new Date(data.lastAccessed));
this.displayName(data.displayName);
this.type(data.type || data.service);
this._super(profile._app, data);
},
'explicit bindable id': 1,
'explicit bindable joined': 1,
'explicit bindable lastAccessed': 1,
'explicit bindable displayName': 1,
'explicit bindable type': 1
});
Spice.Profile = sk.Serializable('spice.io.profile', {
/**
*/
'override __construct': function(data, target)
{
this._janitor = new sk.Janitor();
if(data) this.fromJSON(data, target);
},
'rootGroup': function()
{
return this._root;
},
'accounts': function()
{
if(this._accounts) return this._accounts;
this._accounts = Bindable.apply([]);
var self = this;
self._app.api.getAccounts(function(){}, function(accounts)
{
var acc = [];
for(var i = accounts.length; i--;)
{
acc[i] = new Spice.Account(self, accounts[i]);
}
self.accounts().push.apply(self.accounts(), acc);
});
return this._accounts;
},
'fromJSON': function(data, target)
{
this._app = target._app;
this.accessToken = data.accessToken;
this.displayName = decodeURIComponent(data.displayName);
this.service = data.service;
this.addDisposable(this._root = new Spice.Group(Spice.io(target._app.name)));
this._data = data;
},
'toJSON': function()
{
return this._data;
},
'addDisposable': function(item)
{
this._janitor.addDisposable(item);
},
'dispose': function()
{
this._janitor.dispose();
}
});
Spice.Request = sk.EventEmitter.extend({
/**
* Constructor
* @param app the spice app. used for the accessToken, and uri prefix
* @param the uri to call
* @param method the request method: GET, POST, etc.
* @param the factory which transforms the data
*/
'override __construct': function(app, uri, params, method, callback, factory)
{
this._super();
this._app = app;
this._uri = uri;
this._params = params || {};
this._method = method || 'GET';
this._callback = callback;
this._factory = factory || function(app, data){ return data; }
//loaderup
this.load();
},
/**
* loads the request
*/
'load': function()
{
if(this._app.profile()) this._params.accessToken = this._app.profile().accessToken;
this._params.rand = Math.random();
var ops = {
type: this._method,
url: Spice.getHost(this._app.name) + this._uri,
data: this._params,
dataType: 'jsonp',
success: this.getMethod('onResult'),
error: function(e)
{
}
};
$.ajax(ops);
},
/**
* called when there is a resposnse
*/
'onResult': function(data)
{
//ffx issue
if(typeof data == 'string') data = jQuery.parseJSON(data);
if(data.result) data = this._factory(this._app, data.result);
if(this._callback) this._callback(data);
this.emit(Spice.Events.RESULT, data);
}
});
Spice.ConnectHandler = sk.EventEmitter.extend({
/**
*/
'override __construct': function(app)
{
this._super();
this._app = app;
//the connection handler has a few settings
sk.SettingManager.apply(this, app.name);
},
/**
*/
'explicit setting profile': {
set: function(value)
{
//dispose of the OLD profile if it exists
if(this._value) this._value.dispose();
this._value = value;
}
},
/**
* opens a new window so to connect the user
*/
'connect': function(serviceName, redirect, callback)
{
if(typeof redirect == 'function')
{
callback = redirect;
redirect = undefined;
}
if(!redirect)
{
redirect = window.location.toString();
}
if(redirect.indexOf('http://') == -1)
{
var locPath = window.location.toString().split('/');
locPath.pop();
redirect = locPath.join('/') + '/' + redirect;
}
console.log(redirect)
var connectUrl = Spice.getHost(this._app.name) + '/connect/'+serviceName + '?r=' + Math.random() + '&redirect='+ escape(redirect);
//link up the accounts IF logged in
if(this._app.profile())
{
connectUrl += '&accessToken='+this._app.profile().accessToken;
}
//open the connection window
Spice.openWindow(connectUrl, 'Connect', 800, 600);
var self = this;
//setup the callback
Spice.authorizedConnect = function(profile)
{
Spice.authorizedConnect = undefined;
console.log(profile);
self.profile(new Spice.Profile(profile, self));
self.emit(Spice.Events.CONNECT, profile);
}
return this;
},
/**
* logs the user out
*/
'logout': function()
{
console.log("Spice.io::logout");
this.profile(null);
}
});
Spice.Stream = { };
Spice.Stream.Item = Structr({
'__construct': function(app, data)
{
}
})
Spice.Stream.Article = Spice.Stream.Item.extend({
});
Spice.Stream.Video = Spice.Stream.Item.extend({
});
Spice.Stream.Post = Spice.Stream.Item.extend({
});
Spice.Stream.Image = Spice.Stream.Item.extend({
});
Spice.Stream.ItemFactory = Structr({
/**
*/
'__construct': function(app)
{
this._app = app;
this._classes = {};
this.setClass({
post: Spice.Stream.Post,
image: Spice.Stream.Image,
video: Spice.Stream.Video,
article: Spice.Stream.Article
});
},
/**
*/
'setClass': function(itemType, clazz)
{
//itemtyupe can be an object for batch replacements
if(typeof itemType == 'object')
{
for(var type in itemType)
{
this.setClass(type, itemType[type]);
}
return;
}
this._classes[itemType] = clazz;
},
/**
*/
'getNewItem': function(data, touchCallback)
{
//could be a collection of items
if(data instanceof Array)
{
var collection = [];
for(var i = data.length; i--;)
{
var item = this.getNewItem(data[i]);
if(!item) continue;
colleciton.push(item);
}
return collection;
}
var clazz = this._classes[itemType];
if(!clazz)
{
console.warn('class '+itemType+' does NOT exist!');
return null;
}
return new clazz(this._app, data);
}
});
Spice.api = Spice.ServiceHandler.extend({
/**
* @param app used to log the user in
*/
'override __construct': function(app)
{
this._super(app);
},
/**
* returns all accounts for given profile
*/
'getAccounts': function(callback, factory)
{
this._get('accounts', callback, factory);
},
/**
* saves an article
*/
'saveArticle': function (groupId, articleUrl, callback)
{
if(!callback)
{
callback = tags;
tags = undefined;
}
var uri = 'save/article/' + groupId + '/' + encodeURIComponent(articleUrl);
this._get(uri, callback);
},
/**
* removes an article
*/
'removeArticle': function(groupId, articleUrlOrId, callback)
{
if(!callback)
{
callback = tags;
tags = undefined;
}
var uri = 'remove/article/' + groupId + '/' + encodeURIComponent(articleUrlOrId);
this._get(uri, callback);
},
/**
* unsubscribes from a newsletter
*/
'unsubscribe': function (contact, uri, method, callback)
{
this._get('unsubscribe/' + contact + '/' + method + '/' + uri, callback);
},
/**
* subscribes to a particular uri
*/
'subscribe': function(contact, uri, method, frequency, callback)
{
if(!callback)
{
callback = frequency;
frequency = undefined;
}
if(!frequency) frequency = 'weekly';
this._get('subscribe/' + frequency + '/' + contact + '/' + method + '/' + uri, callback);
},
'follow': function(service, data, callback)
{
this._get('follow/' + service, data, callback);
},
'unfollow': function(service, data, callback)
{
this._get('unfollow/' + service, data, callback);
},
'followable': function(service, callback)
{
this._get('followable/' + service, callback);
},
'following': function(service, data, callback)
{
this._get('following/' + service, data, callback);
},
/**
* returns input regex for supported services
*/
'getQuickInputRegex': function (callback)
{
this._get('quick/input/regex', callback);
},
/**
* unsubscribes all users from a particular group. group needs to be owned by user
*/
'updateAllSubscriptions': function (groupId, method, frequency, callback)
{
this._get('update/all/subscriptions/' + frequency + '/' + method + '/' + groupId, callback);
},
/**
* returns the subscribers for given group
*/
'getSubscribers': function (groupId, method, callback)
{
this._get('subscribers/' + method + '/' + groupId, callback);
},
/**
* returns groups
*/
'getGroups': function(params, callback, factory)
{
this._get('groups', params, callback, factory || Spice.Group.fromObject);
},
/**
* checks if a group exists
*/
'groupExists': function(groupIdOrPublicName, callback)
{
this._get('group/exists/' + groupIdOrPublicName, callback);
},
/**
* saves a group, and its data
*/
'saveGroup': function (data, callback)
{
this._post('save/group', data, callback);
},
/**
* removes a group
*/
'removeGroup': function (groupId, callback)
{
this._get('remove/group/' + groupId, callback, Spice.Group.fromObject);
},
/**
* watches a feed for a group
*/
'watchFeeds': function (groupId, feeds, callback)
{
this._get('watch/feed/' + groupId, { feed: feeds.toString() }, callback);
},
/**
* updates a feed living in a group
*/
'updateFeed': function(groupId, feedId, feedData, callback)
{
this._get('update/feed/' + groupId + '/' + feedId, {feed: JSON.stringify(feedData)}, callback);
},
/**
* unwatches a feed from a group
*/
'unwatchFeeds': function(groupId, feeds, callback)
{
this._get('unwatch/feed/' + groupId, { feed: feeds.toString() }, callback);
},
/**
* loads streamed data
*/
'loadStream': function(type, uri, params, callback)
{
this._get(type + '/' + uri, params, callback, this._app.itemFactory.getNewItem);
}
});
Spice.app = sk.EventEmitter.extend({
/**
*/
'override __construct': function(name)
{
this._super();
//the app name
this.name = name;
},
/**
* initializes
*/
'init': function()
{
//need to make this instance bindable.
sk.Bindable.apply(this);
//the api
this.api = new Spice.api(this);
//the connection handler connects the USER account to the app
this._connectionHandler = new Spice.ConnectHandler(this);
//the item factory which creates stream data objects
this.itemFactory = new Spice.Stream.ItemFactory(this);
//this is just like the composite pattern :P
this.profile = this._connectionHandler.profile;
this.logout = this._connectionHandler.logout;
return this;
},
/**
* connects the user to the app
*/
'connect': function(service, redirect, callback)
{
if(typeof redirect == 'function')
{
callback = redirect;
redirect = undefined;
}
//if the callback exists, the subscribe once - throw it away after an account has been found
if(callback) this.profile.subscribeOnce(callback);
//and then connect the account up
return this._connectionHandler.connect(service, redirect);
}
})
Spice.io = function(appName)
{
if(Spice.apps[appName]) return Spice.apps[appName];
//instantiate first
var app = Spice.apps[appName] = new Spice.app(appName);
//then init, because serialized objects *might* pull the spice app
return app.init();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment