Convert Special:Search input to OOUI
authorFlorian <florian.schmidt.stargatewissen@gmail.com>
Thu, 12 Nov 2015 17:34:12 +0000 (18:34 +0100)
committerFlorian <florian.schmidt.stargatewissen@gmail.com>
Tue, 22 Mar 2016 17:38:52 +0000 (18:38 +0100)
Bug: T100898
Change-Id: I24801495dfad71f457bc4afdd28474f74b219024

includes/specials/SpecialSearch.php
includes/widget/SearchInputWidget.php
resources/Resources.php
resources/src/mediawiki.special/mediawiki.special.search.js
resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js
resources/src/mediawiki/mediawiki.searchSuggest.js

index dfab8d4..45ef679 100644 (file)
@@ -97,7 +97,7 @@ class SpecialSearch extends SpecialPage {
                $out->allowClickjacking();
                $out->addModuleStyles( [
                        'mediawiki.special', 'mediawiki.special.search', 'mediawiki.ui', 'mediawiki.ui.button',
-                       'mediawiki.ui.input',
+                       'mediawiki.ui.input', 'mediawiki.widgets.SearchInputWidget.styles',
                ] );
                $this->addHelpLink( 'Help:Searching' );
 
@@ -323,6 +323,7 @@ class SpecialSearch extends SpecialPage {
                $num = $titleMatchesNum + $textMatchesNum;
                $totalRes = $numTitleMatches + $numTextMatches;
 
+               $out->enableOOUI();
                $out->addHTML(
                        # This is an awful awful ID name. It's not a table, but we
                        # named it poorly from when this was a table so now we're
@@ -1218,23 +1219,23 @@ class SpecialSearch extends SpecialPage {
         * @return string
         */
        protected function shortDialog( $term, $resultsShown, $totalNum ) {
-               $out = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() );
-               $out .= Html::hidden( 'profile', $this->profile ) . "\n";
-               // Term box
-               $out .= Html::input( 'search', $term, 'search', [
-                       'id' => $this->isPowerSearch() ? 'powerSearchText' : 'searchText',
-                       'size' => '50',
+               $searchWidget = new MediaWiki\Widget\SearchInputWidget( [
+                       'id' => 'searchText',
+                       'name' => 'search',
                        'autofocus' => trim( $term ) === '',
-                       'class' => 'mw-ui-input mw-ui-input-inline',
-                       // identifies the location of the search bar for tracking purposes
-                       'data-search-loc' => 'content',
-               ] ) . "\n";
-               $out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
-               $out .= Html::submitButton(
-                       $this->msg( 'searchbutton' )->text(),
-                       [ 'class' => 'mw-ui-button mw-ui-progressive' ],
-                       [ 'mw-ui-progressive' ]
-               ) . "\n";
+                       'value' => $term,
+               ] );
+
+               $out =
+                       Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
+                       Html::hidden( 'profile', $this->profile ) .
+                       Html::hidden( 'fulltext', 'Search' ) .
+                       $searchWidget .
+                       new OOUI\ButtonInputWidget( [
+                               'type' => 'submit',
+                               'label' => $this->msg( 'searchbutton' )->text(),
+                               'flags' => [ 'progressive', 'primary' ],
+                       ] );
 
                // Results-info
                if ( $totalNum > 0 && $this->offset < $totalNum ) {
index 7b3de4a..5ff411d 100644 (file)
@@ -13,6 +13,7 @@ namespace MediaWiki\Widget;
 class SearchInputWidget extends TitleInputWidget {
 
        protected $pushPending = false;
+       protected $performSearchOnClick = true;
        protected $validateTitle = false;
        protected $highlightFirst = false;
 
@@ -20,23 +21,36 @@ class SearchInputWidget extends TitleInputWidget {
         * @param array $config Configuration options
         * @param int|null $config['pushPending'] Whether the input should be visually marked as
         *  "pending", while requesting suggestions (default: true)
+        * @param boolean|null $config['performSearchOnClick'] If true, the script will start a search
+        *  whenever a user hits a suggestion. If false, the text of the suggestion is inserted into the
+        *  text field only (default: true)
         */
        public function __construct( array $config = [] ) {
+               $config = array_merge( [
+                       'infusable' => true,
+                       'maxLength' => null,
+                       'type' => 'search',
+                       'icon' => 'search',
+                       'dataLocation' => 'content',
+               ], $config );
+
                // Parent constructor
-               parent::__construct(
-                       array_merge( [
-                               'infusable' => true,
-                               'maxLength' => null,
-                               'type' => 'search',
-                               'icon' => 'search'
-                       ], $config )
-               );
+               parent::__construct( $config );
 
                // Properties, which are ignored in PHP and just shipped back to JS
                if ( isset( $config['pushPending'] ) ) {
                        $this->pushPending = $config['pushPending'];
                }
 
+               if ( isset( $config['performSearchOnClick'] ) ) {
+                       $this->performSearchOnClick = $config['performSearchOnClick'];
+               }
+
+               if ( $config['dataLocation'] ) {
+                       // identifies the location of the search bar for tracking purposes
+                       $this->dataLocation = $config['dataLocation'];
+               }
+
                // Initialization
                $this->addClasses( [ 'mw-widget-searchInputWidget' ] );
        }
@@ -47,6 +61,10 @@ class SearchInputWidget extends TitleInputWidget {
 
        public function getConfig( &$config ) {
                $config['pushPending'] = $this->pushPending;
+               $config['performSearchOnClick'] = $this->performSearchOnClick;
+               if ( $this->dataLocation ) {
+                       $config['dataLocation'] = $this->dataLocation;
+               }
                return parent::getConfig( $config );
        }
 }
index 07cd7b5..0be4693 100644 (file)
@@ -1847,6 +1847,7 @@ return [
                'position' => 'top',
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.search.js',
                'styles' => 'resources/src/mediawiki.special/mediawiki.special.search.css',
+               'dependencies' => 'mediawiki.widgets.SearchInputWidget',
                'messages' => [
                        'powersearch-togglelabel',
                        'powersearch-toggleall',
index ab83e1a..e809f2e 100644 (file)
@@ -3,7 +3,7 @@
  */
 ( function ( mw, $ ) {
        $( function () {
-               var $checkboxes, $headerLinks;
+               var $checkboxes, $headerLinks, updateHeaderLinks, searchWidget;
 
                // Emulate HTML5 autofocus behavior in non HTML5 compliant browsers
                if ( !( 'autofocus' in document.createElement( 'input' ) ) ) {
@@ -33,8 +33,8 @@
 
                // Change the header search links to what user entered
                $headerLinks = $( '.search-types a' );
-               $( '#searchText, #powerSearchText' ).change( function () {
-                       var searchterm = $( this ).val();
+               searchWidget = OO.ui.infuse( 'searchText' );
+               updateHeaderLinks = function ( value ) {
                        $headerLinks.each( function () {
                                var parts = $( this ).attr( 'href' ).split( 'search=' ),
                                        lastpart = '',
                                } else {
                                        prefix = '&search=';
                                }
-                               this.href = parts[ 0 ] + prefix + encodeURIComponent( searchterm ) + lastpart;
+                               this.href = parts[ 0 ] + prefix + encodeURIComponent( value ) + lastpart;
                        } );
-               } ).trigger( 'change' );
+               };
+               searchWidget.on( 'change', updateHeaderLinks );
+               updateHeaderLinks( searchWidget.getValue() );
 
                // When saving settings, use the proper request method (POST instead of GET).
                $( '#mw-search-powersearch-remember' ).change( function () {
index 1f526e2..8c2b53a 100644 (file)
         * @constructor
         * @cfg {boolean} [pushPending=true] Visually mark the input field as "pending", while
         *  requesting suggestions.
+        * @cfg {boolean} [performSearchOnClick=true] If true, the script will start a search when-
+        *  ever a user hits a suggestion. If false, the text of the suggestion is inserted into the
+        *  text field only.
         */
        mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
                config = $.extend( {
                        type: 'search',
                        icon: 'search',
-                       maxLength: undefined
+                       maxLength: undefined,
+                       performSearchOnClick: true
                }, config );
 
                // Parent constructor
                if ( !config.pushPending ) {
                        this.pushPending = false;
                }
+               if ( config.dataLocation ) {
+                       this.dataLocation = config.dataLocation;
+               }
+               if ( config.performSearchOnClick ) {
+                       this.performSearchOnClick = config.performSearchOnClick;
+               }
                this.setLookupsDisabled( !this.suggestions );
        };
 
         * @inheritdoc mw.widgets.TitleWidget
         */
        mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
-               var api = new mw.Api();
+               var api = new mw.Api(),
+                       promise,
+                       self = this;
 
                // reuse the searchSuggest function from mw.searchSuggest
-               return mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit );
+               promise = mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit );
+
+               // tracking purposes
+               promise.done( function ( data, jqXHR ) {
+                       self.requestType = jqXHR.getResponseHeader( 'X-OpenSearch-Type' );
+               } );
+
+               return promise;
        };
 
        /**
         * @inheritdoc mw.widgets.TitleInputWidget
         */
        mw.widgets.SearchInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
+               var resp;
+
                // mw.widgets.TitleInputWidget uses response.query, which doesn't exist for opensearch,
                // so return the whole response (titles only, and links)
-               return response || {};
+               resp = {
+                       data: response || {},
+                       metadata: {
+                               type: this.requestType || 'unknown',
+                               query: this.getQueryValue()
+                       }
+               };
+               this.requestType = undefined;
+
+               return resp;
        };
 
        /**
                // mw.widgets.TitleWidget does a lot more work here, because the TitleOptionWidgets can
                // differ a lot, depending on the returned data from the request. With the request used here
                // we get only the search results.
-               $.each( data[ 1 ], function ( i, result ) {
+               $.each( data.data[ 1 ], function ( i, result ) {
                        items.push( new mw.widgets.TitleOptionWidget(
                                // data[ 3 ][ i ] is the link for this result
-                               self.getOptionWidgetData( result, null, data[ 3 ][ i ] )
+                               self.getOptionWidgetData( result, null, data.data[ 3 ][ i ] )
                        ) );
                } );
 
                mw.track( 'mw.widgets.SearchInputWidget', {
                        action: 'impression-results',
                        numberOfResults: items.length,
-                       resultSetType: mw.searchSuggest.type
+                       resultSetType: data.metadata.type,
+                       query: data.metadata.query,
+                       inputLocation: this.dataLocation || 'header'
                } );
 
                return items;
                };
        };
 
+       /**
+        * @inheritdoc
+        */
+       mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function ( item ) {
+               var items;
+
+               // get items which was suggested before the input changes
+               items = this.lookupMenu.items;
+
+               mw.widgets.SearchInputWidget.parent.prototype.onLookupMenuItemChoose.apply( this, arguments );
+
+               mw.track( 'mw.widgets.SearchInputWidget', {
+                       action: 'click-result',
+                       numberOfResults: items.length,
+                       clickIndex: items.indexOf( item ) + 1
+               } );
+
+               if ( this.performSearchOnClick ) {
+                       this.$element.closest( 'form' ).submit();
+               }
+       };
+
 }( jQuery, mediaWiki ) );
index 17a3b21..2d603bf 100644 (file)
                searchboxesSelectors = [
                        // Primary searchbox on every page in standard skins
                        '#searchInput',
-                       // Special:Search
-                       '#powerSearchText',
-                       '#searchText',
                        // Generic selector for skins with multiple searchboxes (used by CologneBlue)
                        // and for MediaWiki itself (special pages with page title inputs)
                        '.mw-searchInput'