2 * MediaWiki Widgets - MediaSearchWidget class.
4 * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
5 * @license The MIT License (MIT); see LICENSE.txt
10 * Creates an mw.widgets.MediaSearchWidget object.
13 * @extends OO.ui.SearchWidget
16 * @param {Object} [config] Configuration options
17 * @param {number} [size] Vertical size of thumbnails
19 mw
.widgets
.MediaSearchWidget
= function MwWidgetsMediaSearchWidget( config
) {
20 // Configuration initialization
22 placeholder
: mw
.msg( 'mw-widgets-mediasearch-input-placeholder' )
26 mw
.widgets
.MediaSearchWidget
.super.call( this, config
);
30 this.lastQueryValue
= '';
31 this.searchQueue
= new mw
.widgets
.MediaSearchQueue( {
32 limit
: this.constructor.static.limit
,
33 threshold
: this.constructor.static.threshold
36 this.queryTimeout
= null;
39 this.lang
= config
.lang
|| 'en';
40 this.$panels
= config
.$panels
;
42 this.externalLinkUrlProtocolsRegExp
= new RegExp(
43 '^(' + mw
.config
.get( 'wgUrlProtocols' ) + ')',
47 // Masonry fit properties
49 this.rowHeight
= config
.rowHeight
|| 200;
50 this.layoutQueue
= [];
52 this.currentItemCache
= [];
54 this.resultsSize
= {};
58 this.noItemsMessage
= new OO
.ui
.LabelWidget( {
59 label
: mw
.msg( 'mw-widgets-mediasearch-noresults' ),
60 classes
: [ 'mw-widget-mediaSearchWidget-noresults' ]
62 this.noItemsMessage
.toggle( false );
65 this.$results
.on( 'scroll', this.onResultsScroll
.bind( this ) );
66 this.$query
.append( this.noItemsMessage
.$element
);
67 this.results
.connect( this, {
69 remove
: 'onResultsRemove'
72 this.resizeHandler
= OO
.ui
.debounce( this.afterResultsResize
.bind( this ), 500 );
75 this.$element
.addClass( 'mw-widget-mediaSearchWidget' );
80 OO
.inheritClass( mw
.widgets
.MediaSearchWidget
, OO
.ui
.SearchWidget
);
82 /* Static properties */
84 mw
.widgets
.MediaSearchWidget
.static.limit
= 10;
86 mw
.widgets
.MediaSearchWidget
.static.threshold
= 5;
91 * Respond to window resize and check if the result display should
94 mw
.widgets
.MediaSearchWidget
.prototype.afterResultsResize = function () {
95 var items
= this.currentItemCache
;
100 this.resultsSize
.width
!== this.$results
.width() ||
101 this.resultsSize
.height
!== this.$results
.height()
106 this.processQueueResults( items
);
107 if ( this.results
.getItems().length
> 0 ) {
108 this.lazyLoadResults();
113 width
: this.$results
.width(),
114 height
: this.$results
.height()
120 * Teardown the widget; disconnect the window resize event.
122 mw
.widgets
.MediaSearchWidget
.prototype.teardown = function () {
123 $( window
).off( 'resize', this.resizeHandler
);
127 * Setup the widget; activate the resize event.
129 mw
.widgets
.MediaSearchWidget
.prototype.setup = function () {
130 $( window
).on( 'resize', this.resizeHandler
);
134 * Query all sources for media.
138 mw
.widgets
.MediaSearchWidget
.prototype.queryMediaQueue = function () {
140 value
= this.getQueryValue();
142 if ( value
=== '' ) {
146 this.query
.pushPending();
147 search
.noItemsMessage
.toggle( false );
149 this.searchQueue
.setSearchQuery( value
);
150 this.searchQueue
.get( this.constructor.static.limit
)
151 .then( function ( items
) {
152 if ( items
.length
> 0 ) {
153 search
.processQueueResults( items
);
154 search
.currentItemCache
= search
.currentItemCache
.concat( items
);
157 search
.query
.popPending();
158 search
.noItemsMessage
.toggle( search
.results
.getItems().length
=== 0 );
159 if ( search
.results
.getItems().length
> 0 ) {
160 search
.lazyLoadResults();
167 * Process the media queue giving more items
170 * @param {Object[]} items Given items by the media queue
172 mw
.widgets
.MediaSearchWidget
.prototype.processQueueResults = function ( items
) {
175 inputSearchQuery
= this.getQueryValue(),
176 queueSearchQuery
= this.searchQueue
.getSearchQuery();
178 if ( inputSearchQuery
=== '' || queueSearchQuery
!== inputSearchQuery
) {
182 for ( i
= 0, len
= items
.length
; i
< len
; i
++ ) {
183 title
= new mw
.Title( items
[ i
].title
).getMainText();
184 // Do not insert duplicates
185 if ( !Object
.prototype.hasOwnProperty
.call( this.itemCache
, title
) ) {
186 this.itemCache
[ title
] = true;
188 new mw
.widgets
.MediaResultWidget( {
190 rowHeight
: this.rowHeight
,
191 maxWidth
: this.results
.$element
.width() / 3,
193 rowWidth
: this.results
.$element
.width()
198 this.results
.addItems( resultWidgets
);
203 * Get the sanitized query value from the input
205 * @return {string} Query value
207 mw
.widgets
.MediaSearchWidget
.prototype.getQueryValue = function () {
208 var queryValue
= this.query
.getValue().trim();
210 if ( queryValue
.match( this.externalLinkUrlProtocolsRegExp
) ) {
211 queryValue
= queryValue
.match( /.+\/([^\/]+)/ )[ 1 ];
217 * Handle search value change
219 * @param {string} value New value
221 mw
.widgets
.MediaSearchWidget
.prototype.onQueryChange = function () {
222 // Get the sanitized query value
223 var queryValue
= this.getQueryValue();
225 if ( queryValue
=== this.lastQueryValue
) {
230 mw
.widgets
.MediaSearchWidget
.super.prototype.onQueryChange
.apply( this, arguments
);
234 this.currentItemCache
= [];
237 // Empty the results queue
238 this.layoutQueue
= [];
240 // Change resource queue query
241 this.searchQueue
.setSearchQuery( queryValue
);
242 this.lastQueryValue
= queryValue
;
245 clearTimeout( this.queryTimeout
);
246 this.queryTimeout
= setTimeout( this.queryMediaQueue
.bind( this ), 350 );
250 * Handle results scroll events.
252 * @param {jQuery.Event} e Scroll event
254 mw
.widgets
.MediaSearchWidget
.prototype.onResultsScroll = function () {
255 var position
= this.$results
.scrollTop() + this.$results
.outerHeight(),
256 threshold
= this.results
.$element
.outerHeight() - this.rowHeight
* 3;
258 // Check if we need to ask for more results
259 if ( !this.query
.isPending() && position
> threshold
) {
260 this.queryMediaQueue();
263 this.lazyLoadResults();
267 * Lazy-load the images that are visible.
269 mw
.widgets
.MediaSearchWidget
.prototype.lazyLoadResults = function () {
271 items
= this.results
.getItems(),
272 resultsScrollTop
= this.$results
.scrollTop(),
273 position
= resultsScrollTop
+ this.$results
.outerHeight();
276 for ( i
= 0; i
< items
.length
; i
++ ) {
277 elementTop
= items
[ i
].$element
.position().top
;
278 if ( elementTop
<= position
&& !items
[ i
].hasSrc() ) {
280 items
[ i
].lazyLoad();
286 * Reset all the rows; destroy the jQuery elements and reset
289 mw
.widgets
.MediaSearchWidget
.prototype.resetRows = function () {
292 for ( i
= 0, len
= this.rows
.length
; i
< len
; i
++ ) {
293 this.rows
[ i
].$element
.remove();
301 * Find an available row at the end. Either we will need to create a new
302 * row or use the last available row if it isn't full.
304 * @return {number} Row index
306 mw
.widgets
.MediaSearchWidget
.prototype.getAvailableRow = function () {
309 if ( this.rows
.length
=== 0 ) {
312 row
= this.rows
.length
- 1;
315 if ( !this.rows
[ row
] ) {
321 $element
: $( '<div>' )
322 .addClass( 'mw-widget-mediaResultWidget-row' )
327 .attr( 'data-full', false )
330 this.results
.$element
.append( this.rows
[ row
].$element
);
331 } else if ( this.rows
[ row
].isFull
) {
338 $element
: $( '<div>' )
339 .addClass( 'mw-widget-mediaResultWidget-row' )
344 .attr( 'data-full', false )
347 this.results
.$element
.append( this.rows
[ row
].$element
);
354 * Respond to add results event in the results widget.
355 * Override the way SelectWidget and GroupElement append the items
356 * into the group so we can append them in groups of rows.
358 * @param {mw.widgets.MediaResultWidget[]} items An array of item elements
360 mw
.widgets
.MediaSearchWidget
.prototype.onResultsAdd = function ( items
) {
363 // Add method to a queue; this queue will only run when the widget
365 this.layoutQueue
.push( function () {
366 var i
, j
, ilen
, jlen
, itemWidth
, row
, effectiveWidth
,
368 maxRowWidth
= search
.results
.$element
.width() - 15;
370 // Go over the added items
371 row
= search
.getAvailableRow();
372 for ( i
= 0, ilen
= items
.length
; i
< ilen
; i
++ ) {
373 itemWidth
= items
[ i
].$element
.outerWidth( true );
375 // Add items to row until it is full
376 if ( search
.rows
[ row
].width
+ itemWidth
>= maxRowWidth
) {
377 // Mark this row as full
378 search
.rows
[ row
].isFull
= true;
379 search
.rows
[ row
].$element
.attr( 'data-full', true );
381 // Find the resize factor
382 effectiveWidth
= search
.rows
[ row
].width
;
383 resizeFactor
= maxRowWidth
/ effectiveWidth
;
385 search
.rows
[ row
].$element
.attr( 'data-effectiveWidth', effectiveWidth
);
386 search
.rows
[ row
].$element
.attr( 'data-resizeFactor', resizeFactor
);
387 search
.rows
[ row
].$element
.attr( 'data-row', row
);
389 // Resize all images in the row to fit the width
390 for ( j
= 0, jlen
= search
.rows
[ row
].items
.length
; j
< jlen
; j
++ ) {
391 search
.rows
[ row
].items
[ j
].resizeThumb( resizeFactor
);
395 row
= search
.getAvailableRow();
398 // Add the cumulative
399 search
.rows
[ row
].width
+= itemWidth
;
401 // Store reference to the item and to the row
402 search
.rows
[ row
].items
.push( items
[ i
] );
403 items
[ i
].setRow( row
);
406 search
.rows
[ row
].$element
.append( items
[ i
].$element
);
409 // If we have less than 4 rows, call for more images
410 if ( search
.rows
.length
< 4 ) {
411 search
.queryMediaQueue();
414 this.runLayoutQueue();
418 * Run layout methods from the queue only if the element is visible.
420 mw
.widgets
.MediaSearchWidget
.prototype.runLayoutQueue = function () {
423 if ( this.$element
.is( ':visible' ) ) {
424 for ( i
= 0, len
= this.layoutQueue
.length
; i
< len
; i
++ ) {
425 this.layoutQueue
.pop()();
431 * Respond to removing results event in the results widget.
432 * Clear the relevant rows.
434 * @param {OO.ui.OptionWidget[]} items Removed items
436 mw
.widgets
.MediaSearchWidget
.prototype.onResultsRemove = function ( items
) {
437 if ( items
.length
> 0 ) {
438 // In the case of the media search widget, if any items are removed
439 // all are removed (new search)
441 this.currentItemCache
= [];
446 * Set language for the search results.
448 * @param {string} lang Language
450 mw
.widgets
.MediaSearchWidget
.prototype.setLang = function ( lang
) {
455 * Get language for the search results.
457 * @return {string} lang Language
459 mw
.widgets
.MediaSearchWidget
.prototype.getLang = function () {
462 }( jQuery
, mediaWiki
) );