/*!
* VisualEditor UserInterface MWMediaSelectWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw*/
/**
* Creates an ve.ui.MWMediaSelectWidget object.
*
* @class
* @extends ve.ui.Widget
*
* @constructor
* @param {Object} [config] Config options
* @param {number} [size] Vertical size of thumbnails
*/
ve.ui.MWMediaSelectWidget = function VeUiMWMediaSelectWidget( config ) {
// Configuration intialization
config = config || {};
// Parent constructor
ve.ui.Widget.call( this, config );
// Properties
this.input = new ve.ui.TextInputWidget( {
'$$': this.$$,
'placeholder': ve.msg( 'visualeditor-media-input-placeholder' ),
'value': mw.config.get( 'wgTitle' ),
'icon': 'search'
} );
this.select = new ve.ui.SelectWidget( { '$$': this.$$ } );
this.$query = this.$$( '
' );
this.$results = this.$$( '
' );
this.sources = ve.copyArray( ve.init.platform.getMediaSources() );
this.size = config.size || 150;
this.inputTimeout = null;
this.titles = {};
this.queryMediaSourcesCallback = ve.bind( this.queryMediaSources, this );
// Events
this.input.connect( this, { 'change': 'onInputChange' } );
this.select.connect( this, { 'select': 'onSelectSelect' } );
this.$results.on( 'scroll', ve.bind( this.onResultsScroll, this ) );
// Initialization
this.$query
.addClass( 've-ui-mwMediaSelectWidget-query' )
.append( this.input.$ );
this.$results
.addClass( 've-ui-mwMediaSelectWidget-results' )
.append( this.select.$ );
this.$
.addClass( 've-ui-mwMediaSelectWidget' )
.append( this.$results, this.$query );
this.queryMediaSources();
};
/* Inheritance */
ve.inheritClass( ve.ui.MWMediaSelectWidget, ve.ui.Widget );
/* Events */
/**
* @event select
* @param {Object} item Media item info
*/
/* Methods */
/**
* Handle select widget select events.
*
* @param {string} value New value
*/
ve.ui.MWMediaSelectWidget.prototype.onInputChange = function () {
var i, len;
// Reset
this.select.clearItems();
this.titles = {};
for ( i = 0, len = this.sources.length; i < len; i++ ) {
delete this.sources[i].gsroffset;
}
// Queue
clearTimeout( this.inputTimeout );
this.inputTimeout = setTimeout( this.queryMediaSourcesCallback, 100 );
};
/**
* Handle select widget select events.
*
* @param {ve.ui.MWMediaSelectItemWidget} item Selected item
* @emits select
*/
ve.ui.MWMediaSelectWidget.prototype.onSelectSelect = function ( item ) {
this.emit( 'select', item ? item.getData() : null );
};
/**
* Handle results scroll events.
*
* @param {jQuery.Event} e Scroll event
*/
ve.ui.MWMediaSelectWidget.prototype.onResultsScroll = function () {
var position = this.$results.scrollTop() + this.$results.outerHeight(),
threshold = this.select.$.outerHeight() - this.size;
if ( !this.input.isPending() && position > threshold ) {
this.queryMediaSources();
}
};
/**
* Query all sources for media.
*
* @method
*/
ve.ui.MWMediaSelectWidget.prototype.queryMediaSources = function () {
var i, len, source,
value = this.input.getValue();
if ( value === '' ) {
return;
}
for ( i = 0, len = this.sources.length; i < len; i++ ) {
source = this.sources[i];
if ( source.request ) {
source.request.abort();
}
if ( !source.gsroffset ) {
source.gsroffset = 0;
}
this.input.pushPending();
source.request = $.ajax( {
'url': source.url,
'data': {
'format': 'json',
'action': 'query',
'generator': 'search',
'gsrsearch': value,
'gsrnamespace': 6,
'gsrlimit': 15,
'gsroffset': source.gsroffset,
'prop': 'imageinfo',
'iiprop': 'dimensions|url',
'iiurlheight': this.size
},
// This request won't be cached since the JSON-P callback is unique. However make sure
// to allow jQuery to cache otherwise so it won't e.g. add "&_=(random)" which will
// trigger a MediaWiki API error for invalid parameter "_".
'cache': true,
// TODO: Only use JSON-P for cross-domain.
// jQuery has this logic built-in (if url is not same-origin ..)
// but isn't working for some reason.
'dataType': 'jsonp'
} )
.done( ve.bind( this.onMediaQueryDone, this, source ) )
.always( ve.bind( this.onMediaQueryAlways, this, source ) );
source.value = value;
}
};
/**
* Handle media query response events.
*
* @method
* @param {Object} source Media query source
*/
ve.ui.MWMediaSelectWidget.prototype.onMediaQueryAlways = function ( source ) {
source.request = null;
this.input.popPending();
};
/**
* Handle media query load events.
*
* @method
* @param {Object} source Media query source
* @param {Object} data Media query response
*/
ve.ui.MWMediaSelectWidget.prototype.onMediaQueryDone = function ( source, data ) {
if ( !data.query || !data.query.pages ) {
return;
}
var page, title,
items = [],
pages = data.query.pages,
value = this.input.getValue();
if ( value === '' || value !== source.value ) {
return;
}
if ( data['query-continue'] && data['query-continue'].search ) {
source.gsroffset = data['query-continue'].search.gsroffset;
}
for ( page in pages ) {
title = pages[page].title;
if ( !( title in this.titles ) ) {
this.titles[title] = true;
items.push(
new ve.ui.MWMediaSelectItemWidget(
pages[page],
{ '$$': this.$$, 'size': this.size }
)
);
}
}
this.select.addItems( items );
};