Improved flexibility of user item lists
authorMarcel Werk <burntime@woltlab.com>
Tue, 1 Jan 2019 18:03:09 +0000 (19:03 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 1 Jan 2019 18:03:09 +0000 (19:03 +0100)
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/ItemList.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/ItemList/User.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Suggestion.js
wcfsetup/install/files/lib/data/user/UserAction.class.php

index 9e0b6e7a6e4d0c3ce4cf8dd10abc9687bc339c67..406fc01c0e6fb894c4102d7be49aa220f26fe171 100644 (file)
@@ -81,6 +81,10 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                                callbackChange: null,
                                // callback once the form is about to be submitted
                                callbackSubmit: null,
+                               // Callback for the custom shadow synchronization.
+                               callbackSyncShadow: null,
+                               // Callback to set values during the setup.
+                               callbackSetupValues: null,
                                // value may contain the placeholder `{$objectId}`
                                submitFieldName: ''
                        }, options);
@@ -132,7 +136,13 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                                suggestion: suggestion
                        });
                        
-                       values = (data.values.length) ? data.values : values;
+                       if (options.callbackSetupValues) {
+                               values = options.callbackSetupValues();
+                       }
+                       else {
+                               values = (data.values.length) ? data.values : values;
+                       }
+                       
                        if (Array.isArray(values)) {
                                var value;
                                for (var i = 0, length = values.length; i < length; i++) {
@@ -162,7 +172,8 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                        elBySelAll('.item > span', data.list, function(span) {
                                values.push({
                                        objectId: ~~elData(span, 'object-id'),
-                                       value: span.textContent
+                                       value: span.textContent,
+                                       type: elData(span, 'type')
                                });
                        });
                        
@@ -239,10 +250,20 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                        element.addEventListener('keypress', _callbackKeyPress);
                        element.addEventListener('keyup', _callbackKeyUp);
                        element.addEventListener('paste', _callbackPaste);
+                       var hasFocus = element === document.activeElement;
+                       if (hasFocus) {
+                               //noinspection JSUnresolvedFunction
+                               element.blur();
+                       }
                        element.addEventListener('blur', _callbackBlur);
-                       
                        element.parentNode.insertBefore(list, element);
                        listItem.appendChild(element);
+                       if (hasFocus) {
+                               window.setTimeout(function() {
+                                       //noinspection JSUnresolvedFunction
+                                       element.focus();
+                               }, 1);
+                       }
                        
                        if (options.maxLength !== -1) {
                                elAttr(element, 'maxLength', options.maxLength);
@@ -441,6 +462,7 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                        var content = elCreate('span');
                        content.className = 'content';
                        elData(content, 'object-id', value.objectId);
+                       if (value.type) elData(content, 'type', value.type);
                        content.textContent = value.value;
                        
                        var button = elCreate('a');
@@ -497,6 +519,9 @@ define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSu
                 */
                _syncShadow: function(data) {
                        if (!data.options.isCSV) return null;
+                       if (typeof data.options.callbackSyncShadow === 'function') {
+                               return data.options.callbackSyncShadow(data);
+                       }
                        
                        var value = '', values = this.getValues(data.element.id);
                        for (var i = 0, length = values.length; i < length; i++) {
index c3a40380e81bf309970e09d9aa8dccf67623d1b4..3327b04fd2a85bcd1d6be482617875e6e9e237f6 100644 (file)
@@ -22,6 +22,8 @@ define(['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
         * @exports     WoltLabSuite/Core/Ui/ItemList/User
         */
        return {
+               _shadowGroups: null,
+               
                /**
                 * Initializes user suggestion support for an element.
                 * 
@@ -29,16 +31,21 @@ define(['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
                 * @param       {object}        options         option list
                 */
                init: function(elementId, options) {
+                       this._shadowGroups = null;
+                       
                        UiItemList.init(elementId, [], {
                                ajax: {
                                        className: 'wcf\\data\\user\\UserAction',
                                        parameters: {
                                                data: {
-                                                       includeUserGroups: ~~options.includeUserGroups
+                                                       includeUserGroups: ~~options.includeUserGroups,
+                                                       restrictUserGroupIDs: (Array.isArray(options.restrictUserGroupIDs) ? options.restrictUserGroupIDs : [])
                                                }
                                        }
                                },
                                callbackChange: (typeof options.callbackChange === 'function' ? options.callbackChange : null),
+                               callbackSyncShadow: options.csvPerType ? this._syncShadow.bind(this) : null,
+                               callbackSetupValues: (typeof options.callbackSetupValues === 'function' ? options.callbackSetupValues : null),
                                excludedSearchValues: (Array.isArray(options.excludedSearchValues) ? options.excludedSearchValues : []),
                                isCSV: true,
                                maxItems: ~~options.maxItems || -1,
@@ -51,6 +58,27 @@ define(['WoltLabSuite/Core/Ui/ItemList'], function(UiItemList) {
                 */
                getValues: function(elementId) {
                        return UiItemList.getValues(elementId);
+               },
+               
+               _syncShadow: function(data) {
+                       var values = this.getValues(data.element.id);
+                       var users = [], groups = [];
+                       
+                       values.forEach(function(value) {
+                               if (value.type && value.type === 'group') groups.push(value.objectId);
+                               else users.push(value.value);
+                       });
+                       
+                       data.shadow.value = users.join(',');
+                       if (!this._shadowGroups) {
+                               this._shadowGroups = elCreate('input');
+                               this._shadowGroups.type = 'hidden';
+                               this._shadowGroups.name = data.shadow.name + 'GroupIDs';
+                               data.shadow.parentNode.insertBefore(this._shadowGroups, data.shadow);
+                       }
+                       this._shadowGroups.value = groups.join(',');
+                       
+                       return values;
                }
        };
 });
index 037fc06f2b73c2aae79c4dc6aa4ca81eda266c15..8deb248bf90fb69b0d9b508e13971cbe61b009b8 100644 (file)
@@ -162,7 +162,8 @@ define(['Ajax', 'Core', 'Ui/SimpleDropdown'], function(Ajax, Core, UiSimpleDropd
                                item = item.currentTarget.parentNode;
                        }
                        
-                       this._options.callbackSelect(this._element.id, { objectId: elData(item.children[0], 'object-id'), value: item.textContent });
+                       var anchor = item.children[0];
+                       this._options.callbackSelect(this._element.id, { objectId: elData(anchor, 'object-id'), value: item.textContent, type: elData(anchor, 'type') });
                        
                        if (isEvent) {
                                this._element.focus();
@@ -239,6 +240,7 @@ define(['Ajax', 'Core', 'Ui/SimpleDropdown'], function(Ajax, Core, UiSimpleDropd
                                                anchor.textContent = item.label;
                                        }
                                        elData(anchor, 'object-id', item.objectID);
+                                       if (item.type) elData(anchor, 'type', item.type);
                                        anchor.addEventListener(WCF_CLICK_EVENT, this._select.bind(this));
                                        
                                        listItem = elCreate('li');
index 0d7308022993613a6f1c516d058f47e7f7dcbb3f..24b50335cab42b3c272addd8b92dced8492ed01c 100644 (file)
@@ -478,6 +478,7 @@ class UserAction extends AbstractDatabaseObjectAction implements IClipboardActio
        public function validateGetSearchResultList() {
                $this->readBoolean('includeUserGroups', false, 'data');
                $this->readString('searchString', false, 'data');
+               $this->readIntegerArray('restrictUserGroupIDs', false, 'data');
                
                if (isset($this->parameters['data']['excludedSearchValues']) && !is_array($this->parameters['data']['excludedSearchValues'])) {
                        throw new UserInputException('excludedSearchValues');
@@ -498,11 +499,16 @@ class UserAction extends AbstractDatabaseObjectAction implements IClipboardActio
                if ($this->parameters['data']['includeUserGroups']) {
                        $accessibleGroups = UserGroup::getAccessibleGroups();
                        foreach ($accessibleGroups as $group) {
+                               if (!empty($this->parameters['data']['restrictUserGroupIDs']) && !in_array($group->groupID, $this->parameters['data']['restrictUserGroupIDs'])) {
+                                       continue;
+                               }
+                               
                                $groupName = $group->getName();
                                if (!in_array($groupName, $excludedSearchValues)) {
                                        $pos = mb_strripos($groupName, $searchString);
                                        if ($pos !== false && $pos == 0) {
                                                $list[] = [
+                                                       'icon' => '<span class="icon icon16 fa-users"></span>',
                                                        'label' => $groupName,
                                                        'objectID' => $group->groupID,
                                                        'type' => 'group'