mediawiki-extensions-Visual.../modules/ve/ui/widgets/ve.ui.LookupInputWidget.js
Roan Kattouw d5365d2b5a Correctly distinguish between Deferred and Promise
Deferred is read-write, Promise is read-only. Document and return
the correct types.

Change-Id: Icfd6efb07b222c73d49b97ddbfe6565b8542ad00
2013-06-18 10:56:56 -07:00

224 lines
5.5 KiB
JavaScript

/*!
* VisualEditor UserInterface LookupInputWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Lookup input widget.
*
* Mixin that adds a menu showing suggested values to a text input. Subclasses must handle `select`
* events on #lookupMenu to make use of selections.
*
* @class
* @abstract
*
* @constructor
* @param {ve.ui.TextInputWidget} input Input widget
* @param {Object} [config] Config options
* @cfg {jQuery} [$overlay=this.$$( '.ve-surface-overlay-local:last' )] Overlay layer
*/
ve.ui.LookupInputWidget = function VeUiLookupInputWidget( input, config ) {
// Config intialization
config = config || {};
// Properties
this.lookupInput = input;
this.$overlay = config.$overlay || this.$$( '.ve-surface-overlay-local:last' );
this.lookupMenu = new ve.ui.TextInputMenuWidget( this, {
'$$': ve.Element.static.get$$( this.$overlay ),
'input': this.lookupInput,
'$container': config.$container
} );
this.lookupCache = {};
this.lookupQuery = null;
this.lookupRequest = null;
// Events
this.$overlay.append( this.lookupMenu.$ );
this.lookupInput.$input.on( {
'focus': ve.bind( this.onLookupInputFocus, this ),
'blur': ve.bind( this.onLookupInputBlur, this ),
'mousedown': ve.bind( this.onLookupInputMouseDown, this )
} );
this.lookupInput.connect( this, { 'change': 'onLookupInputChange' } );
// Initialization
this.$.addClass( 've-ui-lookupWidget' );
this.lookupMenu.$.addClass( 've-ui-lookupWidget-menu' );
};
/* Methods */
/**
* Handle input focus event.
*
* @method
* @param {jQuery.Event} e Input focus event
*/
ve.ui.LookupInputWidget.prototype.onLookupInputFocus = function () {
this.openLookupMenu();
};
/**
* Handle input blur event.
*
* @method
* @param {jQuery.Event} e Input blur event
*/
ve.ui.LookupInputWidget.prototype.onLookupInputBlur = function () {
this.lookupMenu.hide();
};
/**
* Handle input mouse down event.
*
* @method
* @param {jQuery.Event} e Input mouse down event
*/
ve.ui.LookupInputWidget.prototype.onLookupInputMouseDown = function () {
this.openLookupMenu();
};
/**
* Handle input change event.
*
* @method
* @param {string} value New input value
*/
ve.ui.LookupInputWidget.prototype.onLookupInputChange = function () {
this.openLookupMenu();
};
/**
* Open the menu.
*
* @method
* @chainable
*/
ve.ui.LookupInputWidget.prototype.openLookupMenu = function () {
var value = this.lookupInput.getValue();
if ( value.length && $.trim( value ) !== '' ) {
this.populateLookupMenu();
if ( !this.lookupMenu.isVisible() ) {
this.lookupMenu.show();
}
} else {
this.lookupMenu.hide();
}
return this;
};
/**
* Populate lookup menu with current information.
*
* @method
* @chainable
*/
ve.ui.LookupInputWidget.prototype.populateLookupMenu = function () {
var items = this.getLookupMenuItems();
this.lookupMenu.clearItems();
if ( items.length ) {
this.lookupMenu.addItems( items );
this.initializeLookupMenuSelection();
}
return this;
};
/**
* Set selection in the lookup menu with current information.
*
* @method
* @chainable
*/
ve.ui.LookupInputWidget.prototype.initializeLookupMenuSelection = function () {
if ( !this.lookupMenu.getSelectedItem() ) {
this.lookupMenu.selectItem( this.lookupMenu.getClosestSelectableItem( 0 ), true );
}
this.lookupMenu.highlightItem( this.lookupMenu.getSelectedItem() );
};
/**
* Get lookup menu items for the current query.
*
* @method
* @returns {ve.ui.MenuItemWidget[]} Menu items
*/
ve.ui.LookupInputWidget.prototype.getLookupMenuItems = function () {
var value = this.lookupInput.getValue();
if ( value && value !== this.lookupQuery ) {
// Abort current request if query has changed
if ( this.lookupRequest ) {
this.lookupRequest.abort();
this.lookupQuery = null;
this.lookupRequest = null;
}
if ( value in this.lookupCache ) {
return this.getLookupMenuItemsFromData( this.lookupCache[value] );
} else {
this.lookupQuery = value;
this.lookupRequest = this.getLookupRequest()
.always( ve.bind( function () {
this.lookupQuery = null;
this.lookupRequest = null;
}, this ) )
.done( ve.bind( function ( data ) {
this.lookupCache[value] = this.getLookupCacheItemFromData( data );
this.openLookupMenu();
}, this ) );
this.pushPending();
this.lookupRequest.always( ve.bind( function () {
this.popPending();
}, this ) );
}
}
return [];
};
/**
* Get a new request object of the current lookup query value.
*
* @method
* @abstract
* @returns {jqXHR} jQuery AJAX object, or promise object with an .abort() method
*/
ve.ui.LookupInputWidget.prototype.getLookupRequest = function () {
// Stub, implemented in subclass
return null;
};
/**
* Handle successful lookup request.
*
* Overriding methods should call #populateLookupMenu when results are available and cache results
* for future lookups in #lookupCache as an array of #ve.ui.MenuItemWidget objects.
*
* @method
* @abstract
* @param {Mixed} data Response from server
*/
ve.ui.LookupInputWidget.prototype.onLookupRequestDone = function () {
// Stub, implemented in subclass
};
/**
* Get a list of menu item widgets from the data stored by the lookup request's done handler.
*
* @method
* @abstract
* @param {Mixed} data Cached result data, usually an array
* @returns {ve.ui.MenuItemWidget[]} Menu items
*/
ve.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () {
// Stub, implemented in subclass
return [];
};