2 * MediaWiki Widgets - TitleInputWidget class.
4 * @copyright 2011-2015 MediaWiki Widgets Team and others; see AUTHORS.txt
5 * @license The MIT License (MIT); see LICENSE.txt
9 * Creates an mw.widgets.TitleInputWidget object.
12 * @extends OO.ui.TextInputWidget
13 * @mixins OO.ui.mixin.LookupElement
16 * @param {Object} [config] Configuration options
17 * @cfg {number} [limit=10] Number of results to show
18 * @cfg {number} [namespace] Namespace to prepend to queries
19 * @cfg {boolean} [showRedirectTargets=true] Show the targets of redirects
20 * @cfg {boolean} [showRedlink] Show red link to exact match if it doesn't exist
21 * @cfg {boolean} [showImages] Show page images
22 * @cfg {boolean} [showDescriptions] Show page descriptions
23 * @cfg {Object} [cache] Result cache which implements a 'set' method, taking keyed values as an argument
25 mw
.widgets
.TitleInputWidget
= function MwWidgetsTitleInputWidget( config
) {
28 // Config initialization
29 config
= config
|| {};
32 OO
.ui
.TextInputWidget
.call( this, config
);
35 OO
.ui
.mixin
.LookupElement
.call( this, config
);
38 this.limit
= config
.limit
|| 10;
39 this.namespace = config
.namespace || null;
40 this.showRedirectTargets
= config
.showRedirectTargets
!== false;
41 this.showRedlink
= !!config
.showRedlink
;
42 this.showImages
= !!config
.showImages
;
43 this.showDescriptions
= !!config
.showDescriptions
;
44 this.cache
= config
.cache
;
47 this.$element
.addClass( 'mw-widget-titleInputWidget' );
48 this.lookupMenu
.$element
.addClass( 'mw-widget-titleInputWidget-menu' );
49 if ( this.showImages
) {
50 this.lookupMenu
.$element
.addClass( 'mw-widget-titleInputWidget-menu-withImages' );
52 if ( this.showDescriptions
) {
53 this.lookupMenu
.$element
.addClass( 'mw-widget-titleInputWidget-menu-withDescriptions' );
56 this.interwikiPrefixes
= [];
57 this.interwikiPrefixesPromise
= new mw
.Api().get( {
60 siprop
: 'interwikimap'
61 } ).done( function ( data
) {
62 $.each( data
.query
.interwikimap
, function ( index
, interwiki
) {
63 widget
.interwikiPrefixes
.push( interwiki
.prefix
);
70 OO
.inheritClass( mw
.widgets
.TitleInputWidget
, OO
.ui
.TextInputWidget
);
72 OO
.mixinClass( mw
.widgets
.TitleInputWidget
, OO
.ui
.mixin
.LookupElement
);
79 mw
.widgets
.TitleInputWidget
.prototype.onLookupMenuItemChoose = function ( item
) {
80 this.closeLookupMenu();
81 this.setLookupsDisabled( true );
82 this.setValue( item
.getData() );
83 this.setLookupsDisabled( false );
89 mw
.widgets
.TitleInputWidget
.prototype.focus = function () {
92 // Prevent programmatic focus from opening the menu
93 this.setLookupsDisabled( true );
96 retval
= OO
.ui
.TextInputWidget
.prototype.focus
.apply( this, arguments
);
98 this.setLookupsDisabled( false );
106 mw
.widgets
.TitleInputWidget
.prototype.getLookupRequest = function () {
109 promiseAbortObject
= { abort: function () {
110 // Do nothing. This is just so OOUI doesn't break due to abort being undefined.
113 if ( mw
.Title
.newFromText( this.value
) ) {
114 return this.interwikiPrefixesPromise
.then( function () {
116 interwiki
= widget
.value
.substring( 0, widget
.value
.indexOf( ':' ) );
118 interwiki
&& interwiki
!== '' &&
119 widget
.interwikiPrefixes
.indexOf( interwiki
) !== -1
121 return $.Deferred().resolve( { query
: {
125 } } ).promise( promiseAbortObject
);
129 generator
: 'prefixsearch',
130 gpssearch
: widget
.value
,
131 gpsnamespace
: widget
.namespace !== null ? widget
.namespace : undefined,
132 gpslimit
: widget
.limit
,
133 ppprop
: 'disambiguation'
135 props
= [ 'info', 'pageprops' ];
136 if ( widget
.showRedirectTargets
) {
137 params
.redirects
= '1';
139 if ( widget
.showImages
) {
140 props
.push( 'pageimages' );
141 params
.pithumbsize
= 80;
142 params
.pilimit
= widget
.limit
;
144 if ( widget
.showDescriptions
) {
145 props
.push( 'pageterms' );
146 params
.wbptterms
= 'description';
148 params
.prop
= props
.join( '|' );
149 req
= new mw
.Api().get( params
);
150 promiseAbortObject
.abort
= req
.abort
.bind( req
); // todo: ew
153 } ).promise( promiseAbortObject
);
155 // Don't send invalid titles to the API.
156 // Just pretend it returned nothing so we can show the 'invalid title' section
157 return $.Deferred().resolve( {} ).promise( promiseAbortObject
);
162 * Get lookup cache item from server response data.
165 * @param {Mixed} data Response from server
167 mw
.widgets
.TitleInputWidget
.prototype.getLookupCacheDataFromResponse = function ( data
) {
168 return data
.query
|| {};
172 * Get list of menu items from a server response.
174 * @param {Object} data Query result
175 * @returns {OO.ui.MenuOptionWidget[]} Menu items
177 mw
.widgets
.TitleInputWidget
.prototype.getLookupMenuOptionsFromData = function ( data
) {
178 var i
, len
, index
, pageExists
, pageExistsExact
, suggestionPage
, page
, redirect
, redirects
,
181 titleObj
= mw
.Title
.newFromText( this.value
),
185 if ( data
.redirects
) {
186 for ( i
= 0, len
= data
.redirects
.length
; i
< len
; i
++ ) {
187 redirect
= data
.redirects
[i
];
188 redirectsTo
[redirect
.to
] = redirectsTo
[redirect
.to
] || [];
189 redirectsTo
[redirect
.to
].push( redirect
.from );
193 for ( index
in data
.pages
) {
194 suggestionPage
= data
.pages
[index
];
195 pageData
[suggestionPage
.title
] = {
196 missing
: suggestionPage
.missing
!== undefined,
197 redirect
: suggestionPage
.redirect
!== undefined,
198 disambiguation
: OO
.getProp( suggestionPage
, 'pageprops', 'disambiguation' ) !== undefined,
199 imageUrl
: OO
.getProp( suggestionPage
, 'thumbnail', 'source' ),
200 description
: OO
.getProp( suggestionPage
, 'terms', 'description' )
202 titles
.push( suggestionPage
.title
);
204 redirects
= redirectsTo
[suggestionPage
.title
] || [];
205 for ( i
= 0, len
= redirects
.length
; i
< len
; i
++ ) {
206 pageData
[redirects
[i
]] = {
209 disambiguation
: false,
210 description
: mw
.msg( 'mw-widgets-titleinput-description-redirect', suggestionPage
.title
)
212 titles
.push( redirects
[i
] );
216 // If not found, run value through mw.Title to avoid treating a match as a
217 // mismatch where normalisation would make them matching (bug 48476)
219 pageExistsExact
= titles
.indexOf( this.value
) !== -1;
220 pageExists
= pageExistsExact
|| (
221 titleObj
&& titles
.indexOf( titleObj
.getPrefixedText() ) !== -1
225 pageData
[this.value
] = {
226 missing
: true, redirect
: false, disambiguation
: false,
227 description
: mw
.msg( 'mw-widgets-titleinput-description-new-page' )
232 this.cache
.set( pageData
);
235 // Offer the exact text as a suggestion if the page exists
236 if ( pageExists
&& !pageExistsExact
) {
237 titles
.unshift( this.value
);
239 // Offer the exact text as a new page if the title is valid
240 if ( this.showRedlink
&& !pageExists
&& titleObj
) {
241 titles
.push( this.value
);
243 for ( i
= 0, len
= titles
.length
; i
< len
; i
++ ) {
244 page
= pageData
[titles
[i
]] || {};
245 items
.push( new mw
.widgets
.TitleOptionWidget( this.getOptionWidgetData( titles
[i
], page
) ) );
252 * Get menu option widget data from the title and page data
254 * @param {mw.Title} title Title object
255 * @param {Object} data Page data
256 * @return {Object} Data for option widget
258 mw
.widgets
.TitleInputWidget
.prototype.getOptionWidgetData = function ( title
, data
) {
259 var mwTitle
= new mw
.Title( title
);
261 data
: this.namespace !== null ? mwTitle
.getRelativeText( this.namespace ) : title
,
262 imageUrl
: this.showImages
? data
.imageUrl
: null,
263 description
: this.showDescriptions
? data
.description
: null,
264 missing
: data
.missing
,
265 redirect
: data
.redirect
,
266 disambiguation
: data
.disambiguation
,
272 * Get title object corresponding to #getValue
274 * @returns {mw.Title|null} Title object, or null if value is invalid
276 mw
.widgets
.TitleInputWidget
.prototype.getTitle = function () {
277 var title
= this.getValue(),
278 // mw.Title doesn't handle null well
279 titleObj
= mw
.Title
.newFromText( title
, this.namespace !== null ? this.namespace : undefined );
287 mw
.widgets
.TitleInputWidget
.prototype.isValid = function () {
288 return $.Deferred().resolve( !!this.getTitle() ).promise();
291 }( jQuery
, mediaWiki
) );