Commit | Line | Data |
---|---|---|
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 | */ | |
8 | WCF.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 | }); |