Added replacements for WCF.Like, WCF.User.List and $.ui.wcfPages
authorAlexander Ebert <ebert@woltlab.com>
Tue, 18 Aug 2015 15:48:52 +0000 (17:48 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 18 Aug 2015 15:49:48 +0000 (17:49 +0200)
wcfsetup/install/files/js/WCF.Like.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Dialog.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Like/Handler.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpTo.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Pagination.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/User/List.js [new file with mode: 0644]

index 1df53cac6d5876dd76398cf9c0776f110d89f4e0..0588c7907bdf9094808302d6e06ef26badb661f3 100644 (file)
@@ -6,6 +6,8 @@
  * @author     Alexander Ebert
  * @copyright  2001-2015 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * 
+ * @deprecated 2.2 - please use `WoltLab/WCF/Ui/Like/Handler` instead
  */
 WCF.Like = Class.extend({
        /**
index 9faba717a3701b48858de35dcd5e3b290b162e6d..7873dc92d634b6fc20fd0fef74aba8dfbd34ec06 100755 (executable)
@@ -5724,145 +5724,17 @@ WCF.System.Fullscreen = {
 
 /**
  * Provides the 'jump to page' overlay.
+ * 
+ * @deprecated 2.2 - use `WoltLab/WCF/Ui/Page/JumpTo` instead
  */
 WCF.System.PageNavigation = {
-       /**
-        * submit button
-        * @var jQuery
-        */
-       _button: null,
-       
-       /**
-        * page No description
-        * @var jQuery
-        */
-       _description: null,
-       
-       /**
-        * dialog overlay
-        * @var jQuery
-        */
-       _dialog: null,
-       
-       /**
-        * active element id
-        * @var string
-        */
-       _elementID: '',
-       
-       /**
-        * list of tracked navigation bars
-        * @var object
-        */
-       _elements: { },
-       
-       /**
-        * page No input
-        * @var jQuery
-        */
-       _pageNo: null,
-       
-       /**
-        * Initializes the 'jump to page' overlay for given selector.
-        * 
-        * @param       string          selector
-        * @param       object          callback
-        */
        init: function(selector, callback) {
-               var $elements = $(selector);
-               if (!$elements.length) {
-                       return;
-               }
-               
-               callback = callback || null;
-               if (callback !== null && !$.isFunction(callback)) {
-                       console.debug("[WCF.System.PageNavigation] Callback for selector '" + selector + "' is invalid, aborting.");
-                       return;
-               }
-               
-               this._initElements($elements, callback);
-       },
-       
-       /**
-        * Initializes the 'jump to page' overlay for given elements.
-        * 
-        * @param       jQuery          elements
-        * @param       object          callback
-        */
-       _initElements: function(elements, callback) {
-               var self = this;
-               elements.each(function(index, element) {
-                       var $element = $(element);
-                       var $elementID = $element.wcfIdentify();
-                       
-                       if (self._elements[$elementID] === undefined) {
-                               self._elements[$elementID] = $element;
-                               $element.find('li.jumpTo').data('elementID', $elementID).click($.proxy(self._click, self));
+               require(['WoltLab/WCF/Ui/Page/JumpTo'], function(UiPageJumpTo) {
+                       var elements = elBySelAll(selector);
+                       for (var i = 0, length = elements.length; i < length; i++) {
+                               UiPageJumpTo.init(elements[i], callback);
                        }
-               }).data('callback', callback);
-       },
-       
-       /**
-        * Shows the 'jump to page' overlay.
-        * 
-        * @param       object          event
-        */
-       _click: function(event) {
-               this._elementID = $(event.currentTarget).data('elementID');
-               
-               if (this._dialog === null) {
-                       this._dialog = $('<div id="pageNavigationOverlay" />').hide().appendTo(document.body);
-                       
-                       var $fieldset = $('<fieldset><legend>' + WCF.Language.get('wcf.global.page.jumpTo') + '</legend></fieldset>').appendTo(this._dialog);
-                       $('<dl><dt><label for="jsPageNavigationPageNo">' + WCF.Language.get('wcf.global.page.jumpTo') + '</label></dt><dd></dd></dl>').appendTo($fieldset);
-                       this._pageNo = $('<input type="number" id="jsPageNavigationPageNo" value="1" min="1" max="1" class="tiny" />').keyup($.proxy(this._keyUp, this)).appendTo($fieldset.find('dd'));
-                       this._description = $('<small></small>').insertAfter(this._pageNo);
-                       var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog);
-                       this._button = $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.submit') + '</button>').click($.proxy(this._submit, this)).appendTo($formSubmit);
-               }
-               
-               this._button.enable();
-               this._description.html(WCF.Language.get('wcf.global.page.jumpTo.description').replace(/#pages#/, this._elements[this._elementID].data('pages')));
-               this._pageNo.val(this._elements[this._elementID].data('pages')).attr('max', this._elements[this._elementID].data('pages'));
-               
-               this._dialog.wcfDialog({
-                       'title': WCF.Language.get('wcf.global.page.pageNavigation')
                });
-       },
-       
-       /**
-        * Validates the page No input.
-        * 
-        * @param       Event           event
-        */
-       _keyUp: function(event) {
-               if (event.which == $.ui.keyCode.ENTER && !this._button.prop('disabled')) {
-                       this._submit();
-                       return;
-               }
-               
-               var $pageNo = parseInt(this._pageNo.val()) || 0;
-               if ($pageNo < 1 || $pageNo > this._pageNo.attr('max')) {
-                       this._button.disable();
-               }
-               else {
-                       this._button.enable();
-               }
-       },
-       
-       /**
-        * Redirects to given page No.
-        */
-       _submit: function() {
-               var $pageNavigation = this._elements[this._elementID];
-               if ($pageNavigation.data('callback') === null) {
-                       var $redirectURL = $pageNavigation.data('link').replace(/pageNo=%d/, 'pageNo=' + this._pageNo.val());
-                       window.location = $redirectURL;
-               }
-               else {
-                       $pageNavigation.data('callback')(this._pageNo.val());
-                       this._dialog.wcfDialog('close');
-               }
        }
 };
 
@@ -8203,32 +8075,44 @@ jQuery.fn.extend({
 
 /**
  * jQuery widget implementation of the wcf pagination.
+ * 
+ * @deprecated 2.2 - use `WoltLab/WCF/Ui/Pagination` instead
  */
 $.widget('ui.wcfPages', {
+       _api: null,
+       
        SHOW_LINKS: 11,
        SHOW_SUB_LINKS: 20,
        
        options: {
                // vars
                activePage: 1,
-               maxPage: 1,
-               
-               // language
-               // we use options here instead of language variables, because the paginator is not only usable with pages
-               nextPage: null,
-               previousPage: null
+               maxPage: 1
        },
        
        /**
         * Creates the pages widget.
         */
        _create: function() {
-               if (this.options.nextPage === null) this.options.nextPage = WCF.Language.get('wcf.global.page.next');
-               if (this.options.previousPage === null) this.options.previousPage = WCF.Language.get('wcf.global.page.previous');
-               
-               this.element.addClass('pageNavigation');
-               
-               this._render();
+               require(['WoltLab/WCF/Ui/Pagination'], (function(UiPagination) {
+                       this._api = new UiPagination(this.element, {
+                               activePage: this.options.activePage,
+                               maxPage: this.options.maxPage,
+                               
+                               callbackShouldSwitch: (function(pageNo) {
+                                       var result = this._trigger('shouldSwitch', undefined, {
+                                               nextPage: pageNo
+                                       });
+                                       
+                                       return (result !== false);
+                               }).bind(this),
+                               callbackSwitch: (function(pageNo) {
+                                       this._trigger('switched', undefined, {
+                                               activePage: pageNo
+                                       });
+                               }).bind(this)
+                       });
+               }).bind(this));
        },
        
        /**
@@ -8237,193 +8121,8 @@ $.widget('ui.wcfPages', {
        destroy: function() {
                $.Widget.prototype.destroy.apply(this, arguments);
                
-               this.element.children().remove();
-       },
-       
-       /**
-        * Renders the pages widget.
-        */
-       _render: function() {
-               // only render if we have more than 1 page
-               if (!this.options.disabled && this.options.maxPage > 1) {
-                       var $hasHiddenPages = false;
-                       
-                       // make sure pagination is visible
-                       if (this.element.hasClass('hidden')) {
-                               this.element.removeClass('hidden');
-                       }
-                       this.element.show();
-                       
-                       this.element.children().remove();
-                       
-                       var $pageList = $('<ul />');
-                       this.element.append($pageList);
-                       
-                       var $previousElement = $('<li class="button skip" />');
-                       $pageList.append($previousElement);
-                       
-                       if (this.options.activePage > 1) {
-                               var $previousLink = $('<a' + ((this.options.previousPage != null) ? (' title="' + this.options.previousPage + '"') : ('')) + '></a>');
-                               $previousElement.append($previousLink);
-                               this._bindSwitchPage($previousLink, this.options.activePage - 1);
-                               
-                               var $previousImage = $('<span class="icon icon16 icon-double-angle-left" />');
-                               $previousLink.append($previousImage);
-                       }
-                       else {
-                               var $previousImage = $('<span class="icon icon16 icon-double-angle-left" />');
-                               $previousElement.append($previousImage);
-                               $previousElement.addClass('disabled').removeClass('button');
-                               $previousImage.addClass('disabled');
-                       }
-                       
-                       // add first page
-                       $pageList.append(this._renderLink(1));
-                       
-                       // calculate page links
-                       var $maxLinks = this.SHOW_LINKS - 4;
-                       var $linksBefore = this.options.activePage - 2;
-                       if ($linksBefore < 0) $linksBefore = 0;
-                       var $linksAfter = this.options.maxPage - (this.options.activePage + 1);
-                       if ($linksAfter < 0) $linksAfter = 0;
-                       if (this.options.activePage > 1 && this.options.activePage < this.options.maxPage) $maxLinks--;
-                       
-                       var $half = $maxLinks / 2;
-                       var $left = this.options.activePage;
-                       var $right = this.options.activePage;
-                       if ($left < 1) $left = 1;
-                       if ($right < 1) $right = 1;
-                       if ($right > this.options.maxPage - 1) $right = this.options.maxPage - 1;
-                       
-                       if ($linksBefore >= $half) {
-                               $left -= $half;
-                       }
-                       else {
-                               $left -= $linksBefore;
-                               $right += $half - $linksBefore;
-                       }
-                       
-                       if ($linksAfter >= $half) {
-                               $right += $half;
-                       }
-                       else {
-                               $right += $linksAfter;
-                               $left -= $half - $linksAfter;
-                       }
-                       
-                       $right = Math.ceil($right);
-                       $left = Math.ceil($left);
-                       if ($left < 1) $left = 1;
-                       if ($right > this.options.maxPage) $right = this.options.maxPage;
-                       
-                       // left ... links
-                       if ($left > 1) {
-                               if ($left - 1 < 2) {
-                                       $pageList.append(this._renderLink(2));
-                               }
-                               else {
-                                       $('<li class="button jumpTo"><a title="' + WCF.Language.get('wcf.global.page.jumpTo') + '" class="jsTooltip">...</a></li>').appendTo($pageList);
-                                       $hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // visible links
-                       for (var $i = $left + 1; $i < $right; $i++) {
-                               $pageList.append(this._renderLink($i));
-                       }
-                       
-                       // right ... links
-                       if ($right < this.options.maxPage) {
-                               if (this.options.maxPage - $right < 2) {
-                                       $pageList.append(this._renderLink(this.options.maxPage - 1));
-                               }
-                               else {
-                                       $('<li class="button jumpTo"><a title="' + WCF.Language.get('wcf.global.page.jumpTo') + '" class="jsTooltip">...</a></li>').appendTo($pageList);
-                                       $hasHiddenPages = true;
-                               }
-                       }
-                       
-                       // add last page
-                       $pageList.append(this._renderLink(this.options.maxPage));
-                       
-                       // add next button
-                       var $nextElement = $('<li class="button skip" />');
-                       $pageList.append($nextElement);
-                       
-                       if (this.options.activePage < this.options.maxPage) {
-                               var $nextLink = $('<a' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '></a>');
-                               $nextElement.append($nextLink);
-                               this._bindSwitchPage($nextLink, this.options.activePage + 1);
-                               
-                               var $nextImage = $('<span class="icon icon16 icon-double-angle-right" />');
-                               $nextLink.append($nextImage);
-                       }
-                       else {
-                               var $nextImage = $('<span class="icon icon16 icon-double-angle-right" />');
-                               $nextElement.append($nextImage);
-                               $nextElement.addClass('disabled').removeClass('button');
-                               $nextImage.addClass('disabled');
-                       }
-                       
-                       if ($hasHiddenPages) {
-                               $pageList.data('pages', this.options.maxPage);
-                               WCF.System.PageNavigation.init('#' + $pageList.wcfIdentify(), $.proxy(function(pageNo) {
-                                       this.switchPage(pageNo);
-                               }, this));
-                       }
-               }
-               else {
-                       // otherwise hide the paginator if not already hidden
-                       this.element.hide();
-               }
-       },
-       
-       /**
-        * Renders a page link.
-        * 
-        * @parameter   integer         page
-        * @return      jQuery
-        */
-       _renderLink: function(page, lineBreak) {
-               var $pageElement = $('<li class="button"></li>');
-               if (lineBreak != undefined && lineBreak) {
-                       $pageElement.addClass('break');
-               }
-               if (page != this.options.activePage) {
-                       var $pageLink = $('<a>' + WCF.String.addThousandsSeparator(page) + '</a>');
-                       $pageElement.append($pageLink);
-                       this._bindSwitchPage($pageLink, page);
-               }
-               else {
-                       $pageElement.addClass('active');
-                       var $pageSubElement = $('<span>' + WCF.String.addThousandsSeparator(page) + '</span><span class="invisible">' + WCF.Language.get('wcf.page.pagePosition', { pageNo: page, pages: this.options.maxPage }) + '</span>');
-                       $pageElement.append($pageSubElement);
-               }
-               
-               return $pageElement;
-       },
-       
-       /**
-        * Binds the 'click'-event for the page switching to the given element.
-        * 
-        * @parameter   $(element)      element
-        * @paremeter   integer         page
-        */
-       _bindSwitchPage: function(element, page) {
-               var $self = this;
-               element.click(function() {
-                       $self.switchPage(page);
-               });
-       },
-       
-       /**
-        * Switches to the given page
-        * 
-        * @parameter   Event           event
-        * @parameter   integer         page
-        */
-       switchPage: function(page) {
-               this._setOption('activePage', page);
+               this._api = null;
+               this.element[0].innerHTML = '';
        },
        
        /**
@@ -8440,11 +8139,8 @@ $.widget('ui.wcfPages', {
                                });
                                
                                if ($result || $result !== undefined) {
-                                       this.options[key] = value;
-                                       this._render();
-                                       this._trigger('switched', undefined, {
-                                               activePage: value
-                                       });
+                                       this._api.switchPage(value);
+                                       
                                }
                                else {
                                        this._trigger('notSwitched', undefined, {
@@ -8453,81 +8149,8 @@ $.widget('ui.wcfPages', {
                                }
                        }
                }
-               else {
-                       this.options[key] = value;
-                       
-                       if (key == 'disabled') {
-                               if (value) {
-                                       this.element.children().remove();
-                               }
-                               else {
-                                       this._render();
-                               }
-                       }
-                       else if (key == 'maxPage') {
-                               this._render();
-                       }
-               }
                
                return this;
-       },
-       
-       /**
-        * Start input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _startInput: function(event) {
-               // hide a-tag
-               var $childLink = $(event.currentTarget);
-               if (!$childLink.is('a')) $childLink = $childLink.parent('a');
-               
-               $childLink.hide();
-               
-               // show input-tag
-               var $childInput = $childLink.parent('li').children('input')
-                       .css('display', 'block')
-                       .val('');
-               
-               $childInput.focus();
-       },
-       
-       /**
-        * Stops input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _stopInput: function(event) {
-               // hide input-tag
-               var $childInput = $(event.currentTarget);
-               $childInput.css('display', 'none');
-               
-               // show a-tag
-               var $childContainer = $childInput.parent('li');
-               if ($childContainer != undefined && $childContainer != null) {
-                       $childContainer.children('a').show();
-               }
-       },
-       
-       /**
-        * Handles input of pagenumber
-        * 
-        * @parameter   Event           event
-        */
-       _handleInput: function(event) {
-               var $ie7 = ($.browser.msie && $.browser.version == '7.0');
-               if (event.type != 'keyup' || $ie7) {
-                       if (!$ie7 || ((event.which == 13 || event.which == 27) && event.type == 'keyup')) {
-                               if (event.which == 13) {
-                                       this.switchPage(parseInt($(event.currentTarget).val()));
-                               }
-                               
-                               if (event.which == 13 || event.which == 27) {
-                                       this._stopInput(event);
-                                       event.stopPropagation();
-                               }
-                       }
-               }
        }
 });
 
index db2821e5dba48d6ce997c7cf799d6cc806aa88b0..24ec030795e3a16ffe9f5c15dcb91452ae38b4ec 100644 (file)
@@ -327,6 +327,10 @@ define(
                        
                        DomUtil.prepend(dialog, _container);
                        
+                       if (typeof options.onSetup === 'function') {
+                               options.onSetup(content);
+                       }
+                       
                        if (createOnly !== true) {
                                this._updateDialog(id, null);
                        }
index 1aa90ae320d9250a45545bf9c3ffe2063427f0a9..311ba61a77a51cf83e6345dfdb320f182b3064f7 100644 (file)
@@ -1,10 +1,38 @@
-define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Dom/ChangeListener', 'Dom/Util', 'Ui/Dialog'], function(Ajax, Core, Dictionary, Language, ObjectMap, StringUtil, DomChangeListener, DomUtil, UiDialog) {
+/**
+ * Provides interface elements to display and review likes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Like/Handler
+ */
+define(
+       [
+               'Ajax',      'Core',                     'Dictionary',         'Language',
+               'ObjectMap', 'StringUtil',               'Dom/ChangeListener', 'Dom/Util',
+               'Ui/Dialog', 'WoltLab/WCF/Ui/User/List'
+       ],
+       function(
+               Ajax,        Core,                        Dictionary,           Language,
+               ObjectMap,   StringUtil,                  DomChangeListener,    DomUtil,
+               UiDialog,    UiUserList
+       )
+{
        "use strict";
        
        var _isBusy = false;
        
+       /**
+        * @constructor
+        */
        function UiLikeHandler(objectType, options) { this.init(objectType, options); };
        UiLikeHandler.prototype = {
+               /**
+                * Initializes the like handler.
+                * 
+                * @param       {string}        objectType      object type
+                * @param       {object}        options         initilization options
+                */
                init: function(objectType, options) {
                        if (options.containerSelector === '') {
                                throw new Error("[WoltLab/WCF/Ui/Like/Handler] Expected a non-emtpy string for option 'containerSelector'.");
@@ -32,6 +60,9 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        this.initContainers(options, objectType);
                },
                
+               /**
+                * Initializes all applicable containers.
+                */
                initContainers: function() {
                        var element, elements = elBySelAll(this._options.containerSelector), elementData, triggerChange = false;
                        for (var i = 0, length = elements.length; i < length; i++) {
@@ -64,6 +95,12 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        }
                },
                
+               /**
+                * Creates the interface elements.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {object}        elementData     like data
+                */
                _buildWidget: function(element, elementData) {
                        // build summary
                        if (this._options.canViewSummary) {
@@ -114,6 +151,14 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        }
                },
                
+               /**
+                * Creates a like or dislike button.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {Element}       insertBefore    insert button before given element
+                * @param       {boolean}       isLike          false if this is a dislike button
+                * @return      {Element}       button element 
+                */
                _createButton: function(element, insertBefore, isLike) {
                        var title = Language.get('wcf.like.button.' + (isLike ? 'like' : 'dislike'));
                        
@@ -134,16 +179,25 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        return button;
                },
                
+               /**
+                * Shows the summary of likes/dislikes.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {object}        event           event object
+                */
                _showSummary: function(element, event) {
                        event.preventDefault();
                        
                        if (!this._details.has(element)) {
-                               // @TODO
-                               this._details.set(element, new WCF.User.List('wcf\\data\\like\\LikeAction', Language.get('wcf.like.details'), {
-                                       data: {
-                                               containerID: DomUtil.identify(element),
-                                               objectID: this._containers.get(element).objectId,
-                                               objectType: this._objectType
+                               this._details.set(element, new UiUserList({
+                                       className: 'wcf\\data\\like\\LikeAction',
+                                       dialogTitle: Language.get('wcf.like.details'),
+                                       parameters: {
+                                               data: {
+                                                       containerID: DomUtil.identify(element),
+                                                       objectID: this._containers.get(element).objectId,
+                                                       objectType: this._objectType
+                                               }
                                        }
                                }));
                        }
@@ -151,6 +205,11 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        this._details.get(element).open();
                },
                
+               /**
+                * Updates the display of cumulative likes.
+                * 
+                * @param       {Element}       element         container element
+                */
                _updateBadge: function(element) {
                        var data = this._containers.get(element);
                        
@@ -183,6 +242,11 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        }
                },
                
+               /**
+                * Updates the like summary.
+                * 
+                * @param       {Element}       element         container element
+                */
                _updateSummary: function(element) {
                        var data = this._containers.get(element);
                        
@@ -203,6 +267,11 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        }
                },
                
+               /**
+                * Updates the active like/dislike button state.
+                * 
+                * @param       {Element}       element         container element
+                */
                _updateActiveState: function(element) {
                        var data = this._containers.get(element);
                        
@@ -217,6 +286,12 @@ define(['Ajax', 'Core', 'Dictionary', 'Language', 'ObjectMap', 'StringUtil', 'Do
                        }
                },
                
+               /**
+                * Likes or dislikes an element.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {object}        event           event object
+                */
                _like: function(element, event) {
                        event.preventDefault();
                        
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpTo.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpTo.js
new file mode 100644 (file)
index 0000000..2c86acc
--- /dev/null
@@ -0,0 +1,141 @@
+/**
+ * Utility class to provide a 'Jump To' overlay. 
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Page/JumpTo
+ */
+define(['Language', 'ObjectMap', 'Ui/Dialog'], function(Language, ObjectMap, UiDialog) {
+       "use strict";
+       
+       var _activeElement = null;
+       var _buttonSubmit = null;
+       var _description = null;
+       var _elements = new ObjectMap();
+       var _input = null;
+       
+       /**
+        * @exports     WoltLab/WCF/Ui/Page/JumpTo
+        */
+       var UiPageJumpTo = {
+               /**
+                * Initializes a 'Jump To' element.
+                * 
+                * @param       {Element}       element         trigger element
+                * @param       {function}      callback        callback function, receives the page number as first argument
+                */
+               init: function(element, callback) {
+                       callback = callback || null;
+                       if (callback === null) {
+                               var redirectUrl = elAttr(element, 'data-link');
+                               if (redirectUrl) {
+                                       callback = function(pageNo) {
+                                               window.location = redirectUrl.replace(/pageNo=%d/, 'pageNo=' + pageNo);
+                                       };
+                               }
+                               else {
+                                       callback = function() {};
+                               }
+                               
+                       }
+                       else if (typeof callback !== 'function') {
+                               throw new TypeError("Expected a valid function for parameter 'callback'.");
+                       }
+                       
+                       if (!_elements.has(element)) {
+                               var jumpTo = elBySel('.jumpTo', element);
+                               if (jumpTo !== null) {
+                                       jumpTo.addEventListener('click', this._click.bind(this, element));
+                                       
+                                       _elements.set(element, { callback: callback });
+                               }
+                       }
+               },
+               
+               /**
+                * Handles clicks on the trigger element.
+                * 
+                * @param       {Element}       element         trigger element
+                * @param       {object}        event           event object
+                */
+               _click: function(element, event) {
+                       _activeElement = element;
+                       
+                       if (typeof event === 'object') {
+                               event.preventDefault();
+                       }
+                       
+                       UiDialog.open(this);
+                       
+                       var pages = elAttr(element, 'data-pages');
+                       _input.value = pages;
+                       _input.setAttribute('max', pages);
+                       
+                       _description.textContent = Language.get('wcf.global.page.jumpTo.description').replace(/#pages#/, pages);
+               },
+               
+               /**
+                * Handles changes to the page number input field.
+                * 
+                * @param       {object}        event           event object
+                */
+               _keyUp: function(event) {
+                       if (event.which === 13 && _buttonSubmit.disabled === false) {
+                               this._submit();
+                               return;
+                       }
+                       
+                       var pageNo = ~~_input.value;
+                       if (pageNo < 1 || pageNo > ~~elAttr(_input, 'max')) {
+                               _buttonSubmit.disabled = true;
+                       }
+                       else {
+                               _buttonSubmit.disabled = false;
+                       }
+               },
+               
+               /**
+                * Invokes the callback with the chosen page number as first argument.
+                * 
+                * @param       {object}        event           event object
+                */
+               _submit: function(event) {
+                       _elements.get(_activeElement).callback(~~_input.value);
+                       
+                       UiDialog.close(this);
+               },
+               
+               _dialogSetup: function() {
+                       var source = '<dl>'
+                                       + '<dt><label for="jsPageNavigationPageNo">' + Language.get('wcf.global.page.jumpTo') + '</label></dt>'
+                                       + '<dd>'
+                                               + '<input type="number" id="jsPageNavigationPageNo" value="1" min="1" max="1" class="tiny">'
+                                               + '<small></small>'
+                                       + '</dd>'
+                               + '</dl>'
+                               + '<div class="formSubmit">'
+                                       + '<button class="buttonPrimary">' + Language.get('wcf.global.button.submit') + '</button>'
+                               + '</div>';
+                       
+                       return {
+                               id: 'pageNavigationOverlay',
+                               options: {
+                                       onSetup: (function(content) {
+                                               _input = elByTag('input', content)[0];
+                                               _input.addEventListener('keyup', this._keyUp.bind(this));
+                                               
+                                               _description = elByTag('small', content)[0];
+                                               
+                                               _buttonSubmit = elByTag('button', content)[0];
+                                               _buttonSubmit.addEventListener('click', this._submit.bind(this));
+                                       }).bind(this),
+                                       title: Language.get('wcf.global.page.pageNavigation')
+                               },
+                               source: source
+                       };
+               }
+       };
+       
+       return UiPageJumpTo;
+});
\ No newline at end of file
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Pagination.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Pagination.js
new file mode 100644 (file)
index 0000000..9f07975
--- /dev/null
@@ -0,0 +1,238 @@
+/**
+ * Callback-based pagination.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Pagination
+ */
+define(['Core', 'Language', 'ObjectMap', 'StringUtil', 'WoltLab/WCF/Ui/Page/JumpTo'], function(Core, Language, ObjectMap, StringUtil, UiPageJumpTo) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function UiPagination(element, options) { this.init(element, options); };
+       UiPagination.prototype = {
+               /**
+                * maximum number of displayed page links, should match the PHP implementation
+                * @var {integer}
+                */
+               SHOW_LINKS: 11,
+               
+               /**
+                * Initializes the pagination.
+                * 
+                * @param       {Element}       element         container element
+                * @param       {object}        options         list of initilization options
+                */
+               init: function(element, options) {
+                       this._element = element;
+                       this._options = Core.extend({
+                               activePage: 1,
+                               maxPage: 1,
+                               
+                               callbackShouldSwitch: null,
+                               callbackSwitch: null
+                       }, options);
+                       
+                       if (typeof this._options.callbackShouldSwitch !== 'function') this._options.callbackShouldSwitch = null;
+                       if (typeof this._options.callbackSwitch !== 'function') this._options.callbackSwitch = null;
+                       
+                       this._element.classList.add('pageNavigation');
+                       this._element.classList.add('small');
+                       
+                       this._rebuild(this._element);
+               },
+               
+               /**
+                * Rebuilds the entire pagination UI.
+                */
+               _rebuild: function() {
+                       var hasHiddenPages = false;
+                       
+                       // clear content
+                       this._element.innerHTML = '';
+                       
+                       var list = elCreate('ul'), link;
+                       
+                       var listItem = elCreate('li');
+                       listItem.className = 'skip';
+                       list.appendChild(listItem);
+                       
+                       var iconClassNames = 'icon icon16 fa-angle-double-left';
+                       if (this._options.activePage > 1) {
+                               link = elCreate('a');
+                               link.className = iconClassNames + ' jsTooltip';
+                               link.href = '#';
+                               link.title = Language.get('wcf.global.page.previous');
+                               listItem.appendChild(link);
+                               
+                               link.addEventListener('click', this.switchPage.bind(this, this._options.activePage - 1));
+                       }
+                       else {
+                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
+                               listItem.classList.add('disabled');
+                       }
+                       
+                       // add first page
+                       list.appendChild(this._createLink(1, this._options));
+                       
+                       // calculate page links
+                       var maxLinks = this.SHOW_LINKS - 4;
+                       var linksBefore = this._options.activePage - 2;
+                       if (linksBefore < 0) linksBefore = 0;
+                       var linksAfter = this._options.maxPage - (this._options.activePage + 1);
+                       if (linksAfter < 0) linksAfter = 0;
+                       if (this._options.activePage > 1 && this._options.activePage < this._options.maxPage) maxLinks--;
+                       
+                       var half = maxLinks / 2;
+                       var left = this._options.activePage;
+                       var right = this._options.activePage;
+                       if (left < 1) left = 1;
+                       if (right < 1) right = 1;
+                       if (right > this._options.maxPage - 1) right = this._options.maxPage - 1;
+                       
+                       if (linksBefore >= half) {
+                               left -= half;
+                       }
+                       else {
+                               left -= linksBefore;
+                               right += half - linksBefore;
+                       }
+                       
+                       if (linksAfter >= half) {
+                               right += half;
+                       }
+                       else {
+                               right += linksAfter;
+                               left -= half - linksAfter;
+                       }
+                       
+                       right = Math.ceil(right);
+                       left = Math.ceil(left);
+                       if (left < 1) left = 1;
+                       if (right > this._options.maxPage) right = this._options.maxPage;
+                       
+                       // left ... links
+                       var jumpToHtml = '<a class="jsTooltip" title="' + Language.get('wcf.global.page.jumpTo') + '">&hellip;</a>';
+                       if (left > 1) {
+                               if (left - 1 < 2) {
+                                       list.appendChild(this._createLink(2, this._options));
+                               }
+                               else {
+                                       var listItem = elCreate('li');
+                                       listItem.className = 'jumpTo';
+                                       listItem.innerHTML = jumpToHtml;
+                                       list.appendChild(listItem);
+                                       
+                                       hasHiddenPages = true;
+                               }
+                       }
+                       
+                       // visible links
+                       for (var i = left + 1; i < right; i++) {
+                               list.appendChild(this._createLink(i, this._options));
+                       }
+                       
+                       // right ... links
+                       if (right < this._options.maxPage) {
+                               if (this._options.maxPage - right < 2) {
+                                       list.appendChild(this._createLink(this._options.maxPage - 1, this._options));
+                               }
+                               else {
+                                       var listItem = elCreate('li');
+                                       listItem.className = 'jumpTo';
+                                       listItem.innerHTML = jumpToHtml;
+                                       list.appendChild(listItem);
+                                       
+                                       hasHiddenPages = true;
+                               }
+                       }
+                       
+                       // add last page
+                       list.appendChild(this._createLink(this._options.maxPage, this._options));
+                       
+                       // add next button
+                       listItem = elCreate('li');
+                       listItem.className = 'skip';
+                       list.appendChild(listItem);
+                       
+                       iconClassNames = 'icon icon16 fa-angle-double-right';
+                       if (this._options.activePage < this._options.maxPage) {
+                               link = elCreate('a');
+                               link.className = iconClassNames + ' jsTooltip';
+                               link.href = '#';
+                               link.title = Language.get('wcf.global.page.next');
+                               listItem.appendChild(link);
+                               
+                               link.addEventListener('click', this.switchPage.bind(this, this._options.activePage + 1));
+                       }
+                       else {
+                               listItem.innerHTML = '<span class="' + iconClassNames + '"></span>';
+                               listItem.classList.add('disabled');
+                       }
+                       
+                       if (hasHiddenPages) {
+                               list.setAttribute('data-pages', this._options.maxPage);
+                               
+                               UiPageJumpTo.init(list, this.switchPage.bind(this));
+                       }
+                       
+                       this._element.appendChild(list);
+               },
+               
+               /**
+                * Creates a link to a specific page.
+                * 
+                * @param       {integer}       pageNo          page number
+                * @return      {Element}       link element
+                */
+               _createLink: function(pageNo) {
+                       var listItem = elCreate('li');
+                       if (pageNo !== this._options.activePage) {
+                               var link = elCreate('a');
+                               link.textContent = StringUtil.addThousandsSeparator(pageNo);
+                               link.addEventListener('click', this.switchPage.bind(this, pageNo));
+                               listItem.appendChild(link);
+                       }
+                       else {
+                               listItem.classList.add('active');
+                               listItem.innerHTML = '<span>' + StringUtil.addThousandsSeparator(pageNo) + '</span><span class="invisible">' + Language.get('wcf.page.pagePosition', { pageNo: pageNo, pages: this._options.maxPage }) + '</span>';
+                       }
+                       
+                       return listItem;
+               },
+               
+               /**
+                * Switches to given page number.
+                * 
+                * @param       {integer}       pageNo          page number
+                * @param       {object}        event           event object
+                */
+               switchPage: function(pageNo, event) {
+                       if (typeof event === 'object') {
+                               event.preventDefault();
+                       }
+                       
+                       pageNo = ~~pageNo;
+                       
+                       if (pageNo > 0 && this._options.activePage !== pageNo && pageNo <= this._options.maxPage) {
+                               if (this._options.callbackShouldSwitch !== null) {
+                                       if (this._options.callbackShouldSwitch(pageNo) !== true) {
+                                               return;
+                                       }
+                               }
+                               
+                               this._options.activePage = pageNo;
+                               this._rebuild();
+                               
+                               if (this._options.callbackSwitch !== null) {
+                                       this._options.callbackSwitch(pageNo);
+                               }
+                       }
+               }
+       };
+       
+       return UiPagination;
+});
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/User/List.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/User/List.js
new file mode 100644 (file)
index 0000000..10f7c49
--- /dev/null
@@ -0,0 +1,111 @@
+/**
+ * Object-based user list.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/User/List
+ */
+define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'Ui/Dialog', 'WoltLab/WCF/Ui/Pagination'], function(Ajax, Core, Dictionary, DomUtil, UiDialog, UiPagination) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function UiUserList(options) { this.init(options); };
+       UiUserList.prototype = {
+               /**
+                * Initializes the user list.
+                * 
+                * @param       {object}        options         list of initialization options
+                */
+               init: function(options) {
+                       this._cache = new Dictionary();
+                       this._pageCount = 0;
+                       this._pageNo = 1;
+                       
+                       this._options = Core.extend({
+                               className: '',
+                               dialogTitle: '',
+                               parameters: {}
+                       }, options);
+               },
+               
+               /**
+                * Opens the user list.
+                */
+               open: function() {
+                       this._pageNo = 1;
+                       this._showPage();
+               },
+               
+               /**
+                * Shows the current or given page.
+                * 
+                * @param       {integer=}      pageNo          page number
+                */
+               _showPage: function(pageNo) {
+                       if (typeof pageNo === 'number') {
+                               this._pageNo = ~~pageNo;
+                       }
+                       
+                       if (this._pageCount !== 0 && (this._pageNo < 1 || this._pageNo > this._pageCount)) {
+                               throw new RangeError("pageNo must be between 1 and " + this._pageCount + " (" + this._pageNo + " given).");
+                       }
+                       
+                       if (this._cache.has(this._pageNo)) {
+                               var dialog = UiDialog.open(this, this._cache.get(this._pageNo));
+                               
+                               if (this._pageCount > 1) {
+                                       var element = elBySel('.jsPagination', dialog.content);
+                                       if (element !== null) {
+                                               new UiPagination(element, {
+                                                       activePage: this._pageNo,
+                                                       maxPage: this._pageCount,
+                                                       
+                                                       callbackSwitch: this._showPage.bind(this)
+                                               });
+                                       }
+                               }
+                       }
+                       else {
+                               this._options.parameters.pageNo = this._pageNo;
+                               
+                               Ajax.api(this, {
+                                       parameters: this._options.parameters
+                               });
+                       }
+               },
+               
+               _ajaxSuccess: function(data) {
+                       if (data.returnValues.pageCount !== undefined) {
+                               this._pageCount = ~~data.returnValues.pageCount;
+                       }
+                       
+                       this._cache.set(this._pageNo, data.returnValues.template);
+                       this._showPage();
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: {
+                                       actionName: 'getGroupedUserList',
+                                       className: this._options.className,
+                                       interfaceName: 'wcf\\data\\IGroupedUserListAction'
+                               }
+                       };
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: DomUtil.getUniqueId(),
+                               options: {
+                                       title: this._options.dialogTitle
+                               },
+                               source: null
+                       };
+               }
+       };
+       
+       return UiUserList;
+});