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