Merge "RCFilters: Change tooltip messages for view buttons"
[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 OO.ui.SearchInputWidget
14 * @mixins mw.widgets.TitleWidget
15 * @mixins OO.ui.mixin.LookupElement
16 *
17 * @constructor
18 * @param {Object} [config] Configuration options
19 * @cfg {boolean} [pushPending=false] Visually mark the input field as "pending", while
20 * requesting suggestions.
21 * @cfg {boolean} [performSearchOnClick=true] If true, the script will start a search when-
22 * ever a user hits a suggestion. If false, the text of the suggestion is inserted into the
23 * text field only.
24 * @cfg {string} [dataLocation='header'] Where the search input field will be
25 * used (header or content).
26 */
27 mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) {
28 // The parent constructors will detach this from the DOM, and won't
29 // be reattached until after this function is completed. As such
30 // grab a handle here. If no config.$input is passed tracking of
31 // form submissions won't work.
32 var $form = config.$input ? config.$input.closest( 'form' ) : $();
33
34 config = $.extend( {
35 performSearchOnClick: true,
36 dataLocation: 'header'
37 }, config );
38
39 // Parent constructor
40 mw.widgets.SearchInputWidget.parent.call( this, $.extend( {}, config, {
41 autocomplete: false
42 } ) );
43
44 // Mixin constructors
45 mw.widgets.TitleWidget.call( this, config );
46 OO.ui.mixin.LookupElement.call( this, config );
47
48 // Initialization
49 this.$element.addClass( 'mw-widget-searchInputWidget' );
50 this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' );
51 this.lastLookupItems = [];
52 if ( !config.pushPending ) {
53 // TODO This actually overrides a method, that's pretty crazy. Surely there's a better way?
54 this.pushPending = false;
55 }
56 if ( config.dataLocation ) {
57 this.dataLocation = config.dataLocation;
58 }
59 if ( config.performSearchOnClick ) {
60 this.performSearchOnClick = config.performSearchOnClick;
61 }
62 this.setLookupsDisabled( !this.suggestions );
63
64 $form.on( 'submit', function () {
65 mw.track( 'mw.widgets.SearchInputWidget', {
66 action: 'submit-form',
67 numberOfResults: this.lastLookupItems.length,
68 $form: $form,
69 inputLocation: this.dataLocation || 'header',
70 index: this.lastLookupItems.indexOf(
71 this.$input.val()
72 )
73 } );
74 }.bind( this ) );
75 };
76
77 /* Setup */
78
79 OO.inheritClass( mw.widgets.SearchInputWidget, OO.ui.SearchInputWidget );
80 OO.mixinClass( mw.widgets.SearchInputWidget, mw.widgets.TitleWidget );
81 OO.mixinClass( mw.widgets.SearchInputWidget, OO.ui.mixin.LookupElement );
82
83 /* Methods */
84
85 /**
86 * @inheritdoc mw.widgets.TitleWidget
87 */
88 mw.widgets.SearchInputWidget.prototype.getQueryValue = function () {
89 return this.getValue();
90 };
91
92 /**
93 * @inheritdoc OO.ui.mixin.LookupElement
94 */
95 mw.widgets.SearchInputWidget.prototype.getLookupRequest = function () {
96 return this.getSuggestionsPromise();
97 };
98
99 /**
100 * @inheritdoc mw.widgets.TitleWidget
101 */
102 mw.widgets.SearchInputWidget.prototype.getSuggestionsPromise = function () {
103 var api = this.getApi(),
104 promise,
105 self = this;
106
107 // reuse the searchSuggest function from mw.searchSuggest
108 promise = mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit, this.getNamespace() );
109
110 // tracking purposes
111 promise.done( function ( data, jqXHR ) {
112 self.requestType = jqXHR.getResponseHeader( 'X-OpenSearch-Type' );
113 } );
114
115 return promise;
116 };
117
118 /**
119 * @inheritdoc OO.ui.mixin.LookupElement
120 */
121 mw.widgets.SearchInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
122 var resp;
123
124 resp = {
125 data: response || {},
126 metadata: {
127 type: this.requestType || 'unknown',
128 query: this.getQueryValue()
129 }
130 };
131 this.requestType = undefined;
132
133 return resp;
134 };
135
136 /**
137 * @inheritdoc mw.widgets.TitleWidget
138 */
139 mw.widgets.SearchInputWidget.prototype.getOptionsFromData = function ( data ) {
140 var items = [],
141 self = this;
142
143 // mw.widgets.TitleWidget does a lot more work here, because the TitleOptionWidgets can
144 // differ a lot, depending on the returned data from the request. With the request used here
145 // we get only the search results.
146 $.each( data.data[ 1 ], function ( i, result ) {
147 items.push( new mw.widgets.TitleOptionWidget(
148 // data[ 3 ][ i ] is the link for this result
149 self.getOptionWidgetData( result, null, data.data[ 3 ][ i ] )
150 ) );
151 } );
152
153 mw.track( 'mw.widgets.SearchInputWidget', {
154 action: 'impression-results',
155 numberOfResults: items.length,
156 resultSetType: data.metadata.type,
157 query: data.metadata.query,
158 inputLocation: this.dataLocation || 'header'
159 } );
160
161 return items;
162 };
163
164 /**
165 * @inheritdoc mw.widgets.TitleWidget
166 *
167 * @param {string} title
168 * @param {Object} data
169 * @param {string} url The Url to the result
170 */
171 mw.widgets.SearchInputWidget.prototype.getOptionWidgetData = function ( title, data, url ) {
172 // the values used in mw.widgets-TitleWidget doesn't exist here, that's why
173 // the values are hard-coded here
174 return {
175 data: title,
176 url: url,
177 imageUrl: null,
178 description: null,
179 missing: false,
180 redirect: false,
181 disambiguation: false,
182 query: this.getQueryValue()
183 };
184 };
185
186 /**
187 * @inheritdoc OO.ui.mixin.LookupElement
188 */
189 mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function ( item ) {
190 this.closeLookupMenu();
191 this.setLookupsDisabled( true );
192 this.setValue( item.getData() );
193 this.setLookupsDisabled( !this.suggestions );
194
195 if ( this.performSearchOnClick ) {
196 this.$element.closest( 'form' ).submit();
197 }
198 };
199
200 /**
201 * @inheritdoc OO.ui.mixin.LookupElement
202 */
203 mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function ( response ) {
204 var items = this.getOptionsFromData( response );
205
206 this.lastLookupItems = items.map( function ( item ) {
207 return item.data;
208 } );
209
210 return items;
211 };
212
213 /**
214 * @inheritdoc
215 */
216 mw.widgets.SearchInputWidget.prototype.focus = function () {
217 var retval;
218
219 // Prevent programmatic focus from opening the menu
220 this.setLookupsDisabled( true );
221
222 // Parent method
223 retval = mw.widgets.SearchInputWidget.parent.prototype.focus.apply( this, arguments );
224
225 this.setLookupsDisabled( !this.suggestions );
226
227 return retval;
228 };
229
230 }( jQuery, mediaWiki ) );