4 * User-related classes.
6 * @author Alexander Ebert
7 * @copyright 2001-2018 WoltLab GmbH
8 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14 * @param boolean isQuickLogin
16 WCF
.User
.Login
= Class
.extend({
21 _loginSubmitButton
: null,
30 * password input container
33 _passwordContainer
: null,
42 * cookie input container
45 _useCookiesContainer
: null,
48 * Initializes the user login
50 * @param boolean isQuickLogin
52 init: function(isQuickLogin
) {
53 this._loginSubmitButton
= $('#loginSubmitButton');
54 this._password
= $('#password'),
55 this._passwordContainer
= this._password
.parents('dl');
56 this._useCookies
= $('#useCookies');
57 this._useCookiesContainer
= this._useCookies
.parents('dl');
59 var $loginForm
= $('#loginForm');
60 $loginForm
.find('input[name=action]').change($.proxy(this._change
, this));
63 WCF
.User
.QuickLogin
.init();
68 * Handle toggle between login and register.
72 _change: function(event
) {
73 if ($(event
.currentTarget
).val() === 'register') {
74 this._setState(false, WCF
.Language
.get('wcf.user.button.register'));
77 this._setState(true, WCF
.Language
.get('wcf.user.button.login'));
84 * @param boolean enable
85 * @param string buttonTitle
87 _setState: function(enable
, buttonTitle
) {
89 this._password
.enable();
90 this._passwordContainer
.removeClass('disabled');
91 this._useCookies
.enable();
92 this._useCookiesContainer
.removeClass('disabled');
95 this._password
.disable();
96 this._passwordContainer
.addClass('disabled');
97 this._useCookies
.disable();
98 this._useCookiesContainer
.addClass('disabled');
101 this._loginSubmitButton
.val(buttonTitle
);
106 * Namespace for User Panel classes.
108 WCF
.User
.Panel
= { };
110 if (COMPILER_TARGET_DEFAULT
) {
112 * Abstract implementation for user panel items providing an interactive dropdown.
114 * @param jQuery triggerElement
115 * @param string identifier
116 * @param object options
118 WCF
.User
.Panel
.Abstract
= Class
.extend({
126 * interactive dropdown instance
127 * @var WCF.Dropdown.Interactive.Instance
132 * dropdown identifier
138 * true if data should be loaded using an AJAX request
144 * header icon to mark all items belonging to this user panel item as read
147 _markAllAsReadLink
: null,
150 * list of options required to dropdown initialization and UI features
157 * @var WCF.Action.Proxy
165 _triggerElement
: null,
168 * Initializes the WCF.User.Panel.Abstract class.
170 * @param jQuery triggerElement
171 * @param string identifier
172 * @param object options
174 init: function (triggerElement
, identifier
, options
) {
175 this._dropdown
= null;
176 this._loadData
= true;
177 this._identifier
= identifier
;
178 this._triggerElement
= triggerElement
;
179 this._options
= options
;
181 this._proxy
= new WCF
.Action
.Proxy({
182 showLoadingOverlay
: false,
183 success
: $.proxy(this._success
, this)
186 this._triggerElement
.click($.proxy(this.toggle
, this));
188 if (this._options
.showAllLink
) {
189 this._triggerElement
.dblclick($.proxy(this._dblClick
, this));
192 if (this._options
.staticDropdown
=== true) {
193 this._loadData
= false;
196 var $badge
= this._triggerElement
.find('span.badge');
198 this._badge
= $badge
;
204 * Toggles the interactive dropdown.
206 * @param {Event?} event
209 toggle: function (event
) {
210 if (event
instanceof Event
) {
211 event
.preventDefault();
214 if (this._dropdown
=== null) {
215 this._dropdown
= this._initDropdown();
218 if (this._dropdown
.toggle()) {
219 if (!this._loadData
) {
220 // check if there are outstanding items but there are no outstanding ones in the current list
221 if (this._badge
!== null) {
222 var $count
= parseInt(this._badge
.text()) || 0;
223 if ($count
&& !this._dropdown
.getItemList().children('.interactiveDropdownItemOutstanding').length
) {
224 this._loadData
= true;
229 if (this._loadData
) {
230 this._loadData
= false;
239 * Forward to original URL by double clicking the trigger element.
241 * @param object event
244 _dblClick: function (event
) {
245 event
.preventDefault();
247 window
.location
= this._options
.showAllLink
;
253 * Initializes the dropdown on first usage.
255 * @return WCF.Dropdown.Interactive.Instance
257 _initDropdown: function () {
258 var $dropdown
= WCF
.Dropdown
.Interactive
.Handler
.create(this._triggerElement
, this._identifier
, this._options
);
259 $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF
.Language
.get('wcf.global.loading') + '</span></li>').appendTo($dropdown
.getItemList());
265 * Loads item list data via AJAX.
268 // override this in your own implementation to fetch display data
272 * Handles successful AJAX requests.
276 _success: function (data
) {
277 if (data
.returnValues
.template
!== undefined) {
278 var $itemList
= this._dropdown
.getItemList().empty();
279 $(data
.returnValues
.template
).appendTo($itemList
);
281 if (!$itemList
.children().length
) {
282 $('<li class="noItems">' + this._options
.noItems
+ '</li>').appendTo($itemList
);
285 if (this._options
.enableMarkAsRead
) {
286 var $outstandingItems
= this._dropdown
.getItemList().children('.interactiveDropdownItemOutstanding');
287 if (this._markAllAsReadLink
=== null && $outstandingItems
.length
&& this._options
.markAllAsReadConfirmMessage
) {
288 var $button
= this._markAllAsReadLink
= $('<li class="interactiveDropdownItemMarkAllAsRead"><a href="#" title="' + WCF
.Language
.get('wcf.user.panel.markAllAsRead') + '" class="jsTooltip"><span class="icon icon24 fa-check" /></a></li>').appendTo(this._dropdown
.getLinkList());
289 $button
.click((function (event
) {
290 this._dropdown
.close();
292 WCF
.System
.Confirmation
.show(this._options
.markAllAsReadConfirmMessage
, (function (action
) {
293 if (action
=== 'confirm') {
294 this._markAllAsRead();
302 $outstandingItems
.each((function (index
, item
) {
303 var $item
= $(item
).addClass('interactiveDropdownItemOutstandingIcon');
304 var $objectID
= $item
.data('objectID');
306 var $button
= $('<div class="interactiveDropdownItemMarkAsRead"><a href="#" title="' + WCF
.Language
.get('wcf.user.panel.markAsRead') + '" class="jsTooltip"><span class="icon icon16 fa-check" /></a></div>').appendTo($item
);
307 $button
.click((function (event
) {
308 this._markAsRead(event
, $objectID
);
315 this._dropdown
.getItemList().children().each(function (index
, item
) {
317 var $link
= $item
.data('link');
320 if ($.browser
.msie
) {
321 $item
.click(function (event
) {
322 if (event
.target
.tagName
!== 'A') {
323 window
.location
= $link
;
330 $item
.addClass('interactiveDropdownItemShadow');
331 $('<a href="' + $link
+ '" class="interactiveDropdownItemShadowLink" />').appendTo($item
);
334 if ($item
.data('linkReplaceAll')) {
335 $item
.find('> .box48 a:not(.userLink)').prop('href', $link
);
340 this._dropdown
.rebuildScrollbar();
343 if (data
.returnValues
.totalCount
!== undefined) {
344 this.updateBadge(data
.returnValues
.totalCount
);
347 if (this._options
.enableMarkAsRead
) {
348 if (data
.returnValues
.markAsRead
) {
349 var $item
= this._dropdown
.getItemList().children('li[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
351 $item
.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
352 $item
.children('.interactiveDropdownItemMarkAsRead').remove();
355 else if (data
.returnValues
.markAllAsRead
) {
363 * Marks an item as read.
365 * @param object event
366 * @param integer objectID
368 _markAsRead: function (event
, objectID
) {
369 // override this in your own implementation to mark an item as read
373 * Marks all items as read.
375 _markAllAsRead: function () {
376 // override this in your own implementation to mark all items as read
380 * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
382 * @param integer count
384 updateBadge: function (count
) {
385 count
= parseInt(count
) || 0;
388 if (this._badge
=== null) {
389 this._badge
= $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement
.children('a'));
390 this._badge
.before(' ');
393 this._badge
.text(count
);
395 else if (this._badge
!== null) {
396 this._badge
.remove();
400 if (this._options
.enableMarkAsRead
) {
401 if (!count
&& this._markAllAsReadLink
!== null) {
402 this._markAllAsReadLink
.remove();
403 this._markAllAsReadLink
= null;
407 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
409 identifier
: this._identifier
414 * Resets the dropdown's inner item list.
416 resetItems: function () {
417 // this method could be called from outside, but the dropdown was never
418 // toggled and thus never initialized
419 if (this._dropdown
!== null) {
420 this._dropdown
.resetItems();
421 this._loadData
= true;
427 * User Panel implementation for user notifications.
429 * @see WCF.User.Panel.Abstract
431 WCF
.User
.Panel
.Notification
= WCF
.User
.Panel
.Abstract
.extend({
439 * @see WCF.User.Panel.Abstract.init()
441 init: function (options
) {
442 options
.enableMarkAsRead
= true;
444 this._super($('#userNotifications'), 'userNotifications', options
);
447 this._favico
= new Favico({
452 if (this._badge
!== null) {
453 var $count
= parseInt(this._badge
.text()) || 0;
454 this._favico
.badge($count
);
458 console
.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e
.message
);
461 WCF
.System
.PushNotification
.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount
, this));
463 require(['EventHandler'], (function (EventHandler
) {
464 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', (function (data
) {
465 if (data
.identifier
=== 'com.woltlab.wcf.notifications') {
473 * @see WCF.User.Panel.Abstract._initDropdown()
475 _initDropdown: function () {
476 var $dropdown
= this._super();
478 $('<li><a href="' + this._options
.settingsLink
+ '" title="' + WCF
.Language
.get('wcf.user.panel.settings') + '" class="jsTooltip"><span class="icon icon24 fa-cog" /></a></li>').appendTo($dropdown
.getLinkList());
484 * @see WCF.User.Panel.Abstract._load()
487 this._proxy
.setOption('data', {
488 actionName
: 'getOutstandingNotifications',
489 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
491 this._proxy
.sendRequest();
495 * @see WCF.User.Panel.Abstract._markAsRead()
497 _markAsRead: function (event
, objectID
) {
498 this._proxy
.setOption('data', {
499 actionName
: 'markAsConfirmed',
500 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
501 objectIDs
: [objectID
]
503 this._proxy
.sendRequest();
507 * @see WCF.User.Panel.Abstract._markAllAsRead()
509 _markAllAsRead: function (event
) {
510 this._proxy
.setOption('data', {
511 actionName
: 'markAllAsConfirmed',
512 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
514 this._proxy
.sendRequest();
518 * @see WCF.User.Panel.Abstract.resetItems()
520 resetItems: function () {
523 if (this._markAllAsReadLink
) {
524 this._markAllAsReadLink
.remove();
525 this._markAllAsReadLink
= null;
530 * @see WCF.User.Panel.Abstract.updateBadge()
532 updateBadge: function (count
) {
533 count
= parseInt(count
) || 0;
535 // update data attribute
536 $('#userNotifications').attr('data-count', count
);
538 if (this._favico
!== null) {
539 this._favico
.badge(count
);
546 * Updates the badge counter and resets the dropdown's item list.
548 * @param integer count
550 updateUserNotificationCount: function (count
) {
551 if (this._dropdown
!== null) {
552 this._dropdown
.resetItems();
555 this.updateBadge(count
);
560 * User Panel implementation for user menu dropdown.
562 * @see WCF.User.Panel.Abstract
564 WCF
.User
.Panel
.UserMenu
= WCF
.User
.Panel
.Abstract
.extend({
566 * @see WCF.User.Panel.Abstract.init()
569 this._super($('#userMenu'), 'userMenu', {
570 pointerOffset
: '13px',
577 WCF
.User
.Panel
.Abstract
= Class
.extend({
582 _markAllAsReadLink
: {},
587 toggle: function() {},
588 _dblClick: function() {},
589 _initDropdown: function() {},
590 _load: function() {},
591 _success: function() {},
592 _markAsRead: function() {},
593 _markAllAsRead: function() {},
594 updateBadge: function() {},
595 resetItems: function() {}
598 WCF
.User
.Panel
.Notification
= WCF
.User
.Panel
.Abstract
.extend({
601 _initDropdown: function() {},
602 _load: function() {},
603 _markAsRead: function() {},
604 _markAllAsRead: function() {},
605 resetItems: function() {},
606 updateBadge: function() {},
607 updateUserNotificationCount: function() {},
612 _markAllAsReadLink
: {},
616 toggle: function() {},
617 _dblClick: function() {},
618 _success: function() {}
621 WCF
.User
.Panel
.UserMenu
= WCF
.User
.Panel
.Abstract
.extend({
627 _markAllAsReadLink
: {},
631 toggle: function() {},
632 _dblClick: function() {},
633 _initDropdown: function() {},
634 _load: function() {},
635 _success: function() {},
636 _markAsRead: function() {},
637 _markAllAsRead: function() {},
638 updateBadge: function() {},
639 resetItems: function() {}
646 WCF
.User
.QuickLogin
= {
648 * Initializes the quick login box
651 require(['EventHandler', 'Ui/Dialog'], function(EventHandler
, UiDialog
) {
652 var loginForm
= elById('loginForm');
653 var loginSection
= elBySel('.loginFormLogin', loginForm
);
654 if (loginSection
&& !loginSection
.nextElementSibling
) {
655 loginForm
.classList
.add('loginFormLoginOnly');
658 var registrationBlock
= elBySel('.loginFormRegister', loginForm
);
660 var callbackOpen = function(event
) {
661 if (event
instanceof Event
) {
662 event
.preventDefault();
663 event
.stopPropagation();
666 loginForm
.style
.removeProperty('display');
668 UiDialog
.openStatic('loginForm', null, {
669 title
: WCF
.Language
.get('wcf.user.login')
672 // The registration part should always be on the right
673 // but some browser (Firefox and IE) have a really bad
674 // support for forcing column breaks, requiring us to
675 // work around it by force pushing it to the right.
676 if (loginSection
!== null && registrationBlock
!== null) {
677 var loginOffset
= loginSection
.offsetTop
;
679 if (loginForm
.clientWidth
> loginSection
.clientWidth
* 2) {
680 while (loginOffset
< (registrationBlock
.offsetTop
- 50)) {
681 // push the registration down by 100 pixel each time
683 loginSection
.style
.setProperty('margin-bottom', margin
+ 'px', '');
689 var links
= document
.getElementsByClassName('loginLink');
690 for (var i
= 0, length
= links
.length
; i
< length
; i
++) {
691 links
[i
].addEventListener(WCF_CLICK_EVENT
, callbackOpen
);
694 var input
= loginForm
.querySelector('#loginForm input[name=url]');
695 if (input
!== null && !input
.value
.match(/^https?:\/\//)) {
696 input
.setAttribute('value', window
.location
.protocol
+ '//' + window
.location
.host
+ input
.getAttribute('value'));
699 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', function(data
) {
700 if (data
.identifier
=== 'com.woltlab.wcf.login') {
701 data
.handler
.close(true);
711 * UserProfile namespace
713 WCF
.User
.Profile
= {};
716 * Shows the activity point list for users.
718 WCF
.User
.Profile
.ActivityPointList
= {
720 * list of cached templates
732 * initialization state
739 * @var WCF.Action.Proxy
744 * Initializes the WCF.User.Profile.ActivityPointList class.
753 this._proxy
= new WCF
.Action
.Proxy({
754 success
: $.proxy(this._success
, this)
759 WCF
.DOMNodeInsertedHandler
.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init
, this));
761 this._didInit
= true;
765 * Initializes display for activity points.
768 $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click
, this));
772 * Shows or loads the activity point for selected user.
774 * @param object event
776 _click: function(event
) {
777 event
.preventDefault();
778 var $userID
= $(event
.currentTarget
).data('userID');
780 if (this._cache
[$userID
] === undefined) {
781 this._proxy
.setOption('data', {
782 actionName
: 'getDetailedActivityPointList',
783 className
: 'wcf\\data\\user\\UserProfileAction',
784 objectIDs
: [ $userID
]
786 this._proxy
.sendRequest();
794 * Displays activity points for given user.
796 * @param integer userID
798 _show: function(userID
) {
799 if (this._dialog
=== null) {
800 this._dialog
= $('<div>' + this._cache
[userID
] + '</div>').hide().appendTo(document
.body
);
801 this._dialog
.wcfDialog({
802 title
: WCF
.Language
.get('wcf.user.activityPoint')
806 this._dialog
.html(this._cache
[userID
]);
807 this._dialog
.wcfDialog('open');
812 * Handles successful AJAX requests.
815 * @param string textStatus
816 * @param jQuery jqXHR
818 _success: function(data
, textStatus
, jqXHR
) {
819 this._cache
[data
.returnValues
.userID
] = data
.returnValues
.template
;
820 this._show(data
.returnValues
.userID
);
825 * Provides methods to load tab menu content upon request.
827 WCF
.User
.Profile
.TabMenu
= Class
.extend({
838 _profileContent
: null,
842 * @var WCF.Action.Proxy
853 * Initializes the tab menu loader.
855 * @param integer userID
857 init: function(userID
) {
858 this._profileContent
= $('#profileContent');
859 this._userID
= userID
;
861 var $activeMenuItem
= this._profileContent
.data('active');
862 var $enableProxy
= false;
864 // fetch content state
865 this._profileContent
.find('div.tabMenuContent').each($.proxy(function(index
, container
) {
866 var $containerID
= $(container
).wcfIdentify();
868 if ($activeMenuItem
=== $containerID
) {
869 this._hasContent
[$containerID
] = true;
872 this._hasContent
[$containerID
] = false;
877 // enable loader if at least one container is empty
879 this._proxy
= new WCF
.Action
.Proxy({
880 success
: $.proxy(this._success
, this)
883 this._profileContent
.on('wcftabsbeforeactivate', $.proxy(this._loadContent
, this));
885 // check which tab is selected
886 this._profileContent
.find('> nav.tabMenu > ul > li').each($.proxy(function(index
, listItem
) {
887 var $listItem
= $(listItem
);
888 if ($listItem
.hasClass('ui-state-active')) {
890 this._loadContent(null, {
891 newPanel
: $('#' + $listItem
.attr('aria-controls'))
900 $('.userProfileUser .contentDescription a[href$="#likes"]').click((function (event
) {
901 event
.preventDefault();
903 require(['Ui/TabMenu'], function (UiTabMenu
) {
904 UiTabMenu
.getTabMenu('profileContent').select('likes');
910 * Prepares to load content once tabs are being switched.
912 * @param object event
915 _loadContent: function(event
, ui
) {
916 var $panel
= $(ui
.newPanel
);
917 var $containerID
= $panel
.attr('id');
919 if (!this._hasContent
[$containerID
]) {
920 this._proxy
.setOption('data', {
921 actionName
: 'getContent',
922 className
: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
925 containerID
: $containerID
,
926 menuItem
: $panel
.data('menuItem'),
931 this._proxy
.sendRequest();
936 * Shows previously requested content.
939 * @param string textStatus
940 * @param jQuery jqXHR
942 _success: function(data
, textStatus
, jqXHR
) {
943 var $containerID
= data
.returnValues
.containerID
;
944 this._hasContent
[$containerID
] = true;
946 // insert content, uses non jQuery because DomUtil.insertHtml() moves <script> elements
947 // to the bottom of the element by default which is exactly what is required here
948 require(['Dom/ChangeListener', 'Dom/Util'], function(DomChangeListener
, DomUtil
) {
949 DomUtil
.insertHtml(data
.returnValues
.template
, elById($containerID
), 'append');
951 DomChangeListener
.trigger();
956 if (COMPILER_TARGET_DEFAULT
) {
958 * User profile inline editor.
960 * @param integer userID
961 * @param boolean editOnInit
963 WCF
.User
.Profile
.Editor
= Class
.extend({
973 * list of interface buttons
986 * @var WCF.Action.Proxy
1003 * Initializes the WCF.User.Profile.Editor object.
1005 * @param integer userID
1006 * @param boolean editOnInit
1008 init: function (userID
, editOnInit
) {
1009 this._actionName
= '';
1010 this._active
= false;
1011 this._cachedTemplate
= '';
1012 this._tab
= $('#about');
1013 this._userID
= userID
;
1014 this._proxy
= new WCF
.Action
.Proxy({
1015 success
: $.proxy(this._success
, this)
1018 this._initButtons();
1020 // begin editing on page load
1027 * Initializes interface buttons.
1029 _initButtons: function () {
1032 beginEdit
: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit
.bind(this))
1039 * @param {Event?} event event object
1041 _beginEdit: function (event
) {
1042 if (event
) event
.preventDefault();
1044 if (this._active
) return;
1045 this._active
= true;
1047 this._actionName
= 'beginEdit';
1048 this._buttons
.beginEdit
.parent().addClass('active');
1049 $('#profileContent').wcfTabs('select', 'about');
1052 this._proxy
.setOption('data', {
1053 actionName
: 'beginEdit',
1054 className
: 'wcf\\data\\user\\UserProfileAction',
1055 objectIDs
: [this._userID
]
1057 this._proxy
.sendRequest();
1061 * Saves input values.
1063 _save: function () {
1064 // check if there is an editor and if it is in WYSIWYG mode
1065 var scrollToEditor
= null;
1066 elBySelAll('.redactor-layer', this._tab
[0], function(redactorLayer
) {
1069 throwError
: elInnerError
1073 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.redactor2', 'validate_' + elData(redactorLayer
, 'element-id'), data
);
1075 if (!data
.valid
&& scrollToEditor
=== null) {
1076 scrollToEditor
= redactorLayer
.parentNode
;
1080 if (scrollToEditor
) {
1081 scrollToEditor
.scrollIntoView({ behavior
: 'smooth' });
1085 this._actionName
= 'save';
1088 var $regExp
= /values\[([a-zA-Z0-9._-]+)\]/;
1090 this._tab
.find('input, textarea, select').each(function (index
, element
) {
1091 var $element
= $(element
);
1094 switch ($element
.getTagName()) {
1096 var $type
= $element
.attr('type');
1098 if (($type
=== 'radio' || $type
=== 'checkbox') && !$element
.prop('checked')) {
1104 if ($element
.data('redactor')) {
1105 $value
= $element
.redactor('code.get');
1110 var $name
= $element
.attr('name');
1111 if ($regExp
.test($name
)) {
1112 var $fieldName
= RegExp
.$1;
1113 if ($value
=== null) $value
= $element
.val();
1115 // check for checkboxes
1116 if ($element
.attr('type') === 'checkbox' && /\[\]$/.test($name
)) {
1117 if (!Array
.isArray($values
[$fieldName
])) {
1118 $values
[$fieldName
] = [];
1121 $values
[$fieldName
].push($value
);
1124 $values
[$fieldName
] = $value
;
1129 this._proxy
.setOption('data', {
1131 className
: 'wcf\\data\\user\\UserProfileAction',
1132 objectIDs
: [this._userID
],
1137 this._proxy
.sendRequest();
1141 * Restores back to default view.
1143 _restore: function () {
1144 this._actionName
= 'restore';
1145 this._active
= false;
1146 this._buttons
.beginEdit
.parent().removeClass('active');
1148 this._destroyEditor();
1150 this._tab
.html(this._cachedTemplate
).children().css({height
: 'auto'});
1154 * Handles successful AJAX requests.
1156 * @param object data
1157 * @param string textStatus
1158 * @param jQuery jqXHR
1160 _success: function (data
, textStatus
, jqXHR
) {
1161 switch (this._actionName
) {
1163 this._prepareEdit(data
);
1167 // save was successful, show parsed template
1168 if (data
.returnValues
.success
) {
1169 this._cachedTemplate
= data
.returnValues
.template
;
1173 this._prepareEdit(data
, true);
1180 * Prepares editing mode.
1182 * @param object data
1183 * @param boolean disableCache
1185 _prepareEdit: function (data
, disableCache
) {
1186 this._destroyEditor();
1190 this._tab
.html(function (index
, oldHTML
) {
1191 if (disableCache
!== true) {
1192 self
._cachedTemplate
= oldHTML
;
1195 return data
.returnValues
.template
;
1198 // block autocomplete
1199 this._tab
.find('input[type=text]').attr('autocomplete', 'off');
1201 // bind event listener
1202 this._tab
.find('.formSubmit > button[data-type=save]').click($.proxy(this._save
, this));
1203 this._tab
.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore
, this));
1204 this._tab
.find('input').keyup(function (event
) {
1205 if (event
.which
=== $.ui
.keyCode
.ENTER
) {
1208 event
.preventDefault();
1215 * Destroys all editor instances within current tab.
1217 _destroyEditor: function () {
1218 // destroy all editor instances
1219 this._tab
.find('textarea').each(function (index
, container
) {
1220 var $container
= $(container
);
1221 if ($container
.data('redactor')) {
1222 $container
.redactor('core.destroy');
1229 WCF
.User
.Profile
.Editor
= Class
.extend({
1233 _cachedTemplate
: "",
1237 init: function() {},
1238 _initButtons: function() {},
1239 _beginEdit: function() {},
1240 _save: function() {},
1241 _restore: function() {},
1242 _success: function() {},
1243 _prepareEdit: function() {},
1244 _destroyEditor: function() {}
1249 * Namespace for registration functions.
1251 WCF
.User
.Registration
= {};
1254 * Validates the password.
1256 * @param jQuery element
1257 * @param jQuery confirmElement
1258 * @param object options
1260 WCF
.User
.Registration
.Validation
= Class
.extend({
1274 * confirmation input element
1277 _confirmElement
: null,
1286 * list of error messages
1289 _errorMessages
: { },
1292 * list of additional options
1299 * @var WCF.Action.Proxy
1304 * Initializes the validation.
1306 * @param jQuery element
1307 * @param jQuery confirmElement
1308 * @param object options
1310 init: function(element
, confirmElement
, options
) {
1311 this._element
= element
;
1312 this._element
.blur($.proxy(this._blur
, this));
1313 this._confirmElement
= confirmElement
|| null;
1315 if (this._confirmElement
!== null) {
1316 this._confirmElement
.blur($.proxy(this._blurConfirm
, this));
1319 options
= options
|| { };
1320 this._setOptions(options
);
1322 this._proxy
= new WCF
.Action
.Proxy({
1323 success
: $.proxy(this._success
, this),
1324 showLoadingOverlay
: false
1327 this._setErrorMessages();
1331 * Sets additional options
1333 _setOptions: function(options
) { },
1336 * Sets error messages.
1338 _setErrorMessages: function() {
1339 this._errorMessages
= {
1346 * Validates once focus on input is lost.
1348 * @param object event
1350 _blur: function(event
) {
1351 var $value
= this._element
.val();
1353 return this._showError(this._element
, WCF
.Language
.get('wcf.global.form.error.empty'));
1356 if (this._confirmElement
!== null) {
1357 var $confirmValue
= this._confirmElement
.val();
1358 if ($confirmValue
!= '' && $value
!= $confirmValue
) {
1359 return this._showError(this._confirmElement
, this._errorMessages
.notEqual
);
1363 if (!this._validateOptions()) {
1367 this._proxy
.setOption('data', {
1368 actionName
: this._actionName
,
1369 className
: this._className
,
1370 parameters
: this._getParameters()
1372 this._proxy
.sendRequest();
1376 * Returns a list of parameters.
1380 _getParameters: function() {
1385 * Validates input by options.
1389 _validateOptions: function() {
1394 * Validates value once confirmation input focus is lost.
1396 * @param object event
1398 _blurConfirm: function(event
) {
1399 var $value
= this._confirmElement
.val();
1401 return this._showError(this._confirmElement
, WCF
.Language
.get('wcf.global.form.error.empty'));
1408 * Handles AJAX responses.
1410 * @param object data
1411 * @param string textStatus
1412 * @param jQuery jqXHR
1414 _success: function(data
, textStatus
, jqXHR
) {
1415 if (data
.returnValues
.isValid
) {
1416 this._showSuccess(this._element
);
1417 if (this._confirmElement
!== null && this._confirmElement
.val()) {
1418 this._showSuccess(this._confirmElement
);
1422 this._showError(this._element
, WCF
.Language
.get(this._errorMessages
.ajaxError
+ data
.returnValues
.error
));
1427 * Shows an error message.
1429 * @param jQuery element
1430 * @param string message
1432 _showError: function(element
, message
) {
1433 element
.parent().parent().addClass('formError').removeClass('formSuccess');
1435 var $innerError
= element
.parent().find('small.innerError');
1436 if (!$innerError
.length
) {
1437 $innerError
= $('<small />').addClass('innerError').insertAfter(element
);
1440 $innerError
.text(message
);
1444 * Displays a success message.
1446 * @param jQuery element
1448 _showSuccess: function(element
) {
1449 element
.parent().parent().addClass('formSuccess').removeClass('formError');
1450 element
.next('small.innerError').remove();
1455 * Username validation for registration.
1457 * @see WCF.User.Registration.Validation
1459 WCF
.User
.Registration
.Validation
.Username
= WCF
.User
.Registration
.Validation
.extend({
1461 * @see WCF.User.Registration.Validation._actionName
1463 _actionName
: 'validateUsername',
1466 * @see WCF.User.Registration.Validation._className
1468 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1471 * @see WCF.User.Registration.Validation._setOptions()
1473 _setOptions: function(options
) {
1474 this._options
= $.extend(true, {
1481 * @see WCF.User.Registration.Validation._setErrorMessages()
1483 _setErrorMessages: function() {
1484 this._errorMessages
= {
1485 ajaxError
: 'wcf.user.username.error.'
1490 * @see WCF.User.Registration.Validation._validateOptions()
1492 _validateOptions: function() {
1493 var $value
= this._element
.val();
1494 if ($value
.length
< this._options
.minlength
|| $value
.length
> this._options
.maxlength
) {
1495 this._showError(this._element
, WCF
.Language
.get('wcf.user.username.error.invalid'));
1503 * @see WCF.User.Registration.Validation._getParameters()
1505 _getParameters: function() {
1507 username
: this._element
.val()
1513 * Email validation for registration.
1515 * @see WCF.User.Registration.Validation
1517 WCF
.User
.Registration
.Validation
.EmailAddress
= WCF
.User
.Registration
.Validation
.extend({
1519 * @see WCF.User.Registration.Validation._actionName
1521 _actionName
: 'validateEmailAddress',
1524 * @see WCF.User.Registration.Validation._className
1526 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1529 * @see WCF.User.Registration.Validation._getParameters()
1531 _getParameters: function() {
1533 email
: this._element
.val()
1538 * @see WCF.User.Registration.Validation._setErrorMessages()
1540 _setErrorMessages: function() {
1541 this._errorMessages
= {
1542 ajaxError
: 'wcf.user.email.error.',
1543 notEqual
: WCF
.Language
.get('wcf.user.confirmEmail.error.notEqual')
1549 * Password validation for registration.
1551 * @see WCF.User.Registration.Validation
1553 WCF
.User
.Registration
.Validation
.Password
= WCF
.User
.Registration
.Validation
.extend({
1555 * @see WCF.User.Registration.Validation._actionName
1557 _actionName
: 'validatePassword',
1560 * @see WCF.User.Registration.Validation._className
1562 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1565 * @see WCF.User.Registration.Validation._getParameters()
1567 _getParameters: function() {
1569 password
: this._element
.val()
1574 * @see WCF.User.Registration.Validation._setErrorMessages()
1576 _setErrorMessages: function() {
1577 this._errorMessages
= {
1578 ajaxError
: 'wcf.user.password.error.',
1579 notEqual
: WCF
.Language
.get('wcf.user.confirmPassword.error.notEqual')
1585 * Toggles input fields for lost password form.
1587 WCF
.User
.Registration
.LostPassword
= Class
.extend({
1601 * Initializes LostPassword-form class.
1604 // bind input fields
1605 this._email
= $('#emailInput');
1606 this._username
= $('#usernameInput');
1608 // bind event listener
1609 this._email
.keyup($.proxy(this._checkEmail
, this));
1610 this._username
.keyup($.proxy(this._checkUsername
, this));
1612 if ($.browser
.mozilla
&& $.browser
.touch
) {
1613 this._email
.on('input', $.proxy(this._checkEmail
, this));
1614 this._username
.on('input', $.proxy(this._checkUsername
, this));
1617 // toggle fields on init
1619 this._checkUsername();
1623 * Checks for content in email field and toggles username.
1625 _checkEmail: function() {
1626 if (this._email
.val() == '') {
1627 this._username
.enable();
1628 this._username
.parents('dl:eq(0)').removeClass('disabled');
1631 this._username
.disable();
1632 this._username
.parents('dl:eq(0)').addClass('disabled');
1633 this._username
.val('');
1638 * Checks for content in username field and toggles email.
1640 _checkUsername: function() {
1641 if (this._username
.val() == '') {
1642 this._email
.enable();
1643 this._email
.parents('dl:eq(0)').removeClass('disabled');
1646 this._email
.disable();
1647 this._email
.parents('dl:eq(0)').addClass('disabled');
1648 this._email
.val('');
1654 * Notification system for WCF.
1656 * @author Alexander Ebert
1657 * @copyright 2001-2018 WoltLab GmbH
1658 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1660 WCF
.Notification
= { };
1662 if (COMPILER_TARGET_DEFAULT
) {
1664 * Handles the notification list.
1666 WCF
.Notification
.List
= Class
.extend({
1669 * @var WCF.Action.Proxy
1674 * Initializes the WCF.Notification.List object.
1677 this._proxy
= new WCF
.Action
.Proxy({
1678 success
: $.proxy(this._success
, this)
1681 // handle 'mark all as confirmed' buttons
1682 $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function () {
1683 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function (action
) {
1684 if (action
=== 'confirm') {
1685 new WCF
.Action
.Proxy({
1688 actionName
: 'markAllAsConfirmed',
1689 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
1691 success: function () {
1692 window
.location
.reload();
1699 // handle regular items
1700 this._convertList();
1704 * Converts the notification item list to be in sync with the notification dropdown.
1706 _convertList: function () {
1707 $('.userNotificationItemList > .notificationItem').each((function (index
, item
) {
1708 var $item
= $(item
);
1710 if (!$item
.data('isRead')) {
1711 $item
.find('a:not(.userLink)').prop('href', $item
.data('link'));
1713 var $markAsConfirmed
= $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF
.Language
.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item
);
1714 $markAsConfirmed
.click($.proxy(this._markAsConfirmed
, this));
1717 // work-around for legacy notifications
1718 if (!$item
.find('a:not(.notificationItemMarkAsConfirmed)').length
) {
1719 $item
.find('.details > p:eq(0)').html(function (index
, oldHTML
) {
1720 return '<a href="' + $item
.data('link') + '">' + oldHTML
+ '</a>';
1725 WCF
.DOMNodeInsertedHandler
.execute();
1729 * Marks a single notification as confirmed.
1731 * @param object event
1733 _markAsConfirmed: function (event
) {
1734 event
.preventDefault();
1736 var $notificationID
= $(event
.currentTarget
).parents('.notificationItem:eq(0)').data('objectID');
1738 this._proxy
.setOption('data', {
1739 actionName
: 'markAsConfirmed',
1740 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
1741 objectIDs
: [$notificationID
]
1743 this._proxy
.sendRequest();
1749 * Handles successful AJAX requests.
1751 * @param object data
1752 * @param string textStatus
1753 * @param jQuery jqXHR
1755 _success: function (data
, textStatus
, jqXHR
) {
1756 var $item
= $('.userNotificationItemList > .notificationItem[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
1758 $item
.data('isRead', true);
1759 $item
.find('.newContentBadge').remove();
1760 $item
.find('.notificationItemMarkAsConfirmed').remove();
1761 $item
.removeClass('notificationUnconfirmed');
1766 * Signature preview.
1768 * @see WCF.Message.Preview
1770 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
1772 * @see WCF.Message.Preview._handleResponse()
1774 _handleResponse: function (data
) {
1775 // get preview container
1776 var $preview
= $('#previewContainer');
1777 if (!$preview
.length
) {
1778 $preview
= $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF
.Language
.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
1781 $preview
.children('div').first().html(data
.returnValues
.message
);
1786 WCF
.Notification
.List
= Class
.extend({
1788 init: function() {},
1789 _convertList: function() {},
1790 _markAsConfirmed: function() {},
1791 _success: function() {}
1794 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
1795 _handleResponse: function() {},
1797 _messageFieldID
: "",
1801 _previewButtonLabel
: "",
1802 init: function() {},
1803 _click: function() {},
1804 _getParameters: function() {},
1805 _getMessage: function() {},
1806 _success: function() {},
1807 _failure: function() {}
1812 * Loads recent activity events once the user scrolls to the very bottom.
1814 * @param integer userID
1816 WCF
.User
.RecentActivityLoader
= Class
.extend({
1824 * true if list should be filtered by followed users
1827 _filteredByFollowedUsers
: false,
1830 * button to load next events
1837 * @var WCF.Action.Proxy
1848 * Initializes a new RecentActivityLoader object.
1850 * @param integer userID
1851 * @param boolean filteredByFollowedUsers
1853 init: function(userID
, filteredByFollowedUsers
) {
1854 this._container
= $('#recentActivities');
1855 this._filteredByFollowedUsers
= (filteredByFollowedUsers
=== true);
1856 this._userID
= userID
;
1858 if (this._userID
!== null && !this._userID
) {
1859 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1863 this._proxy
= new WCF
.Action
.Proxy({
1864 success
: $.proxy(this._success
, this)
1867 if (this._container
.children('li').length
) {
1868 this._loadButton
= $('<li class="showMore"><button class="small">' + WCF
.Language
.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container
);
1869 this._loadButton
= this._loadButton
.children('button').click($.proxy(this._click
, this));
1872 $('<li class="showMore"><small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container
);
1875 if (WCF
.User
.userID
) {
1876 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext
, this));
1881 * Loads next activity events.
1883 _click: function() {
1884 this._loadButton
.enable();
1887 lastEventID
: this._container
.data('lastEventID'),
1888 lastEventTime
: this._container
.data('lastEventTime')
1891 $parameters
.userID
= this._userID
;
1893 else if (this._filteredByFollowedUsers
) {
1894 $parameters
.filteredByFollowedUsers
= 1;
1897 this._proxy
.setOption('data', {
1899 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1900 parameters
: $parameters
1902 this._proxy
.sendRequest();
1906 * Switches recent activity context.
1908 _switchContext: function(event
) {
1909 event
.preventDefault();
1911 if (!$(event
.currentTarget
).hasClass('active')) {
1912 new WCF
.Action
.Proxy({
1915 actionName
: 'switchContext',
1916 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1918 success: function() {
1919 window
.location
.hash
= '#dashboardBoxRecentActivity';
1920 window
.location
.reload();
1927 * Handles successful AJAX requests.
1929 * @param object data
1930 * @param string textStatus
1931 * @param jQuery jqXHR
1933 _success: function(data
, textStatus
, jqXHR
) {
1934 if (data
.returnValues
.template
) {
1935 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
1937 this._container
.data('lastEventTime', data
.returnValues
.lastEventTime
);
1938 this._container
.data('lastEventID', data
.returnValues
.lastEventID
);
1939 this._loadButton
.enable();
1942 $('<small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton
.parent());
1943 this._loadButton
.remove();
1949 * Loads likes once the user scrolls to the very bottom.
1951 * @param integer userID
1953 WCF
.User
.LikeLoader
= Class
.extend({
1964 _likeType
: 'received',
1973 * button to load next events
1979 * 'no more entries' element
1982 _noMoreEntries
: null,
1986 * @var WCF.Action.Proxy
1997 * Initializes a new RecentActivityLoader object.
1999 * @param integer userID
2000 * @param boolean filteredByFollowedUsers
2002 init: function(userID
) {
2003 this._container
= $('#likeList');
2004 this._userID
= userID
;
2006 if (!this._userID
) {
2007 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
2011 this._proxy
= new WCF
.Action
.Proxy({
2012 success
: $.proxy(this._success
, this)
2015 var $container
= $('<li class="likeListMore showMore"><button class="small">' + WCF
.Language
.get('wcf.like.likes.more') + '</button><small>' + WCF
.Language
.get('wcf.like.likes.noMoreEntries') + '</small></li>').appendTo(this._container
);
2016 this._loadButton
= $container
.children('button').click($.proxy(this._click
, this));
2017 this._noMoreEntries
= $container
.children('small').hide();
2019 if (this._container
.find('> li').length
== 2) {
2020 this._loadButton
.hide();
2021 this._noMoreEntries
.show();
2024 $('#likeType .button').click($.proxy(this._clickLikeType
, this));
2025 $('#likeValue .button').click($.proxy(this._clickLikeValue
, this));
2029 * Handles like type change.
2031 _clickLikeType: function(event
) {
2032 var $button
= $(event
.currentTarget
);
2033 if (this._likeType
!= $button
.data('likeType')) {
2034 this._likeType
= $button
.data('likeType');
2035 $('#likeType .button').removeClass('active');
2036 $button
.addClass('active');
2042 * Handles like value change.
2044 _clickLikeValue: function(event
) {
2045 var $button
= $(event
.currentTarget
);
2046 if (this._likeValue
!= $button
.data('likeValue')) {
2047 this._likeValue
= $button
.data('likeValue');
2048 $('#likeValue .button').removeClass('active');
2049 $button
.addClass('active');
2051 // change button labels
2052 $('#likeType > li:first-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesReceived'));
2053 $('#likeType > li:last-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesGiven'));
2055 this._container
.find('> li.likeListMore button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.more'));
2056 this._container
.find('> li.likeListMore small').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.noMoreEntries'));
2065 _reload: function() {
2066 this._container
.find('> li:not(:first-child):not(:last-child)').remove();
2067 this._container
.data('lastLikeTime', 0);
2074 _click: function() {
2075 this._loadButton
.enable();
2078 lastLikeTime
: this._container
.data('lastLikeTime'),
2079 userID
: this._userID
,
2080 likeType
: this._likeType
,
2081 likeValue
: this._likeValue
2084 this._proxy
.setOption('data', {
2086 className
: 'wcf\\data\\like\\LikeAction',
2087 parameters
: $parameters
2089 this._proxy
.sendRequest();
2093 * Handles successful AJAX requests.
2095 * @param object data
2096 * @param string textStatus
2097 * @param jQuery jqXHR
2099 _success: function(data
, textStatus
, jqXHR
) {
2100 if (data
.returnValues
.template
) {
2101 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
2103 this._container
.data('lastLikeTime', data
.returnValues
.lastLikeTime
);
2104 this._noMoreEntries
.hide();
2105 this._loadButton
.show().enable();
2108 this._noMoreEntries
.show();
2109 this._loadButton
.hide();
2115 * Loads user profile previews.
2119 WCF
.User
.ProfilePreview
= WCF
.Popover
.extend({
2122 * @var WCF.Action.Proxy
2127 * list of user profiles
2133 * @see WCF.Popover.init()
2136 this._super('.userLink');
2138 this._proxy
= new WCF
.Action
.Proxy({
2139 showLoadingOverlay
: false
2142 // register instance
2143 WCF
.System
.ObjectStore
.add('WCF.User.ProfilePreview', this);
2147 * @see WCF.Popover._loadContent()
2149 _loadContent: function() {
2150 var $element
= $('#' + this._activeElementID
);
2151 var $userID
= $element
.data('userID');
2153 if (this._userProfiles
[$userID
]) {
2154 // use cached user profile
2155 this._insertContent(this._activeElementID
, this._userProfiles
[$userID
], true);
2158 this._proxy
.setOption('data', {
2159 actionName
: 'getUserProfile',
2160 className
: 'wcf\\data\\user\\UserProfileAction',
2161 objectIDs
: [ $userID
]
2164 var $elementID
= this._activeElementID
;
2166 this._proxy
.setOption('success', function(data
, textStatus
, jqXHR
) {
2167 // cache user profile
2168 self
._userProfiles
[$userID
] = data
.returnValues
.template
;
2170 // show user profile
2171 self
._insertContent($elementID
, data
.returnValues
.template
, true);
2173 this._proxy
.setOption('failure', function(data
, jqXHR
, textStatus
, errorThrown
) {
2174 // cache user profile
2175 self
._userProfiles
[$userID
] = data
.message
;
2177 // show user profile
2178 self
._insertContent($elementID
, data
.message
, true);
2182 this._proxy
.sendRequest();
2187 * Purages a cached user profile.
2189 * @param integer userID
2191 purge: function(userID
) {
2192 delete this._userProfiles
[userID
];
2194 // purge content cache
2200 * Initializes WCF.User.Action namespace.
2202 WCF
.User
.Action
= {};
2204 if (COMPILER_TARGET_DEFAULT
) {
2206 * Handles user follow and unfollow links.
2208 WCF
.User
.Action
.Follow
= Class
.extend({
2210 * list with elements containing follow and unfollow buttons
2213 _containerList
: null,
2216 * CSS selector for follow buttons
2219 _followButtonSelector
: '.jsFollowButton',
2222 * id of the user that is currently being followed/unfollowed
2228 * Initializes new WCF.User.Action.Follow object.
2230 * @param array containerList
2231 * @param string followButtonSelector
2233 init: function (containerList
, followButtonSelector
) {
2234 if (!containerList
.length
) {
2237 this._containerList
= containerList
;
2239 if (followButtonSelector
) {
2240 this._followButtonSelector
= followButtonSelector
;
2244 this._proxy
= new WCF
.Action
.Proxy({
2245 success
: $.proxy(this._success
, this)
2248 // bind event listeners
2249 this._containerList
.each($.proxy(function (index
, container
) {
2250 $(container
).find(this._followButtonSelector
).click($.proxy(this._click
, this));
2255 * Handles a click on a follow or unfollow button.
2257 * @param object event
2259 _click: function (event
) {
2260 event
.preventDefault();
2261 var link
= $(event
.target
);
2262 if (!link
.is('a')) {
2263 link
= link
.closest('a');
2265 this._userID
= link
.data('objectID');
2267 this._proxy
.setOption('data', {
2268 'actionName': link
.data('following') ? 'unfollow' : 'follow',
2269 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2272 userID
: this._userID
2276 this._proxy
.sendRequest();
2280 * Handles the successful (un)following of a user.
2282 * @param object data
2283 * @param string textStatus
2284 * @param jQuery jqXHR
2286 _success: function (data
, textStatus
, jqXHR
) {
2287 this._containerList
.each($.proxy(function (index
, container
) {
2288 var button
= $(container
).find(this._followButtonSelector
).get(0);
2290 if (button
&& $(button
).data('objectID') == this._userID
) {
2293 // toogle icon title
2294 if (data
.returnValues
.following
) {
2295 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
2296 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unfollow'));
2299 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
2300 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.follow'));
2303 button
.data('following', data
.returnValues
.following
);
2309 var $notification
= new WCF
.System
.Notification();
2310 $notification
.show();
2315 * Handles user ignore and unignore links.
2317 WCF
.User
.Action
.Ignore
= Class
.extend({
2319 * list with elements containing ignore and unignore buttons
2322 _containerList
: null,
2325 * CSS selector for ignore buttons
2328 _ignoreButtonSelector
: '.jsIgnoreButton',
2331 * id of the user that is currently being ignored/unignored
2337 * Initializes new WCF.User.Action.Ignore object.
2339 * @param array containerList
2340 * @param string ignoreButtonSelector
2342 init: function (containerList
, ignoreButtonSelector
) {
2343 if (!containerList
.length
) {
2346 this._containerList
= containerList
;
2348 if (ignoreButtonSelector
) {
2349 this._ignoreButtonSelector
= ignoreButtonSelector
;
2353 this._proxy
= new WCF
.Action
.Proxy({
2354 success
: $.proxy(this._success
, this)
2357 // bind event listeners
2358 this._containerList
.each($.proxy(function (index
, container
) {
2359 $(container
).find(this._ignoreButtonSelector
).click($.proxy(this._click
, this));
2364 * Handles a click on a ignore or unignore button.
2366 * @param object event
2368 _click: function (event
) {
2369 event
.preventDefault();
2370 var link
= $(event
.target
);
2371 if (!link
.is('a')) {
2372 link
= link
.closest('a');
2374 this._userID
= link
.data('objectID');
2376 this._proxy
.setOption('data', {
2377 'actionName': link
.data('ignored') ? 'unignore' : 'ignore',
2378 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2381 userID
: this._userID
2385 this._proxy
.sendRequest();
2389 * Handles the successful (un)ignoring of a user.
2391 * @param object data
2392 * @param string textStatus
2393 * @param jQuery jqXHR
2395 _success: function (data
, textStatus
, jqXHR
) {
2396 this._containerList
.each($.proxy(function (index
, container
) {
2397 var button
= $(container
).find(this._ignoreButtonSelector
).get(0);
2399 if (button
&& $(button
).data('objectID') == this._userID
) {
2402 // toogle icon title
2403 if (data
.returnValues
.isIgnoredUser
) {
2404 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
2405 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unignore'));
2408 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
2409 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.ignore'));
2412 button
.data('ignored', data
.returnValues
.isIgnoredUser
);
2418 var $notification
= new WCF
.System
.Notification();
2419 $notification
.show();
2421 // force rebuilding of popover cache
2423 WCF
.System
.ObjectStore
.invoke('WCF.User.ProfilePreview', function (profilePreview
) {
2424 profilePreview
.purge(self
._userID
);
2430 WCF
.User
.Action
.Follow
= Class
.extend({
2432 _followButtonSelector
: "",
2434 init: function() {},
2435 _click: function() {},
2436 _success: function() {}
2439 WCF
.User
.Action
.Ignore
= Class
.extend({
2441 _ignoreButtonSelector
: "",
2443 init: function() {},
2444 _click: function() {},
2445 _success: function() {}
2450 * Namespace for avatar functions.
2452 WCF
.User
.Avatar
= {};
2454 if (COMPILER_TARGET_DEFAULT
) {
2456 * Avatar upload function
2460 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
2462 * user id of avatar owner
2468 * Initializes a new WCF.User.Avatar.Upload object.
2470 * @param integer userID
2472 init: function (userID
) {
2473 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2474 this._userID
= userID
|| 0;
2476 $('#avatarForm input[type=radio]').change(function () {
2477 if ($(this).val() == 'custom') {
2478 $('#avatarUpload > dd > div').show();
2481 $('#avatarUpload > dd > div').hide();
2484 if (!$('#avatarForm input[type=radio][value=custom]:checked').length
) {
2485 $('#avatarUpload > dd > div').hide();
2490 * @see WCF.Upload._initFile()
2492 _initFile: function (file
) {
2493 return $('#avatarUpload > dt > img');
2497 * @see WCF.Upload._success()
2499 _success: function (uploadID
, data
) {
2500 if (data
.returnValues
.url
) {
2501 this._updateImage(data
.returnValues
.url
);
2504 $('#avatarUpload > dd > .innerError').remove();
2506 // show success message
2507 var $notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.user.avatar.upload.success'));
2508 $notification
.show();
2510 else if (data
.returnValues
.errorType
) {
2512 this._getInnerErrorElement().text(WCF
.Language
.get('wcf.user.avatar.upload.error.' + data
.returnValues
.errorType
));
2517 * Updates the displayed avatar image.
2521 _updateImage: function (url
) {
2522 $('#avatarUpload > dt > img').remove();
2523 var $image
= $('<img src="' + url
+ '" class="userAvatarImage" alt="" />').css({
2525 'max-height': '96px',
2526 'max-width': '96px',
2530 $('#avatarUpload > dt').prepend($image
);
2532 WCF
.DOMNodeInsertedHandler
.execute();
2536 * Returns the inner error element.
2540 _getInnerErrorElement: function () {
2541 var $span
= $('#avatarUpload > dd > .innerError');
2542 if (!$span
.length
) {
2543 $span
= $('<small class="innerError"></span>');
2544 $('#avatarUpload > dd').append($span
);
2551 * @see WCF.Upload._getParameters()
2553 _getParameters: function () {
2555 userID
: this._userID
2561 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
2563 init: function() {},
2564 _initFile: function() {},
2565 _success: function() {},
2566 _updateImage: function() {},
2567 _getInnerErrorElement: function() {},
2568 _getParameters: function() {},
2570 _buttonSelector
: {},
2571 _fileListSelector
: {},
2578 _supportsAJAXUpload
: true,
2580 _createButton: function() {},
2581 _insertButton: function() {},
2582 _removeButton: function() {},
2583 _upload: function() {},
2584 _createUploadMatrix: function() {},
2585 _error: function() {},
2586 _progress: function() {},
2587 _showOverlay: function() {},
2588 _evaluateResponse: function() {},
2589 _getFilename: function() {}
2594 * Generic implementation for grouped user lists.
2596 * @param string className
2597 * @param string dialogTitle
2598 * @param object additionalParameters
2600 WCF
.User
.List
= Class
.extend({
2602 * list of additional parameters
2605 _additionalParameters
: { },
2608 * list of cached pages
2645 * @var WCF.Action.Proxy
2650 * Initializes a new grouped user list.
2652 * @param string className
2653 * @param string dialogTitle
2654 * @param object additionalParameters
2656 init: function(className
, dialogTitle
, additionalParameters
) {
2657 this._additionalParameters
= additionalParameters
|| { };
2659 this._className
= className
;
2660 this._dialog
= null;
2661 this._dialogTitle
= dialogTitle
;
2662 this._pageCount
= 0;
2665 this._proxy
= new WCF
.Action
.Proxy({
2666 success
: $.proxy(this._success
, this)
2671 * Opens the dialog overlay.
2679 * Displays the specified page.
2681 * @param object event
2682 * @param object data
2684 _showPage: function(event
, data
) {
2685 if (data
&& data
.activePage
) {
2686 this._pageNo
= data
.activePage
;
2689 if (this._pageCount
!= 0 && (this._pageNo
< 1 || this._pageNo
> this._pageCount
)) {
2690 console
.debug("[WCF.User.List] Cannot access page " + this._pageNo
+ " of " + this._pageCount
);
2694 if (this._cache
[this._pageNo
]) {
2695 var $dialogCreated
= false;
2696 if (this._dialog
=== null) {
2697 this._dialog
= $('#userList' + this._className
.hashCode());
2698 if (this._dialog
.length
=== 0) {
2699 this._dialog
= $('<div id="userList' + this._className
.hashCode() + '" />').hide().appendTo(document
.body
);
2700 $dialogCreated
= true;
2704 // remove current view
2705 this._dialog
.empty();
2708 this._dialog
.html(this._cache
[this._pageNo
]);
2711 if (this._pageCount
> 1) {
2712 this._dialog
.find('.jsPagination').wcfPages({
2713 activePage
: this._pageNo
,
2714 maxPage
: this._pageCount
2715 }).on('wcfpagesswitched', $.proxy(this._showPage
, this));
2718 this._dialog
.find('.jsPagination').hide();
2722 if ($dialogCreated
) {
2723 this._dialog
.wcfDialog({
2724 title
: this._dialogTitle
2728 this._dialog
.wcfDialog('option', 'title', this._dialogTitle
);
2729 this._dialog
.wcfDialog('open').wcfDialog('render');
2732 WCF
.DOMNodeInsertedHandler
.execute();
2735 this._additionalParameters
.pageNo
= this._pageNo
;
2737 // load template via AJAX
2738 this._proxy
.setOption('data', {
2739 actionName
: 'getGroupedUserList',
2740 className
: this._className
,
2741 interfaceName
: 'wcf\\data\\IGroupedUserListAction',
2742 parameters
: this._additionalParameters
2744 this._proxy
.sendRequest();
2749 * Handles successful AJAX requests.
2751 * @param object data
2752 * @param string textStatus
2753 * @param jQuery jqXHR
2755 _success: function(data
, textStatus
, jqXHR
) {
2756 if (data
.returnValues
.pageCount
) {
2757 this._pageCount
= data
.returnValues
.pageCount
;
2760 this._cache
[this._pageNo
] = data
.returnValues
.template
;
2766 * Namespace for object watch functions.
2768 WCF
.User
.ObjectWatch
= {};
2770 if (COMPILER_TARGET_DEFAULT
) {
2772 * Handles subscribe/unsubscribe links.
2774 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
2776 * CSS selector for subscribe buttons
2779 _buttonSelector
: '.jsSubscribeButton',
2794 * system notification
2795 * @var WCF.System.Notification
2797 _notification
: null,
2800 * reload page on unsubscribe
2803 _reloadOnUnsubscribe
: false,
2806 * WCF.User.ObjectWatch.Subscribe object.
2808 * @param boolean reloadOnUnsubscribe
2810 init: function (reloadOnUnsubscribe
) {
2812 this._notification
= null;
2813 this._reloadOnUnsubscribe
= (reloadOnUnsubscribe
=== true);
2816 this._proxy
= new WCF
.Action
.Proxy({
2817 success
: $.proxy(this._success
, this)
2820 // bind event listeners
2821 $(this._buttonSelector
).each($.proxy(function (index
, button
) {
2822 var $button
= $(button
);
2823 $button
.addClass('pointer');
2824 var $objectType
= $button
.data('objectType');
2825 var $objectID
= $button
.data('objectID');
2827 if (this._buttons
[$objectType
] === undefined) {
2828 this._buttons
[$objectType
] = {};
2831 this._buttons
[$objectType
][$objectID
] = $button
.click($.proxy(this._click
, this));
2834 WCF
.System
.Event
.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus
, this));
2838 * Handles a click on a subscribe button.
2840 * @param object event
2842 _click: function (event
) {
2843 event
.preventDefault();
2844 var $button
= $(event
.currentTarget
);
2846 this._proxy
.setOption('data', {
2847 actionName
: 'manageSubscription',
2848 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2850 objectID
: $button
.data('objectID'),
2851 objectType
: $button
.data('objectType')
2854 this._proxy
.sendRequest();
2858 * Handles successful AJAX requests.
2860 * @param object data
2861 * @param string textStatus
2862 * @param jQuery jqXHR
2864 _success: function (data
, textStatus
, jqXHR
) {
2865 if (data
.actionName
=== 'manageSubscription') {
2866 if (this._dialog
=== null) {
2867 this._dialog
= $('<div>' + data
.returnValues
.template
+ '</div>').hide().appendTo(document
.body
);
2868 this._dialog
.wcfDialog({
2869 title
: WCF
.Language
.get('wcf.user.objectWatch.manageSubscription')
2873 this._dialog
.html(data
.returnValues
.template
);
2874 this._dialog
.wcfDialog('open');
2877 // bind event listener
2878 this._dialog
.find('.formSubmit > .jsButtonSave').data('objectID', data
.returnValues
.objectID
).data('objectType', data
.returnValues
.objectType
).click($.proxy(this._save
, this));
2879 var $enableNotification
= this._dialog
.find('input[name=enableNotification]').disable();
2881 // toggle subscription
2882 this._dialog
.find('input[name=subscribe]').change(function (event
) {
2883 var $input
= $(event
.currentTarget
);
2884 if ($input
.val() == 1) {
2885 $enableNotification
.enable();
2888 $enableNotification
.disable();
2893 var $selectedOption
= this._dialog
.find('input[name=subscribe]:checked');
2894 if ($selectedOption
.length
&& $selectedOption
.val() == 1) {
2895 $enableNotification
.enable();
2898 else if (data
.actionName
=== 'saveSubscription' && this._dialog
.is(':visible')) {
2899 this._dialog
.wcfDialog('close');
2901 this._updateSubscriptionStatus({
2902 isSubscribed
: data
.returnValues
.subscribe
,
2903 objectID
: data
.returnValues
.objectID
2907 // show notification
2908 if (this._notification
=== null) {
2909 this._notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success.edit'));
2912 this._notification
.show();
2917 * Saves the subscription.
2919 * @param object event
2921 _save: function (event
) {
2922 var $button
= this._buttons
[$(event
.currentTarget
).data('objectType')][$(event
.currentTarget
).data('objectID')];
2923 var $subscribe
= this._dialog
.find('input[name=subscribe]:checked').val();
2924 var $enableNotification
= (this._dialog
.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
2926 this._proxy
.setOption('data', {
2927 actionName
: 'saveSubscription',
2928 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2930 enableNotification
: $enableNotification
,
2931 objectID
: $button
.data('objectID'),
2932 objectType
: $button
.data('objectType'),
2933 subscribe
: $subscribe
2936 this._proxy
.sendRequest();
2940 * Updates subscription status and icon.
2942 * @param object data
2944 _updateSubscriptionStatus: function (data
) {
2945 var $button
= $(this._buttonSelector
+ '[data-object-id=' + data
.objectID
+ ']');
2946 var $icon
= $button
.children('.icon');
2947 if (data
.isSubscribed
) {
2948 $icon
.removeClass('fa-bookmark-o').addClass('fa-bookmark');
2949 $button
.data('isSubscribed', true);
2952 if ($button
.data('removeOnUnsubscribe')) {
2953 $button
.parent().remove();
2956 $icon
.removeClass('fa-bookmark').addClass('fa-bookmark-o');
2957 $button
.data('isSubscribed', false);
2960 if (this._reloadOnUnsubscribe
) {
2961 window
.location
.reload();
2966 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data
);
2971 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
2972 _buttonSelector
: "",
2976 _reloadOnUnsubscribe
: false,
2977 init: function() {},
2978 _click: function() {},
2979 _success: function() {},
2980 _save: function() {},
2981 _updateSubscriptionStatus: function() {}