Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / mediawiki.widgets / mw.widgets.SearchInputWidget.js
blob00612a2e010ee6c2598358177dde001c0fade57e
1 /*!
2  * MediaWiki Widgets - SearchInputWidget class.
3  *
4  * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
5  * @license The MIT License (MIT); see LICENSE.txt
6  */
7 ( function () {
9         /**
10          * @classdesc Search input widget.
11          *
12          * @class
13          * @extends mw.widgets.TitleInputWidget
14          *
15          * @constructor
16          * @description Create a mw.widgets.SearchInputWidget object.
17          * @param {Object} [config] Configuration options
18          * @param {boolean} [config.performSearchOnClick=true] If true, the script will start a search when-
19          *  ever a user hits a suggestion. If false, the text of the suggestion is inserted into the
20          *  text field only.
21          * @param {string} [config.dataLocation='header'] Where the search input field will be
22          *  used (header or content).
23          */
24         mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
25                 // The parent constructors will detach this from the DOM, and won't
26                 // be reattached until after this function is completed. As such
27                 // grab a handle here. If no config.$input is passed tracking of
28                 // form submissions won't work.
29                 const $form = config.$input ? config.$input.closest( 'form' ) : $();
31                 config = Object.assign( {
32                         icon: 'search',
33                         maxLength: undefined,
34                         showPendingRequest: false,
35                         performSearchOnClick: true,
36                         dataLocation: 'header'
37                 }, config );
39                 // Parent constructor
40                 mw.widgets.SearchInputWidget.super.call( this, config );
42                 // Initialization
43                 this.$element.addClass( 'mw-widget-searchInputWidget' );
44                 this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' );
45                 this.lastLookupItems = [];
46                 if ( config.dataLocation ) {
47                         this.dataLocation = config.dataLocation;
48                 }
49                 if ( config.performSearchOnClick ) {
50                         this.performSearchOnClick = config.performSearchOnClick;
51                 }
52                 this.setLookupsDisabled( !this.suggestions );
54                 $form.on( 'submit', () => {
55                         mw.track( 'mw.widgets.SearchInputWidget', {
56                                 action: 'submit-form',
57                                 numberOfResults: this.lastLookupItems.length,
58                                 $form: $form,
59                                 inputLocation: this.dataLocation || 'header',
60                                 index: this.lastLookupItems.indexOf(
61                                         this.$input.val()
62                                 )
63                         } );
64                 } );
66                 this.connect( this, {
67                         change: 'onChange'
68                 } );
70                 this.$element.addClass( 'oo-ui-textInputWidget-type-search' );
71                 this.updateSearchIndicator();
72                 this.connect( this, {
73                         disable: 'onDisable'
74                 } );
75         };
77         /* Setup */
79         OO.inheritClass( mw.widgets.SearchInputWidget, mw.widgets.TitleInputWidget );
81         /* Methods */
83         /**
84          * @inheritdoc
85          * @protected
86          */
87         mw.widgets.SearchInputWidget.prototype.getInputElement = function () {
88                 return $( '<input>' ).attr( 'type', 'search' );
89         };
91         /**
92          * @inheritdoc
93          */
94         mw.widgets.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
95                 if ( e.which === OO.ui.MouseButtons.LEFT ) {
96                         // Clear the text field
97                         this.setValue( '' );
98                         this.$input[ 0 ].focus();
99                         return false;
100                 }
101         };
103         /**
104          * Update the 'clear' indicator displayed on type: 'search' text
105          * fields, hiding it when the field is already empty or when it's not
106          * editable.
107          */
108         mw.widgets.SearchInputWidget.prototype.updateSearchIndicator = function () {
109                 if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
110                         this.setIndicator( null );
111                 } else {
112                         this.setIndicator( 'clear' );
113                 }
114         };
116         /**
117          * @inheritdoc
118          */
119         mw.widgets.SearchInputWidget.prototype.onChange = function () {
120                 this.updateSearchIndicator();
121         };
123         /**
124          * Handle disable events.
125          *
126          * @param {boolean} disabled Element is disabled
127          * @private
128          */
129         mw.widgets.SearchInputWidget.prototype.onDisable = function () {
130                 this.updateSearchIndicator();
131         };
133         /**
134          * @inheritdoc
135          */
136         mw.widgets.SearchInputWidget.prototype.setReadOnly = function ( state ) {
137                 mw.widgets.SearchInputWidget.super.prototype.setReadOnly.call( this, state );
138                 this.updateSearchIndicator();
139                 return this;
140         };
142         /**
143          * @inheritdoc
144          */
145         mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
146                 const api = this.getApi();
148                 // While the name is, for historical reasons, 'session-start', this indicates
149                 // a new backend request is being performed.
150                 mw.track( 'mw.widgets.SearchInputWidget', {
151                         action: 'session-start'
152                 } );
154                 // reuse the searchSuggest function from mw.searchSuggest
155                 const promise = mw.searchSuggest.request( api, this.getQueryValue(), () => {}, this.limit, this.getNamespace() );
157                 // tracking purposes
158                 promise.done( ( data, jqXHR ) => {
159                         this.requestType = jqXHR.getResponseHeader( 'X-OpenSearch-Type' );
160                         this.searchId = jqXHR.getResponseHeader( 'X-Search-ID' );
161                 } );
163                 return promise;
164         };
166         /**
167          * @inheritdoc
168          */
169         mw.widgets.SearchInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
170                 // mw.widgets.TitleInputWidget uses response.query, which doesn't exist for opensearch,
171                 // so return the whole response (titles only, and links)
172                 const resp = {
173                         data: response || {},
174                         metadata: {
175                                 type: this.requestType || 'unknown',
176                                 searchId: this.searchId || null,
177                                 query: this.getQueryValue()
178                         }
179                 };
180                 this.requestType = undefined;
181                 this.searchId = undefined;
183                 return resp;
184         };
186         /**
187          * @inheritdoc
188          */
189         mw.widgets.SearchInputWidget.prototype.getOptionsFromData = function ( data ) {
190                 const items = [],
191                         titles = data.data[ 1 ],
192                         descriptions = data.data[ 2 ],
193                         urls = data.data[ 3 ];
195                 // eslint-disable-next-line no-jquery/no-each-util
196                 $.each( titles, ( i, result ) => {
197                         items.push( new mw.widgets.TitleOptionWidget(
198                                 this.getOptionWidgetData(
199                                         result,
200                                         // Create a result object that looks like the one from
201                                         // the parent's API query.
202                                         {
203                                                 data: result,
204                                                 url: urls[ i ],
205                                                 imageUrl: null, // The JSON 'opensearch' API doesn't have images
206                                                 description: descriptions[ i ],
207                                                 missing: false,
208                                                 redirect: false,
209                                                 disambiguation: false
210                                         }
211                                 )
212                         ) );
213                 } );
215                 mw.track( 'mw.widgets.SearchInputWidget', {
216                         action: 'impression-results',
217                         numberOfResults: items.length,
218                         resultSetType: data.metadata.type,
219                         searchId: data.metadata.searchId,
220                         query: data.metadata.query,
221                         inputLocation: this.dataLocation || 'header'
222                 } );
224                 return items;
225         };
227         /**
228          * @inheritdoc
229          */
230         mw.widgets.SearchInputWidget.prototype.onLookupMenuChoose = function () {
231                 mw.widgets.SearchInputWidget.super.prototype.onLookupMenuChoose.apply( this, arguments );
233                 if ( this.performSearchOnClick ) {
234                         this.$element.closest( 'form' ).trigger( 'submit' );
235                 }
236         };
238         /**
239          * @inheritdoc
240          */
241         mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function () {
242                 const items = mw.widgets.SearchInputWidget.super.prototype.getLookupMenuOptionsFromData.apply(
243                         this, arguments
244                 );
246                 this.lastLookupItems = items.map( ( item ) => item.data );
248                 return items;
249         };
251 }() );