Created
July 2, 2011 17:23
-
-
Save crcn/1061377 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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