194f7149debf5b15ace3441e7834760a961b0a60
[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 namespace: 0
38 }, config );
39
40 // Parent constructor
41 mw.widgets.SearchInputWidget.parent.call( this, config );
42
43 // Initialization
44 this.$element.addClass( 'mw-widget-searchInputWidget' );
45 this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' );
46 this.lastLookupItems = [];
47 if ( !config.pushPending ) {
48 this.pushPending = false;
49 }
50 if ( config.dataLocation ) {
51 this.dataLocation = config.dataLocation;
52 }
53 if ( config.performSearchOnClick ) {
54 this.performSearchOnClick = config.performSearchOnClick;
55 }
56 this.setLookupsDisabled( !this.suggestions );
57
58 $form.on( 'submit', function () {
59 mw.track( 'mw.widgets.SearchInputWidget', {
60 action: 'submit-form',
61 numberOfResults: this.lastLookupItems.length,
62 $form: $form,
63 inputLocation: this.dataLocation || 'header',
64 index: this.lastLookupItems.indexOf(
65 this.$input.val()
66 )
67 } );
68 }.bind( this ) );
69
70 this.connect( this, {
71 change: 'onChange'
72 } );
73
74 this.$element.addClass( 'oo-ui-textInputWidget-type-search' );
75 this.updateSearchIndicator();
76 this.connect( this, {
77 disable: 'onDisable'
78 } );
79 };
80
81 /* Setup */
82
83 OO.inheritClass( mw.widgets.SearchInputWidget, mw.widgets.TitleInputWidget );
84
85 /* Methods */
86
87 /**
88 * @inheritdoc
89 * @protected
90 */
91 mw.widgets.SearchInputWidget.prototype.getInputElement = function () {
92 return $( '<input>' ).attr( 'type', 'search' );
93 };
94
95 /**
96 * @inheritdoc
97 */
98 mw.widgets.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
99 if ( e.which === OO.ui.MouseButtons.LEFT ) {
100 // Clear the text field
101 this.setValue( '' );
102 this.$input[ 0 ].focus();
103 return false;
104 }
105 };
106
107 /**
108 * Update the 'clear' indicator displayed on type: 'search' text
109 * fields, hiding it when the field is already empty or when it's not
110 * editable.
111 */
112 mw.widgets.SearchInputWidget.prototype.updateSearchIndicator = function () {
113 if ( this.getValue() === '' || this.isDisabled() || this.isReadOnly() ) {
114 this.setIndicator( null );
115 } else {
116 this.setIndicator( 'clear' );
117 }
118 };
119
120 /**
121 * @see OO.ui.SearchInputWidget#onChange
122 */
123 mw.widgets.SearchInputWidget.prototype.onChange = function () {
124 this.updateSearchIndicator();
125 };
126
127 /**
128 * Handle disable events.
129 *
130 * @param {boolean} disabled Element is disabled
131 * @private
132 */
133 mw.widgets.SearchInputWidget.prototype.onDisable = function () {
134 this.updateSearchIndicator();
135 };
136
137 /**
138 * @inheritdoc
139 */
140 mw.widgets.SearchInputWidget.prototype.setReadOnly = function ( state ) {
141 mw.widgets.SearchInputWidget.parent.prototype.setReadOnly.call( this, state );
142 this.updateSearchIndicator();
143 return this;
144 };
145
146 /**
147 * @inheritdoc mw.widgets.TitleWidget
148 */
149 mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
150 var api = this.getApi(),
151 promise,
152 self = this;
153
154 // reuse the searchSuggest function from mw.searchSuggest
155 promise = mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit, this.getNamespace() );
156
157 // tracking purposes
158 promise.done( function ( data, jqXHR ) {
159 self.requestType = jqXHR.getResponseHeader( 'X-OpenSearch-Type' );
160 } );
161
162 return promise;
163 };
164
165 /**
166 * @inheritdoc mw.widgets.TitleInputWidget
167 */
168 mw.widgets.SearchInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
169 var resp;
170
171 // mw.widgets.TitleInputWidget uses response.query, which doesn't exist for opensearch,
172 // so return the whole response (titles only, and links)
173 resp = {
174 data: response || {},
175 metadata: {
176 type: this.requestType || 'unknown',
177 query: this.getQueryValue()
178 }
179 };
180 this.requestType = undefined;
181
182 return resp;
183 };
184
185 /**
186 * @inheritdoc mw.widgets.TitleWidget
187 */
188 mw.widgets.SearchInputWidget.prototype.getOptionsFromData = function ( data ) {
189 var items = [],
190 self = this;
191
192 $.each( data.data[ 1 ], function ( i, result ) {
193 items.push( new mw.widgets.TitleOptionWidget(
194 self.getOptionWidgetData(
195 result,
196 // Create a result object that looks like the one from
197 // the parent's API query.
198 {
199 data: result,
200 // data[ 3 ][ i ] is the link for this result
201 url: data.data[ 3 ][ i ],
202 imageUrl: null,
203 description: null,
204 missing: false,
205 redirect: false,
206 disambiguation: false
207 }
208 )
209 ) );
210 } );
211
212 mw.track( 'mw.widgets.SearchInputWidget', {
213 action: 'impression-results',
214 numberOfResults: items.length,
215 resultSetType: data.metadata.type,
216 query: data.metadata.query,
217 inputLocation: this.dataLocation || 'header'
218 } );
219
220 return items;
221 };
222
223 /**
224 * @inheritdoc
225 */
226 mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function () {
227 mw.widgets.SearchInputWidget.parent.prototype.onLookupMenuItemChoose.apply( this, arguments );
228
229 if ( this.performSearchOnClick ) {
230 this.$element.closest( 'form' ).submit();
231 }
232 };
233
234 /**
235 * @inheritdoc
236 */
237 mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function () {
238 var items = mw.widgets.SearchInputWidget.parent.prototype.getLookupMenuOptionsFromData.apply(
239 this, arguments
240 );
241
242 this.lastLookupItems = items.map( function ( item ) {
243 return item.data;
244 } );
245
246 return items;
247 };
248
249 }( jQuery, mediaWiki ) );