Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active December 23, 2015 00:39
Show Gist options
  • Save CMCDragonkai/6555321 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/6555321 to your computer and use it in GitHub Desktop.
JS: AngularJS Disqus Comments & Comment Count Directive
define(['angular'], function(angular){
'use strict';
//TODO:
//1. Convert to JSONP
//2. Use a cache shim like store.js
/*
<div
disqus-thread-dir
disqus-shortname="{{dreamItAppConfig.apiKeys.disqusShortname}}"
disqus-identifier="{{idea.id}}"
disqus-title="{{idea.title}}"
disqus-url="{{baseUrl + 'ideas/' + idea.id + '/' + idea.titleUrl}}"
disqus-developer="true"
></div>
<span
disqus-comment-count-dir
disqus-shortname="{{dreamItAppConfig.apiKeys.disqusShortname}}"
disqus-api-key="{{dreamItAppConfig.apiKeys.disqusApiKey}}"
disqus-ident="{{idea.id}}"
disqus-cache="commentCache"
></span>
*/
angular.module('Directives')
.provider('DisqusServ', function(){
var disqusGlobalConfig = {};
/**
* Setup "backup" global configuration for Disqus from the run
* portion of AngularJS. It means you don't have to define
* common parameters everytime you declare a disqus directive.
* This is the backup configuration object. If you miss any parameters
* in the directive declaration, the directives/service will look into this
* object to see if they exist. If you declare parameters, they will take
* precedence over this object.
* @param {object} params Object can only contain:
* {shortname, apiKey, categoryId, containerId, developer, cache}
* @return {Void}
*/
this.setupGlobalConfiguration = function(params){
disqusGlobalConfig = params;
};
this.$get = [
'$window',
'$document',
'$resource',
function($window, $document, $resource){
var disqusService = {
/**
* Boolean switch for whether Disqus config and script has been loaded
* @type {Boolean}
*/
disqusLoaded: false,
/**
* Setup the global configuration for Disqus.
* Use this before loading the Disqus script.
* This should only run once at the startup of the first Disqus thread module.
* Everything is optional except disqusShortname.
* @param {String} disqusShortname Forum name
* @param {String} disqusIdentifier Thread id
* @param {String} disqusTitle Title of the thread
* @param {String} disqusUrl Absolute URL of the thread
* @param {Integer} disqusCategoryId Optional category ID.
* @param {String} disqusContainerId Optional container CSS id, defaults to disqus_thread. Not documented.
* @param {Boolean} disqusDeveloper Optional boolean to allow the usage of disqus when working on localhost
* @return {Void}
*/
setupInitialConfig: function(disqusShortname, disqusIdentifier, disqusTitle, disqusUrl, disqusCategoryId, disqusContainerId, disqusDeveloper){
$window.disqus_shortname = disqusShortname;
if(disqusIdentifier){
$window.disqus_identifier = disqusIdentifier;
}
if(disqusTitle){
$window.disqus_title = disqusTitle;
}
if(disqusUrl){
$window.disqus_url = disqusUrl;
}
if(disqusCategoryId){
$window.disqus_category_id = disqusCategoryId;
}
if(disqusContainerId){
$window.disqus_container_id = disqusContainerId;
}
if(disqusDeveloper){
$window.disqus_developer = 1;
}
},
/**
* Loads the Disqus script. Embeds it into the head or body.
* This has to be loaded after Disqus id and url are setup.
* Will only be loaded once.
* @param {String} disqusShortname
* @return {Void}
*/
loadDisqusScript: function(disqusShortname){
var disqus = $document[0].createElement('script');
disqus.type = 'text/javascript';
disqus.async = true;
disqus.src = '//' + disqusShortname + '.disqus.com/embed.js';
($document[0].getElementsByTagName('head')[0] || $document[0].getElementsByTagName('body')[0]).appendChild(disqus);
},
/**
* Resets disqus upon each iteration of the disqus directive executing. Thus allowing dynamic disqus
* changing in a single page application. Note that it is currently impossible to have more than
* one disqus comments thread run at the same page. Currently it seems only identifier, title and
* url can be refreshed. The other parameters will stay permanent across the application.
* @param {String} disqusIdentifier Thread id
* @param {String} disqusTitle Thread title
* @param {String} disqusUrl Absolute url to the thread
* @return {Void}
*/
resetDisqus: function(disqusIdentifier, disqusTitle, disqusUrl){
$window.DISQUS.reset({
reload: true,
config: function(){
this.page.identifier = disqusIdentifier;
this.page.title = disqusTitle;
this.page.url = disqusUrl;
}
});
},
/**
* Implements the disqus thread. Recall this whenever you need to change to a different disqus thread.
* This will take backup parameters from the backup config.
* @param {String} disqusShortname Forum name
* @param {String} disqusIdentifier Thread id
* @param {String} disqusTitle Title of the thread
* @param {String} disqusUrl Absolute URL of the thread
* @param {Integer} disqusCategoryId Optional category ID.
* @param {String} disqusContainerId Optional container CSS id, defaults to disqus_thread. Not documented.
* @param {Boolean} disqusDeveloper Optional boolean to allow the usage of disqus when working on localhost
* @return {Void}
*/
implementDisqus: function(disqusShortname, disqusIdentifier, disqusTitle, disqusUrl, disqusCategoryId, disqusContainerId, disqusDeveloper){
if(typeof disqusShortname === 'undefined') disqusShortname = disqusGlobalConfig.shortname;
if(typeof disqusCategoryId === 'undefined') disqusCategoryId = disqusGlobalConfig.categoryId;
if(typeof disqusContainerId === 'undefined') disqusContainerId = disqusGlobalConfig.containerId;
if(typeof disqusDeveloper === 'undefined') disqusDeveloper = disqusGlobalConfig.developer;
if(!this.disqusLoaded){
this.setupInitialConfig(
disqusShortname,
disqusIdentifier,
disqusTitle,
disqusUrl,
disqusCategoryId,
disqusContainerId,
disqusDeveloper
);
this.loadDisqusScript(disqusShortname);
this.disqusLoaded = true;
}else{
this.resetDisqus(
disqusIdentifier,
disqusTitle,
disqusUrl
);
}
},
/**
* Resource container for the Disqus API.
* @type {Object}
*/
disqusApi: null,
/**
* Setup the disqus API resource.
* This will take parameters from the global config if none are passed in.
* @param {String} disqusShortname
* @param {String} disqusApiKey
* @return {Void}
*/
setupApi: function(disqusShortname, disqusApiKey){
if(typeof disqusShortname === 'undefined') disqusShortname = disqusGlobalConfig.shortname;
if(typeof disqusApiKey === 'undefined') disqusApiKey = disqusGlobalConfig.apiKey;
if(!this.disqusApi){
this.disqusApi = $resource('http://disqus.com/api/3.0/:resource/:action.json',
{
'api_key': disqusApiKey,
'forum': disqusShortname
}
);
}
},
/**
* Object container for the Disqus Cache. You can use any cache service
* you want as long as the object contains 2 methods:
* 1. get(key, callback(key, value)) - where callback should be passed in with the (key, value)
* of whatever that is being extracted from the cache, this is optional but recommended
* 2. put(key, value) - where value can be an object
* This directive does not set any expiry time on the cache, your cache
* service should either set a global expiry time for this particular
* cache section, or provide a wrapper/facade object that sets the expiry
* time when put() is called.
* @type {Object}
*/
disqusCache: null,
/**
* Sets up the cache object. Only one cache object will be used.
* So the cached object can be shared across all directive instances.
* @param {Object} cache Cache object
* @return {Void}
*/
setupDisqusCache: function(cache){
if(typeof cache === 'undefined') cache = disqusGlobalConfig.cache;
if(!this.disqusCache){
this.disqusCache = cache;
}
},
/**
* This will check if any of the config object's properties are empty strings,
* if any of them are, the config object is therefore not ready.
* This is to allow for asynchronous configuration from AJAX loaded data or deferred data.
* Of course you have to make sure all your directive properties are loaded with some value
* or else disqus will never be implemented.
* @param {Object} object The combined config object
* @return {Boolean}
*/
isConfigObjectReady: function(object){
for(var key in object){
if(object[key] === ''){
return false;
}
}
return true;
}
};
return disqusService;
}
];
})
/**
* Disqus Thread Directive
* Just declare this directive whenever you want a disqus thread.
* Only one is allowed to exist at any point in the page.
* Intelligently resets this for single page applications.
* @attribute {Void} disqus-thread-dir Initialise Directive
* @attribute {String} disqus-shortname Forum name
* @attribute {String} disqus-identifier Thread identifier
* @attribute {String} disqus-title Thread title
* @attribute {String} disqus-url Thread url
* @attribute {String} disqus-category-id Thread category - not changeable across disqus instances
* @attribute {String} disqus-container-id CSS container id - not changeable across disqus instances
* @attribute {String} disqus-developer For localhost development - not changeable across disqus instances
*/
.directive('disqusThreadDir', [
'$location',
'$timeout',
'DisqusServ',
function($location, $timeout, DisqusServ){
return {
replace: true,
template: '<div id="{{disqusContainerId}}"></div>',
scope:{
disqusShortname: '@',
disqusIdentifier: '@',
disqusTitle: '@',
disqusUrl: '@',
disqusCategoryId: '@',
disqusContainerId: '@',
disqusDeveloper: '@'
},
link: function(scope, element, attributes){
var disqusConfig = {};
scope.$watch(
function(){
//default url
if(!scope.disqusUrl){
scope.disqusUrl = $location.absUrl();
}
//default container id
if(!scope.disqusContainerId){
scope.disqusContainerId = 'disqus_thread';
}
disqusConfig.shortname = scope.disqusShortname;
disqusConfig.identifier = scope.disqusIdentifier;
disqusConfig.title = scope.disqusTitle;
disqusConfig.url = scope.disqusUrl;
disqusConfig.categoryId = scope.disqusCategoryId;
disqusConfig.containerId = scope.disqusContainerId;
disqusConfig.developer = scope.disqusDeveloper;
return disqusConfig;
},
function(disqusConfig){
//make sure the config object is populated
if(DisqusServ.isConfigObjectReady(disqusConfig)){
//run disqus after the template has been replaced
//or else the container id would not have been rendered
//by the time disqus executes
$timeout(function(){
DisqusServ.implementDisqus(
disqusConfig.shortname,
disqusConfig.identifier,
disqusConfig.title,
disqusConfig.url,
disqusConfig.categoryId,
disqusConfig.containerId,
disqusConfig.developer
);
}, 0, false);
}
},
true
);
}
};
}
])
/**
* Disqus Comment Count Directive.
* You can use either link or ident, don't use both.
* This makes use of caching since Disqus limits their rate use to 1000 requests/hour.
* I recommend using angular-cache (http://jmdobry.github.io/angular-cache/)
* However it is up to you to pass in a cache object that fulfills the API requirements
* of: get(key, callback(key, value)) AND put(key, value)
* The callback function should be called with the key and value of whatever object is currently
* being requested. The cache is of course optional.
* Also make sure to write {{commentCount}} inside the template for the directive so you'll get
* an actual number.
* @attribute {Void} disqus-comment-count-dir Initialise Directive
* @attribute {String} disqus-shortname Forum name
* @attribute {String} disqus-api-key Public API Disqus Key
* @attribute {String} disqus-link Link to be used as the thread identifier
* @attribute {String} disqus-ident ID to be used as the thread identifier
* @attribute {Object Expression} disqus-cache Expression bound cache object
* @attribute {String} disqus-log Determine whether directive should log status messages (string true or false)
*/
.directive('disqusCommentCountDir', [
'$log',
'DisqusServ',
function($log, DisqusServ){
return {
scope: {
disqusShortname: '@',
disqusApiKey: '@',
disqusLink: '@',
disqusIdent: '@',
disqusCache: '&'
},
link: function(scope, element, attributes){
var identifierConfig = {};
scope.$watch(
function(){
identifierConfig.shortname = scope.disqusShortname;
identifierConfig.apiKey = scope.disqusApiKey;
identifierConfig.link = scope.disqusLink;
identifierConfig.ident = scope.disqusIdent;
identifierConfig.cache = scope.disqusCache;
identifierConfig.logging = attributes.disqusLog;
return identifierConfig;
},
function(identifierConfig){
var cacheKey,
previousCachedCommentCount;
if(DisqusServ.isConfigObjectReady(identifierConfig)){
//cancel logging if log was not passed as 'true'
if(!identifierConfig.logging || identifierConfig.logging === 'false'){
$log = {
info: angular.noop,
error: angular.noop
};
}
DisqusServ.setupApi(identifierConfig.shortname, identifierConfig.apiKey);
DisqusServ.setupDisqusCache(identifierConfig.cache());
//if the cache exists, we're going to try to get the comment count from cache first
if(DisqusServ.disqusCache){
cacheKey = (identifierConfig.link || identifierConfig.ident);
//assign the comment count and provide a callback that can be optionally
//called in order to allow the previously cached item
//to be remembered in case we need it even though it expired
scope.commentCount = DisqusServ.disqusCache.get(cacheKey, function(oldValue){
previousCachedCommentCount = oldValue;
});
}
//if comment count was not extracted from the cache, we're going to call Disqus
//commentCount may be 0 falsy value, so we're checking for undefined instead
if(typeof scope.commentCount === 'undefined'){
var successResponse = function(response){
scope.commentCount = response.response.posts;
//cache the comment
if(DisqusServ.disqusCache){
DisqusServ.disqusCache.put(cacheKey, response.response.posts);
}
};
var failResponse = function(response){
if(response.data.code == 2){
$log.info('Disqus thread or forum has not yet been created. Therefore there are zero comment.');
scope.commentCount = 0;
}else if(response.data.code == 13 || response.data.code == 14){
$log.info('Exceeded Disqus rate limit for comment count. Showing zero instead.');
if(previousCachedCommentCount){
scope.commentCount = previousCachedCommentCount;
}else{
scope.commentCount = 0;
}
}else if(response.data.code == 5){
$log.error('Invalid Disqus API Key');
}
};
//it is possible to use the link or ident, but don't use both
if(identifierConfig.link){
DisqusServ.disqusApi.get(
{
resource: 'threads',
action: 'details',
thread: 'link:' + identifierConfig.link
},
successResponse,
failResponse
);
}else if(identifierConfig.ident){
DisqusServ.disqusApi.get(
{
resource: 'threads',
action: 'details',
thread: 'ident:' + identifierConfig.ident
},
successResponse,
failResponse
);
}
}
}
},
true
);
}
};
}
]);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment