407 lines
12 KiB
JavaScript
407 lines
12 KiB
JavaScript
/**
|
||
* @preserve
|
||
* Jribbble v2.0.3 | Tue May 26 21:23:05 2015 -0400
|
||
* Copyright (c) 2015, Tyler Gaw me@tylergaw.com
|
||
* Released under the ISC-LICENSE
|
||
*/
|
||
;(function($, window, document, undefined) {
|
||
'use strict';
|
||
|
||
// This is our public access point.
|
||
$.jribbble = {};
|
||
|
||
var ACCESS_TOKEN = null;
|
||
var API_URL = 'https://api.dribbble.com/v1';
|
||
|
||
// The types of shot lists that are available through the API.
|
||
// The default shot list–retrieved by shots()–is any type.
|
||
var SHOT_LIST_TYPES = [
|
||
'animated',
|
||
'attachments',
|
||
'debuts',
|
||
'playoffs',
|
||
'rebounds',
|
||
'teams'
|
||
];
|
||
|
||
// Rather than pepper the code with error messages, we use this object to store
|
||
// them. There are also a number of convenience methods here for creating
|
||
// common error messages for different resources.
|
||
var ERROR_MSGS = {
|
||
token: 'Jribbble: Missing Dribbble access token. Set one with ' +
|
||
'$.jribbble.accessToken = YOUR_ACCESS_TOKEN. If you do not have an ' +
|
||
'access token, you must register a new application at ' +
|
||
'https://dribbble.com/account/applications/new',
|
||
|
||
singular: function(str) {
|
||
return str.substr(0, str.length - 1);
|
||
},
|
||
|
||
idRequired: function(resource) {
|
||
return 'Jribbble: You have to provide a ' + this.singular(resource) +
|
||
' ID. ex: $.jribbble.%@("1234").'.replace(/%@/g, resource);
|
||
},
|
||
|
||
subResource: function(resource) {
|
||
return 'Jribbble: You have to provide a ' + this.singular(resource) +
|
||
' ID to get %@. ex: $.jribbble.%@("1234").%@()'.replace(/%@/g, resource);
|
||
},
|
||
|
||
// A shot ID is required to get shot sub-resources.
|
||
shotId: function(resource) {
|
||
return 'Jribbble: You have to provide a shot ID to get %@. ex: ' +
|
||
' $.jribbble.shots("1234").%@()'.replace(/%@/g, resource);
|
||
},
|
||
|
||
commentLikes: 'Jribbble: You have to provide a comment ID to get likes. ex: ' +
|
||
' $.jribbble.shots("1234").comments("456").likes()'
|
||
};
|
||
|
||
// A number of resources do not allow for bare calls to them, they require a
|
||
// resource ID. If the ID is not provided, things will not function so we err.
|
||
var checkId = function(id, resource) {
|
||
if (!id || typeof id === 'object') {
|
||
throw new Error(ERROR_MSGS.idRequired(resource));
|
||
} else {
|
||
return id;
|
||
}
|
||
};
|
||
|
||
// Many top-level resources–users,buckets, etc–have subresources that all behave
|
||
// in a similar way. We use this function to create new methods on the top-level
|
||
// object's prototype.
|
||
var createSubResources = function(subResources) {
|
||
var obj = {};
|
||
|
||
subResources.forEach(function(resource) {
|
||
obj[resource] = subResourceWithOpts.call(this, resource);
|
||
}.bind(this));
|
||
|
||
return obj;
|
||
};
|
||
|
||
// Provide an object of key: value params. Get back a URL encoded string if
|
||
// params has keys.
|
||
var parseParams = function(params) {
|
||
var p = $.param(params);
|
||
|
||
if (p) {
|
||
return '?' + p;
|
||
} else {
|
||
return '';
|
||
}
|
||
};
|
||
|
||
// Because we want our API to be as flexible as possible, there are a number
|
||
// of methods where we'll allow different types of arguments to be passed as
|
||
// the first argument and then we'll figure out here what the user meant.
|
||
var negotiateArgs = function(args) {
|
||
// If there's nothing here, just bail, that's OK. Calls like `shots()` are
|
||
// fine on their own.
|
||
if (args.length !== 0) {
|
||
var firstArg = args[0];
|
||
var type = typeof firstArg;
|
||
var params = {};
|
||
|
||
// If the first argument is a number or string, we're going to assume that
|
||
// the user wants a resource by id or name.
|
||
if (type === 'number' || type === 'string') {
|
||
var list = SHOT_LIST_TYPES.indexOf(firstArg);
|
||
|
||
// Shots can be retrieved by the shot name. The Dribbble API wants this
|
||
// passed as a query paramter, but as a conveinence, you can pass the
|
||
// name of a shot list to shots() and we'll check here if that name
|
||
// is a valid shot list name.
|
||
if (list > -1) {
|
||
params.list = firstArg;
|
||
} else {
|
||
params.resource = firstArg;
|
||
}
|
||
// If we see an object as the first parameter, we assume the user is
|
||
// providing options to be passed as query parameters.
|
||
} else if (type === 'object') {
|
||
params = firstArg;
|
||
}
|
||
|
||
return params;
|
||
}
|
||
};
|
||
|
||
// All initial Jribbble API methods–shots, buckets, project, etc–share common
|
||
// functionality. We mix this base functionality into each one.
|
||
var jribbbleBase = function() {
|
||
var ext = $.extend({}, $.Deferred());
|
||
|
||
var Queue = function() {
|
||
this.methods = [];
|
||
this.response = null;
|
||
this.flushed = false;
|
||
|
||
this.add = function(fn) {
|
||
if (this.flushed) {
|
||
fn(this.scope);
|
||
} else {
|
||
this.methods.push(fn);
|
||
}
|
||
};
|
||
|
||
this.flush = function(scope) {
|
||
if (this.flushed) {
|
||
return;
|
||
}
|
||
|
||
this.scope = scope;
|
||
this.flushed = true;
|
||
|
||
while(this.methods[0]) {
|
||
this.methods.shift()(scope);
|
||
}
|
||
|
||
return scope;
|
||
};
|
||
|
||
return this;
|
||
};
|
||
|
||
ext.queue = new Queue();
|
||
ext.url = API_URL;
|
||
|
||
ext.get = function() {
|
||
if (!ACCESS_TOKEN) {
|
||
console.error(ERROR_MSGS.token);
|
||
|
||
return false;
|
||
}
|
||
|
||
$.ajax({
|
||
type: 'GET',
|
||
url: this.url,
|
||
beforeSend: function(jqxhr) {
|
||
jqxhr.setRequestHeader('Authorization', 'Bearer ' + ACCESS_TOKEN);
|
||
},
|
||
success: function(res) {
|
||
this.resolve(res);
|
||
}.bind(this),
|
||
error: function(jqxhr) {
|
||
this.reject(jqxhr);
|
||
}.bind(this)
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
return ext;
|
||
};
|
||
|
||
// Because a number of API resources are set up the same way, we can create
|
||
// new Jribble API methods for them using currying. This extends the method
|
||
// with the JribbbleBase, adds a method to the queue, and sets the needed
|
||
// timeout for flushing the queue.
|
||
// See jribbble.buckets for example usage.
|
||
var resourceWithoutOpts = function(resource) {
|
||
return function(resourceId) {
|
||
$.extend(this, jribbbleBase());
|
||
|
||
this.queue.add(function(self) {
|
||
self.url += '/' + resource + '/' + resourceId;
|
||
});
|
||
|
||
setTimeout(function() {
|
||
this.queue.flush(this).get();
|
||
}.bind(this));
|
||
|
||
return this;
|
||
};
|
||
};
|
||
|
||
// Because a number of API resources are set up the same way, we can create
|
||
// new Jribble API methods for them using currying. This function returns a
|
||
// function that allows for creating URLS like:
|
||
// /resource/subresource/?foo=1&bar=2
|
||
var subResourceWithOpts = function(resource) {
|
||
return function(opts) {
|
||
this.queue.add(function(self) {
|
||
self.url += '/' + resource + '/' + parseParams(opts || {});
|
||
});
|
||
|
||
return this;
|
||
};
|
||
};
|
||
|
||
$.jribbble.shots = function(undefined, opts) {
|
||
var shotArgsNegotiated = negotiateArgs([].slice.call(arguments)) || {};
|
||
var shotsParams = opts || {};
|
||
|
||
// Because most shot subresources; likes, projects, buckets, etc. all do
|
||
// pretty much the same thing, we can avoid repeating code by using
|
||
// currying. For each subresource we call this function and pass it the name
|
||
// of the resource, it returns jribbble API method for that resource.
|
||
// Yay programming!
|
||
var shotSubResource = function(resource) {
|
||
return function(undefined, opts) {
|
||
var negotiated = negotiateArgs([].slice.call(arguments)) || {};
|
||
var params = opts || {};
|
||
|
||
this.queue.add(function(self) {
|
||
if (!self.shotId) {
|
||
throw new Error(ERROR_MSGS.shotId(resource));
|
||
}
|
||
|
||
self.url += '/' + resource + '/';
|
||
|
||
if (negotiated.resource) {
|
||
self.url += negotiated.resource;
|
||
delete negotiated.resource;
|
||
}
|
||
|
||
self.url += parseParams($.extend(negotiated, params));
|
||
});
|
||
|
||
return this;
|
||
};
|
||
};
|
||
|
||
var Shots = function() {
|
||
$.extend(this, jribbbleBase());
|
||
|
||
this.url += '/shots/';
|
||
|
||
this.queue.add(function(self) {
|
||
if (shotArgsNegotiated.resource) {
|
||
self.shotId = shotArgsNegotiated.resource;
|
||
self.url += shotArgsNegotiated.resource;
|
||
delete shotArgsNegotiated.resource;
|
||
}
|
||
|
||
self.url += parseParams($.extend(shotArgsNegotiated, shotsParams));
|
||
});
|
||
|
||
// Jribbble seems to need an async queue, because we need to run the
|
||
// server request at the end of the chain, but we will never know how
|
||
// long the chain is. This is a super hack way of "waiting" to make sure
|
||
// the queue is stocked before we flush it.
|
||
setTimeout(function() {
|
||
this.queue.flush(this).get();
|
||
}.bind(this));
|
||
|
||
return this;
|
||
};
|
||
|
||
Shots.prototype.attachments = shotSubResource('attachments');
|
||
Shots.prototype.buckets = shotSubResource('buckets');
|
||
Shots.prototype.likes = shotSubResource('likes');
|
||
Shots.prototype.projects = shotSubResource('projects');
|
||
Shots.prototype.rebounds = shotSubResource('rebounds');
|
||
|
||
// Comments is a slightly different subresource because it has it's own
|
||
// likes subresource. Comments shares a number of things with the other
|
||
// shot subresources, but I haven't been able to figure out how to use
|
||
// the shotSubResource currying function here to reduce repitition because
|
||
// of the likes subresource.
|
||
// I think I could get that to work if I created comments as a new Object
|
||
// like comments = new Comments(). Then likes could be added to the
|
||
// prototype of the Comments instance?
|
||
// TODO: Figure that out.
|
||
Shots.prototype.comments = function(undefined, opts) {
|
||
var commentsArgsNegotiated = negotiateArgs([].slice.call(arguments)) || {};
|
||
var commentsParams = opts || {};
|
||
|
||
this.queue.add(function(self) {
|
||
if (!self.shotId) {
|
||
throw new Error(ERROR_MSGS.shotId('comments'));
|
||
}
|
||
|
||
self.url += '/comments/';
|
||
|
||
// If we're looking for a specific comment by its ID.
|
||
if (commentsArgsNegotiated.resource) {
|
||
self.commentId = commentsArgsNegotiated.resource;
|
||
self.url += commentsArgsNegotiated.resource + '/';
|
||
delete commentsArgsNegotiated.resource;
|
||
}
|
||
|
||
self.url += parseParams($.extend(commentsArgsNegotiated, commentsParams));
|
||
});
|
||
|
||
this.likes = function(opts) {
|
||
var params = opts || {};
|
||
|
||
this.queue.add(function(self) {
|
||
if (!self.commentId) {
|
||
throw new Error(ERROR_MSGS.commentLikes);
|
||
}
|
||
|
||
self.url += 'likes/' + parseParams(params);
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
return this;
|
||
};
|
||
|
||
return new Shots();
|
||
};
|
||
|
||
$.jribbble.teams = function(id) {
|
||
var resource = 'teams';
|
||
var resourceId = checkId(id, resource);
|
||
var Teams = resourceWithoutOpts.call(this, resource);
|
||
|
||
Teams.prototype = createSubResources.call(this, [
|
||
'members',
|
||
'shots'
|
||
]);
|
||
|
||
return new Teams(resourceId);
|
||
};
|
||
|
||
$.jribbble.users = function(id) {
|
||
var resource = 'users';
|
||
var resourceId = checkId(id, resource);
|
||
var Users = resourceWithoutOpts.call(this, resource);
|
||
|
||
Users.prototype = createSubResources.call(this, [
|
||
'buckets',
|
||
'followers',
|
||
'following',
|
||
'likes',
|
||
'projects',
|
||
'shots',
|
||
'teams'
|
||
]);
|
||
|
||
Users.prototype.isFollowing = function(targetUser) {
|
||
this.queue.add(function(self) {
|
||
self.url += '/following/' + targetUser;
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
return new Users(resourceId);
|
||
};
|
||
|
||
$.jribbble.buckets = function(id) {
|
||
var resource = 'buckets';
|
||
var resourceId = checkId(id, resource);
|
||
var Buckets = resourceWithoutOpts.call(this, resource);
|
||
Buckets.prototype = createSubResources.call(this, ['shots']);
|
||
|
||
return new Buckets(resourceId);
|
||
};
|
||
|
||
$.jribbble.projects = function(id) {
|
||
var resource = 'projects';
|
||
var resourceId = checkId(id, resource);
|
||
var Projects = resourceWithoutOpts.call(this, resource);
|
||
Projects.prototype = createSubResources.call(this, ['shots']);
|
||
|
||
return new Projects(resourceId);
|
||
};
|
||
|
||
$.jribbble.setToken = function(token) {
|
||
ACCESS_TOKEN = token;
|
||
return this;
|
||
};
|
||
})(jQuery, window , document);
|