Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WCF.Like.js
CommitLineData
00b6f956
MW
1/**
2 * Like support for WCF
3 *
4 * @author Alexander Ebert
ca4ba303 5 * @copyright 2001-2014 WoltLab GmbH
00b6f956
MW
6 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7 */
8WCF.Like = Class.extend({
9 /**
10 * true, if users can like their own content
11 * @var boolean
12 */
13 _allowForOwnContent: false,
14
15 /**
e3369fd2 16 * user can like
00b6f956
MW
17 * @var boolean
18 */
19 _canLike: false,
20
21 /**
22 * list of containers
23 * @var object
24 */
25 _containers: { },
26
27 /**
28 * container meta data
29 * @var object
30 */
31 _containerData: { },
32
33 /**
34 * enables the dislike option
35 */
36 _enableDislikes: true,
37
38 /**
39 * prevents like/dislike until the server responded
40 * @var boolean
41 */
42 _isBusy: false,
43
44 /**
32514cd5 45 * cached grouped user lists for like details
00b6f956
MW
46 * @var object
47 */
48 _likeDetails: { },
49
00b6f956
MW
50 /**
51 * proxy object
52 * @var WCF.Action.Proxy
53 */
54 _proxy: null,
55
56 /**
57 * shows the detailed summary of users who liked the object
32514cd5 58 * @var boolean
00b6f956
MW
59 */
60 _showSummary: true,
61
62 /**
63 * Initializes like support.
64 *
65 * @param boolean canLike
66 * @param boolean enableDislikes
67 * @param boolean showSummary
68 * @param boolean allowForOwnContent
69 */
70 init: function(canLike, enableDislikes, showSummary, allowForOwnContent) {
71 this._canLike = canLike;
72 this._enableDislikes = enableDislikes;
73 this._isBusy = false;
74 this._likeDetails = { };
00b6f956
MW
75 this._showSummary = showSummary;
76 this._allowForOwnContent = allowForOwnContent;
77
78 var $containers = this._getContainers();
79 this._initContainers($containers);
80
81 this._proxy = new WCF.Action.Proxy({
82 success: $.proxy(this._success, this)
83 });
84
85 // bind dom node inserted listener
86 var $date = new Date();
87 var $identifier = $date.toString().hashCode + $date.getUTCMilliseconds();
88 WCF.DOMNodeInsertedHandler.addCallback('WCF.Like' + $identifier, $.proxy(this._domNodeInserted, this));
89 },
90
91 /**
92 * Initialize containers once new nodes are inserted.
93 */
94 _domNodeInserted: function() {
95 var $containers = this._getContainers();
96 this._initContainers($containers);
97
98 },
99
100 /**
101 * Initializes like containers.
102 *
103 * @param object containers
104 */
105 _initContainers: function(containers) {
106 var $createdWidgets = false;
107 containers.each($.proxy(function(index, container) {
108 // set container
109 var $container = $(container);
110 var $containerID = $container.wcfIdentify();
111
112 if (!this._containers[$containerID]) {
113 this._containers[$containerID] = $container;
114
115 // set container data
116 this._containerData[$containerID] = {
117 'likeButton': null,
118 'badge': null,
119 'dislikeButton': null,
120 'likes': $container.data('like-likes'),
121 'dislikes': $container.data('like-dislikes'),
122 'objectType': $container.data('objectType'),
123 'objectID': this._getObjectID($containerID),
124 'users': eval($container.data('like-users')),
125 'liked': $container.data('like-liked')
126 };
127
128 // create UI
129 this._createWidget($containerID);
130
131 $createdWidgets = true;
132 }
133 }, this));
134
135 if ($createdWidgets) {
136 new WCF.PeriodicalExecuter(function(pe) {
137 pe.stop();
138
42d7d2cc 139 WCF.DOMNodeInsertedHandler.execute();
00b6f956
MW
140 }, 250);
141 }
142 },
143
144 /**
145 * Returns a list of available object containers.
146 *
147 * @return jQuery
148 */
149 _getContainers: function() { },
150
151 /**
152 * Returns widget container for target object container.
153 *
154 * @param string containerID
155 * @return jQuery
156 */
157 _getWidgetContainer: function(containerID) { },
158
159 /**
160 * Returns object id for targer object container.
161 *
162 * @param string containerID
163 * @return integer
164 */
165 _getObjectID: function(containerID) { },
166
167 /**
168 * Adds the like widget.
169 *
170 * @param integer containerID
171 * @param jQuery widget
172 */
173 _addWidget: function(containerID, widget) {
174 var $widgetContainer = this._getWidgetContainer(containerID);
175
176 widget.appendTo($widgetContainer);
177 },
178
179 /**
180 * Builds the like widget.
181 *
182 * @param integer containerID
183 * @param jQuery likeButton
184 * @param jQuery dislikeButton
185 * @param jQuery badge
186 * @param jQuery summary
187 */
188 _buildWidget: function(containerID, likeButton, dislikeButton, badge, summary) {
189 var $widget = $('<aside class="likesWidget"><ul></ul></aside>');
190 if (this._canLike) {
191 likeButton.appendTo($widget.find('ul'));
192 dislikeButton.appendTo($widget.find('ul'));
193 }
194 badge.appendTo($widget);
195
196 this._addWidget(containerID, $widget);
197 },
198
199 /**
200 * Creates the like widget.
201 *
202 * @param integer containerID
203 */
204 _createWidget: function(containerID) {
60320b16
MW
205 var $likeButton = $('<li class="likeButton"><a title="'+WCF.Language.get('wcf.like.button.like')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-up-alt" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.like')+'</span></a></li>');
206 var $dislikeButton = $('<li class="dislikeButton"><a title="'+WCF.Language.get('wcf.like.button.dislike')+'" class="jsTooltip"><span class="icon icon16 icon-thumbs-down-alt" /> <span class="invisible">'+WCF.Language.get('wcf.like.button.dislike')+'</span></a></li>');
00b6f956
MW
207 if (!this._enableDislikes) $dislikeButton.hide();
208
209 if (!this._allowForOwnContent && (WCF.User.userID == this._containers[containerID].data('userID'))) {
27c7fed5
MW
210 $likeButton = $('');
211 $dislikeButton = $('');
00b6f956
MW
212 }
213
214 var $badge = $('<a class="badge jsTooltip likesBadge" />').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
215
216 var $summary = null;
217 if (this._showSummary) {
218 $summary = $('<p class="likesSummary"><span class="pointer" /></p>');
219 $summary.children('span').data('containerID', containerID).click($.proxy(this._showLikeDetails, this));
220 }
221 this._buildWidget(containerID, $likeButton, $dislikeButton, $badge, $summary);
222
223 this._containerData[containerID].likeButton = $likeButton;
224 this._containerData[containerID].dislikeButton = $dislikeButton;
225 this._containerData[containerID].badge = $badge;
226 this._containerData[containerID].summary = $summary;
227
228 $likeButton.data('containerID', containerID).data('type', 'like').click($.proxy(this._click, this));
229 $dislikeButton.data('containerID', containerID).data('type', 'dislike').click($.proxy(this._click, this));
230 this._setActiveState($likeButton, $dislikeButton, this._containerData[containerID].liked);
231 this._updateBadge(containerID);
232 if (this._showSummary) this._updateSummary(containerID);
233 },
234
235 /**
236 * Displays like details for an object.
237 *
238 * @param object event
239 * @param string containerID
240 */
241 _showLikeDetails: function(event, containerID) {
242 var $containerID = (event === null) ? containerID : $(event.currentTarget).data('containerID');
243
244 if (this._likeDetails[$containerID] === undefined) {
32514cd5
MS
245 this._likeDetails[$containerID] = new WCF.User.List('wcf\\data\\like\\LikeAction', WCF.Language.get('wcf.like.details'), {
246 data: {
247 containerID: $containerID,
248 objectID: this._containerData[$containerID].objectID,
249 objectType: this._containerData[$containerID].objectType
00b6f956
MW
250 }
251 });
00b6f956 252 }
32514cd5
MS
253
254 this._likeDetails[$containerID].open();
00b6f956
MW
255 },
256
257 /**
258 * Handles likes and dislikes.
259 *
260 * @param object event
261 */
262 _click: function(event) {
263 var $button = $(event.currentTarget);
264 if ($button === null) {
265 console.debug("[WCF.Like] Unable to find target button, aborting.");
266 return;
267 }
268
269 this._sendRequest($button.data('containerID'), $button.data('type'));
270 },
271
272 /**
273 * Sends request through proxy.
274 *
275 * @param integer containerID
276 * @param string type
277 */
278 _sendRequest: function(containerID, type) {
279 // ignore retards spamming clicks on the buttons
280 if (this._isBusy) {
281 return;
282 }
283
ba906dcd
AE
284 this._isBusy = true;
285
00b6f956
MW
286 this._proxy.setOption('data', {
287 actionName: type,
288 className: 'wcf\\data\\like\\LikeAction',
289 parameters: {
290 data: {
291 containerID: containerID,
292 objectID: this._containerData[containerID].objectID,
293 objectType: this._containerData[containerID].objectType
294 }
295 }
296 });
297
298 this._proxy.sendRequest();
299 },
300
301 /**
302 * Updates likeable object.
303 *
304 * @param object data
305 * @param string textStatus
306 * @param object jqXHR
307 */
308 _success: function(data, textStatus, jqXHR) {
309 var $containerID = data.returnValues.containerID;
310
311 if (!this._containers[$containerID]) {
312 return;
313 }
314
315 switch (data.actionName) {
316 case 'dislike':
317 case 'like':
318 // update container data
319 this._containerData[$containerID].likes = parseInt(data.returnValues.likes);
320 this._containerData[$containerID].dislikes = parseInt(data.returnValues.dislikes);
321 this._containerData[$containerID].users = data.returnValues.users;
322
323 // update label
324 this._updateBadge($containerID);
325 // update summary
326 if (this._showSummary) this._updateSummary($containerID);
327
328 // mark button as active
329 var $likeButton = this._containerData[$containerID].likeButton;
330 var $dislikeButton = this._containerData[$containerID].dislikeButton;
331 var $likeStatus = 0;
332 if (data.returnValues.isLiked) $likeStatus = 1;
333 else if (data.returnValues.isDisliked) $likeStatus = -1;
334 this._setActiveState($likeButton, $dislikeButton, $likeStatus);
335
336 // invalidate cache for like details
337 if (this._likeDetails[$containerID] !== undefined) {
338 delete this._likeDetails[$containerID];
339 }
340
341 this._isBusy = false;
342 break;
00b6f956
MW
343 }
344 },
345
346 _updateBadge: function(containerID) {
347 if (!this._containerData[containerID].likes && !this._containerData[containerID].dislikes) {
348 this._containerData[containerID].badge.hide();
349 }
350 else {
351 this._containerData[containerID].badge.show();
352
353 // update like counter
354 var $cumulativeLikes = this._containerData[containerID].likes - this._containerData[containerID].dislikes;
355 var $badge = this._containerData[containerID].badge;
356 $badge.removeClass('green red');
357 if ($cumulativeLikes > 0) {
358 $badge.text('+' + WCF.String.formatNumeric($cumulativeLikes));
359 $badge.addClass('green');
360 }
361 else if ($cumulativeLikes < 0) {
362 $badge.text(WCF.String.formatNumeric($cumulativeLikes));
363 $badge.addClass('red');
364 }
365 else {
366 $badge.text('\u00B10');
367 }
368
369 // update tooltip
370 var $likes = this._containerData[containerID].likes;
371 var $dislikes = this._containerData[containerID].dislikes;
a2067610 372 $badge.data('tooltip', WCF.Language.get('wcf.like.tooltip', { likes: $likes, dislikes: $dislikes }));
00b6f956
MW
373 }
374 },
375
376 _updateSummary: function(containerID) {
377 if (!this._containerData[containerID].likes) {
378 this._containerData[containerID].summary.hide();
379 }
380 else {
381 this._containerData[containerID].summary.show();
382
383 var $users = this._containerData[containerID].users;
384 var $userArray = [];
385 for (var $userID in $users) $userArray.push($users[$userID].username);
386 var $others = this._containerData[containerID].likes - $userArray.length;
387
a2067610 388 this._containerData[containerID].summary.children('span').html(WCF.Language.get('wcf.like.summary', { users: $userArray, others: $others }));
00b6f956
MW
389 }
390 },
391
392 /**
393 * Sets button active state.
394 *
06355ec3
MS
395 * @param jquery likeButton
396 * @param jquery dislikeButton
00b6f956
MW
397 * @param integer likeStatus
398 */
399 _setActiveState: function(likeButton, dislikeButton, likeStatus) {
400 likeButton.removeClass('active');
401 dislikeButton.removeClass('active');
402
403 if (likeStatus == 1) {
404 likeButton.addClass('active');
405 }
406 else if (likeStatus == -1) {
407 dislikeButton.addClass('active');
408 }
409 }
410});