Created
February 25, 2013 02:18
-
-
Save dgs700/5026933 to your computer and use it in GitHub Desktop.
First Angular.js implementation example. Tibbr plugin components "Like" and "Follow" that were previously handled with untestable jQuery spaghetti refactored to Angular conventions. Besides its main role as a tight data binding (MVVM) framework, Angular is showing promise for creating reusable and exportable UI components without too much overhead.
This file contains hidden or 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
//Tibbr plugins html directives and associated controllers | |
(function () { | |
'use strict'; | |
//declare the plugins module with dependancies | |
var tibbrPlugins = angular.module('TibbrPlugins', ['TibbrFilters', 'Tibbr', 'EventsBus', 'PageBus', 'TibbrAPI']); | |
//code to run when a like tag is encountered | |
tibbrPlugins.directive('tibrLike', function factory($window) { | |
var directiveDefinitionObject = { | |
//template: '<div>hi</div>', | |
templateUrl: 'partials/like_button.html', | |
replace: true, | |
scope: {}, | |
controller: 'LikeController', | |
//transclude: true, | |
link: function postLink(scope, elm, attrs) { | |
//pull the resource info form the tag attributes | |
scope.resource = { | |
id: attrs.ogId || null, | |
key: attrs.ogKey || null, | |
url: attrs.ogUrl || $window.location.href, | |
title: attrs.ogTitle || '', | |
image: attrs.ogImage || null, | |
description: attrs.ogDescription || null, | |
type: "og:link" | |
} | |
} | |
}; | |
return directiveDefinitionObject; | |
}); | |
//app logic and template vars for the like template | |
tibbrPlugins.controller('LikeController', function factory($scope, $_tibbr, $_events, $_pagebus, $_tibbrAPI, $_constants, $location, $window) { | |
//set some default values for the template vars | |
//this could easily be the toJSON() output from Backbone models | |
var likeModel = { | |
currentUser: $_tibbr.currentUser, | |
host: $_tibbr._host, | |
likeStatus: '', | |
likeCount: null, | |
you: '', | |
and: '', | |
others: '', | |
firstOne: 'first_one', | |
likeThis: 'like_this', | |
hideBubble: false, | |
hideProfile: false, | |
likers: [], | |
resourceType: "og:link" | |
}; | |
angular.extend($scope, likeModel); | |
//callback function passed as a param to the like api methods | |
$scope.onLikeResponse = function (data, rc) { | |
//be sure to reset any necessary vars | |
var user = {}; | |
if (rc !== "error") { | |
$scope.you = $scope.and = $scope.others = ''; | |
$scope.likeStatus = (data.ilike) ? 'unlike' : 'like'; | |
$scope.likeCount = data.count; | |
$scope.hideBubble = (data.count == 0); | |
$scope.likers = []; | |
//if the current user likes whatever | |
if (data.ilike) { | |
var cu = $scope.currentUser = $_tibbr.currentUser; | |
$scope.you = 'you'; | |
$scope.profileUrl = user.profileUrl = '//' + $_tibbr._host + '/#!/users/' + cu.id + '/profile'; | |
user.displayName = cu.display_name; | |
$scope.likers.push(user); | |
} | |
if (data.others.length) { | |
$scope.and = ($scope.you) ? 'and' : ''; | |
$scope.others = data.others.length + ' others'; | |
//if there are other likers add up to five including the current user | |
} | |
$scope.$digest(); //refresh the view | |
} else { | |
$window.console.warn("Error posting like to tibbr"); | |
} | |
}; | |
$scope.getLikeStatus = function () { | |
$_tibbrAPI({ | |
//debug: true, | |
url: $_constants.URLS.get_like, | |
method: "GET", | |
params: { | |
client_id: $_tibbr.client_id, | |
resource: $scope.resource | |
}, | |
onResponse: $scope.onLikeResponse | |
}); | |
}; | |
$scope.postLikeStatus = function () { | |
var p = { | |
//factor up to general resource maker | |
resource: $scope.resource, | |
like: $scope.likeStatus, | |
client_id: $_tibbr.client_id | |
}; | |
$_tibbrAPI({ | |
url: $_constants.URLS.post_like, | |
method: "POST", | |
params: p, | |
onResponse: $scope.onLikeResponse | |
}); | |
} | |
//scope methods- some of these might need to be factored out into services | |
//event handler for button click | |
$scope.checkSession = function (type) { | |
$_pagebus.publish("tibbr:session:test:request", type); | |
}; | |
//I think what this does is test for an active (logged in) session before the actual proxied ajax call | |
$_pagebus.subscribe("tibbr:session:test:response", function (data) { | |
if (data._tc == "on") { | |
if (data._callback == 'likeMe') { | |
$scope.postLikeStatus(); | |
} | |
} else { | |
$_tibbr.login(); // should be our own service | |
} | |
}); | |
// legacy code depends on an outside login event for proper functioning | |
$_events.on('login', function (data) { | |
$scope.getLikeStatus(); | |
}); | |
//$scope.getLikeStatus(); // this call fails - why? | |
}); | |
//////////////////////////////////////////////////////////////////////////////////// | |
///////////////////////////////////////////////////////////////////////////////////// | |
//code to run when a follow tag is encountered | |
tibbrPlugins.directive('tibrFollow', function factory($window) { | |
var directiveDefinitionObject = { | |
//template: '<div>hi</div>', | |
templateUrl: 'partials/follow_button.html', | |
replace: true, | |
scope: {}, | |
controller: 'FollowController', | |
//transclude: true, | |
link: function postLink(scope, elm, attrs) { | |
//pull the EOG resource info form the tag attributes | |
scope.resource = { | |
id: attrs.ogId || null, | |
key: attrs.ogKey || null, | |
url: attrs.ogUrl || $window.location.href, | |
title: attrs.ogTitle || '', | |
image: attrs.ogImage || null, | |
description: attrs.ogDescription || null, | |
type: "og:link" | |
} | |
} | |
}; | |
return directiveDefinitionObject; | |
}); | |
//app logic and template vars for the like template | |
tibbrPlugins.controller('FollowController', function factory($scope, $_tibbr, $_pagebus, $_tibbrAPI, $_constants, $window) { | |
//set some default values for the template vars | |
var followModel = { | |
currentUser: $_tibbr.currentUser, | |
host: $_tibbr._host, | |
followStatus: 'follow', | |
isFollowing: false, | |
resourceType: "og:link" | |
}; | |
angular.extend($scope, followModel); | |
//it should toggle the values of followStatus and isFollowing switch to the opposite of the current value | |
$scope.onFollowResponse = function (data, rc) { | |
//be sure to reset any necessary vars | |
if (rc == "200") { | |
//toggle the 'follow' status | |
$scope.followStatus = ($scope.isFollowing) ? 'follow' : 'unfollow'; | |
$scope.isFollowing = ($scope.isFollowing) ? false : true; | |
$scope.$digest(); //refresh the view | |
} else { | |
$window.console.warn("Error posting follow to tibbr"); | |
} | |
}; | |
//it should post a follow/unfollow api call based on the current isFollowing switch | |
$scope.postFollowStatus = function () { | |
var action, method; | |
if($scope.isFollowing){ | |
action = $_constants.URLS.unfollowResource; | |
method = 'DELETE'; | |
}else{ | |
action = $_constants.URLS.followResource; | |
method = 'POST'; | |
} | |
var params = { | |
//todo: factor up to general resource maker | |
resource: $scope.resource, | |
follow: $scope.followStatus, | |
client_id: $_tibbr.client_id | |
}; | |
var config = { | |
url: action, | |
method: method, | |
params: params, | |
onResponse: $scope.onFollowResponse | |
}; | |
$_tibbrAPI(config); | |
return config; //for testing | |
} | |
//scope methods- some of these might need to be factored out into services | |
//event handler for button click | |
$scope.followThis = function (type) { | |
$_pagebus.publish("tibbr:session:test:request", type); | |
}; | |
//this one makes no sense | |
$_pagebus.subscribe("tibbr:session:test:response", function (data) { | |
if (data._tc == "on") { | |
if (data._callback == 'followMe') { | |
$scope.postFollowStatus(); | |
} | |
} else { | |
$_tibbr.login(); // should be our own service | |
} | |
}); | |
// todo: determine if server api for initial 'follow' status exists yet | |
//$_events.on('login', function (data) { | |
//token, //loginstuff | |
//$scope.getLikeStatus(); | |
//}); | |
//$scope.getLikeStatus(); // this call fails - why? | |
}); | |
})(); | |
This file contains hidden or 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
'use strict'; | |
/* jasmine specs for controllers go here */ | |
describe('Tibbr Plugin controllers', function () { | |
beforeEach(module('TibbrPlugins')); | |
describe('FollowController', function () { | |
var followController, config; | |
var scope = { | |
followStatus: 'follow', | |
isFollowing: false, | |
}; | |
beforeEach(inject(function ($controller) { | |
followController = $controller(FollowController, {$scope: scope});; | |
})); | |
it('should configure a REST POST api call when follow is clicked', function () { | |
config = $scope.postFollowStatus(); | |
expect(config.url).toEqual('/resources/follow'); | |
expect(config.method).toEqual('POST'); | |
}); | |
it('should configure a REST DELETE api call when unfollow is clicked', function () { | |
$scope.isFollowing = true; | |
config = $scope.postFollowStatus(); | |
expect(config.url).toEqual('/resources/unfollow'); | |
expect(config.method).toEqual('DELETE'); | |
}); | |
it('should correctly toggle the isFollowing switch', function () { | |
$scope.isFollowing = true; | |
$scope.onFollowResponse({}, '200'); | |
expect($scope.isFollowing).toEqual(false); | |
$scope.onFollowResponse({}, '200'); | |
expect($scope.isFollowing).toEqual(true); | |
}); | |
}); | |
describe('LikeController', function () { | |
var likeController; | |
var scope = { | |
likeStatus: '', | |
likeCount: null | |
}; | |
var data = { | |
ilike: true, | |
others: [] | |
}; | |
beforeEach(inject(function ($controller) { | |
likeController = $controller(FollowController, {$scope: scope});; | |
})); | |
it('should toggle the like status based on data returned with the onLikeResponse handler', function () { | |
$scope.onLikeResponse(data, '200'); | |
expect($scope.likeStatus).toEqual('unlike'); | |
data.ilike = false; | |
expect($scope.likeStatus).toEqual('like'); | |
}); | |
}); | |
}); |
This file contains hidden or 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
<!DOCTYPE html> | |
<html lang="en" xmlns="http://www.w3.org/1999/html"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Tibbr Template Application</title> | |
<link rel="stylesheet" href="http://dshapiro.tibbr.com:3001/connect/stylesheets/tibbr_share-min.css"/> | |
</head> | |
<body data-spy="scroll" data-offset="50"> | |
<h2>Tibbr Template Application</h2> | |
<div class="container"> | |
<span tibr-like | |
og-id="" | |
og-key="" | |
og-url="" | |
og-image="" | |
og-title="My app title" | |
og-description="This is the description of the thing I am liking"> | |
</span> | |
<span tibr-follow | |
og-id="" | |
og-key="" | |
og-url="" | |
og-image="" | |
og-title="My app title" | |
og-description="This is the description of the thing I am following"> | |
</span> | |
</div> <!-- /container --> | |
<script type="text/javascript"> | |
var tib_init = { | |
prefix: "", | |
client_id: 1, | |
host: "dshapiro.tibbr.com:3001", // host, domain (and port if non-standard) to your Tibbr instance | |
tunnelUrl: "http://dshapiro.tibbr.com/tunnel.html" // full url to tunnel.html on your application server | |
}; | |
//for now loading the TIB stuff and initializing synchronously until its loading scheme can be corrected | |
</script> | |
<script src="http://dshapiro.tibbr.com:3001/connect/js/TIB3.js"></script> | |
<script src="http://dshapiro.tibbr.com:3001/connect/js/pagebus-ie8plus-min.js"></script> | |
<script src="http://dshapiro.tibbr.com:3001/connect/js/tibbr.pagebus.js"></script> | |
<script src="http://dshapiro.tibbr.com:3001/connect/js/tib-min.js"></script> | |
<!--<script src="http://dshapiro.tibbr.com:3001/connect/js/plugins.js"></script>--> | |
<script> | |
TIB._setupCORS(); | |
var TIBR = TIB; | |
</script> | |
<script src="http://dshapiro.tibbr.com/angular-seed/app/lib/angular/angular.js"></script> | |
<script src="http://dshapiro.tibbr.com/angular-seed/app/js/app.js"></script> | |
<script src="http://dshapiro.tibbr.com/angular-seed/app/js/services.js"></script> | |
<script> | |
//bootstrap angular w/o using markup outside of our widgets | |
angular.element(document).ready(function () { | |
angular.bootstrap(document, ["TibbrPlugins"]); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains hidden or 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
//tibbr services (wrappers around XDM/ajax for now) and filters | |
(function () { | |
'use strict'; | |
/* Services - most of these are just stubs or wrappers for now*/ | |
//grab an angular reference to global TIBR (for now) | |
angular.module('Tibbr', []).factory('$_tibbr', function ($window) { | |
var PB = $window.TIB; | |
return PB; | |
}); | |
// get tibbr api resource info either passed in by param or scrapped from meta tags | |
angular.module('EventsBus', ['Tibbr']).factory('$_events', function ($_tibbr) { | |
var evts = $_tibbr.__events; | |
return evts; | |
}); | |
// get tibbr api resource info either passed in by param or scrapped from meta tags | |
angular.module('PageBus', ['Tibbr']).factory('$_pagebus', function ($_tibbr) { | |
var pagebus = $_tibbr.PageBus; | |
return pagebus; | |
}); | |
//grab an angular reference to global TIBR.api | |
angular.module('TibbrAPI', ['Tibbr', 'PageBus']).factory('$_tibbrAPI', function ($_tibbr, $_pagebus, $q) { | |
var api = function (args) { | |
if (!args) { | |
return function () { | |
console.warn('No argument object passed to tibbr.api()') | |
}; | |
} | |
var handlerId = args.handlerId = "api:response:" + Math.floor(Math.random() * 1111); | |
args.url = $_tibbr._protocal() + $_tibbr._host + "" + args.url; | |
// assign client id in each api call if not provided by end user | |
if (args.params && !args.params.client_id && $_tibbr.client_id) { | |
args.params.client_id = $_tibbr.client_id; | |
} | |
var params = { | |
u: args.url, | |
m: args.method, | |
t: args.type, | |
d: args.params, | |
hid: handlerId | |
}; | |
$_pagebus.publish("api:call", params); | |
$_pagebus.subscribe(handlerId, function (data) { | |
args.onResponse(data.r, data.rc); | |
$_pagebus.unsubscribe(handlerId); | |
}); | |
}; | |
return api; | |
}); | |
// get tibbr api resource info either passed in by param or scrapped from meta tags | |
angular.module('ResourcePrep', ['Tibbr']).factory('$_resourcePrep', function ($_tibbr, $window) { | |
return function (resourceConfig) { | |
resourceConfig = resourceConfig || null; | |
// add code to pull the info from meta tags or use the config or build a default | |
var resource = resourceConfig; | |
return resource; | |
}; | |
}); | |
//possible combined sevices definition | |
//angular.module('PluginServices', ['Tibbr', 'TibbrAPI', 'ResourcePrep', 'EventsBus']).factory('plugins', function ($_tibbr, tibbrAPI, resourcePrep, events, $location) { | |
// var services = function () {}; | |
// return services; | |
//}); | |
//filters like i18n | |
angular.module('TibbrFilters', ['Constants']).filter('translate', function ($_constants) { | |
return function (input) { | |
var out = ($_constants.I18N[input]) ? $_constants.I18N[input] : input; | |
return out; | |
} | |
}); | |
//TIBR strings (don't inject any dependancies here) | |
angular.module('Constants', []).factory('$_constants', function () { | |
var isNotIE8 = (function () { | |
var rv = 100; | |
if (navigator.appName == 'Microsoft Internet Explorer') { | |
var ua = navigator.userAgent; | |
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); | |
if (re.exec(ua) != null) { | |
rv = parseFloat(RegExp.$1); | |
} | |
} | |
return (rv >= 9) ? true : false; | |
})(); | |
var JS_PATH = "/connect/js/"; | |
var TIBBR_PATH = ""; | |
return { | |
//javascript | |
PAGEBUS: JS_PATH + ((isNotIE8) ? 'pagebus-ie8plus-min' : 'pagebus'), | |
TIB_MIN: JS_PATH + "tib-min", | |
PLUGINS: JS_PATH + "plugins", | |
PARENT_CONNECTOR: JS_PATH + 'parent_connector', | |
//html, css | |
PLUGIN_CSS: "/connect/stylesheets/tibbr_share-min.css", | |
CONNECT_PROXY: '/connect/connect_proxy_min', | |
//api urls | |
USERS_URL: "/users/find_by_session", | |
I18N_URL: "/plugins/connect_translate", | |
I18N: { | |
share: "Share", | |
follow: "Follow", | |
unfollow: "Unfollow", | |
like: "Like", | |
unlike: "Unlike", | |
first_one: "Be the first one to like this", | |
you: "You", | |
and: " and ", | |
like_this: " like this", | |
likes_this: " likes this", | |
one_other: " and 1 other like this", | |
others: " others like this", | |
not_logged_in: "You need to login to tibbr.", | |
login: " Click <a href='#' class='tib-login-link'>here</a> to login." | |
}, | |
URLS: { | |
share: "plugins/#!/share", | |
shareCount: "/plugins/share_count", | |
comments: "plugins/#!/comments", | |
post_like: "/plugins/like", | |
followResource: "/resources/follow", | |
unfollowResource: "/resources/unfollow", | |
getResourcePermissions: "/resources/user_permissions", | |
get_like: "/plugins/get_like" | |
} | |
} | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment