Added replacement for `WCF.Search.Base`
authorAlexander Ebert <ebert@woltlab.com>
Tue, 8 Mar 2016 13:13:54 +0000 (14:13 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 8 Mar 2016 13:13:54 +0000 (14:13 +0100)
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Search/Input.js [new file with mode: 0644]

index 1a1e2c35cd2aaff82dd2edc45172a0db3e4168f9..fcf4fb6a4d0d9f8268575e4268c2a7507c53dc08 100755 (executable)
@@ -4402,6 +4402,8 @@ WCF.Search = {};
 
 /**
  * Performs a quick search.
+ * 
+ * @deprecated  2.2 - please use `WoltLab/WCF/Ui/Search/Input` instead
  */
 WCF.Search.Base = Class.extend({
        /**
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Search/Input.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Search/Input.js
new file mode 100644 (file)
index 0000000..464b238
--- /dev/null
@@ -0,0 +1,332 @@
+/**
+ * Provides suggestions using an input field, designed to work with `wcf\data\ISearchAction`.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Search/Input
+ */
+define(['Ajax', 'Core', 'EventKey', 'Dom/Util', 'Ui/SimpleDropdown'], function(Ajax, Core, EventKey, DomUtil, UiSimpleDropdown) {
+       "use strict";
+       
+       /**
+        * @param       {Element}       element         target input[type="text"]
+        * @param       {Object}        options         search options and settings
+        * @constructor
+        */
+       function UiSearchInput(element, options) { this.init(element, options); }
+       UiSearchInput.prototype = {
+               /**
+                * Initializes the search input field.
+                * 
+                * @param       {Element}       element         target input[type="text"]
+                * @param       {Object}        options         search options and settings
+                */
+               init: function(element, options) {
+                       this._element = element;
+                       if (!(this._element instanceof Element)) {
+                               throw new TypeError("Expected a valid DOM element.");
+                       }
+                       else if (this._element.nodeName !== 'INPUT' || this._element.type !== 'text') {
+                               throw new Error('Expected an input[type="text"].');
+                       }
+                       
+                       this._activeItem = null;
+                       this._dropdownContainerId = '';
+                       this._lastValue = '';
+                       this._list = null;
+                       this._request = null;
+                       this._timerDelay = null;
+                       
+                       this._options = Core.extend({
+                               ajax: {
+                                       actionName: 'getSearchResultList',
+                                       className: '',
+                                       interfaceName: 'wcf\\data\\ISearchAction'
+                               },
+                               callbackSelect: null,
+                               delay: 500,
+                               minLength: 3,
+                               noResultPlaceholder: '',
+                               preventSubmit: false
+                       }, options);
+                       
+                       // disable auto-complete as it collides with the suggestion dropdown
+                       elAttr(this._element, 'autocomplete', 'off');
+                       
+                       this._element.addEventListener('keydown', this._keydown.bind(this));
+                       this._element.addEventListener('keyup', this._keyup.bind(this));
+               },
+               
+               /**
+                * Handles the 'keydown' event.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _keydown: function(event) {
+                       if (this._activeItem !== null || this._options.preventSubmit) {
+                               if (EventKey.Enter(event)) {
+                                       event.preventDefault();
+                               }
+                       }
+                       
+                       if (EventKey.ArrowUp(event) || EventKey.ArrowDown(event)) {
+                               event.preventDefault();
+                       }
+               },
+               
+               /**
+                * Handles the 'keyup' event, provides keyboard navigation and executes search queries.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _keyup: function(event) {
+                       // handle dropdown keyboard navigation
+                       if (this._activeItem !== null) {
+                               if (EventKey.ArrowUp(event)) {
+                                       event.preventDefault();
+                                       
+                                       return this._keyboardPreviousItem();
+                               }
+                               else if (EventKey.ArrowDown(event)) {
+                                       event.preventDefault();
+                                       
+                                       return this._keyboardNextItem();
+                               }
+                               else if (EventKey.Enter(event)) {
+                                       event.preventDefault();
+                                       
+                                       return this._keyboardSelectItem();
+                               }
+                       }
+                       
+                       var value = this._element.value.trim();
+                       if (this._lastValue === value) {
+                               // value did not change, e.g. previously it was "Test" and now it is "Test ",
+                               // but the trailing whitespace has been ignored
+                               return;
+                       }
+                       
+                       this._lastValue = value;
+                       
+                       if (value.length < this._options.minLength) {
+                               if (this._dropdownContainerId) {
+                                       UiSimpleDropdown.close(this._dropdownContainerId);
+                               }
+                               
+                               // value below threshold
+                               return;
+                       }
+                       
+                       if (this._options.delay) {
+                               if (this._timerDelay !== null) {
+                                       window.clearTimeout(this._timerDelay);
+                               }
+                               
+                               this._timerDelay = window.setTimeout((function() {
+                                       this._search(value);
+                               }).bind(this), this._options.delay);
+                       }
+                       else {
+                               this._search(value);
+                       }
+               },
+               
+               /**
+                * Queries the server with the provided search string.
+                * 
+                * @param       {string}        value   search string
+                * @protected
+                */
+               _search: function(value) {
+                       if (this._request) {
+                               this._request.abortPrevious();
+                       }
+                       
+                       this._request = Ajax.api(this, {
+                               parameters: {
+                                       data: {
+                                               searchString: value
+                                       }
+                               }
+                       });
+               },
+               
+               /**
+                * Selects the next dropdown item.
+                * 
+                * @protected
+                */
+               _keyboardNextItem: function() {
+                       this._activeItem.classList.remove('active');
+                       
+                       if (this._activeItem.nextElementSibling) {
+                               this._activeItem = this._activeItem.nextElementSibling;
+                       }
+                       else {
+                               this._activeItem = this._list.children[0];
+                       }
+                       
+                       this._activeItem.classList.add('active');
+               },
+               
+               /**
+                * Selects the previous dropdown item.
+                * 
+                * @protected
+                */
+               _keyboardPreviousItem: function() {
+                       this._activeItem.classList.remove('active');
+                       
+                       if (this._activeItem.previousElementSibling) {
+                               this._activeItem = this._activeItem.previousElementSibling;
+                       }
+                       else {
+                               this._activeItem = this._list.children[this._list.childElementCount - 1];
+                       }
+                       
+                       this._activeItem.classList.add('active');
+               },
+               
+               /**
+                * Selects the active item from the dropdown.
+                * 
+                * @protected
+                */
+               _keyboardSelectItem: function() {
+                       this._selectItem(this._activeItem);
+               },
+               
+               /**
+                * Selects an item from the dropdown by clicking it.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _clickSelectItem: function(event) {
+                       this._selectItem(event.currentTarget);
+               },
+               
+               /**
+                * Selects an item.
+                * 
+                * @param       {Element}       item    selected item
+                * @protected
+                */
+               _selectItem: function(item) {
+                       this._element.value = elData(item, 'label');
+                       
+                       this._activeItem = null;
+                       UiSimpleDropdown.close(this._dropdownContainerId);
+               },
+               
+               /**
+                * Handles successful AJAX requests.
+                * 
+                * @param       {Object}        data    response data
+                * @protected
+                */
+               _ajaxSuccess: function(data) {
+                       var createdList = false;
+                       if (this._list === null) {
+                               this._list = elCreate('ul');
+                               this._list.className = 'dropdownMenu';
+                               
+                               createdList = true;
+                       }
+                       else {
+                               // reset current list
+                               this._list.innerHTML = '';
+                       }
+                       
+                       if (typeof data.returnValues === 'object') {
+                               var callbackClick = this._clickSelectItem.bind(this), listItem;
+                               
+                               for (var key in data.returnValues) {
+                                       if (data.returnValues.hasOwnProperty(key)) {
+                                               listItem = this._createListItem(data.returnValues[key]);
+                                               
+                                               listItem.addEventListener(WCF_CLICK_EVENT, callbackClick);
+                                               this._list.appendChild(listItem);
+                                       }
+                               }
+                       }
+                       
+                       if (createdList) {
+                               DomUtil.insertAfter(this._list, this._element);
+                               UiSimpleDropdown.initFragment(this._element.parentNode, this._list);
+                               
+                               this._dropdownContainerId = DomUtil.identify(this._element.parentNode);
+                       }
+                       
+                       if (this._dropdownContainerId) {
+                               this._activeItem = null;
+                               
+                               if (!this._list.childElementCount && this._handleEmptyResult() === false) {
+                                       UiSimpleDropdown.close(this._dropdownContainerId);
+                               }
+                               else {
+                                       UiSimpleDropdown.open(this._dropdownContainerId);
+                                       
+                                       // mark first item as active
+                                       if (this._list.childElementCount && ~~elData(this._list.children[0], 'object-id')) {
+                                               this._activeItem = this._list.children[0];
+                                               this._activeItem.classList.add('active');
+                                       }
+                               }
+                       }
+               },
+               
+               /**
+                * Handles an empty result set, return a boolean false to hide the dropdown.
+                * 
+                * @return      {boolean}      false to close the dropdown
+                * @protected
+                */
+               _handleEmptyResult: function() {
+                       if (!this._options.noResultPlaceholder) {
+                               return false;
+                       }
+                       
+                       var listItem = elCreate('li');
+                       listItem.className = 'dropdownText';
+                       
+                       var span = elCreate('span');
+                       span.textContent = this._options.noResultPlaceholder;
+                       listItem.appendChild(span);
+                       
+                       this._list.appendChild(listItem);
+                       
+                       return true;
+               },
+               
+               /**
+                * Creates an list item from response data.
+                * 
+                * @param       {Object}        item    response data
+                * @return      {Element}       list item
+                * @protected
+                */
+               _createListItem: function(item) {
+                       var listItem = elCreate('li');
+                       elData(listItem, 'object-id', item.objectID);
+                       elData(listItem, 'label', item.label);
+                       
+                       var span = elCreate('span');
+                       span.textContent = item.label;
+                       listItem.appendChild(span);
+                       
+                       return listItem;
+               },
+               
+               _ajaxSetup: function() {
+                       return {
+                               data: this._options.ajax
+                       };
+               }
+       };
+       
+       return UiSearchInput;
+});