2 * Namespace for comments
7 * Comment support for WCF
9 * @author Alexander Ebert
10 * @copyright 2001-2014 WoltLab GmbH
11 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
13 WCF
.Comment
.Handler
= Class
.extend({
15 * input element to add a comment
21 * list of comment buttons per comment
24 _commentButtonList
: { },
27 * list of comment objects
33 * comment container object
45 * number of currently displayed comments
48 _displayedComments
: 0,
51 * button to load next comments
54 _loadNextComments
: null,
57 * buttons to load next responses per comment
60 _loadNextResponses
: { },
64 * @var WCF.Action.Proxy
69 * list of response objects
81 * Initializes the WCF.Comment.Handler class.
83 * @param string containerID
84 * @param string userAvatar
86 init: function(containerID
, userAvatar
) {
87 this._commentAdd
= null;
88 this._commentButtonList
= { };
90 this._containerID
= containerID
;
91 this._displayedComments
= 0;
92 this._loadNextComments
= null;
93 this._loadNextResponses
= { };
94 this._responses
= { };
95 this._userAvatar
= userAvatar
;
97 this._container
= $('#' + $.wcfEscapeID(this._containerID
));
98 if (!this._container
.length
) {
99 console
.debug("[WCF.Comment.Handler] Unable to find container identified by '" + this._containerID
+ "'");
102 this._proxy
= new WCF
.Action
.Proxy({
103 success
: $.proxy(this._success
, this)
106 this._initComments();
107 this._initResponses();
110 if (this._container
.data('canAdd')) {
111 this._initAddComment();
114 WCF
.DOMNodeInsertedHandler
.execute();
115 WCF
.DOMNodeInsertedHandler
.addCallback('WCF.Comment.Handler', $.proxy(this._domNodeInserted
, this));
119 * Shows a button to load next comments.
121 _handleLoadNextComments: function() {
122 if (this._displayedComments
< this._container
.data('comments')) {
123 if (this._loadNextComments
=== null) {
124 this._loadNextComments
= $('<li class="commentLoadNext"><button class="small">' + WCF
.Language
.get('wcf.comment.more') + '</button></li>').appendTo(this._container
);
125 this._loadNextComments
.children('button').click($.proxy(this._loadComments
, this));
128 this._loadNextComments
.children('button').enable();
130 else if (this._loadNextComments
!== null) {
131 this._loadNextComments
.hide();
136 * Shows a button to load next responses per comment.
138 * @param integer commentID
140 _handleLoadNextResponses: function(commentID
) {
141 var $comment
= this._comments
[commentID
];
142 $comment
.data('displayedResponses', $comment
.find('ul.commentResponseList > li').length
);
144 if ($comment
.data('displayedResponses') < $comment
.data('responses')) {
145 if (this._loadNextResponses
[commentID
] === undefined) {
146 var $difference
= $comment
.data('responses') - $comment
.data('displayedResponses');
147 this._loadNextResponses
[commentID
] = $('<li class="jsCommentLoadNextResponses"><a>' + WCF
.Language
.get('wcf.comment.response.more', { count
: $difference
}) + '</a></li>').appendTo(this._commentButtonList
[commentID
]);
148 this._loadNextResponses
[commentID
].children('a').data('commentID', commentID
).click($.proxy(this._loadResponses
, this));
149 this._commentButtonList
[commentID
].parent().show();
152 else if (this._loadNextResponses
[commentID
] !== undefined) {
153 var $showAddResponse
= this._loadNextResponses
[commentID
].next();
154 this._loadNextResponses
[commentID
].remove();
155 if ($showAddResponse
.length
) {
156 $showAddResponse
.trigger('click');
162 * Loads next comments.
164 _loadComments: function() {
165 this._loadNextComments
.children('button').disable();
167 this._proxy
.setOption('data', {
168 actionName
: 'loadComments',
169 className
: 'wcf\\data\\comment\\CommentAction',
172 objectID
: this._container
.data('objectID'),
173 objectTypeID
: this._container
.data('objectTypeID'),
174 lastCommentTime
: this._container
.data('lastCommentTime')
178 this._proxy
.sendRequest();
182 * Loads next responses for given comment.
184 * @param object event
186 _loadResponses: function(event
) {
187 this._loadResponsesExecute($(event
.currentTarget
).disable().data('commentID'), false);
192 * Executes loading of comments, optionally fetching all at once.
194 * @param integer commentID
195 * @param boolean loadAllResponses
197 _loadResponsesExecute: function(commentID
, loadAllResponses
) {
198 this._proxy
.setOption('data', {
199 actionName
: 'loadResponses',
200 className
: 'wcf\\data\\comment\\response\\CommentResponseAction',
203 commentID
: commentID
,
204 lastResponseTime
: this._comments
[commentID
].data('lastResponseTime'),
205 loadAllResponses
: (loadAllResponses
? 1 : 0)
209 this._proxy
.sendRequest();
213 * Handles DOMNodeInserted events.
215 _domNodeInserted: function() {
216 this._initComments();
217 this._initResponses();
221 * Initializes available comments.
223 _initComments: function() {
225 var $loadedComments
= false;
226 this._container
.find('.jsComment').each(function(index
, comment
) {
227 var $comment
= $(comment
).removeClass('jsComment');
228 var $commentID
= $comment
.data('commentID');
229 self
._comments
[$commentID
] = $comment
;
231 var $insertAfter
= $comment
.find('ul.commentResponseList');
232 if (!$insertAfter
.length
) $insertAfter
= $comment
.find('.commentContent');
234 $container
= $('<div class="commentOptionContainer" />').hide().insertAfter($insertAfter
);
235 self
._commentButtonList
[$commentID
] = $('<ul />').appendTo($container
);
237 self
._handleLoadNextResponses($commentID
);
238 self
._initComment($commentID
, $comment
);
239 self
._displayedComments
++;
241 $loadedComments
= true;
244 if ($loadedComments
) {
245 this._handleLoadNextComments();
250 * Initializes a specific comment.
252 * @param integer commentID
253 * @param jQuery comment
255 _initComment: function(commentID
, comment
) {
256 if (this._container
.data('canAdd')) {
257 this._initAddResponse(commentID
, comment
);
260 if (comment
.data('canEdit')) {
261 var $editButton
= $('<li><a class="jsTooltip" title="' + WCF
.Language
.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF
.Language
.get('wcf.global.button.edit') + '</span></a></li>');
262 $editButton
.data('commentID', commentID
).appendTo(comment
.find('ul.commentOptions:eq(0)')).click($.proxy(this._prepareEdit
, this));
265 if (comment
.data('canDelete')) {
266 var $deleteButton
= $('<li><a class="jsTooltip" title="' + WCF
.Language
.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF
.Language
.get('wcf.global.button.delete') + '</span></a></li>');
267 $deleteButton
.data('commentID', commentID
).appendTo(comment
.find('ul.commentOptions:eq(0)')).click($.proxy(this._delete
, this));
272 * Initializes available responses.
274 _initResponses: function() {
276 this._container
.find('.jsCommentResponse').each(function(index
, response
) {
277 var $response
= $(response
).removeClass('jsCommentResponse');
278 var $responseID
= $response
.data('responseID');
279 self
._responses
[$responseID
] = $response
;
281 self
._initResponse($responseID
, $response
);
286 * Initializes a specific response.
288 * @param integer responseID
289 * @param jQuery response
291 _initResponse: function(responseID
, response
) {
292 if (response
.data('canEdit')) {
293 var $editButton
= $('<li><a class="jsTooltip" title="' + WCF
.Language
.get('wcf.global.button.edit') + '"><span class="icon icon16 icon-pencil" /> <span class="invisible">' + WCF
.Language
.get('wcf.global.button.edit') + '</span></a></li>');
296 $editButton
.data('responseID', responseID
).appendTo(response
.find('ul.commentOptions:eq(0)')).click(function(event
) { self
._prepareEdit(event
, true); });
299 if (response
.data('canDelete')) {
300 var $deleteButton
= $('<li><a class="jsTooltip" title="' + WCF
.Language
.get('wcf.global.button.delete') + '"><span class="icon icon16 icon-remove" /> <span class="invisible">' + WCF
.Language
.get('wcf.global.button.delete') + '</span></a></li>');
303 $deleteButton
.data('responseID', responseID
).appendTo(response
.find('ul.commentOptions:eq(0)')).click(function(event
) { self
._delete(event
, true); });
308 * Initializes the UI components to add a comment.
310 _initAddComment: function() {
312 this._commentAdd
= $('<li class="box32 jsCommentAdd"><span class="framed">' + this._userAvatar
+ '</span><div /></li>').prependTo(this._container
);
313 var $inputContainer
= this._commentAdd
.children('div');
314 var $input
= $('<input type="text" placeholder="' + WCF
.Language
.get('wcf.comment.add') + '" maxlength="65535" class="long" />').appendTo($inputContainer
);
315 $('<small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').appendTo($inputContainer
);
317 $input
.keyup($.proxy(this._keyUp
, this));
321 * Initializes the UI elements to add a response.
323 * @param integer commentID
324 * @param jQuery comment
326 _initAddResponse: function(commentID
, comment
) {
327 var $placeholder
= null;
328 if (!comment
.data('responses') || this._loadNextResponses
[commentID
]) {
329 $placeholder
= $('<li class="jsCommentShowAddResponse"><a>' + WCF
.Language
.get('wcf.comment.button.response.add') + '</a></li>').data('commentID', commentID
).click($.proxy(this._showAddResponse
, this)).appendTo(this._commentButtonList
[commentID
]);
332 var $listItem
= $('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">' + this._userAvatar
+ '</span><div /></div>');
333 if ($placeholder
!== null) {
337 this._commentButtonList
[commentID
].parent().addClass('jsAddResponseActive');
339 $listItem
.appendTo(this._commentButtonList
[commentID
].parent().show());
341 var $inputContainer
= $listItem
.children('div');
342 var $input
= $('<input type="text" placeholder="' + WCF
.Language
.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID
).appendTo($inputContainer
);
343 $('<small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').appendTo($inputContainer
);
346 $input
.keyup(function(event
) { self
._keyUp(event
, true); });
348 comment
.data('responsePlaceholder', $placeholder
).data('responseInput', $listItem
);
352 * Prepares editing of a comment or response.
354 * @param object event
355 * @param boolean isResponse
357 _prepareEdit: function(event
, isResponse
) {
358 var $button
= $(event
.currentTarget
);
360 objectID
: this._container
.data('objectID'),
361 objectTypeID
: this._container
.data('objectTypeID')
364 if (isResponse
=== true) {
365 $data
.responseID
= $button
.data('responseID');
368 $data
.commentID
= $button
.data('commentID');
371 this._proxy
.setOption('data', {
372 actionName
: 'prepareEdit',
373 className
: 'wcf\\data\\comment\\CommentAction',
378 this._proxy
.sendRequest();
382 * Displays the UI elements to create a response.
384 * @param object event
386 _showAddResponse: function(event
) {
387 var $placeholder
= $(event
.currentTarget
);
388 var $commentID
= $placeholder
.data('commentID');
389 if ($placeholder
.prev().hasClass('jsCommentLoadNextResponses')) {
390 this._loadResponsesExecute($commentID
, true);
391 $placeholder
.parent().children('.button').disable();
394 $placeholder
.remove();
396 var $responseInput
= this._comments
[$commentID
].data('responseInput').show();
397 $responseInput
.find('input').focus();
399 $responseInput
.parents('.commentOptionContainer').addClass('jsAddResponseActive');
403 * Handles the keyup event for comments and responses.
405 * @param object event
406 * @param boolean isResponse
408 _keyUp: function(event
, isResponse
) {
409 // ignore every key except for [Enter] and [Esc]
410 if (event
.which
!== 13 && event
.which
!== 27) {
414 var $input
= $(event
.currentTarget
);
417 if (event
.which
=== 27) {
418 $input
.val('').trigger('blur', event
);
422 var $value
= $.trim($input
.val());
424 // ignore empty comments
429 var $actionName
= 'addComment';
432 objectID
: this._container
.data('objectID'),
433 objectTypeID
: this._container
.data('objectTypeID')
435 if (isResponse
=== true) {
436 $actionName
= 'addResponse';
437 $data
.commentID
= $input
.data('commentID');
440 this._proxy
.setOption('data', {
441 actionName
: $actionName
,
442 className
: 'wcf\\data\\comment\\CommentAction',
447 this._proxy
.sendRequest();
450 //$input.val('').blur();
454 * Shows a confirmation message prior to comment or response deletion.
456 * @param object event
457 * @param boolean isResponse
459 _delete: function(event
, isResponse
) {
460 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.comment.delete.confirmMessage'), $.proxy(function(action
) {
461 if (action
=== 'confirm') {
463 objectID
: this._container
.data('objectID'),
464 objectTypeID
: this._container
.data('objectTypeID')
466 if (isResponse
!== true) {
467 $data
.commentID
= $(event
.currentTarget
).data('commentID');
470 $data
.responseID
= $(event
.currentTarget
).data('responseID');
473 this._proxy
.setOption('data', {
474 actionName
: 'remove',
475 className
: 'wcf\\data\\comment\\CommentAction',
480 this._proxy
.sendRequest();
486 * Handles successful AJAX requests.
489 * @param string textStatus
490 * @param jQuery jqXHR
492 _success: function(data
, textStatus
, jqXHR
) {
493 switch (data
.actionName
) {
495 this._commentAdd
.find('input').val('').blur();
496 $(data
.returnValues
.template
).insertAfter(this._commentAdd
).wcfFadeIn();
500 var $comment
= this._comments
[data
.returnValues
.commentID
];
501 $comment
.find('.jsCommentResponseAdd input').val('').blur();
503 var $responseList
= $comment
.find('ul.commentResponseList');
504 if (!$responseList
.length
) $responseList
= $('<ul class="commentResponseList" />').insertBefore($comment
.find('.commentOptionContainer'));
505 $(data
.returnValues
.template
).appendTo($responseList
).wcfFadeIn();
513 this._insertComments(data
);
516 case 'loadResponses':
517 this._insertResponses(data
);
529 WCF
.DOMNodeInsertedHandler
.execute();
533 * Inserts previously loaded comments.
537 _insertComments: function(data
) {
539 $(data
.returnValues
.template
).insertBefore(this._loadNextComments
);
541 // update time of last comment
542 this._container
.data('lastCommentTime', data
.returnValues
.lastCommentTime
);
546 * Inserts previously loaded responses.
550 _insertResponses: function(data
) {
551 var $comment
= this._comments
[data
.returnValues
.commentID
];
554 $(data
.returnValues
.template
).appendTo($comment
.find('ul.commentResponseList'));
556 // update time of last response
557 $comment
.data('lastResponseTime', data
.returnValues
.lastResponseTime
);
559 // update button state to load next responses
560 this._handleLoadNextResponses(data
.returnValues
.commentID
);
564 * Removes a comment or response from list.
568 _remove: function(data
) {
569 if (data
.returnValues
.commentID
) {
570 this._comments
[data
.returnValues
.commentID
].remove();
571 delete this._comments
[data
.returnValues
.commentID
];
574 this._responses
[data
.returnValues
.responseID
].remove();
575 delete this._responses
[data
.returnValues
.responseID
];
580 * Prepares editing of a comment or response.
584 _edit: function(data
) {
585 if (data
.returnValues
.commentID
) {
586 var $content
= this._comments
[data
.returnValues
.commentID
].find('.commentContent:eq(0) .userMessage:eq(0)');
589 var $content
= this._responses
[data
.returnValues
.responseID
].find('.commentContent:eq(0) .userMessage:eq(0)');
592 // replace content with input field
593 $content
.html($.proxy(function(index
, oldHTML
) {
594 var $input
= $('<input type="text" class="long" maxlength="65535" /><small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').val(data
.returnValues
.message
);
595 $input
.data('__html', oldHTML
).keyup($.proxy(this._saveEdit
, this));
597 if (data
.returnValues
.commentID
) {
598 $input
.data('commentID', data
.returnValues
.commentID
);
601 $input
.data('responseID', data
.returnValues
.responseID
);
606 $content
.children('input').focus();
609 $content
.parent().find('.containerHeadline:eq(0)').hide();
610 $content
.parent().find('.buttonGroupNavigation:eq(0)').hide();
614 * Updates a comment or response.
618 _update: function(data
) {
619 if (data
.returnValues
.commentID
) {
620 var $input
= this._comments
[data
.returnValues
.commentID
].find('.commentContent:eq(0) .userMessage:eq(0) > input');
623 var $input
= this._responses
[data
.returnValues
.responseID
].find('.commentContent:eq(0) .userMessage:eq(0) > input');
626 $input
.data('__html', data
.returnValues
.message
);
628 this._cancelEdit($input
);
632 * Saves editing of a comment or response.
634 * @param object event
636 _saveEdit: function(event
) {
637 var $input
= $(event
.currentTarget
);
640 if (event
.which
=== 27) {
641 this._cancelEdit($input
);
644 else if (event
.which
!== 13) {
645 // ignore everything except for [Enter]
649 var $message
= $.trim($input
.val());
651 // ignore empty message
652 if ($message
=== '') {
658 objectID
: this._container
.data('objectID'),
659 objectTypeID
: this._container
.data('objectTypeID')
661 if ($input
.data('commentID')) {
662 $data
.commentID
= $input
.data('commentID');
665 $data
.responseID
= $input
.data('responseID');
668 this._proxy
.setOption('data', {
670 className
: 'wcf\\data\\comment\\CommentAction',
675 this._proxy
.sendRequest()
679 * Cancels editing of a comment or response.
681 * @param jQuery input
683 _cancelEdit: function(input
) {
685 input
.parent().prev('.containerHeadline:eq(0)').show();
686 input
.parent().next('.buttonGroupNavigation:eq(0)').show();
689 input
.parent().html(input
.data('__html'));
694 * Like support for comments
698 WCF
.Comment
.Like
= WCF
.Like
.extend({
700 * @see WCF.Like._getContainers()
702 _getContainers: function() {
703 return $('.commentList > li.comment');
707 * @see WCF.Like._getObjectID()
709 _getObjectID: function(containerID
) {
710 return this._containers
[containerID
].data('commentID');
714 * @see WCF.Like._buildWidget()
716 _buildWidget: function(containerID
, likeButton
, dislikeButton
, badge
, summary
) {
717 this._containers
[containerID
].find('.containerHeadline:eq(0) > h3').append(badge
);
720 likeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
721 dislikeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
726 * @see WCF.Like._getWidgetContainer()
728 _getWidgetContainer: function(containerID
) {},
731 * @see WCF.Like._addWidget()
733 _addWidget: function(containerID
, widget
) {}
737 * Namespace for comment responses
739 WCF
.Comment
.Response
= { };
742 * Like support for comments responses.
746 WCF
.Comment
.Response
.Like
= WCF
.Like
.extend({
748 * @see WCF.Like._addWidget()
750 _addWidget: function(containerID
, widget
) { },
753 * @see WCF.Like._buildWidget()
755 _buildWidget: function(containerID
, likeButton
, dislikeButton
, badge
, summary
) {
756 this._containers
[containerID
].find('.containerHeadline:eq(0) > h3').append(badge
);
759 likeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
760 dislikeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
765 * @see WCF.Like._getContainers()
767 _getContainers: function() {
768 return $('.commentResponseList > li.commentResponse');
772 * @see WCF.Like._getObjectID()
774 _getObjectID: function(containerID
) {
775 return this._containers
[containerID
].data('responseID');
779 * @see WCF.Like._getWidgetContainer()
781 _getWidgetContainer: function(containerID
) { }