4 * User-related classes.
6 * @author Alexander Ebert
7 * @copyright 2001-2017 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
= { };
111 * Abstract implementation for user panel items providing an interactive dropdown.
113 * @param jQuery triggerElement
114 * @param string identifier
115 * @param object options
117 WCF
.User
.Panel
.Abstract
= Class
.extend({
125 * interactive dropdown instance
126 * @var WCF.Dropdown.Interactive.Instance
131 * dropdown identifier
137 * true if data should be loaded using an AJAX request
143 * header icon to mark all items belonging to this user panel item as read
146 _markAllAsReadLink
: null,
149 * list of options required to dropdown initialization and UI features
156 * @var WCF.Action.Proxy
164 _triggerElement
: null,
167 * Initializes the WCF.User.Panel.Abstract class.
169 * @param jQuery triggerElement
170 * @param string identifier
171 * @param object options
173 init: function(triggerElement
, identifier
, options
) {
174 this._dropdown
= null;
175 this._loadData
= true;
176 this._identifier
= identifier
;
177 this._triggerElement
= triggerElement
;
178 this._options
= options
;
180 this._proxy
= new WCF
.Action
.Proxy({
181 showLoadingOverlay
: false,
182 success
: $.proxy(this._success
, this)
185 this._triggerElement
.click($.proxy(this.toggle
, this));
187 if (this._options
.showAllLink
) {
188 this._triggerElement
.dblclick($.proxy(this._dblClick
, this));
191 if (this._options
.staticDropdown
=== true) {
192 this._loadData
= false;
195 var $badge
= this._triggerElement
.find('span.badge');
197 this._badge
= $badge
;
203 * Toggles the interactive dropdown.
205 * @param {Event?} event
208 toggle: function(event
) {
209 if (event
instanceof Event
) {
210 event
.preventDefault();
213 if (this._dropdown
=== null) {
214 this._dropdown
= this._initDropdown();
217 if (this._dropdown
.toggle()) {
218 if (!this._loadData
) {
219 // check if there are outstanding items but there are no outstanding ones in the current list
220 if (this._badge
!== null) {
221 var $count
= parseInt(this._badge
.text()) || 0;
222 if ($count
&& !this._dropdown
.getItemList().children('.interactiveDropdownItemOutstanding').length
) {
223 this._loadData
= true;
228 if (this._loadData
) {
229 this._loadData
= false;
238 * Forward to original URL by double clicking the trigger element.
240 * @param object event
243 _dblClick: function(event
) {
244 event
.preventDefault();
246 window
.location
= this._options
.showAllLink
;
252 * Initializes the dropdown on first usage.
254 * @return WCF.Dropdown.Interactive.Instance
256 _initDropdown: function() {
257 var $dropdown
= WCF
.Dropdown
.Interactive
.Handler
.create(this._triggerElement
, this._identifier
, this._options
);
258 $('<li class="loading"><span class="icon icon24 fa-spinner" /> <span>' + WCF
.Language
.get('wcf.global.loading') + '</span></li>').appendTo($dropdown
.getItemList());
264 * Loads item list data via AJAX.
267 // override this in your own implementation to fetch display data
271 * Handles successful AJAX requests.
275 _success: function(data
) {
276 if (data
.returnValues
.template
!== undefined) {
277 var $itemList
= this._dropdown
.getItemList().empty();
278 $(data
.returnValues
.template
).appendTo($itemList
);
280 if (!$itemList
.children().length
) {
281 $('<li class="noItems">' + this._options
.noItems
+ '</li>').appendTo($itemList
);
284 if (this._options
.enableMarkAsRead
) {
285 var $outstandingItems
= this._dropdown
.getItemList().children('.interactiveDropdownItemOutstanding');
286 if (this._markAllAsReadLink
=== null && $outstandingItems
.length
&& this._options
.markAllAsReadConfirmMessage
) {
287 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());
288 $button
.click((function(event
) {
289 this._dropdown
.close();
291 WCF
.System
.Confirmation
.show(this._options
.markAllAsReadConfirmMessage
, (function(action
) {
292 if (action
=== 'confirm') {
293 this._markAllAsRead();
301 $outstandingItems
.each((function(index
, item
) {
302 var $item
= $(item
).addClass('interactiveDropdownItemOutstandingIcon');
303 var $objectID
= $item
.data('objectID');
305 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
);
306 $button
.click((function(event
) {
307 this._markAsRead(event
, $objectID
);
314 this._dropdown
.getItemList().children().each(function(index
, item
) {
316 var $link
= $item
.data('link');
319 if ($.browser
.msie
) {
320 $item
.click(function(event
) {
321 if (event
.target
.tagName
!== 'A') {
322 window
.location
= $link
;
329 $item
.addClass('interactiveDropdownItemShadow');
330 $('<a href="' + $link
+ '" class="interactiveDropdownItemShadowLink" />').appendTo($item
);
333 if ($item
.data('linkReplaceAll')) {
334 $item
.find('> .box48 a:not(.userLink)').prop('href', $link
);
339 this._dropdown
.rebuildScrollbar();
342 if (data
.returnValues
.totalCount
!== undefined) {
343 this.updateBadge(data
.returnValues
.totalCount
);
346 if (this._options
.enableMarkAsRead
) {
347 if (data
.returnValues
.markAsRead
) {
348 var $item
= this._dropdown
.getItemList().children('li[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
350 $item
.removeClass('interactiveDropdownItemOutstanding').data('isRead', true);
351 $item
.children('.interactiveDropdownItemMarkAsRead').remove();
354 else if (data
.returnValues
.markAllAsRead
) {
362 * Marks an item as read.
364 * @param object event
365 * @param integer objectID
367 _markAsRead: function(event
, objectID
) {
368 // override this in your own implementation to mark an item as read
372 * Marks all items as read.
374 _markAllAsRead: function() {
375 // override this in your own implementation to mark all items as read
379 * Updates the badge's count or removes it if count reaches zero. Passing a negative number is undefined.
381 * @param integer count
383 updateBadge: function(count
) {
384 count
= parseInt(count
) || 0;
387 if (this._badge
=== null) {
388 this._badge
= $('<span class="badge badgeUpdate" />').appendTo(this._triggerElement
.children('a'));
389 this._badge
.before(' ');
392 this._badge
.text(count
);
394 else if (this._badge
!== null) {
395 this._badge
.remove();
399 if (this._options
.enableMarkAsRead
) {
400 if (!count
&& this._markAllAsReadLink
!== null) {
401 this._markAllAsReadLink
.remove();
402 this._markAllAsReadLink
= null;
406 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.userMenu', 'updateBadge', {
408 identifier
: this._identifier
413 * Resets the dropdown's inner item list.
415 resetItems: function() {
416 // this method could be called from outside, but the dropdown was never
417 // toggled and thus never initialized
418 if (this._dropdown
!== null) {
419 this._dropdown
.resetItems();
420 this._loadData
= true;
426 * User Panel implementation for user notifications.
428 * @see WCF.User.Panel.Abstract
430 WCF
.User
.Panel
.Notification
= WCF
.User
.Panel
.Abstract
.extend({
438 * @see WCF.User.Panel.Abstract.init()
440 init: function(options
) {
441 options
.enableMarkAsRead
= true;
443 this._super($('#userNotifications'), 'userNotifications', options
);
446 this._favico
= new Favico({
451 if (this._badge
!== null) {
452 var $count
= parseInt(this._badge
.text()) || 0;
453 this._favico
.badge($count
);
457 console
.debug("[WCF.User.Panel.Notification] Failed to initialized Favico: " + e
.message
);
460 WCF
.System
.PushNotification
.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount
, this));
462 require(['EventHandler'], (function(EventHandler
) {
463 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data
) {
464 if (data
.identifier
=== 'com.woltlab.wcf.notifications') {
472 * @see WCF.User.Panel.Abstract._initDropdown()
474 _initDropdown: function() {
475 var $dropdown
= this._super();
477 $('<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());
483 * @see WCF.User.Panel.Abstract._load()
486 this._proxy
.setOption('data', {
487 actionName
: 'getOutstandingNotifications',
488 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
490 this._proxy
.sendRequest();
494 * @see WCF.User.Panel.Abstract._markAsRead()
496 _markAsRead: function(event
, objectID
) {
497 this._proxy
.setOption('data', {
498 actionName
: 'markAsConfirmed',
499 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
500 objectIDs
: [ objectID
]
502 this._proxy
.sendRequest();
506 * @see WCF.User.Panel.Abstract._markAllAsRead()
508 _markAllAsRead: function(event
) {
509 this._proxy
.setOption('data', {
510 actionName
: 'markAllAsConfirmed',
511 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
513 this._proxy
.sendRequest();
517 * @see WCF.User.Panel.Abstract.resetItems()
519 resetItems: function() {
522 if (this._markAllAsReadLink
) {
523 this._markAllAsReadLink
.remove();
524 this._markAllAsReadLink
= null;
529 * @see WCF.User.Panel.Abstract.updateBadge()
531 updateBadge: function(count
) {
532 count
= parseInt(count
) || 0;
534 // update data attribute
535 $('#userNotifications').attr('data-count', count
);
537 if (this._favico
!== null) {
538 this._favico
.badge(count
);
545 * Updates the badge counter and resets the dropdown's item list.
547 * @param integer count
549 updateUserNotificationCount: function(count
) {
550 if (this._dropdown
!== null) {
551 this._dropdown
.resetItems();
554 this.updateBadge(count
);
559 * User Panel implementation for user menu dropdown.
561 * @see WCF.User.Panel.Abstract
563 WCF
.User
.Panel
.UserMenu
= WCF
.User
.Panel
.Abstract
.extend({
565 * @see WCF.User.Panel.Abstract.init()
568 this._super($('#userMenu'), 'userMenu', {
569 pointerOffset
: '13px',
578 WCF
.User
.QuickLogin
= {
580 * Initializes the quick login box
583 require(['EventHandler', 'Ui/Dialog'], function(EventHandler
, UiDialog
) {
584 var loginForm
= elById('loginForm');
585 var loginSection
= elBySel('.loginFormLogin', loginForm
);
586 if (loginSection
&& !loginSection
.nextElementSibling
) {
587 loginForm
.classList
.add('loginFormLoginOnly');
590 var registrationBlock
= elBySel('.loginFormRegister', loginForm
);
592 var callbackOpen = function(event
) {
593 if (event
instanceof Event
) {
594 event
.preventDefault();
595 event
.stopPropagation();
598 loginForm
.style
.removeProperty('display');
600 UiDialog
.openStatic('loginForm', null, {
601 title
: WCF
.Language
.get('wcf.user.login')
604 // The registration part should always be on the right
605 // but some browser (Firefox and IE) have a really bad
606 // support for forcing column breaks, requiring us to
607 // work around it by force pushing it to the right.
608 if (loginSection
!== null && registrationBlock
!== null) {
609 var loginOffset
= loginSection
.offsetTop
;
611 if (loginForm
.clientWidth
> loginSection
.clientWidth
* 2) {
612 while (loginOffset
< (registrationBlock
.offsetTop
- 50)) {
613 // push the registration down by 100 pixel each time
615 loginSection
.style
.setProperty('margin-bottom', margin
+ 'px', '');
621 var links
= document
.getElementsByClassName('loginLink');
622 for (var i
= 0, length
= links
.length
; i
< length
; i
++) {
623 links
[i
].addEventListener(WCF_CLICK_EVENT
, callbackOpen
);
626 var input
= loginForm
.querySelector('#loginForm input[name=url]');
627 if (input
!== null && !input
.value
.match(/^https?:\/\//)) {
628 input
.setAttribute('value', window
.location
.protocol
+ '//' + window
.location
.host
+ input
.getAttribute('value'));
631 EventHandler
.add('com.woltlab.wcf.UserMenuMobile', 'more', function(data
) {
632 if (data
.identifier
=== 'com.woltlab.wcf.login') {
633 data
.handler
.close(true);
643 * UserProfile namespace
645 WCF
.User
.Profile
= {};
648 * Shows the activity point list for users.
650 WCF
.User
.Profile
.ActivityPointList
= {
652 * list of cached templates
664 * initialization state
671 * @var WCF.Action.Proxy
676 * Initializes the WCF.User.Profile.ActivityPointList class.
685 this._proxy
= new WCF
.Action
.Proxy({
686 success
: $.proxy(this._success
, this)
691 WCF
.DOMNodeInsertedHandler
.addCallback('WCF.User.Profile.ActivityPointList', $.proxy(this._init
, this));
693 this._didInit
= true;
697 * Initializes display for activity points.
700 $('.activityPointsDisplay').removeClass('activityPointsDisplay').click($.proxy(this._click
, this));
704 * Shows or loads the activity point for selected user.
706 * @param object event
708 _click: function(event
) {
709 event
.preventDefault();
710 var $userID
= $(event
.currentTarget
).data('userID');
712 if (this._cache
[$userID
] === undefined) {
713 this._proxy
.setOption('data', {
714 actionName
: 'getDetailedActivityPointList',
715 className
: 'wcf\\data\\user\\UserProfileAction',
716 objectIDs
: [ $userID
]
718 this._proxy
.sendRequest();
726 * Displays activity points for given user.
728 * @param integer userID
730 _show: function(userID
) {
731 if (this._dialog
=== null) {
732 this._dialog
= $('<div>' + this._cache
[userID
] + '</div>').hide().appendTo(document
.body
);
733 this._dialog
.wcfDialog({
734 title
: WCF
.Language
.get('wcf.user.activityPoint')
738 this._dialog
.html(this._cache
[userID
]);
739 this._dialog
.wcfDialog('open');
744 * Handles successful AJAX requests.
747 * @param string textStatus
748 * @param jQuery jqXHR
750 _success: function(data
, textStatus
, jqXHR
) {
751 this._cache
[data
.returnValues
.userID
] = data
.returnValues
.template
;
752 this._show(data
.returnValues
.userID
);
757 * Provides methods to load tab menu content upon request.
759 WCF
.User
.Profile
.TabMenu
= Class
.extend({
770 _profileContent
: null,
774 * @var WCF.Action.Proxy
785 * Initializes the tab menu loader.
787 * @param integer userID
789 init: function(userID
) {
790 this._profileContent
= $('#profileContent');
791 this._userID
= userID
;
793 var $activeMenuItem
= this._profileContent
.data('active');
794 var $enableProxy
= false;
796 // fetch content state
797 this._profileContent
.find('div.tabMenuContent').each($.proxy(function(index
, container
) {
798 var $containerID
= $(container
).wcfIdentify();
800 if ($activeMenuItem
=== $containerID
) {
801 this._hasContent
[$containerID
] = true;
804 this._hasContent
[$containerID
] = false;
809 // enable loader if at least one container is empty
811 this._proxy
= new WCF
.Action
.Proxy({
812 success
: $.proxy(this._success
, this)
815 this._profileContent
.on('wcftabsbeforeactivate', $.proxy(this._loadContent
, this));
817 // check which tab is selected
818 this._profileContent
.find('> nav.tabMenu > ul > li').each($.proxy(function(index
, listItem
) {
819 var $listItem
= $(listItem
);
820 if ($listItem
.hasClass('ui-state-active')) {
822 this._loadContent(null, {
823 newPanel
: $('#' + $listItem
.attr('aria-controls'))
832 $('.userProfileUser .contentDescription a[href$="#likes"]').click((function (event
) {
833 event
.preventDefault();
835 require(['Ui/TabMenu'], function (UiTabMenu
) {
836 UiTabMenu
.getTabMenu('profileContent').select('likes');
842 * Prepares to load content once tabs are being switched.
844 * @param object event
847 _loadContent: function(event
, ui
) {
848 var $panel
= $(ui
.newPanel
);
849 var $containerID
= $panel
.attr('id');
851 if (!this._hasContent
[$containerID
]) {
852 this._proxy
.setOption('data', {
853 actionName
: 'getContent',
854 className
: 'wcf\\data\\user\\profile\\menu\\item\\UserProfileMenuItemAction',
857 containerID
: $containerID
,
858 menuItem
: $panel
.data('menuItem'),
863 this._proxy
.sendRequest();
868 * Shows previously requested content.
871 * @param string textStatus
872 * @param jQuery jqXHR
874 _success: function(data
, textStatus
, jqXHR
) {
875 var $containerID
= data
.returnValues
.containerID
;
876 this._hasContent
[$containerID
] = true;
878 // insert content, uses non jQuery because DomUtil.insertHtml() moves <script> elements
879 // to the bottom of the element by default which is exactly what is required here
880 require(['Dom/ChangeListener', 'Dom/Util'], function(DomChangeListener
, DomUtil
) {
881 DomUtil
.insertHtml(data
.returnValues
.template
, elById($containerID
), 'append');
883 DomChangeListener
.trigger();
889 * User profile inline editor.
891 * @param integer userID
892 * @param boolean editOnInit
894 WCF
.User
.Profile
.Editor
= Class
.extend({
904 * list of interface buttons
917 * @var WCF.Action.Proxy
934 * Initializes the WCF.User.Profile.Editor object.
936 * @param integer userID
937 * @param boolean editOnInit
939 init: function(userID
, editOnInit
) {
940 this._actionName
= '';
941 this._active
= false;
942 this._cachedTemplate
= '';
943 this._tab
= $('#about');
944 this._userID
= userID
;
945 this._proxy
= new WCF
.Action
.Proxy({
946 success
: $.proxy(this._success
, this)
951 // begin editing on page load
958 * Initializes interface buttons.
960 _initButtons: function() {
963 beginEdit
: $('.jsButtonEditProfile:eq(0)').click(this._beginEdit
.bind(this))
970 * @param {Event?} event event object
972 _beginEdit: function(event
) {
973 if (event
) event
.preventDefault();
975 if (this._active
) return;
978 this._actionName
= 'beginEdit';
979 this._buttons
.beginEdit
.parent().addClass('active');
980 $('#profileContent').wcfTabs('select', 'about');
983 this._proxy
.setOption('data', {
984 actionName
: 'beginEdit',
985 className
: 'wcf\\data\\user\\UserProfileAction',
986 objectIDs
: [ this._userID
]
988 this._proxy
.sendRequest();
992 * Saves input values.
995 this._actionName
= 'save';
998 var $regExp
= /values\[([a-zA-Z0-9._-]+)\]/;
1000 this._tab
.find('input, textarea, select').each(function(index
, element
) {
1001 var $element
= $(element
);
1004 switch ($element
.getTagName()) {
1006 var $type
= $element
.attr('type');
1008 if (($type
=== 'radio' || $type
=== 'checkbox') && !$element
.prop('checked')) {
1014 if ($element
.data('redactor')) {
1015 $value
= $element
.redactor('code.get');
1020 var $name
= $element
.attr('name');
1021 if ($regExp
.test($name
)) {
1022 var $fieldName
= RegExp
.$1;
1023 if ($value
=== null) $value
= $element
.val();
1025 // check for checkboxes
1026 if ($element
.attr('type') === 'checkbox' && /\[\]$/.test($name
)) {
1027 if (!Array
.isArray($values
[$fieldName
])) {
1028 $values
[$fieldName
] = [];
1031 $values
[$fieldName
].push($value
);
1034 $values
[$fieldName
] = $value
;
1039 this._proxy
.setOption('data', {
1041 className
: 'wcf\\data\\user\\UserProfileAction',
1042 objectIDs
: [ this._userID
],
1047 this._proxy
.sendRequest();
1051 * Restores back to default view.
1053 _restore: function() {
1054 this._actionName
= 'restore';
1055 this._active
= false;
1056 this._buttons
.beginEdit
.parent().removeClass('active');
1058 this._destroyEditor();
1060 this._tab
.html(this._cachedTemplate
).children().css({ height
: 'auto' });
1064 * Handles successful AJAX requests.
1066 * @param object data
1067 * @param string textStatus
1068 * @param jQuery jqXHR
1070 _success: function(data
, textStatus
, jqXHR
) {
1071 switch (this._actionName
) {
1073 this._prepareEdit(data
);
1077 // save was successful, show parsed template
1078 if (data
.returnValues
.success
) {
1079 this._cachedTemplate
= data
.returnValues
.template
;
1083 this._prepareEdit(data
, true);
1090 * Prepares editing mode.
1092 * @param object data
1093 * @param boolean disableCache
1095 _prepareEdit: function(data
, disableCache
) {
1096 this._destroyEditor();
1100 this._tab
.html(function(index
, oldHTML
) {
1101 if (disableCache
!== true) {
1102 self
._cachedTemplate
= oldHTML
;
1105 return data
.returnValues
.template
;
1108 // block autocomplete
1109 this._tab
.find('input[type=text]').attr('autocomplete', 'off');
1111 // bind event listener
1112 this._tab
.find('.formSubmit > button[data-type=save]').click($.proxy(this._save
, this));
1113 this._tab
.find('.formSubmit > button[data-type=restore]').click($.proxy(this._restore
, this));
1114 this._tab
.find('input').keyup(function(event
) {
1115 if (event
.which
=== $.ui
.keyCode
.ENTER
) {
1118 event
.preventDefault();
1125 * Destroys all editor instances within current tab.
1127 _destroyEditor: function() {
1128 // destroy all editor instances
1129 this._tab
.find('textarea').each(function(index
, container
) {
1130 var $container
= $(container
);
1131 if ($container
.data('redactor')) {
1132 $container
.redactor('core.destroy');
1139 * Namespace for registration functions.
1141 WCF
.User
.Registration
= {};
1144 * Validates the password.
1146 * @param jQuery element
1147 * @param jQuery confirmElement
1148 * @param object options
1150 WCF
.User
.Registration
.Validation
= Class
.extend({
1164 * confirmation input element
1167 _confirmElement
: null,
1176 * list of error messages
1179 _errorMessages
: { },
1182 * list of additional options
1189 * @var WCF.Action.Proxy
1194 * Initializes the validation.
1196 * @param jQuery element
1197 * @param jQuery confirmElement
1198 * @param object options
1200 init: function(element
, confirmElement
, options
) {
1201 this._element
= element
;
1202 this._element
.blur($.proxy(this._blur
, this));
1203 this._confirmElement
= confirmElement
|| null;
1205 if (this._confirmElement
!== null) {
1206 this._confirmElement
.blur($.proxy(this._blurConfirm
, this));
1209 options
= options
|| { };
1210 this._setOptions(options
);
1212 this._proxy
= new WCF
.Action
.Proxy({
1213 success
: $.proxy(this._success
, this),
1214 showLoadingOverlay
: false
1217 this._setErrorMessages();
1221 * Sets additional options
1223 _setOptions: function(options
) { },
1226 * Sets error messages.
1228 _setErrorMessages: function() {
1229 this._errorMessages
= {
1236 * Validates once focus on input is lost.
1238 * @param object event
1240 _blur: function(event
) {
1241 var $value
= this._element
.val();
1243 return this._showError(this._element
, WCF
.Language
.get('wcf.global.form.error.empty'));
1246 if (this._confirmElement
!== null) {
1247 var $confirmValue
= this._confirmElement
.val();
1248 if ($confirmValue
!= '' && $value
!= $confirmValue
) {
1249 return this._showError(this._confirmElement
, this._errorMessages
.notEqual
);
1253 if (!this._validateOptions()) {
1257 this._proxy
.setOption('data', {
1258 actionName
: this._actionName
,
1259 className
: this._className
,
1260 parameters
: this._getParameters()
1262 this._proxy
.sendRequest();
1266 * Returns a list of parameters.
1270 _getParameters: function() {
1275 * Validates input by options.
1279 _validateOptions: function() {
1284 * Validates value once confirmation input focus is lost.
1286 * @param object event
1288 _blurConfirm: function(event
) {
1289 var $value
= this._confirmElement
.val();
1291 return this._showError(this._confirmElement
, WCF
.Language
.get('wcf.global.form.error.empty'));
1298 * Handles AJAX responses.
1300 * @param object data
1301 * @param string textStatus
1302 * @param jQuery jqXHR
1304 _success: function(data
, textStatus
, jqXHR
) {
1305 if (data
.returnValues
.isValid
) {
1306 this._showSuccess(this._element
);
1307 if (this._confirmElement
!== null && this._confirmElement
.val()) {
1308 this._showSuccess(this._confirmElement
);
1312 this._showError(this._element
, WCF
.Language
.get(this._errorMessages
.ajaxError
+ data
.returnValues
.error
));
1317 * Shows an error message.
1319 * @param jQuery element
1320 * @param string message
1322 _showError: function(element
, message
) {
1323 element
.parent().parent().addClass('formError').removeClass('formSuccess');
1325 var $innerError
= element
.parent().find('small.innerError');
1326 if (!$innerError
.length
) {
1327 $innerError
= $('<small />').addClass('innerError').insertAfter(element
);
1330 $innerError
.text(message
);
1334 * Displays a success message.
1336 * @param jQuery element
1338 _showSuccess: function(element
) {
1339 element
.parent().parent().addClass('formSuccess').removeClass('formError');
1340 element
.next('small.innerError').remove();
1345 * Username validation for registration.
1347 * @see WCF.User.Registration.Validation
1349 WCF
.User
.Registration
.Validation
.Username
= WCF
.User
.Registration
.Validation
.extend({
1351 * @see WCF.User.Registration.Validation._actionName
1353 _actionName
: 'validateUsername',
1356 * @see WCF.User.Registration.Validation._className
1358 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1361 * @see WCF.User.Registration.Validation._setOptions()
1363 _setOptions: function(options
) {
1364 this._options
= $.extend(true, {
1371 * @see WCF.User.Registration.Validation._setErrorMessages()
1373 _setErrorMessages: function() {
1374 this._errorMessages
= {
1375 ajaxError
: 'wcf.user.username.error.'
1380 * @see WCF.User.Registration.Validation._validateOptions()
1382 _validateOptions: function() {
1383 var $value
= this._element
.val();
1384 if ($value
.length
< this._options
.minlength
|| $value
.length
> this._options
.maxlength
) {
1385 this._showError(this._element
, WCF
.Language
.get('wcf.user.username.error.invalid'));
1393 * @see WCF.User.Registration.Validation._getParameters()
1395 _getParameters: function() {
1397 username
: this._element
.val()
1403 * Email validation for registration.
1405 * @see WCF.User.Registration.Validation
1407 WCF
.User
.Registration
.Validation
.EmailAddress
= WCF
.User
.Registration
.Validation
.extend({
1409 * @see WCF.User.Registration.Validation._actionName
1411 _actionName
: 'validateEmailAddress',
1414 * @see WCF.User.Registration.Validation._className
1416 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1419 * @see WCF.User.Registration.Validation._getParameters()
1421 _getParameters: function() {
1423 email
: this._element
.val()
1428 * @see WCF.User.Registration.Validation._setErrorMessages()
1430 _setErrorMessages: function() {
1431 this._errorMessages
= {
1432 ajaxError
: 'wcf.user.email.error.',
1433 notEqual
: WCF
.Language
.get('wcf.user.confirmEmail.error.notEqual')
1439 * Password validation for registration.
1441 * @see WCF.User.Registration.Validation
1443 WCF
.User
.Registration
.Validation
.Password
= WCF
.User
.Registration
.Validation
.extend({
1445 * @see WCF.User.Registration.Validation._actionName
1447 _actionName
: 'validatePassword',
1450 * @see WCF.User.Registration.Validation._className
1452 _className
: 'wcf\\data\\user\\UserRegistrationAction',
1455 * @see WCF.User.Registration.Validation._getParameters()
1457 _getParameters: function() {
1459 password
: this._element
.val()
1464 * @see WCF.User.Registration.Validation._setErrorMessages()
1466 _setErrorMessages: function() {
1467 this._errorMessages
= {
1468 ajaxError
: 'wcf.user.password.error.',
1469 notEqual
: WCF
.Language
.get('wcf.user.confirmPassword.error.notEqual')
1475 * Toggles input fields for lost password form.
1477 WCF
.User
.Registration
.LostPassword
= Class
.extend({
1491 * Initializes LostPassword-form class.
1494 // bind input fields
1495 this._email
= $('#emailInput');
1496 this._username
= $('#usernameInput');
1498 // bind event listener
1499 this._email
.keyup($.proxy(this._checkEmail
, this));
1500 this._username
.keyup($.proxy(this._checkUsername
, this));
1502 if ($.browser
.mozilla
&& $.browser
.touch
) {
1503 this._email
.on('input', $.proxy(this._checkEmail
, this));
1504 this._username
.on('input', $.proxy(this._checkUsername
, this));
1507 // toggle fields on init
1509 this._checkUsername();
1513 * Checks for content in email field and toggles username.
1515 _checkEmail: function() {
1516 if (this._email
.val() == '') {
1517 this._username
.enable();
1518 this._username
.parents('dl:eq(0)').removeClass('disabled');
1521 this._username
.disable();
1522 this._username
.parents('dl:eq(0)').addClass('disabled');
1523 this._username
.val('');
1528 * Checks for content in username field and toggles email.
1530 _checkUsername: function() {
1531 if (this._username
.val() == '') {
1532 this._email
.enable();
1533 this._email
.parents('dl:eq(0)').removeClass('disabled');
1536 this._email
.disable();
1537 this._email
.parents('dl:eq(0)').addClass('disabled');
1538 this._email
.val('');
1544 * Notification system for WCF.
1546 * @author Alexander Ebert
1547 * @copyright 2001-2017 WoltLab GmbH
1548 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1550 WCF
.Notification
= { };
1553 * Handles the notification list.
1555 WCF
.Notification
.List
= Class
.extend({
1558 * @var WCF.Action.Proxy
1563 * Initializes the WCF.Notification.List object.
1566 this._proxy
= new WCF
.Action
.Proxy({
1567 success
: $.proxy(this._success
, this)
1570 // handle 'mark all as confirmed' buttons
1571 $('.contentHeaderNavigation .jsMarkAllAsConfirmed').click(function() {
1572 WCF
.System
.Confirmation
.show(WCF
.Language
.get('wcf.user.notification.markAllAsConfirmed.confirmMessage'), function(action
) {
1573 if (action
=== 'confirm') {
1574 new WCF
.Action
.Proxy({
1577 actionName
: 'markAllAsConfirmed',
1578 className
: 'wcf\\data\\user\\notification\\UserNotificationAction'
1580 success: function() { window
.location
.reload(); }
1586 // handle regular items
1587 this._convertList();
1591 * Converts the notification item list to be in sync with the notification dropdown.
1593 _convertList: function() {
1594 $('.userNotificationItemList > .notificationItem').each((function(index
, item
) {
1595 var $item
= $(item
);
1597 if (!$item
.data('isRead')) {
1598 $item
.find('a:not(.userLink)').prop('href', $item
.data('link'));
1600 var $markAsConfirmed
= $('<a href="#" class="icon icon24 fa-check notificationItemMarkAsConfirmed jsTooltip" title="' + WCF
.Language
.get('wcf.user.notification.markAsConfirmed') + '" />').appendTo($item
);
1601 $markAsConfirmed
.click($.proxy(this._markAsConfirmed
, this));
1604 // work-around for legacy notifications
1605 if (!$item
.find('a:not(.notificationItemMarkAsConfirmed)').length
) {
1606 $item
.find('.details > p:eq(0)').html(function(index
, oldHTML
) {
1607 return '<a href="' + $item
.data('link') + '">' + oldHTML
+ '</a>';
1612 WCF
.DOMNodeInsertedHandler
.execute();
1616 * Marks a single notification as confirmed.
1618 * @param object event
1620 _markAsConfirmed: function(event
) {
1621 event
.preventDefault();
1623 var $notificationID
= $(event
.currentTarget
).parents('.notificationItem:eq(0)').data('objectID');
1625 this._proxy
.setOption('data', {
1626 actionName
: 'markAsConfirmed',
1627 className
: 'wcf\\data\\user\\notification\\UserNotificationAction',
1628 objectIDs
: [ $notificationID
]
1630 this._proxy
.sendRequest();
1636 * Handles successful AJAX requests.
1638 * @param object data
1639 * @param string textStatus
1640 * @param jQuery jqXHR
1642 _success: function(data
, textStatus
, jqXHR
) {
1643 var $item
= $('.userNotificationItemList > .notificationItem[data-object-id=' + data
.returnValues
.markAsRead
+ ']');
1645 $item
.data('isRead', true);
1646 $item
.find('.newContentBadge').remove();
1647 $item
.find('.notificationItemMarkAsConfirmed').remove();
1648 $item
.removeClass('notificationUnconfirmed');
1653 * Signature preview.
1655 * @see WCF.Message.Preview
1657 WCF
.User
.SignaturePreview
= WCF
.Message
.Preview
.extend({
1659 * @see WCF.Message.Preview._handleResponse()
1661 _handleResponse: function(data
) {
1662 // get preview container
1663 var $preview
= $('#previewContainer');
1664 if (!$preview
.length
) {
1665 $preview
= $('<section class="section" id="previewContainer"><h2 class="sectionTitle">' + WCF
.Language
.get('wcf.global.preview') + '</h2><div class="htmlContent"></div></section>').insertBefore($('#signatureContainer')).wcfFadeIn();
1668 $preview
.children('div').first().html(data
.returnValues
.message
);
1673 * Loads recent activity events once the user scrolls to the very bottom.
1675 * @param integer userID
1677 WCF
.User
.RecentActivityLoader
= Class
.extend({
1685 * true if list should be filtered by followed users
1688 _filteredByFollowedUsers
: false,
1691 * button to load next events
1698 * @var WCF.Action.Proxy
1709 * Initializes a new RecentActivityLoader object.
1711 * @param integer userID
1712 * @param boolean filteredByFollowedUsers
1714 init: function(userID
, filteredByFollowedUsers
) {
1715 this._container
= $('#recentActivities');
1716 this._filteredByFollowedUsers
= (filteredByFollowedUsers
=== true);
1717 this._userID
= userID
;
1719 if (this._userID
!== null && !this._userID
) {
1720 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1724 this._proxy
= new WCF
.Action
.Proxy({
1725 success
: $.proxy(this._success
, this)
1728 if (this._container
.children('li').length
) {
1729 this._loadButton
= $('<li class="showMore"><button class="small">' + WCF
.Language
.get('wcf.user.recentActivity.more') + '</button></li>').appendTo(this._container
);
1730 this._loadButton
= this._loadButton
.children('button').click($.proxy(this._click
, this));
1733 $('<li class="showMore"><small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small></li>').appendTo(this._container
);
1736 if (WCF
.User
.userID
) {
1737 $('.jsRecentActivitySwitchContext .button').click($.proxy(this._switchContext
, this));
1742 * Loads next activity events.
1744 _click: function() {
1745 this._loadButton
.enable();
1748 lastEventID
: this._container
.data('lastEventID'),
1749 lastEventTime
: this._container
.data('lastEventTime')
1752 $parameters
.userID
= this._userID
;
1754 else if (this._filteredByFollowedUsers
) {
1755 $parameters
.filteredByFollowedUsers
= 1;
1758 this._proxy
.setOption('data', {
1760 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction',
1761 parameters
: $parameters
1763 this._proxy
.sendRequest();
1767 * Switches recent activity context.
1769 _switchContext: function(event
) {
1770 event
.preventDefault();
1772 if (!$(event
.currentTarget
).hasClass('active')) {
1773 new WCF
.Action
.Proxy({
1776 actionName
: 'switchContext',
1777 className
: 'wcf\\data\\user\\activity\\event\\UserActivityEventAction'
1779 success: function() {
1780 window
.location
.hash
= '#dashboardBoxRecentActivity';
1781 window
.location
.reload();
1788 * Handles successful AJAX requests.
1790 * @param object data
1791 * @param string textStatus
1792 * @param jQuery jqXHR
1794 _success: function(data
, textStatus
, jqXHR
) {
1795 if (data
.returnValues
.template
) {
1796 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
1798 this._container
.data('lastEventTime', data
.returnValues
.lastEventTime
);
1799 this._container
.data('lastEventID', data
.returnValues
.lastEventID
);
1800 this._loadButton
.enable();
1803 $('<small>' + WCF
.Language
.get('wcf.user.recentActivity.noMoreEntries') + '</small>').appendTo(this._loadButton
.parent());
1804 this._loadButton
.remove();
1810 * Loads likes once the user scrolls to the very bottom.
1812 * @param integer userID
1814 WCF
.User
.LikeLoader
= Class
.extend({
1825 _likeType
: 'received',
1834 * button to load next events
1840 * 'no more entries' element
1843 _noMoreEntries
: null,
1847 * @var WCF.Action.Proxy
1858 * Initializes a new RecentActivityLoader object.
1860 * @param integer userID
1861 * @param boolean filteredByFollowedUsers
1863 init: function(userID
) {
1864 this._container
= $('#likeList');
1865 this._userID
= userID
;
1867 if (!this._userID
) {
1868 console
.debug("[WCF.User.RecentActivityLoader] Invalid parameter 'userID' given.");
1872 this._proxy
= new WCF
.Action
.Proxy({
1873 success
: $.proxy(this._success
, this)
1876 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
);
1877 this._loadButton
= $container
.children('button').click($.proxy(this._click
, this));
1878 this._noMoreEntries
= $container
.children('small').hide();
1880 if (this._container
.find('> li').length
== 2) {
1881 this._loadButton
.hide();
1882 this._noMoreEntries
.show();
1885 $('#likeType .button').click($.proxy(this._clickLikeType
, this));
1886 $('#likeValue .button').click($.proxy(this._clickLikeValue
, this));
1890 * Handles like type change.
1892 _clickLikeType: function(event
) {
1893 var $button
= $(event
.currentTarget
);
1894 if (this._likeType
!= $button
.data('likeType')) {
1895 this._likeType
= $button
.data('likeType');
1896 $('#likeType .button').removeClass('active');
1897 $button
.addClass('active');
1903 * Handles like value change.
1905 _clickLikeValue: function(event
) {
1906 var $button
= $(event
.currentTarget
);
1907 if (this._likeValue
!= $button
.data('likeValue')) {
1908 this._likeValue
= $button
.data('likeValue');
1909 $('#likeValue .button').removeClass('active');
1910 $button
.addClass('active');
1912 // change button labels
1913 $('#likeType > li:first-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesReceived'));
1914 $('#likeType > li:last-child > .button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likesGiven'));
1916 this._container
.find('> li.likeListMore button').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.more'));
1917 this._container
.find('> li.likeListMore small').text(WCF
.Language
.get('wcf.like.' + (this._likeValue
== -1 ? 'dis' : '') + 'likes.noMoreEntries'));
1926 _reload: function() {
1927 this._container
.find('> li:not(:first-child):not(:last-child)').remove();
1928 this._container
.data('lastLikeTime', 0);
1935 _click: function() {
1936 this._loadButton
.enable();
1939 lastLikeTime
: this._container
.data('lastLikeTime'),
1940 userID
: this._userID
,
1941 likeType
: this._likeType
,
1942 likeValue
: this._likeValue
1945 this._proxy
.setOption('data', {
1947 className
: 'wcf\\data\\like\\LikeAction',
1948 parameters
: $parameters
1950 this._proxy
.sendRequest();
1954 * Handles successful AJAX requests.
1956 * @param object data
1957 * @param string textStatus
1958 * @param jQuery jqXHR
1960 _success: function(data
, textStatus
, jqXHR
) {
1961 if (data
.returnValues
.template
) {
1962 $(data
.returnValues
.template
).insertBefore(this._loadButton
.parent());
1964 this._container
.data('lastLikeTime', data
.returnValues
.lastLikeTime
);
1965 this._noMoreEntries
.hide();
1966 this._loadButton
.show().enable();
1969 this._noMoreEntries
.show();
1970 this._loadButton
.hide();
1976 * Loads user profile previews.
1980 WCF
.User
.ProfilePreview
= WCF
.Popover
.extend({
1983 * @var WCF.Action.Proxy
1988 * list of user profiles
1994 * @see WCF.Popover.init()
1997 this._super('.userLink');
1999 this._proxy
= new WCF
.Action
.Proxy({
2000 showLoadingOverlay
: false
2003 // register instance
2004 WCF
.System
.ObjectStore
.add('WCF.User.ProfilePreview', this);
2008 * @see WCF.Popover._loadContent()
2010 _loadContent: function() {
2011 var $element
= $('#' + this._activeElementID
);
2012 var $userID
= $element
.data('userID');
2014 if (this._userProfiles
[$userID
]) {
2015 // use cached user profile
2016 this._insertContent(this._activeElementID
, this._userProfiles
[$userID
], true);
2019 this._proxy
.setOption('data', {
2020 actionName
: 'getUserProfile',
2021 className
: 'wcf\\data\\user\\UserProfileAction',
2022 objectIDs
: [ $userID
]
2025 var $elementID
= this._activeElementID
;
2027 this._proxy
.setOption('success', function(data
, textStatus
, jqXHR
) {
2028 // cache user profile
2029 self
._userProfiles
[$userID
] = data
.returnValues
.template
;
2031 // show user profile
2032 self
._insertContent($elementID
, data
.returnValues
.template
, true);
2034 this._proxy
.setOption('failure', function(data
, jqXHR
, textStatus
, errorThrown
) {
2035 // cache user profile
2036 self
._userProfiles
[$userID
] = data
.message
;
2038 // show user profile
2039 self
._insertContent($elementID
, data
.message
, true);
2043 this._proxy
.sendRequest();
2048 * Purages a cached user profile.
2050 * @param integer userID
2052 purge: function(userID
) {
2053 delete this._userProfiles
[userID
];
2055 // purge content cache
2061 * Initalizes WCF.User.Action namespace.
2063 WCF
.User
.Action
= {};
2066 * Handles user follow and unfollow links.
2068 WCF
.User
.Action
.Follow
= Class
.extend({
2070 * list with elements containing follow and unfollow buttons
2073 _containerList
: null,
2076 * CSS selector for follow buttons
2079 _followButtonSelector
: '.jsFollowButton',
2082 * id of the user that is currently being followed/unfollowed
2088 * Initializes new WCF.User.Action.Follow object.
2090 * @param array containerList
2091 * @param string followButtonSelector
2093 init: function(containerList
, followButtonSelector
) {
2094 if (!containerList
.length
) {
2097 this._containerList
= containerList
;
2099 if (followButtonSelector
) {
2100 this._followButtonSelector
= followButtonSelector
;
2104 this._proxy
= new WCF
.Action
.Proxy({
2105 success
: $.proxy(this._success
, this)
2108 // bind event listeners
2109 this._containerList
.each($.proxy(function(index
, container
) {
2110 $(container
).find(this._followButtonSelector
).click($.proxy(this._click
, this));
2115 * Handles a click on a follow or unfollow button.
2117 * @param object event
2119 _click: function(event
) {
2120 event
.preventDefault();
2121 var link
= $(event
.target
);
2122 if (!link
.is('a')) {
2123 link
= link
.closest('a');
2125 this._userID
= link
.data('objectID');
2127 this._proxy
.setOption('data', {
2128 'actionName': link
.data('following') ? 'unfollow' : 'follow',
2129 'className': 'wcf\\data\\user\\follow\\UserFollowAction',
2132 userID
: this._userID
2136 this._proxy
.sendRequest();
2140 * Handles the successful (un)following of a user.
2142 * @param object data
2143 * @param string textStatus
2144 * @param jQuery jqXHR
2146 _success: function(data
, textStatus
, jqXHR
) {
2147 this._containerList
.each($.proxy(function(index
, container
) {
2148 var button
= $(container
).find(this._followButtonSelector
).get(0);
2150 if (button
&& $(button
).data('objectID') == this._userID
) {
2153 // toogle icon title
2154 if (data
.returnValues
.following
) {
2155 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unfollow')).children('.icon').removeClass('fa-plus').addClass('fa-minus');
2156 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unfollow'));
2159 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.follow')).children('.icon').removeClass('fa-minus').addClass('fa-plus');
2160 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.follow'));
2163 button
.data('following', data
.returnValues
.following
);
2169 var $notification
= new WCF
.System
.Notification();
2170 $notification
.show();
2175 * Handles user ignore and unignore links.
2177 WCF
.User
.Action
.Ignore
= Class
.extend({
2179 * list with elements containing ignore and unignore buttons
2182 _containerList
: null,
2185 * CSS selector for ignore buttons
2188 _ignoreButtonSelector
: '.jsIgnoreButton',
2191 * id of the user that is currently being ignored/unignored
2197 * Initializes new WCF.User.Action.Ignore object.
2199 * @param array containerList
2200 * @param string ignoreButtonSelector
2202 init: function(containerList
, ignoreButtonSelector
) {
2203 if (!containerList
.length
) {
2206 this._containerList
= containerList
;
2208 if (ignoreButtonSelector
) {
2209 this._ignoreButtonSelector
= ignoreButtonSelector
;
2213 this._proxy
= new WCF
.Action
.Proxy({
2214 success
: $.proxy(this._success
, this)
2217 // bind event listeners
2218 this._containerList
.each($.proxy(function(index
, container
) {
2219 $(container
).find(this._ignoreButtonSelector
).click($.proxy(this._click
, this));
2224 * Handles a click on a ignore or unignore button.
2226 * @param object event
2228 _click: function(event
) {
2229 event
.preventDefault();
2230 var link
= $(event
.target
);
2231 if (!link
.is('a')) {
2232 link
= link
.closest('a');
2234 this._userID
= link
.data('objectID');
2236 this._proxy
.setOption('data', {
2237 'actionName': link
.data('ignored') ? 'unignore' : 'ignore',
2238 'className': 'wcf\\data\\user\\ignore\\UserIgnoreAction',
2241 userID
: this._userID
2245 this._proxy
.sendRequest();
2249 * Handles the successful (un)ignoring of a user.
2251 * @param object data
2252 * @param string textStatus
2253 * @param jQuery jqXHR
2255 _success: function(data
, textStatus
, jqXHR
) {
2256 this._containerList
.each($.proxy(function(index
, container
) {
2257 var button
= $(container
).find(this._ignoreButtonSelector
).get(0);
2259 if (button
&& $(button
).data('objectID') == this._userID
) {
2262 // toogle icon title
2263 if (data
.returnValues
.isIgnoredUser
) {
2264 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.unignore')).children('.icon').removeClass('fa-ban').addClass('fa-circle-o');
2265 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.unignore'));
2268 button
.attr('data-tooltip', WCF
.Language
.get('wcf.user.button.ignore')).children('.icon').removeClass('fa-circle-o').addClass('fa-ban');
2269 button
.children('.invisible').text(WCF
.Language
.get('wcf.user.button.ignore'));
2272 button
.data('ignored', data
.returnValues
.isIgnoredUser
);
2278 var $notification
= new WCF
.System
.Notification();
2279 $notification
.show();
2281 // force rebuilding of popover cache
2283 WCF
.System
.ObjectStore
.invoke('WCF.User.ProfilePreview', function(profilePreview
) {
2284 profilePreview
.purge(self
._userID
);
2290 * Namespace for avatar functions.
2292 WCF
.User
.Avatar
= {};
2295 * Avatar upload function
2299 WCF
.User
.Avatar
.Upload
= WCF
.Upload
.extend({
2301 * user id of avatar owner
2307 * Initalizes a new WCF.User.Avatar.Upload object.
2309 * @param integer userID
2311 init: function(userID
) {
2312 this._super($('#avatarUpload > dd > div'), undefined, 'wcf\\data\\user\\avatar\\UserAvatarAction');
2313 this._userID
= userID
|| 0;
2315 $('#avatarForm input[type=radio]').change(function() {
2316 if ($(this).val() == 'custom') {
2317 $('#avatarUpload > dd > div').show();
2320 $('#avatarUpload > dd > div').hide();
2323 if (!$('#avatarForm input[type=radio][value=custom]:checked').length
) {
2324 $('#avatarUpload > dd > div').hide();
2329 * @see WCF.Upload._initFile()
2331 _initFile: function(file
) {
2332 return $('#avatarUpload > dt > img');
2336 * @see WCF.Upload._success()
2338 _success: function(uploadID
, data
) {
2339 if (data
.returnValues
.url
) {
2340 this._updateImage(data
.returnValues
.url
);
2343 $('#avatarUpload > dd > .innerError').remove();
2345 // show success message
2346 var $notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.user.avatar.upload.success'));
2347 $notification
.show();
2349 else if (data
.returnValues
.errorType
) {
2351 this._getInnerErrorElement().text(WCF
.Language
.get('wcf.user.avatar.upload.error.' + data
.returnValues
.errorType
));
2356 * Updates the displayed avatar image.
2360 _updateImage: function(url
) {
2361 $('#avatarUpload > dt > img').remove();
2362 var $image
= $('<img src="' + url
+ '" class="userAvatarImage" alt="" />').css({
2364 'max-height': '96px',
2365 'max-width': '96px',
2369 $('#avatarUpload > dt').prepend($image
);
2371 WCF
.DOMNodeInsertedHandler
.execute();
2375 * Returns the inner error element.
2379 _getInnerErrorElement: function() {
2380 var $span
= $('#avatarUpload > dd > .innerError');
2381 if (!$span
.length
) {
2382 $span
= $('<small class="innerError"></span>');
2383 $('#avatarUpload > dd').append($span
);
2390 * @see WCF.Upload._getParameters()
2392 _getParameters: function() {
2394 userID
: this._userID
2400 * Generic implementation for grouped user lists.
2402 * @param string className
2403 * @param string dialogTitle
2404 * @param object additionalParameters
2406 WCF
.User
.List
= Class
.extend({
2408 * list of additional parameters
2411 _additionalParameters
: { },
2414 * list of cached pages
2451 * @var WCF.Action.Proxy
2456 * Initializes a new grouped user list.
2458 * @param string className
2459 * @param string dialogTitle
2460 * @param object additionalParameters
2462 init: function(className
, dialogTitle
, additionalParameters
) {
2463 this._additionalParameters
= additionalParameters
|| { };
2465 this._className
= className
;
2466 this._dialog
= null;
2467 this._dialogTitle
= dialogTitle
;
2468 this._pageCount
= 0;
2471 this._proxy
= new WCF
.Action
.Proxy({
2472 success
: $.proxy(this._success
, this)
2477 * Opens the dialog overlay.
2485 * Displays the specified page.
2487 * @param object event
2488 * @param object data
2490 _showPage: function(event
, data
) {
2491 if (data
&& data
.activePage
) {
2492 this._pageNo
= data
.activePage
;
2495 if (this._pageCount
!= 0 && (this._pageNo
< 1 || this._pageNo
> this._pageCount
)) {
2496 console
.debug("[WCF.User.List] Cannot access page " + this._pageNo
+ " of " + this._pageCount
);
2500 if (this._cache
[this._pageNo
]) {
2501 var $dialogCreated
= false;
2502 if (this._dialog
=== null) {
2503 this._dialog
= $('#userList' + this._className
.hashCode());
2504 if (this._dialog
.length
=== 0) {
2505 this._dialog
= $('<div id="userList' + this._className
.hashCode() + '" />').hide().appendTo(document
.body
);
2506 $dialogCreated
= true;
2510 // remove current view
2511 this._dialog
.empty();
2514 this._dialog
.html(this._cache
[this._pageNo
]);
2517 if (this._pageCount
> 1) {
2518 this._dialog
.find('.jsPagination').wcfPages({
2519 activePage
: this._pageNo
,
2520 maxPage
: this._pageCount
2521 }).on('wcfpagesswitched', $.proxy(this._showPage
, this));
2524 this._dialog
.find('.jsPagination').hide();
2528 if ($dialogCreated
) {
2529 this._dialog
.wcfDialog({
2530 title
: this._dialogTitle
2534 this._dialog
.wcfDialog('option', 'title', this._dialogTitle
);
2535 this._dialog
.wcfDialog('open').wcfDialog('render');
2538 WCF
.DOMNodeInsertedHandler
.execute();
2541 this._additionalParameters
.pageNo
= this._pageNo
;
2543 // load template via AJAX
2544 this._proxy
.setOption('data', {
2545 actionName
: 'getGroupedUserList',
2546 className
: this._className
,
2547 interfaceName
: 'wcf\\data\\IGroupedUserListAction',
2548 parameters
: this._additionalParameters
2550 this._proxy
.sendRequest();
2555 * Handles successful AJAX requests.
2557 * @param object data
2558 * @param string textStatus
2559 * @param jQuery jqXHR
2561 _success: function(data
, textStatus
, jqXHR
) {
2562 if (data
.returnValues
.pageCount
) {
2563 this._pageCount
= data
.returnValues
.pageCount
;
2566 this._cache
[this._pageNo
] = data
.returnValues
.template
;
2572 * Namespace for object watch functions.
2574 WCF
.User
.ObjectWatch
= {};
2577 * Handles subscribe/unsubscribe links.
2579 WCF
.User
.ObjectWatch
.Subscribe
= Class
.extend({
2581 * CSS selector for subscribe buttons
2584 _buttonSelector
: '.jsSubscribeButton',
2599 * system notification
2600 * @var WCF.System.Notification
2602 _notification
: null,
2605 * reload page on unsubscribe
2608 _reloadOnUnsubscribe
: false,
2611 * WCF.User.ObjectWatch.Subscribe object.
2613 * @param boolean reloadOnUnsubscribe
2615 init: function(reloadOnUnsubscribe
) {
2616 this._buttons
= { };
2617 this._notification
= null;
2618 this._reloadOnUnsubscribe
= (reloadOnUnsubscribe
=== true);
2621 this._proxy
= new WCF
.Action
.Proxy({
2622 success
: $.proxy(this._success
, this)
2625 // bind event listeners
2626 $(this._buttonSelector
).each($.proxy(function(index
, button
) {
2627 var $button
= $(button
);
2628 $button
.addClass('pointer');
2629 var $objectType
= $button
.data('objectType');
2630 var $objectID
= $button
.data('objectID');
2632 if (this._buttons
[$objectType
] === undefined) {
2633 this._buttons
[$objectType
] = {};
2636 this._buttons
[$objectType
][$objectID
] = $button
.click($.proxy(this._click
, this));
2639 WCF
.System
.Event
.addListener('com.woltlab.wcf.objectWatch', 'update', $.proxy(this._updateSubscriptionStatus
, this));
2643 * Handles a click on a subscribe button.
2645 * @param object event
2647 _click: function(event
) {
2648 event
.preventDefault();
2649 var $button
= $(event
.currentTarget
);
2651 this._proxy
.setOption('data', {
2652 actionName
: 'manageSubscription',
2653 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2655 objectID
: $button
.data('objectID'),
2656 objectType
: $button
.data('objectType')
2659 this._proxy
.sendRequest();
2663 * Handles successful AJAX requests.
2665 * @param object data
2666 * @param string textStatus
2667 * @param jQuery jqXHR
2669 _success: function(data
, textStatus
, jqXHR
) {
2670 if (data
.actionName
=== 'manageSubscription') {
2671 if (this._dialog
=== null) {
2672 this._dialog
= $('<div>' + data
.returnValues
.template
+ '</div>').hide().appendTo(document
.body
);
2673 this._dialog
.wcfDialog({
2674 title
: WCF
.Language
.get('wcf.user.objectWatch.manageSubscription')
2678 this._dialog
.html(data
.returnValues
.template
);
2679 this._dialog
.wcfDialog('open');
2682 // bind event listener
2683 this._dialog
.find('.formSubmit > .jsButtonSave').data('objectID', data
.returnValues
.objectID
).data('objectType', data
.returnValues
.objectType
).click($.proxy(this._save
, this));
2684 var $enableNotification
= this._dialog
.find('input[name=enableNotification]').disable();
2686 // toggle subscription
2687 this._dialog
.find('input[name=subscribe]').change(function(event
) {
2688 var $input
= $(event
.currentTarget
);
2689 if ($input
.val() == 1) {
2690 $enableNotification
.enable();
2693 $enableNotification
.disable();
2698 var $selectedOption
= this._dialog
.find('input[name=subscribe]:checked');
2699 if ($selectedOption
.length
&& $selectedOption
.val() == 1) {
2700 $enableNotification
.enable();
2703 else if (data
.actionName
=== 'saveSubscription' && this._dialog
.is(':visible')) {
2704 this._dialog
.wcfDialog('close');
2706 this._updateSubscriptionStatus({
2707 isSubscribed
: data
.returnValues
.subscribe
,
2708 objectID
: data
.returnValues
.objectID
2712 // show notification
2713 if (this._notification
=== null) {
2714 this._notification
= new WCF
.System
.Notification(WCF
.Language
.get('wcf.global.success.edit'));
2717 this._notification
.show();
2722 * Saves the subscription.
2724 * @param object event
2726 _save: function(event
) {
2727 var $button
= this._buttons
[$(event
.currentTarget
).data('objectType')][$(event
.currentTarget
).data('objectID')];
2728 var $subscribe
= this._dialog
.find('input[name=subscribe]:checked').val();
2729 var $enableNotification
= (this._dialog
.find('input[name=enableNotification]').is(':checked')) ? 1 : 0;
2731 this._proxy
.setOption('data', {
2732 actionName
: 'saveSubscription',
2733 className
: 'wcf\\data\\user\\object\\watch\\UserObjectWatchAction',
2735 enableNotification
: $enableNotification
,
2736 objectID
: $button
.data('objectID'),
2737 objectType
: $button
.data('objectType'),
2738 subscribe
: $subscribe
2741 this._proxy
.sendRequest();
2745 * Updates subscription status and icon.
2747 * @param object data
2749 _updateSubscriptionStatus: function(data
) {
2750 var $button
= $(this._buttonSelector
+ '[data-object-id=' + data
.objectID
+ ']');
2751 var $icon
= $button
.children('.icon');
2752 if (data
.isSubscribed
) {
2753 $icon
.removeClass('fa-bookmark-o').addClass('fa-bookmark');
2754 $button
.data('isSubscribed', true);
2757 if ($button
.data('removeOnUnsubscribe')) {
2758 $button
.parent().remove();
2761 $icon
.removeClass('fa-bookmark').addClass('fa-bookmark-o');
2762 $button
.data('isSubscribed', false);
2765 if (this._reloadOnUnsubscribe
) {
2766 window
.location
.reload();
2771 WCF
.System
.Event
.fireEvent('com.woltlab.wcf.objectWatch', 'updatedSubscription', data
);