mediawiki-extensions-Visual.../modules/ve/ui/widgets/ve.ui.MenuWidget.js
Trevor Parscal 130e446e52 Single-click insertion
Objectives:

* Reduce the number of clicks and mouse maneuvers required to insert
  media, references or template parameters
* Make use of highlighting with mouse movement or arrow key presses,
  similar to menus, to suggest action when clicked
* Improve the way media search results look and feel

Changes:

ve.ui.SelectWidget.js
* Add mouseleave handler to un-highlight when the mouse exits the widget
* Document highlight events (already being emitted)

ve.ui.SearchWidget.js
* Propagate both select and highlight events from results widget
* Make arrow keys change highlight instead of selection
* Get rid of enter event, make enter key select highlighted item instead
* Provide direct access to results widget through getResults method

ve.ui.MenuWidget.js
* Use the selected item as a starting point if nothing is currently
  highlighted when adjusting the highlight position

ve.ui.Dialog.js
* Add footless option to hide the foot element and make the body extend
  all the way down to the bottom
* Remove applyButton, which only some dialogs need, and should be creating
  themselves, along with other buttons as needed

ve.ui.Widget.css
* Change highlight and selected colors of option widgets to match other
  selection colors used elsewhere
* Leave selected and highlighted widget looking selected

ve.ui.Frame.css
* Add background color to combat any color that might have been applied to
  the frame body in the imported CSS from the parent frame

ve.ui.Dialog.css
* Add rules for footless mode

ve.ui.MWReferenceResultWidget.js,
ve.ui.MWParameterResultWidget.js,
ve.ui.MWMediaResultWidget.js
* Allow highlighting

ve.ui.MWParamterSearchWidget.js
* Switch from selecting the first item when filtering to highlighting

ve-mw/ve.ui.Widget.js
* Adjust media result widget styling to better match other elements

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceListDialog.js,
ve.ui.MWReferenceEditDialog.js,
ve.ui.MWMetaDialog.js
ve.ui.MWMediaEditDialog.js
* Add apply button, as per it being removed from parent class

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceInsertDialog.js,
ve.ui.MWMediaInsertDialog.js
* Insert parameter/reference/media on select, instead of clicking an
  insert button
* Use 'insert' instead of 'apply' as argument for close method

Bug: 50774
Bug: 51143
Change-Id: Ia18e79f1f8df2540f465468edb01f5ce989bf843
2013-07-15 23:42:56 +00:00

228 lines
4.7 KiB
JavaScript

/*!
* VisualEditor UserInterface MenuWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Create an ve.ui.MenuWidget object.
*
* @class
* @extends ve.ui.SelectWidget
*
* @constructor
* @param {Object} [config] Config options
* @cfg {ve.ui.InputWidget} [input] Input to bind keyboard handlers to
*/
ve.ui.MenuWidget = function VeUiMenuWidget( config ) {
// Config intialization
config = config || {};
// Parent constructor
ve.ui.SelectWidget.call( this, config );
// Properties
this.newItems = [];
this.$input = config.input ? config.input.$input : null;
this.$previousFocus = null;
this.isolated = !config.input;
this.visible = false;
this.keydownHandler = ve.bind( this.onKeyDown, this );
// Initialization
this.$.hide().addClass( 've-ui-menuWidget' );
};
/* Inheritance */
ve.inheritClass( ve.ui.MenuWidget, ve.ui.SelectWidget );
/* Methods */
/**
* Handles key down events.
*
* @method
* @param {jQuery.Event} e Key down event
*/
ve.ui.MenuWidget.prototype.onKeyDown = function ( e ) {
var handled = false,
highlightItem = this.getHighlightedItem();
if ( !this.disabled && this.visible ) {
if ( !highlightItem ) {
highlightItem = this.getSelectedItem();
}
switch ( e.keyCode ) {
case ve.Keys.ENTER:
this.selectItem( highlightItem );
handled = true;
break;
case ve.Keys.UP:
this.highlightItem( this.getRelativeSelectableItem( highlightItem, -1 ) );
handled = true;
break;
case ve.Keys.DOWN:
this.highlightItem( this.getRelativeSelectableItem( highlightItem, 1 ) );
handled = true;
break;
case ve.Keys.ESCAPE:
if ( highlightItem ) {
highlightItem.setHighlighted( false );
}
this.hide();
handled = true;
break;
}
if ( handled ) {
e.preventDefault();
e.stopPropagation();
return false;
}
}
};
/**
* Check if the menu is visible.
*
* @method
* @returns {boolean} Menu is visible
*/
ve.ui.MenuWidget.prototype.isVisible = function () {
return this.visible;
};
/**
* Bind keydown listener
*
* @method
*/
ve.ui.MenuWidget.prototype.bindKeydownListener = function () {
if ( this.$input ) {
this.$input.on( 'keydown', this.keydownHandler );
} else {
// Capture menu navigation keys
window.addEventListener( 'keydown', this.keydownHandler, true );
}
};
/**
* Unbind keydown listener
*
* @method
*/
ve.ui.MenuWidget.prototype.unbindKeydownListener = function () {
if ( this.$input ) {
this.$input.off( 'keydown' );
} else {
window.removeEventListener( 'keydown', this.keydownHandler, true );
}
};
/**
* Select an item.
*
* The menu will stay open if an item is silently selected.
*
* @method
* @param {ve.ui.OptionWidget} [item] Item to select, omit to deselect all
* @param {boolean} [silent=false] Update UI only, do not emit `select` event
* @chainable
*/
ve.ui.MenuWidget.prototype.selectItem = function ( item, silent ) {
if ( !this.disabled && !silent ) {
if ( item ) {
this.disabled = true;
item.flash( ve.bind( function () {
this.hide();
this.disabled = false;
}, this ) );
} else {
this.hide();
}
}
ve.ui.SelectWidget.prototype.selectItem.call( this, item, silent );
return this;
};
/**
* Add items.
*
* Adding an existing item (by value) will move it.
*
* @method
* @param {ve.ui.MenuItemWidget[]} items Items to add
* @param {number} [index] Index to insert items after
* @chainable
*/
ve.ui.MenuWidget.prototype.addItems = function ( items, index ) {
var i, len, item;
ve.ui.SelectWidget.prototype.addItems.call( this, items, index );
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i];
if ( this.visible ) {
// Defer fitting label until
item.fitLabel();
} else {
this.newItems.push( item );
}
}
return this;
};
/**
* Show the menu.
*
* @method
* @chainable
*/
ve.ui.MenuWidget.prototype.show = function () {
var i, len;
if ( this.items.length ) {
this.$.show();
this.visible = true;
this.bindKeydownListener();
// Change focus to enable keyboard navigation
if ( this.isolated && this.$input && !this.$input.is( ':focus' ) ) {
this.$previousFocus = this.$$( ':focus' );
this.$input.focus();
}
if ( this.newItems.length ) {
for ( i = 0, len = this.newItems.length; i < len; i++ ) {
this.newItems[i].fitLabel();
}
this.newItems = [];
}
}
return this;
};
/**
* Hide the menu.
*
* @method
* @chainable
*/
ve.ui.MenuWidget.prototype.hide = function () {
this.$.hide();
this.visible = false;
this.unbindKeydownListener();
if ( this.isolated && this.$previousFocus ) {
this.$previousFocus.focus();
this.$previousFocus = null;
}
return this;
};