Skip to content

Commit

Permalink
feat(comments): directive to define insert nested comments in template
Browse files Browse the repository at this point in the history
New directive commentsTransclude enables a comment template to decide where
child comments are to be placed, if they are in fact to be placed in the DOM.

This allows for greater customization of the templates.

Documentation has been updated to make use of this feature.
  • Loading branch information
Caitlin Potter committed Oct 23, 2013
1 parent 4e79422 commit 4c796cc
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 50 deletions.
22 changes: 12 additions & 10 deletions misc/demo/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ body {
margin-left: 2em;
}

.comment-header > h4 {
line-height: 2em;
height: 2em;
display: block;
.comment-header {
vertical-align: middle;
line-height: 2em;
}

.comment-header > h4 > button {
position: relative;
top: -.125em;
.comment-header > h4 {
display: inline;
}

.comment > .page-header {
Expand Down Expand Up @@ -73,14 +70,19 @@ body {
}

.comment {
position: relative;
border-top: 1px solid #eee;
}

.comment:first-child {
border: none;
}


.clear-square {
vertical-align: sub;
border: none;
background: none;
display: block;
float: left;
outline: none;
min-width: 36px;
min-height: 34px;
}
70 changes: 56 additions & 14 deletions misc/demo/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ angular.module("views/comment.html", []).run(["$templateCache", function($templa
"<div class=\"container comment\">\n" +
" <div class=\"page-header\">\n" +
" <div class=\"comment-header\">\n" +
" <button ng-if=\"comment.children\" class=\"icon-2x clear-square glyphicon\" " +
" ng-class=\"{'icon-minus-sign-alt': !collapsed, 'icon-plus-sign-alt': collapsed}\" " +
" title=\"toggle children\" ng-click=\"collapse()\"></button>" +
" <button ng-if=\"!comment.children\" class=\"icon-2x clear-square\"></button>" +
" <h4 class=\"comment-user\">\n" +
" <button ng-if=\"comment.children\" class=\"icon-2x clear-square glyphicon\" ng-class=\"{'icon-minus-sign-alt': !collapsed, 'icon-plus-sign-alt': collapsed}\" title=\"toggle children\" ng-click=\"collapse()\"></button>" +
" <button ng-if=\"!comment.children\" class=\"icon-2x clear-square\"></button>" +
" <span class=\"comment-username\" ng-if=\"!comment.profileUrl\">{{comment.name}}</span>\n" +
" <a class=\"comment-username\" ng-if=\"comment.profileUrl\" ng-href=\"{{comment.profileUrl}}\" title=\"{{comment.name}}\">{{comment.name}}</a>\n" +
" <small class=\"comment-date\" ng-if=\"comment.date\" title=\"{{comment.date | calendar}}\">{{comment.date | timeago}}</small>\n" +
" </h4>\n" +
" <img class=\"comment-avatar\" ng-if=\"comment.avatarUrl\" ng-src=\"{{comment.avatarUrl}}\" alt=\"{{comment.name}}\" />\n" +
" <a class=\"comment-username\" ng-if=\"comment.profileUrl\" ng-href=\"{{comment.profileUrl}}\" " +
" title=\"{{comment.name}}\">{{comment.name}}</a>\n" +
" <small class=\"comment-date\" ng-if=\"comment.date\" title=\"{{comment.date | calendar}}\">" +
" {{comment.date | timeago}}" +
" </small>\n" +
" </h4>\n" +
" <img class=\"comment-avatar\" ng-if=\"comment.avatarUrl\" ng-src=\"{{comment.avatarUrl}}\" " +
" alt=\"{{comment.name}}\" />\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"row\">\n" +
" <div class=\"container comment-body\" ng-bind=\"comment.text\"></div>\n" +
" </div>\n" +
" <div class=\"comment-body\" ng-bind=\"comment.text\"></div>" +
" <div comments-transclude></div>" +
"</div>");
}]);

Expand Down Expand Up @@ -62,25 +67,62 @@ angular.module('commentsDemo', ['views/comments.html', 'views/comment.html', 'ui
profileUrl: 'https://github.com/caitp',
text: 'UI-Comments is designed to simplify the process of creating comment systems similar to Reddit, Imgur or Discuss in AngularJS.',
children: [{
name: '@caitp',
name: '@bizarro-caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
profileUrl: 'https://github.com/bizarro-caitp',
text: 'We support nested comments, in a very simple fashion. It\'s great!',
children: [{
name: '@caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
text: 'These nested comments can descend arbitrarily deep, into many levels. This can be used to reflect a long and detailed conversation about typical folly which occurs in comments',
children: [{
name: '@bizarro-caitp',
date: new Date(),
profileUrl: 'https://github.com/bizarro-caitp',
text: 'Having deep conversations on the internet can be used to drive and derive data about important topics, from marketing demographic information to political affiliation and even sexual orientation if you care to find out about that. Isn\'t that exciting?'
}]
},{
name: '@bizarro-caitp',
date: new Date(),
profileUrl: 'https://github.com/bizarro-caitp',
text: 'Is it REALLY all that wonderful? People tend to populate comments with innane nonsense that ought to get them hellbanned!',
comments: [{
name: '@caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
text: 'Having deep conversations on the internet can be used to drive and derive data about important topics, from marketing demographic information to political affiliation and even sexual orientation if you care to find out about that. Isn\'t that exciting?'
text: 'Oh whatever lady, whatever'
}]
}]
}]
}
]
}, {
name: '@caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
text: 'We can have multiple threads of comments at a given moment...',
}, {
name: '@bizarro-caitp',
date: new Date(),
profileUrl: 'https://github.com/bizarro-caitp',
text: 'We can do other fancy things too, maybe...',
children: [{
name: '@caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
text: '...other fancy things, you say?',
}, {
name: '@caitp',
date: new Date(),
profileUrl: 'https://github.com/caitp',
text: 'suddenly I\'m all curious, what else can we do...',
children: [{
name: '@bizarro-caitp',
date: new Date(),
profileUrl: 'https://github.com/bizarro-caitp',
text: 'Oh, you\'ll see...',
}]
}]
}]
})

.filter('timeago', function() {
Expand Down
98 changes: 80 additions & 18 deletions src/comments.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ angular.module('ui.comments.directive', [])
* directive, it will result in an infinite $compile loop. Instead,
* {@link ui.comments.directive:comment comment} generates child collections programmatically.
* Currently, these are simply appended to the body of the comment.
*
* **TODO**: Support customization of entry point for child comment collections.
*/
commentTemplate: 'template/comments/comment.html',
/**
Expand Down Expand Up @@ -257,54 +255,118 @@ angular.module('ui.comments.directive', [])
*/
.directive('comment', function($compile, commentsConfig, $controller) {
return {
require: '^comments',
require: ['^comments', 'comment'],
restrict: 'EA',
transclude: true,
replace: true,
templateUrl: function() { return commentsConfig.commentTemplate; },
scope: {
comment: '=commentData'
},
controller: function($scope) {},
compile: function(scope, elem) {
return function(scope, elem, attr, comments) {
return function(scope, elem, attr, ctrls) {
var comments = ctrls[0], comment = ctrls[1];
var controller = commentsConfig.commentController, controllerInstance;
if (controller) {
controllerInstance = $controller(controller, {
'$scope': scope,
'$element': elem
});
if (controllerInstance) {
elem.data('$commentController', controllerInstance);
elem.data('$CommentController', controllerInstance);
}
}
if (elem.parent().attr('child-comments') === 'true') {
elem.addClass('child-comment');
}
var children = false, compiled, sub =
angular.element('<comments child-comments="true" ' +
'comment-data="comment.children"></comments>');
var children = false, compiled,
sub = angular.element('<comments child-comments="true" ' +
'comment-data="comment.children"></comments>'),
transclude;
function update(data) {
if (angular.isArray(data) && data.length > 0 && !children) {
if (!angular.isArray(data)) {
data = [];
}
if (data.length > 0 && !children) {
compiled = $compile(sub)(scope);
var w = scope.$watch('$$phase', function(newval) {
var w = scope.$watch('$$phase', function(val) {
w();
elem.append(compiled);
elem.triggerHandler('filled.comments', compiled);
if (comment.commentsTransclude) {
transclude = comment.commentsTransclude.clone();
comment.commentsTransclude.replaceWith(compiled);
} else {
elem.append(compiled);
}
children = true;
elem.triggerHandler('filled.comments', compiled);
});
} else if((!angular.isArray(data) || !data.length) && children) {
} else if(!data.length && children) {
children = false;
compiled.remove();
compiled = undefined;
if (comment.commentsTransclude && transclude) {
comment.commentsTransclude.replaceWith(transclude);
} else {
compiled.remove();
}
compiled = transclude = undefined;
elem.triggerHandler('emptied.comments');
}
}

scope.$watchCollection(attr.commentData, function(newval) {
scope.comment = newval;
scope.$watch('comment', function(newval) {
update(scope.comment.children);
});
}, true);
};
}
};
})

/**
* @ngdoc directive
* @name ui.comments.directive:commentsTransclude
* @restrict EA
* @element div
*
* @description
*
* This directive is a helper which allows a user to specify in a
* {@link ui.comments.directive:comment comment} template where they wish child comments to be
* inserted.
*
* If this directive is not used, then the child comments are merely appended to the end of a
* comment template, which is the behaviour of the default templates.
*
* It is not a literal transclusion, and so any class names or categories are completely ignored.
* Instead, the element is replaced by a {@link ui.comments.directive:comments comments} collection
* when comments are available.
*
* An example template might look like the following:
* <pre>
* <div class="comment">
* <div class="comment-header">
* <a class="comment-avatar"
* ng-href="{{comment.profileUrl}}">
* <img ng-src="{{comment.avatarUrl}}"
* alt="{{comment.name}}" />
* </a>
* <a class="comment-username"
* ng-href="{{comment.profileUrl}}"
* title="{{comment.username}}">{{comment.name}}</a>
* <span class="comment-date">{{comment.date | timeAgo}}</span>
* </div>
* <div class="comment-body" ng-bind="comment.text"></div>
* <div comments-transclude></div>
* </div>
* </pre>
*/
.directive('commentsTransclude', function() {
return {
restrict: 'EA',
require: '^comment',
link: {
pre: function(scope, element, attr, comment) {
comment.commentsTransclude = element;
}
}
};
});
14 changes: 6 additions & 8 deletions src/test/comments.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('ui.comments', function() {
return comments.find('.comment').first();
}
firstCtrl = function() {
return firstComment().controller('comment');
return firstComment().controller('Comment');
}
});

Expand Down Expand Up @@ -141,22 +141,20 @@ describe('ui.comments', function() {
$scope.comments = [{children: []}];
comments = $compile('<comments comment-data="comments"></comments>')($scope);
$scope.$digest();
var parent = comments.find('.comment').first(),
callback = jasmine.createSpy('commentsFilled');
parent.bind('filled.comments', callback);
var callback = jasmine.createSpy('commentsFilled');
comments.find('.comment').bind('filled.comments', callback);
$scope.comments[0].children = [{}];
$scope.$digest();
expect(callback).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(HTMLDivElement));
expect(callback).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(HTMLElement));
});


it('fires `comments.emptied` when child comments are no longer available', function() {
$scope.comments = [{children: [{}]}];
comments = $compile('<comments comment-data="comments"></comments>')($scope);
$scope.$digest();
var parent = comments.find('.comment').first(),
callback = jasmine.createSpy('commentsEmptied');
parent.bind('emptied.comments', callback);
var callback = jasmine.createSpy('commentsEmptied');
comments.find('.comment').bind('emptied.comments', callback);
$scope.comments[0].children = [];
$scope.$digest();
expect(callback).toHaveBeenCalled();
Expand Down

0 comments on commit 4c796cc

Please sign in to comment.