2 * Namespace for comments
7 * Comment support for WCF
9 * @author Alexander Ebert
10 * @copyright 2001-2013 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="buttonPrimary 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 $container
= $('<div class="commentOptionContainer" />').hide().insertAfter($comment
.find('ul.commentResponseList'));
232 self
._commentButtonList
[$commentID
] = $('<ul />').appendTo($container
);
234 self
._handleLoadNextResponses($commentID
);
235 self
._initComment($commentID
, $comment
);
236 self
._displayedComments
++;
238 $loadedComments
= true;
241 if ($loadedComments
) {
242 this._handleLoadNextComments();
247 * Initializes a specific comment.
249 * @param integer commentID
250 * @param jQuery comment
252 _initComment: function(commentID
, comment
) {
253 if (this._container
.data('canAdd')) {
254 this._initAddResponse(commentID
, comment
);
257 if (comment
.data('canEdit')) {
258 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>');
259 $editButton
.data('commentID', commentID
).appendTo(comment
.find('ul.commentOptions:eq(0)')).click($.proxy(this._prepareEdit
, this));
262 if (comment
.data('canDelete')) {
263 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>');
264 $deleteButton
.data('commentID', commentID
).appendTo(comment
.find('ul.commentOptions:eq(0)')).click($.proxy(this._delete
, this));
269 * Initializes available responses.
271 _initResponses: function() {
273 this._container
.find('.jsCommentResponse').each(function(index
, response
) {
274 var $response
= $(response
).removeClass('jsCommentResponse');
275 var $responseID
= $response
.data('responseID');
276 self
._responses
[$responseID
] = $response
;
278 self
._initResponse($responseID
, $response
);
283 * Initializes a specific response.
285 * @param integer responseID
286 * @param jQuery response
288 _initResponse: function(responseID
, response
) {
289 if (response
.data('canEdit')) {
290 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>');
293 $editButton
.data('responseID', responseID
).appendTo(response
.find('ul.commentOptions:eq(0)')).click(function(event
) { self
._prepareEdit(event
, true); });
296 if (response
.data('canDelete')) {
297 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>');
300 $deleteButton
.data('responseID', responseID
).appendTo(response
.find('ul.commentOptions:eq(0)')).click(function(event
) { self
._delete(event
, true); });
305 * Initializes the UI components to add a comment.
307 _initAddComment: function() {
309 this._commentAdd
= $('<li class="box32 jsCommentAdd"><span class="framed">' + this._userAvatar
+ '</span><div /></li>').prependTo(this._container
);
310 var $inputContainer
= this._commentAdd
.children('div');
311 var $input
= $('<input type="text" placeholder="' + WCF
.Language
.get('wcf.comment.add') + '" maxlength="65535" class="long" />').appendTo($inputContainer
);
312 $('<small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').appendTo($inputContainer
);
314 $input
.keyup($.proxy(this._keyUp
, this));
318 * Initializes the UI elements to add a response.
320 * @param integer commentID
321 * @param jQuery comment
323 _initAddResponse: function(commentID
, comment
) {
324 var $placeholder
= null;
325 if (!comment
.data('responses') || this._loadNextResponses
[commentID
]) {
326 $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
]);
329 var $listItem
= $('<div class="box32 commentResponseAdd jsCommentResponseAdd"><span class="framed">' + this._userAvatar
+ '</span><div /></div>');
330 if ($placeholder
!== null) {
333 $listItem
.appendTo(this._commentButtonList
[commentID
].parent().show());
335 var $inputContainer
= $listItem
.children('div');
336 var $input
= $('<input type="text" placeholder="' + WCF
.Language
.get('wcf.comment.response.add') + '" maxlength="65535" class="long" />').data('commentID', commentID
).appendTo($inputContainer
);
337 $('<small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').appendTo($inputContainer
);
340 $input
.keyup(function(event
) { self
._keyUp(event
, true); });
342 comment
.data('responsePlaceholder', $placeholder
).data('responseInput', $listItem
);
346 * Prepares editing of a comment or response.
348 * @param object event
349 * @param boolean isResponse
351 _prepareEdit: function(event
, isResponse
) {
352 var $button
= $(event
.currentTarget
);
354 objectID
: this._container
.data('objectID'),
355 objectTypeID
: this._container
.data('objectTypeID')
358 if (isResponse
=== true) {
359 $data
.responseID
= $button
.data('responseID');
362 $data
.commentID
= $button
.data('commentID');
365 this._proxy
.setOption('data', {
366 actionName
: 'prepareEdit',
367 className
: 'wcf\\data\\comment\\CommentAction',
372 this._proxy
.sendRequest();
376 * Displays the UI elements to create a response.
378 * @param object event
380 _showAddResponse: function(event
) {
381 var $placeholder
= $(event
.currentTarget
);
382 var $commentID
= $placeholder
.data('commentID');
383 if ($placeholder
.prev().hasClass('jsCommentLoadNextResponses')) {
384 this._loadResponsesExecute($commentID
, true);
385 $placeholder
.parent().children('.button').disable();
388 $placeholder
.remove();
390 var $responseInput
= this._comments
[$commentID
].data('responseInput').show();
391 $responseInput
.find('input').focus();
395 * Handles the keyup event for comments and responses.
397 * @param object event
398 * @param boolean isResponse
400 _keyUp: function(event
, isResponse
) {
401 // ignore every key except for [Enter] and [Esc]
402 if (event
.which
!== 13 && event
.which
!== 27) {
406 var $input
= $(event
.currentTarget
);
409 if (event
.which
=== 27) {
410 $input
.val('').trigger('blur', event
);
414 var $value
= $.trim($input
.val());
416 // ignore empty comments
421 var $actionName
= 'addComment';
424 objectID
: this._container
.data('objectID'),
425 objectTypeID
: this._container
.data('objectTypeID')
427 if (isResponse
=== true) {
428 $actionName
= 'addResponse';
429 $data
.commentID
= $input
.data('commentID');
432 this._proxy
.setOption('data', {
433 actionName
: $actionName
,
434 className
: 'wcf\\data\\comment\\CommentAction',
439 this._proxy
.sendRequest();
442 $input
.val('').blur();
446 * Shows a confirmation message prior to comment or response deletion.
448 * @param object event
449 * @param boolean isResponse
451 _delete: function(event
, isResponse
) {
452 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.comment.delete.confirmMessage'), $.proxy(function(action
) {
453 if (action
=== 'confirm') {
455 objectID
: this._container
.data('objectID'),
456 objectTypeID
: this._container
.data('objectTypeID')
458 if (isResponse
!== true) {
459 $data
.commentID
= $(event
.currentTarget
).data('commentID');
462 $data
.responseID
= $(event
.currentTarget
).data('responseID');
465 this._proxy
.setOption('data', {
466 actionName
: 'remove',
467 className
: 'wcf\\data\\comment\\CommentAction',
472 this._proxy
.sendRequest();
478 * Handles successful AJAX requests.
481 * @param string textStatus
482 * @param jQuery jqXHR
484 _success: function(data
, textStatus
, jqXHR
) {
485 switch (data
.actionName
) {
487 $(data
.returnValues
.template
).insertAfter(this._commentAdd
).wcfFadeIn();
491 $(data
.returnValues
.template
).appendTo(this._comments
[data
.returnValues
.commentID
].find('ul.commentResponseList')).wcfFadeIn();
499 this._insertComments(data
);
502 case 'loadResponses':
503 this._insertResponses(data
);
515 WCF
.DOMNodeInsertedHandler
.execute();
519 * Inserts previously loaded comments.
523 _insertComments: function(data
) {
525 $(data
.returnValues
.template
).insertBefore(this._loadNextComments
);
527 // update time of last comment
528 this._container
.data('lastCommentTime', data
.returnValues
.lastCommentTime
);
532 * Inserts previously loaded responses.
536 _insertResponses: function(data
) {
537 var $comment
= this._comments
[data
.returnValues
.commentID
];
540 $(data
.returnValues
.template
).appendTo($comment
.find('ul.commentResponseList'));
542 // update time of last response
543 $comment
.data('lastResponseTime', data
.returnValues
.lastResponseTime
);
545 // update button state to load next responses
546 this._handleLoadNextResponses(data
.returnValues
.commentID
);
550 * Removes a comment or response from list.
554 _remove: function(data
) {
555 if (data
.returnValues
.commentID
) {
556 this._comments
[data
.returnValues
.commentID
].remove();
557 delete this._comments
[data
.returnValues
.commentID
];
560 this._responses
[data
.returnValues
.responseID
].remove();
561 delete this._responses
[data
.returnValues
.responseID
];
566 * Prepares editing of a comment or response.
570 _edit: function(data
) {
571 if (data
.returnValues
.commentID
) {
572 var $content
= this._comments
[data
.returnValues
.commentID
].find('.commentContent:eq(0) .userMessage:eq(0)');
575 var $content
= this._responses
[data
.returnValues
.responseID
].find('.commentContent:eq(0) .userMessage:eq(0)');
578 // replace content with input field
579 $content
.html($.proxy(function(index
, oldHTML
) {
580 var $input
= $('<input type="text" class="long" maxlength="65535" /><small>' + WCF
.Language
.get('wcf.comment.description') + '</small>').val(data
.returnValues
.message
);
581 $input
.data('__html', oldHTML
).keyup($.proxy(this._saveEdit
, this));
583 if (data
.returnValues
.commentID
) {
584 $input
.data('commentID', data
.returnValues
.commentID
);
587 $input
.data('responseID', data
.returnValues
.responseID
);
592 $content
.children('input').focus();
595 $content
.parent().find('.containerHeadline:eq(0)').hide();
596 $content
.parent().find('.buttonGroupNavigation:eq(0)').hide();
600 * Updates a comment or response.
604 _update: function(data
) {
605 if (data
.returnValues
.commentID
) {
606 var $input
= this._comments
[data
.returnValues
.commentID
].find('.commentContent:eq(0) .userMessage:eq(0) > input');
609 var $input
= this._responses
[data
.returnValues
.responseID
].find('.commentContent:eq(0) .userMessage:eq(0) > input');
612 $input
.data('__html', data
.returnValues
.message
);
614 this._cancelEdit($input
);
618 * Saves editing of a comment or response.
620 * @param object event
622 _saveEdit: function(event
) {
623 var $input
= $(event
.currentTarget
);
626 if (event
.which
=== 27) {
627 this._cancelEdit($input
);
630 else if (event
.which
!== 13) {
631 // ignore everything except for [Enter]
635 var $message
= $.trim($input
.val());
637 // ignore empty message
638 if ($message
=== '') {
644 objectID
: this._container
.data('objectID'),
645 objectTypeID
: this._container
.data('objectTypeID')
647 if ($input
.data('commentID')) {
648 $data
.commentID
= $input
.data('commentID');
651 $data
.responseID
= $input
.data('responseID');
654 this._proxy
.setOption('data', {
656 className
: 'wcf\\data\\comment\\CommentAction',
661 this._proxy
.sendRequest()
665 * Cancels editing of a comment or response.
667 * @param jQuery input
669 _cancelEdit: function(input
) {
671 input
.parent().prev('.containerHeadline:eq(0)').show();
672 input
.parent().next('.buttonGroupNavigation:eq(0)').show();
675 input
.parent().html(input
.data('__html'));
680 * Like support for comments
684 WCF
.Comment
.Like
= WCF
.Like
.extend({
686 * @see WCF.Like._getContainers()
688 _getContainers: function() {
689 return $('.commentList > li.comment');
693 * @see WCF.Like._getObjectID()
695 _getObjectID: function(containerID
) {
696 return this._containers
[containerID
].data('commentID');
700 * @see WCF.Like._buildWidget()
702 _buildWidget: function(containerID
, likeButton
, dislikeButton
, badge
, summary
) {
703 this._containers
[containerID
].find('.containerHeadline:eq(0) > h3').append(badge
);
706 likeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
707 dislikeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
712 * @see WCF.Like._getWidgetContainer()
714 _getWidgetContainer: function(containerID
) {},
717 * @see WCF.Like._addWidget()
719 _addWidget: function(containerID
, widget
) {}
723 * Namespace for comment responses
725 WCF
.Comment
.Response
= { };
728 * Like support for comments responses.
732 WCF
.Comment
.Response
.Like
= WCF
.Like
.extend({
734 * @see WCF.Like._addWidget()
736 _addWidget: function(containerID
, widget
) { },
739 * @see WCF.Like._buildWidget()
741 _buildWidget: function(containerID
, likeButton
, dislikeButton
, badge
, summary
) {
742 this._containers
[containerID
].find('.containerHeadline:eq(0) > h3').append(badge
);
745 likeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
746 dislikeButton
.appendTo(this._containers
[containerID
].find('.commentOptions:eq(0)'));
751 * @see WCF.Like._getContainers()
753 _getContainers: function() {
754 return $('.commentResponseList > li.commentResponse');
758 * @see WCF.Like._getObjectID()
760 _getObjectID: function(containerID
) {
761 return this._containers
[containerID
].data('responseID');
765 * @see WCF.Like._getWidgetContainer()
767 _getWidgetContainer: function(containerID
) { }