Merge "Remove setting of SearchInputWidget configs to already-default values"
[lhc/web/wiklou.git] / resources / src / mediawiki.widgets / mw.widgets.SearchInputWidget.js
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 ( $, mw ) {
8
9 /**
10 * Creates a mw.widgets.SearchInputWidget object.
11 *
12 * @class
13 * @extends mw.widgets.TitleInputWidget
14 *
15 * @constructor
16 * @param {Object} [config] Configuration options
17 * @cfg {boolean} [pushPending=false] Visually mark the input field as "pending", while
18 * requesting suggestions.
19 * @cfg {boolean} [performSearchOnClick=true] If true, the script will start a search when-
20 * ever a user hits a suggestion. If false, the text of the suggestion is inserted into the
21 * text field only.
22 * @cfg {string} [dataLocation='header'] Where the search input field will be
23 * used (header or content).
24 */
25 mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
26 // The parent constructors will detach this from the DOM, and won't
27 // be reattached until after this function is completed. As such
28 // grab a handle here. If no config.$input is passed tracking of
29 // form submissions won't work.
30 var $form = config.$input ? config.$input.closest( 'form' ) : $();
31
32 config = $.extend( {
33 icon: 'search',
34 maxLength: undefined,
35 performSearchOnClick: true,
36 dataLocation: 'header'
37 }, config );
38
39 // Parent constructor
40 mw.widgets.SearchInputWidget.parent.call( this, config );
41
42 // Initialization
43 this.$element.addClass( 'mw-widget-searchInputWidget' );
44 this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' );
45 this.lastLookupItems = [];
46 if ( !config.pushPending ) {
47 this.pushPending = false;
48 }
49 if ( config.dataLocation ) {
50 this.dataLocation = config.dataLocation;
51 }
52 if ( config.performSearchOnClick ) {
53 this.performSearchOnClick = config.performSearchOnClick;
54 }
55 this.setLookupsDisabled( !this.suggestions );
56
57 $form.on( 'submit', function () {
58 mw.track( 'mw.widgets.SearchInputWidget', {
59 action: 'submit-form',
60 numberOfResults: this.lastLookupItems.length,
61 $form: $form,
62 inputLocation: this.dataLocation || 'header',
63 index: this.lastLookupItems.indexOf(
64 this.$input.val()
65 )
66 } );
67 }.bind( this ) );
68
69 this.$element.addClass( 'oo-ui-textInputWidget-type-search' );
70 this.updateSearchIndicator();
71 this.connect( this, {
72 disable: 'onDisable'
73 } );
74 };
75
76 /* Setup */
77
78 OO.inheritClass( mw.widgets.SearchInputWidget, mw.widgets.TitleInputWidget );
79
80 /* Methods */
81
82 /**
83 * @inheritdoc
84 * @protected
85 */
86 mw.widgets.SearchInputWidget.prototype.getInputElement = function () {
87 return $( '<input>' ).attr( 'type', 'search' );
88 };
89
90 /**
91 * @inheritdoc
92 */
93 mw.widgets.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
94 if ( e.which === OO.ui.MouseButtons.LEFT ) {
95 // Clear the text field
96 this.setValue( '' );
97 this.$input[ 0 ].focus();
98 return false;
99 }
100 };
101
102 /**
103 * Update the 'clear' indicator displayed on type: 'search' text
104 * fields, hiding it when the field is already empty or when it's not
105 * editable.
106 */
107 mw.widgets.SearchInputWidget.prototype.updateSearchIndicator = function () {
108 if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
109 this.setIndicator( null );
110 } else {
111 this.setIndicator( 'clear' );
112 }
113 };
114
115 /**
116 * @see OO.ui.SearchInputWidget#onChange
117 */
118 mw.widgets.SearchInputWidget.prototype.onChange = function () {
119 mw.widgets.SearchInputWidget.parent.prototype.onChange.call( this );
120 this.updateSearchIndicator();
121 };
122
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 };
132
133 /**
134 * @inheritdoc
135 */
136 mw.widgets.SearchInputWidget.prototype.setReadOnly = function ( state ) {
137 mw.widgets.SearchInputWidget.parent.prototype.setReadOnly.call( this, state );
138 this.updateSearchIndicator();
139 return this;
140 };
141
142 /**
143 * @inheritdoc mw.widgets.TitleWidget
144 */
145 mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
146 var api = this.getApi(),
147 promise,
148 self = this;
149
150 // reuse the searchSuggest function from mw.searchSuggest
151 promise = mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit, this.getNamespace() );
152
153 // tracking purposes
154 promise.done( function ( data, jqXHR ) {
155 self.requestType = jqXHR.getResponseHeader( 'X-OpenSearch-Type' );
156 } );
157
158 return promise;
159 };
160
161 /**
162 * @inheritdoc mw.widgets.TitleInputWidget
163 */
164 mw.widgets.SearchInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
165 var resp;
166
167 // mw.widgets.TitleInputWidget uses response.query, which doesn't exist for opensearch,
168 // so return the whole response (titles only, and links)
169 resp = {
170 data: response || {},
171 metadata: {
172 type: this.requestType || 'unknown',
173 query: this.getQueryValue()
174 }
175 };
176 this.requestType = undefined;
177
178 return resp;
179 };
180
181 /**
182 * @inheritdoc mw.widgets.TitleWidget
183 */
184 mw.widgets.SearchInputWidget.prototype.getOptionsFromData = function ( data ) {
185 var items = [],
186 self = this;
187
188 $.each( data.data[ 1 ], function ( i, result ) {
189 items.push( new mw.widgets.TitleOptionWidget(
190 self.getOptionWidgetData(
191 result,
192 // Create a result object that looks like the one from
193 // the parent's API query.
194 {
195 data: result,
196 // data[ 3 ][ i ] is the link for this result
197 url: data.data[ 3 ][ i ],
198 imageUrl: null,
199 description: null,
200 missing: false,
201 redirect: false,
202 disambiguation: false
203 }
204 )
205 ) );
206 } );
207
208 mw.track( 'mw.widgets.SearchInputWidget', {
209 action: 'impression-results',
210 numberOfResults: items.length,
211 resultSetType: data.metadata.type,
212 query: data.metadata.query,
213 inputLocation: this.dataLocation || 'header'
214 } );
215
216 return items;
217 };
218
219 /**
220 * @inheritdoc
221 */
222 mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function () {
223 mw.widgets.SearchInputWidget.parent.prototype.onLookupMenuItemChoose.apply( this, arguments );
224
225 if ( this.performSearchOnClick ) {
226 this.$element.closest( 'form' ).submit();
227 }
228 };
229
230 /**
231 * @inheritdoc
232 */
233 mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function () {
234 var items = mw.widgets.SearchInputWidget.parent.prototype.getLookupMenuOptionsFromData.apply(
235 this, arguments
236 );
237
238 this.lastLookupItems = items.map( function ( item ) {
239 return item.data;
240 } );
241
242 return items;
243 };
244
245 }( jQuery, mediaWiki ) );