Skip to content

Instantly share code, notes, and snippets.

@dgs700
Created August 9, 2013 22:04
Show Gist options
  • Save dgs700/6197687 to your computer and use it in GitHub Desktop.
Save dgs700/6197687 to your computer and use it in GitHub Desktop.
Example of an Angularjs web component included inside a Backbone.js app. A bit rough for now, but will be refined.
<div class="action-links">
<!-- Include our Angular web component in the page as html markup
passing in (injecting) any data, config, or styling objects from the bckbone app-->
<span angular-web-component
og-id="{{model.id}}"
og-url="{{model.url}}"
og-key="{{model.key}}"
og-title="{{model.title}}"
og-image="{{model.image}}"
og-description="{{model.description}}"
include="likes shares comments"
>
</span>
</div>
//typical backbonejs item view with an Angular web component included
define([
'jquery'
, 'underscore'
, 'tibbr'
, 'hbs!templates/apps/backbone_hb_template'
, 'path_to_our_angular_components/component'
]
, function ($, _, Tibbr, tmpl) {
return Tibbr.View.extend({
tagName: 'li',
initialize: function (options) {
_.bindAll(this, 'render');
this.scope = this.buildScope( this.model);
},
//the hb template contains our angular component directive
render: function () {
this.$el.html( this.renderTemplate( tmpl, this.scope ));
return this;
},
//convert backbone model to POJ object for the component controller
buildScope: function (model) {
var scope = {};
scope = model.toJSON();
// scope.id = model.get('id');
// scope.app_id = model.get('app_id');
// scope.action_counts = model.get('action_counts');
// scope.name = model.get('name');
// scope.description = model.get('description');
// scope.image = model.get('image');
// scope.title = model.get('title');
// scope.r_url = model.get('url');
// scope.key = model.get('key');
// scope.action_counts = model.get('action_counts');
// scope.action_list = model.get('action_list');
// scope.user_action_list = model.get('user_action_list');
return scope;
}
});
});
define([
'jquery'
, 'underscore'
, 'tibbr'
, 'collections/resource_items'
, 'models/resource_item'
, 'views/apps/backbone_item_view' //contains our angular web component
, 'hbs!templates/apps/resource_list'
]
, function ($, _, Tibbr, List, Item, ResourceItem, tmpl) {
return Tibbr.View.extend({
className: "default-app-detials-wrap",
initialize: function (options) {
this.collection = new List(this.model.toJSON().resources);
this.collection.parentModel = this.model;
this.collection = this.setCollection(this.model, this.collection, Item);
_.bindAll(this, 'render', 'buildScope', 'setCollection', 'renderList');
this.scope = this.buildScope(this.model);
},
render: function () {
this.$el.empty();
this.$el.html(this.renderTemplate(tmpl, this.scope));
this.$ul = $('<ul class="detials-lists"></ul>');
this.$ul = this.renderList( this.collection, ResourceItem, this.$ul, this);
this.$el.append( this.$ul);
try {
//bootstrap our Angular component programatically after it is appended to the DOM
angular.bootstrap( $('#content'), ["AngularWebComponent"] );
} catch (e) {
console.warn('angularjs not included- resource_list.js line 39');
}
return this;
},
//prepare the template scope object
buildScope: function (model) {
var scope = {};
scope.name = model.get('name');
return scope;
},
//create or reset the data collection
setCollection: function (model, collection, Item, silent) {
var models = [];
var items = model.toJSON().resources.items;
_.each(items, function (item) {
models.push(new Item(item));
});
collection.reset(models);
return collection;
},
//render a new list given a collection view
renderList: function (collection, View, $ul, $scope) {
var view;
collection.each(function (item) {
view = $scope.initChild(View, {model: item}).render();
$ul.append(view.el);
});
return $ul;
}
});
});
/*The default component css should be entirely self contained and account for any page styles which could override.
The component template itself should accept any user overrides. Also the root component class should be unique so it
won't clash with page css*/
.our-angular-component * {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
.our-angular-component ol, .our-angular-component ul {
list-style: none;
}
.our-angular-component blockquote{
quotes: none;
}
.our-angular-component blockquote:before, .our-angular-component blockquote:after {
content: '';
content: none;
}
.our-angular-component ins {
text-decoration: none;
}
.our-angular-component del {
text-decoration: line-through;
}
.our-angular-component table {
border-collapse: collapse;
border-spacing: 0;
}
.our-angular-component .container{
width: 400px;
margin: 100px auto 0;
}
.our-angular-component {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: relative;
width: 500px;
}
.our-angular-component .table-footer {
background: none repeat scroll 0 0 #E5E5E5;
border-right: 0 none;
display: inline-block;
padding: 5px 0 5px 10px;
text-align: right;
}
.our-angular-component a {
color: #777;
cursor: pointer;
font-weight: bold;
display: inline-block;
text-decoration: none;
}
.our-angular-component .stats li {
border-right: 2px solid #D8D8D8;
cursor: pointer;
display: inline;
font-size: 13px;
line-height: 1;
margin: 0 10px 0 0;
padding: 0 10px 0 0;
position: relative;
}
.our-angular-component .stats li:hover a{
color: #006699;
}
.our-angular-component .stats li:last-child{
margin-right: 5px;
border-right: none;
}
.our-angular-component .mid-dot {
margin: 0 5px;
vertical-align: middle;
}
.our-angular-component .stats .count {
vertical-align: middle;
}
.our-angular-component .post-section {
background: #fff;
border: 1px solid #ccc;
display: block;
/*margin: 10px 0 0 0;*/
padding: 10px;
position: absolute !important;
overflow: visible;
width: 625px;
z-index: 1000;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
/* positioning pop-overs */
.our-angular-component .post-section.north-east{
bottom: 0;
right: 0;
margin: 0 0 20px 0;
}
.our-angular-component .post-section.north-west{
bottom: 0;
left: 0;
margin: 0 0 20px 0;
}
.our-angular-component .post-section.south-east{
right: 0;
top: 0;
margin: 20px 0 0 0;
}
.our-angular-component .post-section.south-west{
left: 0;
top: 0;
margin: 20px 0 0 0;
}
/* end of positioning pop-overs*/
.our-angular-component .post-section iframe .tib-box{
padding: 10px;
}
.our-angular-component #posting-tab-wrap{
padding: 10px;
}
/*common for all icon*/
.our-angular-component .ui-inline-icon {
background-repeat: no-repeat;
color: transparent;
display: inline-block;
font-size: 0;
height: 16px;
vertical-align: middle;
width: 20px;
}
/* all - icons*/
.our-angular-component .like-link,
.reply-link,
.reply-like,
.reply-icon,
.share-icon{
display: inline-block;
vertical-align: middle;
}
/*like-block*/
.our-angular-component .like-link{
background-position: 0 0;
height: 15px;
width: 17px;
}
.our-angular-component .stats li:hover .like-link{
background-position: 0 -30px;
}
/*reply-block*/
.our-angular-component .reply-link {
background-position: -30px 0;
height: 15px;
width: 17px;
}
.our-angular-component .stats li:hover .reply-link{
background-position: -30px -30px;
}
/* share-block*/
.our-angular-component .share-icon {
background-position: -60px 0;
height: 13px;
width: 17px;
}
.our-angular-component .stats li:hover .share-icon{
background-position: -60px -30px;
}
//example of a self contained Angular web component with directive and controller.
//this is a container component meant to be filled with various social components.
//the example was extracted from a large backbone.js app for open graph social collaboration.
//the component either self initializes or issues a REST service call for data
(function () {
'use strict';
//declare our component module with dependencies
var angularWebComponent = angular.module('AngularWebComponent', [
'TibbrFilters',
'Tibbr',
'EventsBus',
'PageBus',
'TibbrAPI',
'LoginCheck',
'Params',
'ProxyCallbacks'
]).directive('angularWebComponent', [
'$window',
function factory($window) {
var directiveDefinitionObject = {
//use inline template for production to avoid cross frame html loading restrictions
template: '<div class="our-angular-component">' + //unique class name for our comp
'<div class="table-footer" colspan="4">' +
'<ul class="stats">' +
'<li ng-show="showLike"><span tibr-like-inline></span></li>' +
'<li ng-show="showComment"><span tibr-comment-inline></span></li>' +
'<li ng-show="showShare"><span tibr-share-inline></span></li>' +
'</ul>' +
'</div>' +
'<div class="post-section-inline" ng-bind-html-unsafe="commentContainerInline" style="position:static"></div>' +
'<div class="post-section-inline" ng-bind-html-unsafe="shareContainerInline" style="position:static"></div>' +
'</div>',
replace: true,
scope: {},
controller: 'ComponentController',
compile: function (elm, attrs) {
return function postLink(scope, elm, attrs) {
//pull the config info form the tag attributes
//and modify our data object defaults
scope.resource = {
id: attrs.ogId || null,
url: attrs.ogUrl || $window.location.href,
key: attrs.ogKey || null,
title: attrs.ogTitle || null,
image: attrs.ogImage || null,
description: attrs.ogDescription || null,
type: attrs.ogType || "og:link"
};
//see which pluging are desired
if (attrs.include) {
var plugins = attrs.include;
if (plugins.indexOf('like') != -1)
scope.showLike = true;
if (plugins.indexOf('comment') != -1)
scope.showComment = true;
if (plugins.indexOf('share') != -1)
scope.showShare = true;
}
if (attrs.inline == 'false') {
scope.inline = false;
}
scope.likeCount = (attrs.likeCount) ? attrs.likeCount : 0;
scope.shareCount = (attrs.shareCount) ? attrs.shareCount : 0;
scope.commentCount = (attrs.commentCount) ? attrs.commentCount : 0;
scope.likedActionId = attrs.likedActionId;
if (scope.likedActionId) {
scope.liked = true;
scope.showLike = true;
scope.likeClass = 'like-link liked';
}
scope.shared = attrs.shared;
if (scope.shared) {
scope.shared = true;
scope.showShare = true;
}
scope.commented = attrs.commented;
if (scope.commented) {
scope.commented = true;
scope.showComment = true;
}
scope.initialApiObj.params.resource = scope.resource;
//this is required to avoid an unnecessary api call for initial info
//if we are injecting any action data
if (attrs.initialized) {
scope.initialized = true;
} else {
scope.init();
}
};
}
};
return directiveDefinitionObject;
}]).controller('ComponentController', [
'$scope',
'$_tibbr',
'$_events',
'$_tibbrAPI',
'$_loginCheck',
'$_constants',
function factory($scope, $_tibbr, $_events, $_tibbrAPI, $_loginCheck, $_constants) {
//set some default values for the template vars
$scope.loginCheck = $_loginCheck;
//create our default data obj
var socialModel = {
currentUser: $_tibbr.currentUser,
host: $_tibbr.host,
resourceType: "og:link",
likeCount: 0,
likedActionId: null,
liked: false,
likeClass: 'like-link unliked',
commentCount: 0,
commented: false,
commentContainer: '',
commentContainerInline: '',
shareCount: 0,
shared: false,
shareContainer: '',
shareContainerInline: '',
inline: true,
direction: 'south-east'
};
angular.extend($scope, socialModel);
$scope.initialApiObj = {
url: $_constants.URLS.profile,
params: {
resource: $scope.resource,
client_id: $_tibbr.client_id,
include_user_actions: "og:like,og:share,og:comment",
include_action_count: "og:like,og:share,og:comment",
include_actions: "og:like,og:share,og:comment",
per_page: 5
},
method: "GET",
onResponse: function (data, status) {
$scope.resourceObj = data;
$scope.userPermission = data.actions;
$scope.userPerformedActions = data.og_action_performed;
$scope.likeCount = data.action_counts.og_like;
$scope.shareCount = data.action_counts.og_share;
$scope.commentCount = data.action_counts.og_comment;
if (data.og_action_performed.indexOf("og:like") != -1) {
$scope.liked = true;
$scope.likeClass = 'like-link liked';
$scope.likedActionId = data.user_action_list.og_like.items[0].id;
}
if ($scope.userPermission.indexOf("unfollow") != -1) {
$scope.canFollow = true;
$scope.following = true;
}
if ($scope.userPermission.indexOf("follow") != -1) {
$scope.canFollow = true;
}
$scope.currentUser = $_tibbr.currentUser;
}
};
//make an api call for initial info if not already injected
//social components in 3rd party pages will typically need to call this
$scope.init = function () {
if (!$scope.initialized) {
$_events.on('login', function (data) {
$_tibbrAPI($scope.initialApiObj, $scope).then(function (data) {
$scope.initialApiObj.onResponse(data.r);
}, function (data) {
console.warn(data);
});
$scope.initialized = true;
});
}
};
}]);
})();
//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.TIBR;
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;
});
// get tibbr api resource info either passed in by param or scrapped from meta tags
angular.module('LoginCheck', ['Tibbr']).factory('$_loginCheck', function ($_tibbr) {
var loginCheck = function (callback) {
if ($_tibbr.loggedIn) {
return true;
} else {
$_tibbr.login(callback);
return false;
}
};
return loginCheck;
});
//grab an angular reference to global TIBR.api
//required params - a params object that includes a url, method
//returns a scoped promise
angular.module('TibbrAPI', [
'Tibbr',
'PageBus',
'LoginCheck'
]).factory('$_tibbrAPI', function ($_tibbr, $_pagebus, $_loginCheck, $q) {
var api = function (args, scope) {
var deferred = $q.defer();
if (!args || !scope) {
//return function () {
console.warn('No arguments or scope object passed to tibbr.api()');
deferred.reject('No arguments or scope object passed to tibbr.api()');
return deferred.promise;
//};
}
if (!$_tibbr.loggedIn) {
$_loginCheck();
deferred.reject('Not logged in.');
return deferred.promise;
}
//args.onResponse = args.onResponse || function () {
//};
var handlerId = args.handlerId = "api:response:" + Math.floor(Math.random() * 1111);
args.url = $_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.subscribe(handlerId, function (data) {
if (data.rc == 200) {
scope.$apply(function () {
deferred.resolve(data);
});
}else{
scope.$apply(function () {
deferred.reject(data);
});
}
$_pagebus.unsubscribe(handlerId);
});
$_pagebus.publish("api:call", params);
return deferred.promise;
};
return api;
});
// get tibbr api resource info either passed in by param or scrapped from meta tags
//not used currently
angular.module('ResourcePrep', ['Tibbr']).factory('$_resourcePrep', function ($_tibbrAPI, $_tibbr, $_events) {
var resourcePrep = function (scope, resourceId) {
resourceId = resourceId || {
id: 24
};
// add code to pull the info from meta tags or use the config or build a default
var resource = resourceId;
$_events.on('login', function (data) {
$_tibbrAPI({
url: "/resources/profile",
params: {
resource: resourceId,
client_id: $_tibbr.client_id,
include_user_actions: "og:like,og:share,og:comment",
include_action_count: "og:like,og:share,og:comment",
include_actions: "og:like,og:share,og:comment",
per_page: 5
},
method: "GET"
}, scope).then(function(data){
//do call back stuff
});
});
return resource;
};
return resourcePrep;
});
//things to do when info returns from the client proxy frame
angular.module('ProxyCallbacks', ['Tibbr']).factory('$_proxyCallbacks', function ($_tibbrAPI, $_tibbr, $_events, $_constants) {
//add any subscribe evt handlers to listen for any function invoke requests from any child iframes
//the function name must match and so should the args
var proxyCallbacks = {
setheight: function (scope, id, height) {
//do any other stuff here
return height;
},
resetsharebox: function (scope, id, args, handl) {
//do any other stuff here
$_tibbr.PageBus.unsubscribe(handl);
},
updatesharecount: function (scope, id, resource) {
$_tibbr.api({
url: $_constants.URLS.shareCount,
method: "GET",
params: {
resource: resource
}
}, scope).then(function(data){
//do call back stuff
});
},
resourceclicked: function (scope, id, obj) {
//if (_onResourceClickHandlers[id]) {
// _onResourceClickHandlers[id](obj);
//}
},
onMeetingCreate: function (scope, id, obj){}
};
return proxyCallbacks;
});
// get tibbr api resource info either passed in by param or scrapped from meta tags
angular.module('Params', []).factory('$_params', function () {
function buildParams(prefix, obj, add) {
var name, i, l, rbracket;
rbracket = /\[\]$/;
if (obj instanceof Array) {
for (i = 0, l = obj.length; i < l; i++) {
if (rbracket.test(prefix)) {
add(prefix, obj[i]);
} else {
buildParams(prefix + "[" + ( typeof obj[i] === "object" ? i : "" ) + "]", obj[i], add);
}
}
} else if (typeof obj == "object") {
// Serialize object item.
for (name in obj) {
buildParams(prefix + "[" + name + "]", obj[ name ], add);
}
} else {
// Serialize scalar item.
add(prefix, obj);
}
}
return function (a) {
var prefix, s, add, name, r20, output;
s = [];
r20 = /%20/g;
add = function (key, value) {
// If value is a function, invoke it and return its value
value = ( typeof value == 'function' ) ? value() : ( value == null ? "" : value );
s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
};
if (a instanceof Array) {
for (name in a) {
add(name, a[name]);
}
} else {
for (prefix in a) {
buildParams(prefix, a[ prefix ], add);
}
}
output = s.join("&").replace(r20, "+");
return output;
};
});
//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.",
see_detail: "See detail"
},
URLS: {
share: "/plugins/#!/share",
sharePlugin: "/plugins/#!/share?ojbref=TIBR&",
shareCount: "/plugins/share_count",
comments: "/resources/comments",
commentCount: "/plugins/comments_count", //????
commentsPlugin: "/plugins/#!/comments?ojbref=TIBR&",
publish: "/actions/publish",
unpublish: "/actions/id/unpublish",
post_like: "/plugins/like",
get_like: "/plugins/get_like",
followResource: "/resources/follow",
unfollowResource: "/resources/unfollow",
unfollow: "/resources/unfollow",
followerList: "/resources/followers",
getResourcePermissions: "/resources/user_permissions",
userPermissions: "/resources/user_permissions",
userActions: "/resources/user_actions",
actionUser: "/actions/users",
follow: "/resources/follow",
actionCount: "/actions/count",
profile: "/resources/profile.json"
}
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment