Added `UI/ItemList`, `UI/ItemList/User` and `UI/Suggestion`
authorAlexander Ebert <ebert@woltlab.com>
Wed, 24 Jun 2015 17:27:17 +0000 (19:27 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 24 Jun 2015 17:27:17 +0000 (19:27 +0200)
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList/User.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/UI/Suggestion.js [new file with mode: 0644]
wcfsetup/install/files/style/form.less
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 580f9c5fc22556f0b60b1d4c6029f8c214e4b5c6..ef04cb4058f988690169ef3e1ebaca5ae0799975 100644 (file)
@@ -96,6 +96,7 @@ requirejs.config({
                        'wcf.global.form.error.empty': '{lang}wcf.global.form.error.empty{/lang}',
                        'wcf.global.form.error.greaterThan': '{lang __literal=true}wcf.global.form.error.greaterThan{/lang}',
                        'wcf.global.form.error.lessThan': '{lang __literal=true}wcf.global.form.error.lessThan{/lang}',
+                       'wcf.global.form.input.maxItems': '{lang}wcf.global.form.input.maxItems{/lang}',
                        'wcf.global.language.noSelection': '{lang}wcf.global.language.noSelection{/lang}',
                        'wcf.global.loading': '{lang}wcf.global.loading{/lang}',
                        'wcf.global.page.jumpTo': '{lang}wcf.global.page.jumpTo{/lang}',
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList.js
new file mode 100644 (file)
index 0000000..31946d4
--- /dev/null
@@ -0,0 +1,413 @@
+/**
+ * Flexible UI element featuring both a list of items and an input field with suggestion support.
+ * 
+ * @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/ItemList
+ */
+define(['Core', 'Dictionary', 'Language', 'DOM/Traverse', 'WoltLab/WCF/UI/Suggestion'], function(Core, Dictionary, Language, DOMTraverse, UISuggestion) {
+       "use strict";
+       
+       var _activeId = '';
+       var _data = new Dictionary();
+       var _didInit = false;
+       
+       var _callbackKeyDown = null;
+       var _callbackKeyPress = null;
+       var _callbackKeyUp = null;
+       var _callbackRemoveItem = null;
+       
+       /**
+        * @exports     WoltLab/WCF/UI/ItemList
+        */
+       var UIItemList = {
+               /**
+                * Initializes an item list.
+                * 
+                * The `values` argument must be empty or contain a list of strings or object, e.g.
+                * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
+                * 
+                * @param       {string}                elementId       input element id
+                * @param       {array<mixed>}          values          list of existing values
+                * @param       {object<string>}        options         option list
+                */
+               init: function(elementId, values, options) {
+                       var element = document.getElementById(elementId);
+                       if (element === null) {
+                               throw new Error("Expected a valid element id.");
+                       }
+                       
+                       options = Core.extend({
+                               // search parameters for suggestions
+                               ajax: {
+                                       actionName: 'getSearchResultList',
+                                       className: '',
+                                       data: {}
+                               },
+                               
+                               // list of excluded string values, e.g. `['ignore', 'these strings', 'when', 'searching']`
+                               excludedSearchValues: [],
+                               // maximum number of items this list may contain, `-1` for infinite
+                               maxItems: -1,
+                               // maximum length of an item value, `-1` for infinite
+                               maxLength: -1,
+                               // disallow custom values, only values offered by the suggestion dropdown are accepted
+                               restricted: false,
+                               
+                               // initial value will be interpreted as comma separated value and submitted as such
+                               isCSV: false,
+                               
+                               // will be invoked whenever the items change, receives the element id first and list of values second
+                               callbackChange: null,
+                               // callback once the form is about to be submitted
+                               callbackSubmit: null,
+                               // value may contain the placeholder `{$objectId}`
+                               submitFieldName: ''
+                       }, options);
+                       
+                       var form = DOMTraverse.parentByTag(element, 'FORM');
+                       if (form !== null) {
+                               if (options.isCSV === false) {
+                                       if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
+                                               throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
+                                       }
+                                       
+                                       form.addEventListener('submit', (function() {
+                                               var values = this.getValues(elementId);
+                                               if (options.submitFieldName.length) {
+                                                       var input;
+                                                       for (var i = 0, length = values.length; i < length; i++) {
+                                                               input = document.createElement('input');
+                                                               input.type = 'hidden';
+                                                               input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
+                                                               input.value = values[i].value;
+                                                               
+                                                               form.appendChild(input);
+                                                       }
+                                               }
+                                               else {
+                                                       options.callbackSubmit(form, values);
+                                               }
+                                       }).bind(this));
+                               }
+                       }
+                       
+                       this._setup();
+                       
+                       var data = this._createUI(element, options, values);
+                       var suggestion = new UISuggestion(elementId, {
+                               ajax: options.ajax,
+                               callbackSelect: this._addItem.bind(this),
+                               excludedSearchValues: options.excludedSearchValues
+                       });
+                       
+                       _data.set(elementId, {
+                               dropdownMenu: null,
+                               element: data.element,
+                               list: data.list,
+                               listItem: data.element.parentNode,
+                               options: options,
+                               shadow: data.shadow,
+                               suggestion: suggestion
+                       });
+                       
+                       values = (data.values.length) ? data.values : values;
+                       if (Array.isArray(values)) {
+                               var value;
+                               for (var i = 0, length = values.length; i < length; i++) {
+                                       value = values[i];
+                                       if (typeof value === 'string') {
+                                               value = { objectId: 0, value: value };
+                                       }
+                                       
+                                       this._addItem(elementId, value);
+                               }
+                       }
+               },
+               
+               /**
+                * Returns the list of current values.
+                * 
+                * @param       {string}                element id      input element id
+                * @return      {array<object>}         list of objects containing object id and value
+                */
+               getValues: function(elementId) {
+                       if (!_data.has(elementId)) {
+                               throw new Error("Element id '" + elementId + "' is unknown.");
+                       }
+                       
+                       var data = _data.get(elementId);
+                       var items = DOMTraverse.childrenByClass(data.list, 'item');
+                       var values = [], value, item;
+                       for (var i = 0, length = items.length; i < length; i++) {
+                               item = items[i];
+                               value = {
+                                       objectId: item.getAttribute('data-object-id'),
+                                       value: DOMTraverse.childByTag(item, 'SPAN').textContent
+                               };
+                               
+                               values.push(value);
+                       }
+                       
+                       return values;
+               },
+               
+               /**
+                * Binds static event listeners.
+                */
+               _setup: function() {
+                       if (_didInit) {
+                               return;
+                       }
+                       
+                       _didInit = true;
+                       
+                       _callbackKeyDown = this._keyDown.bind(this);
+                       _callbackKeyPress = this._keyPress.bind(this);
+                       _callbackKeyUp = this._keyUp.bind(this);
+                       _callbackRemoveItem = this._removeItem.bind(this);
+               },
+               
+               /**
+                * Creates the DOM structure for target element. If `element` is a `<textarea>`
+                * it will be automatically replaced with an `<input>` element.
+                * 
+                * @param       {Element}               element         input element
+                * @param       {object<string>}        options         option list
+                */
+               _createUI: function(element, options) {
+                       var list = document.createElement('ol');
+                       list.className = 'inputItemList';
+                       list.setAttribute('data-element-id', element.id);
+                       list.addEventListener('click', function(event) {
+                               if (event.target === list) element.focus();
+                       });
+                       
+                       var listItem = document.createElement('li');
+                       listItem.className = 'input';
+                       list.appendChild(listItem);
+                       
+                       element.addEventListener('keydown', _callbackKeyDown);
+                       element.addEventListener('keypress', _callbackKeyPress);
+                       element.addEventListener('keyup', _callbackKeyUp);
+                       
+                       element.parentNode.insertBefore(list, element);
+                       listItem.appendChild(element);
+                       
+                       if (options.maxLength !== -1) {
+                               element.setAttribute('maxLength', options.maxLength);
+                       }
+                       
+                       var shadow = null, values = [];
+                       if (options.isCSV) {
+                               shadow = document.createElement('input');
+                               shadow.className = 'itemListInputShadow';
+                               shadow.type = 'hidden';
+                               shadow.name = element.name;
+                               element.removeAttribute('name');
+                               
+                               list.parentNode.insertBefore(shadow, list);
+                               
+                               if (element.nodeName === 'TEXTAREA') {
+                                       var value, tmp = element.value.split(',');
+                                       for (var i = 0, length = tmp.length; i < length; i++) {
+                                               value = tmp[i].trim();
+                                               if (value.length) {
+                                                       values.push(value);
+                                               }
+                                       }
+                                       
+                                       var inputElement = document.createElement('input');
+                                       element.parentNode.insertBefore(inputElement, element);
+                                       inputElement.id = element.id;
+                                       
+                                       element.parentNode.removeChild(element);
+                                       element = inputElement;
+                               }
+                       }
+                       
+                       return {
+                               element: element,
+                               list: list,
+                               shadow: shadow,
+                               values: values
+                       };
+               },
+               
+               /**
+                * Enforces the maximum number of items.
+                * 
+                * @param       {string}        elementId       input element id
+                */
+               _handleLimit: function(elementId) {
+                       var data = _data.get(elementId);
+                       if (data.options.maxItems === -1) {
+                               return;
+                       }
+                       
+                       if (data.list.childElementCount - 1 < data.options.maxItems) {
+                               if (data.element.disabled) {
+                                       data.element.disabled = false;
+                                       data.element.removeAttribute('placeholder');
+                               }
+                       }
+                       else if (!data.element.disabled) {
+                               data.element.disabled = true;
+                               data.element.setAttribute('placeholder', Language.get('wcf.global.form.input.maxItems'));
+                       }
+               },
+               
+               /**
+                * Sets the active item list id and handles keyboard access to remove an existing item.
+                * 
+                * @param       {object}        event           event object
+                */
+               _keyDown: function(event) {
+                       var input = event.currentTarget;
+                       var lastItem = input.parentNode.previousElementSibling;
+                       
+                       _activeId = input.id;
+                       
+                       if (event.keyCode === 8) {
+                               // 8 = [BACKSPACE]
+                               if (input.value.length === 0) {
+                                       if (lastItem !== null) {
+                                               if (lastItem.classList.contains('active')) {
+                                                       this._removeItem(null, lastItem);
+                                               }
+                                               else {
+                                                       lastItem.classList.add('active');
+                                               }
+                                       }
+                               }
+                       }
+                       else if (event.keyCode === 27) {
+                               // 27 = [ESC]
+                               if (lastItem !== null && lastItem.classList.contains('active')) {
+                                       lastItem.classList.remove('active');
+                               }
+                       }
+               },
+               
+               /**
+                * Handles the `[ENTER]` and `[,]` key to add an item to the list unless it is restricted.
+                * 
+                * @param       {object}        event           event object
+                */
+               _keyPress: function(event) {
+                       // 13 = [ENTER], 44 = [,]
+                       if (event.charCode === 13 || event.charCode === 44) {
+                               event.preventDefault();
+                               
+                               if (_data.get(event.currentTarget.id).options.restricted) {
+                                       // restricted item lists only allow results from the dropdown to be picked
+                                       return;
+                               }
+                               
+                               var value = event.currentTarget.value.trim();
+                               if (value.length) {
+                                       this._addItem(event.currentTarget.id, { objectId: 0, value: value });
+                               }
+                       }
+               },
+               
+               /**
+                * Handles the keyup event to unmark an item for deletion.
+                * 
+                * @param       {object}        event           event object
+                */
+               _keyUp: function(event) {
+                       var input = event.currentTarget;
+                       
+                       if (input.value.length > 0) {
+                               var lastItem = input.parentNode.previousElementSibling;
+                               if (lastItem !== null) {
+                                       lastItem.classList.remove('active');
+                               }
+                       }
+               },
+               
+               /**
+                * Adds an item to the list.
+                * 
+                * @param       {string}        elementId       input element id
+                * @param       {string}        value           item value
+                */
+               _addItem: function(elementId, value) {
+                       var data = _data.get(elementId);
+                       
+                       var listItem = document.createElement('li');
+                       listItem.className = 'item';
+                       
+                       var content = document.createElement('span');
+                       content.className = 'content';
+                       content.setAttribute('data-object-id', value.objectId);
+                       content.textContent = value.value;
+                       
+                       var button = document.createElement('a');
+                       button.className = 'icon icon16 fa-times';
+                       button.addEventListener('click', _callbackRemoveItem);
+                       listItem.appendChild(content);
+                       listItem.appendChild(button);
+                       
+                       data.list.insertBefore(listItem, data.listItem);
+                       data.suggestion.addExcludedValue(value.value);
+                       data.element.value = '';
+                       
+                       this._handleLimit(elementId);
+                       var values = this._syncShadow(data);
+                       
+                       if (typeof data.options.callbackChange === 'function') {
+                               if (values === null) values = this.getValues(elementId);
+                               data.options.callbackChange(elementId, values);
+                       }
+               },
+               
+               /**
+                * Removes an item from the list.
+                * 
+                * @param       {?object}       event           event object
+                * @param       {Element=}      item            list item
+                */
+               _removeItem: function(event, item) {
+                       item = (event === null) ? item : event.currentTarget.parentNode;
+                       
+                       var parent = item.parentNode;
+                       var elementId = parent.getAttribute('data-element-id');
+                       var data = _data.get(elementId);
+                       
+                       data.suggestion.removeExcludedValue(item.children[0].textContent);
+                       parent.removeChild(item);
+                       data.element.focus();
+                       
+                       this._handleLimit(elementId);
+                       var values = this._syncShadow(data);
+                       
+                       if (typeof data.options.callbackChange === 'function') {
+                               if (values === null) values = this.getValues(elementId);
+                               data.options.callbackChange(elementId, values);
+                       }
+               },
+               
+               /**
+                * Synchronizes the shadow input field with the current list item values.
+                * 
+                * @param       {object}        data            element data
+                */
+               _syncShadow: function(data) {
+                       if (!data.isCSV) return null;
+                       
+                       var value = '', values = this.getValues(data.element.id);
+                       for (var i = 0, length = values.length; i < length; i++) {
+                               value += (value.length ? ',' : '') + values[i].value;
+                       }
+                       
+                       data.shadow.value = value;
+                       
+                       return values;
+               }
+       };
+       
+       return UIItemList;
+});
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList/User.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/ItemList/User.js
new file mode 100644 (file)
index 0000000..739ddc2
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Provides an item list for users and groups.
+ * 
+ * @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/ItemList/User
+ */
+define(['WoltLab/WCF/UI/ItemList'], function(UIItemList) {
+       "use strict";
+       
+       /**
+        * @exports     WoltLab/WCF/UI/ItemList/User
+        */
+       var UIItemListUser = {
+               /**
+                * Initializes user suggestion support for an element.
+                * 
+                * @param       {string}        elementId       input element id
+                * @param       {object}        options         option list
+                */
+               init: function(elementId, options) {
+                       UIItemList.init(elementId, [], {
+                               ajax: {
+                                       className: 'wcf\\data\\user\\UserAction',
+                                       parameters: {
+                                               data: {
+                                                       includeUserGroups: ~~options.includeUserGroups
+                                               }
+                                       }
+                               },
+                               callbackChange: (typeof options.callbackChange === 'function' ? options.callbackChange : null),
+                               excludedSearchValues: (Array.isArray(options.excludedSearchValues) ? options.excludedSearchValues : []),
+                               isCSV: true,
+                               maxItems: ~~options.maxItems || -1,
+                               restricted: true
+                       });
+               },
+               
+               /**
+                * @see WoltLab/WCF/UI/ItemList::getValues()
+                */
+               getValues: function(elementId) {
+                       return UIItemList.getValues(elementId);
+               }
+       };
+       
+       return UIItemListUser;
+});
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/Suggestion.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/Suggestion.js
new file mode 100644 (file)
index 0000000..8a04097
--- /dev/null
@@ -0,0 +1,245 @@
+/**
+ * Flexible UI element featuring both a list of items and an input field with suggestion support.
+ * 
+ * @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/Suggestion
+ */
+define(['Ajax', 'Core', 'UI/SimpleDropdown'], function(Ajax, Core, UISimpleDropdown) {
+       "use strict";
+       
+       /**
+        * @constructor
+        * @param       {string}                elementId       input element id
+        * @param       {object<mixed>}         options         option list
+        */
+       function UISuggestion(elementId, options) { this.init(elementId, options); };
+       UISuggestion.prototype = {
+               /**
+                * Initializes a new suggestion input.
+                * 
+                * @param       {string}                element id      input element id
+                * @param       {object<mixed>}         options         option list
+                */
+               init: function(elementId, options) {
+                       this._dropdownMenu = null;
+                       this._value = '';
+                       
+                       this._element = document.getElementById(elementId);
+                       if (this._element === null) {
+                               throw new Error("Expected a valid element id.");
+                       }
+                       
+                       this._options = Core.extend({
+                               ajax: {
+                                       actionName: 'getSearchResultList',
+                                       className: '',
+                                       interfaceName: 'wcf\\data\\ISearchAction',
+                                       parameters: {
+                                               data: {}
+                                       }
+                               },
+                               
+                               // will be executed once a value from the dropdown has been selected
+                               callbackSelect: null,
+                               // list of excluded search values
+                               excludedSearchValues: [],
+                               // minimum number of characters required to trigger a search request
+                               treshold: 3
+                       }, options);
+                       
+                       if (typeof this._options.callbackSelect !== 'function') {
+                               throw new Error("Expected a valid callback for option 'callbackSelect'.");
+                       }
+                       
+                       this._element.addEventListener('click', function(event) { event.stopPropagation(); });
+                       this._element.addEventListener('keydown', this._keyDown.bind(this));
+                       this._element.addEventListener('keyup', this._keyUp.bind(this));
+               },
+               
+               /**
+                * Adds an excluded search value.
+                * 
+                * @param       {string}        value           excluded value
+                */
+               addExcludedValue: function(value) {
+                       if (this._options.excludedSearchValues.indexOf(value) === -1) {
+                               this._options.excludedSearchValues.push(value);
+                       }
+               },
+               
+               /**
+                * Removes an excluded search value.
+                * 
+                * @param       {string}        value           excluded value
+                */
+               removeExcludedValue: function(value) {
+                       var index = this._options.excludedSearchValues.indexOf(value);
+                       if (index !== -1) {
+                               this._options.excludedSearchValues.splice(index, 1);
+                       }
+               },
+               
+               /**
+                * Handles the keyboard navigation for interaction with the suggestion list.
+                * 
+                * @param       {object}        event           event object
+                */
+               _keyDown: function(event) {
+                       if (this._dropdownMenu === null || !UISimpleDropdown.isOpen(this._element.id)) {
+                               return true;
+                       }
+                       
+                       if (event.keyCode !== 13 && event.keyCode !== 27 && event.keyCode !== 38 && event.keyCode !== 40) {
+                               return true;
+                       }
+                       
+                       var active, i = 0, length = this._dropdownMenu.childElementCount;
+                       while (i < length) {
+                               active = this._dropdownMenu.children[i];
+                               if (active.classList.contains('active')) {
+                                       break;
+                               }
+                               
+                               i++;
+                       }
+                       
+                       if (event.keyCode === 13) {
+                               // Enter
+                               UISimpleDropdown.close(this._element.id);
+                               
+                               this._select(active);
+                       }
+                       else if (event.keyCode === 27) {
+                               if (UISimpleDropdown.isOpen(this._element.id)) {
+                                       UISimpleDropdown.close(this._element.id);
+                               }
+                               else {
+                                       // let the event pass through
+                                       return true;
+                               }
+                       }
+                       else {
+                               var index = 0;
+                               
+                               if (event.keyCode === 38) {
+                                       // ArrowUp
+                                       index = ((i === 0) ? length : i) - 1;
+                               }
+                               else if (event.keyCode === 40) {
+                                       // ArrowDown
+                                       index = i + 1;
+                                       if (index === length) index = 0;
+                               }
+                               
+                               if (index !== i) {
+                                       active.classList.remove('active');
+                                       this._dropdownMenu.children[index].classList.add('active');
+                               }
+                       }
+                       
+                       event.preventDefault();
+                       return false;
+               },
+               
+               /**
+                * Selects an item from the list.
+                * 
+                * @param       {(Element|Event)}       item    list item or event object
+                */
+               _select: function(item) {
+                       var isEvent = (item instanceof Event);
+                       if (isEvent) {
+                               item = item.currentTarget.parentNode;
+                       }
+                       
+                       this._options.callbackSelect(this._element.id, { objectId: item.children[0].getAttribute('data-object-id'), value: item.textContent });
+                       
+                       if (isEvent) {
+                               this._element.focus();
+                       }
+               },
+               
+               /**
+                * Performs a search for the input value unless it is below the treshold.
+                * 
+                * @param       {object}                event           event object
+                */
+               _keyUp: function(event) {
+                       var value = event.currentTarget.value.trim();
+                       
+                       if (this._value === value) {
+                               return;
+                       }
+                       else if (value.length < this._options.treshold) {
+                               if (this._dropdownMenu !== null) {
+                                       UISimpleDropdown.close(this._element.id);
+                               }
+                               
+                               this._value = value;
+                               
+                               return;
+                       }
+                       
+                       this._value = value;
+                       
+                       Ajax.api(this, {
+                               parameters: {
+                                       data: {
+                                               excludedSearchValues: this._options.excludedSearchValues,
+                                               searchString: value
+                                       }
+                               }
+                       });
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: this._options.ajax
+                       };
+               },
+               
+               /**
+                * Handles successful Ajax requests.
+                * 
+                * @param       {object}        data            response values
+                */
+               _ajaxSuccess: function(data) {
+                       if (this._dropdownMenu === null) {
+                               this._dropdownMenu = document.createElement('div');
+                               this._dropdownMenu.className = 'dropdownMenu';
+                               
+                               UISimpleDropdown.initFragment(this._element, this._dropdownMenu);
+                       }
+                       else {
+                               this._dropdownMenu.innerHTML = '';
+                       }
+                       
+                       if (data.returnValues.length) {
+                               var anchor, item, listItem;
+                               for (var i = 0, length = data.returnValues.length; i < length; i++) {
+                                       item = data.returnValues[i];
+                                       
+                                       anchor = document.createElement('a');
+                                       anchor.textContent = item.label;
+                                       anchor.setAttribute('data-object-id', item.objectID);
+                                       anchor.addEventListener('click', this._select.bind(this));
+                                       
+                                       listItem = document.createElement('li');
+                                       if (i === 0) listItem.className = 'active';
+                                       listItem.appendChild(anchor);
+                                       
+                                       this._dropdownMenu.appendChild(listItem);
+                               }
+                               
+                               UISimpleDropdown.open(this._element.id);
+                       }
+                       else {
+                               UISimpleDropdown.close(this._element.id);
+                       }
+               }
+       };
+       
+       return UISuggestion;
+});
index 1f653fe4c2b539fd9b5503fb8681a872be75658c..d3ff58668395212913aa98682c8c068af1908829 100644 (file)
@@ -720,3 +720,90 @@ select > option {
                }
        }
 }
+
+/* input item list */
+.inputItemList {
+       background-color: @wcfInputBackgroundColor;
+       border: 1px solid @wcfInputBorderColor;
+       border-radius: @wcfInputBorderRadius;
+       color: @wcfInputColor;
+       cursor: text;
+       display: flex;
+       flex-wrap: wrap;
+       padding: @wcfGapTiny @wcfGapTiny 0;
+       
+       transition-duration: .2s;
+       transition-property: background-color, border-color, box-shadow;
+       transition-timing-function: linear;
+       
+       &:hover {
+               background-color: @wcfInputHoverBackgroundColor;
+               border-color: @wcfInputHoverBorderColor;
+       }
+       
+       > li {
+               align-items: center;
+               display: flex;
+               flex: 0 auto;
+               margin: 0 @wcfGapSmall @wcfGapTiny 0;
+               padding: @wcfGapTiny @wcfGapSmall;
+               
+               &.item {
+                       background-color: @wcfColor;
+                       border-radius: 3px;
+                       padding-right: @wcfGapLarge;
+                       position: relative;
+                       
+                       &:hover,
+                       &.active {
+                               background-color: @wcfTabularBoxBackgroundColor;
+                               color: @wcfTabularBoxColor;
+                       }
+                       
+                       > span,
+                       > .icon {
+                               color: @wcfContentBackgroundColor;
+                               font-size: .85rem;
+                               text-shadow: none;
+                               white-space: nowrap;
+                               word-wrap: normal;
+                       }
+                       
+                       > .icon {
+                               bottom: 0;
+                               height: auto;
+                               position: absolute;
+                               right: 0;
+                               top: 0;
+                               width: @wcfGapLarge;
+                               
+                               &:before {
+                                       left: 50%;
+                                       position: absolute;
+                                       top: 50%;
+                                       transform: translateX(-50%) translateY(-50%);
+                               }
+                       }
+               }
+               
+               &.input {
+                       padding: 0;
+                       width: 200px;
+                       
+                       > input {
+                               background-color: transparent !important;
+                               background-image: none !important;
+                               border-width: 0 !important;
+                               box-shadow: none !important;
+                               outline: none !important;
+                               padding: 0 !important;
+                               width: 100% !important;
+                               
+                               &::-ms-clear {
+                                       /* hide "clear value" button in IE (displayed as "X") */
+                                       display: none;
+                               }
+                       }
+               }
+       }
+}
index efe52883efda710816b9adb4c8f8cb231ec8ed5c..1d87b45e53bbd0b5bf475b69400e00a6a38cd3d1 100644 (file)
@@ -2264,6 +2264,7 @@ Fehler sind beispielsweise:
                <item name="wcf.global.form.error.multilingual"><![CDATA[Bitte füllen Sie dieses Eingabefeld für jede Sprache aus.]]></item>
                <item name="wcf.global.form.error.noValidSelection"><![CDATA[Wählen Sie eine der angebotenen Optionen aus.]]></item>
                <item name="wcf.global.form.error.securityToken"><![CDATA[Ihre Sitzung ist abgelaufen, bitte senden Sie das Formular erneut ab.]]></item>
+               <item name="wcf.global.form.input.maxItems"><![CDATA[Maximale Anzahl erreicht]]></item>
                
                <!-- deprecated since 2.1 -->
                <item name="wcf.global.form.error.lessThan.javaScript"><![CDATA[{literal}Der eingegebene Wert muss kleiner sein als {#$lessThan}.{/literal}]]></item>
index 5044b0bd43419448a5e0afb9b7d36f11a16abc92..106d6c4c0d65166fc8b60682e2f3146defea2daf 100644 (file)
@@ -2261,6 +2261,7 @@ Errors are:
                <item name="wcf.global.form.error.multilingual"><![CDATA[Please fill in this field for all languages.]]></item>
                <item name="wcf.global.form.error.noValidSelection"><![CDATA[Choose one of the available options.]]></item>
                <item name="wcf.global.form.error.securityToken"><![CDATA[Your session has expired, please submit the form again.]]></item>
+               <item name="wcf.global.form.input.maxItems"><![CDATA[Maximum items reached]]></item>
                
                <!-- deprecated since 2.1 -->
                <item name="wcf.global.form.error.greaterThan.javaScript"><![CDATA[{literal}The entered value has to be greater than {#$greaterThan}.{/literal}]]></item>