/**
* ButtonElement is often mixed into other classes to generate a button, which is a clickable
* interface element that can be configured with access keys for accessibility.
* See the [OOjs UI documentation on MediaWiki] [1] for examples.
*
* [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons
* @abstract
* @class
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {jQuery} [$button] The button element created by the class.
* If this configuration is omitted, the button element will use a generated ``.
* @cfg {boolean} [framed=true] Render the button with a frame
* @cfg {string} [accessKey] Button's access key
*/
OO.ui.ButtonElement = function OoUiButtonElement( config ) {
// Configuration initialization
config = config || {};
// Properties
this.$button = null;
this.framed = null;
this.accessKey = null;
this.active = false;
this.onMouseUpHandler = this.onMouseUp.bind( this );
this.onMouseDownHandler = this.onMouseDown.bind( this );
this.onKeyDownHandler = this.onKeyDown.bind( this );
this.onKeyUpHandler = this.onKeyUp.bind( this );
this.onClickHandler = this.onClick.bind( this );
this.onKeyPressHandler = this.onKeyPress.bind( this );
// Initialization
this.$element.addClass( 'oo-ui-buttonElement' );
this.toggleFramed( config.framed === undefined || config.framed );
this.setAccessKey( config.accessKey );
this.setButtonElement( config.$button || $( '' ) );
};
/* Setup */
OO.initClass( OO.ui.ButtonElement );
/* Static Properties */
/**
* Cancel mouse down events.
*
* This property is usually set to `true` to prevent the focus from changing when the button is clicked.
* Classes such as {@link OO.ui.DraggableElement DraggableElement} and {@link OO.ui.ButtonOptionWidget ButtonOptionWidget}
* use a value of `false` so that dragging behavior is possible and mousedown events can be handled by a
* parent widget.
*
* @static
* @inheritable
* @property {boolean}
*/
OO.ui.ButtonElement.static.cancelButtonMouseDownEvents = true;
/* Events */
/**
* A 'click' event is emitted when the button element is clicked.
*
* @event click
*/
/* Methods */
/**
* Set the button element.
*
* This method is used to retarget a button mixin so that its functionality applies to
* the specified button element instead of the one created by the class. If a button element
* is already set, the method will remove the mixin’s effect on that element.
*
* @param {jQuery} $button Element to use as button
*/
OO.ui.ButtonElement.prototype.setButtonElement = function ( $button ) {
if ( this.$button ) {
this.$button
.removeClass( 'oo-ui-buttonElement-button' )
.removeAttr( 'role accesskey' )
.off( {
mousedown: this.onMouseDownHandler,
keydown: this.onKeyDownHandler,
click: this.onClickHandler,
keypress: this.onKeyPressHandler
} );
}
this.$button = $button
.addClass( 'oo-ui-buttonElement-button' )
.attr( { role: 'button', accesskey: this.accessKey } )
.on( {
mousedown: this.onMouseDownHandler,
keydown: this.onKeyDownHandler,
click: this.onClickHandler,
keypress: this.onKeyPressHandler
} );
};
/**
* Handles mouse down events.
*
* @protected
* @param {jQuery.Event} e Mouse down event
*/
OO.ui.ButtonElement.prototype.onMouseDown = function ( e ) {
if ( this.isDisabled() || e.which !== 1 ) {
return;
}
this.$element.addClass( 'oo-ui-buttonElement-pressed' );
// Run the mouseup handler no matter where the mouse is when the button is let go, so we can
// reliably remove the pressed class
this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true );
// Prevent change of focus unless specifically configured otherwise
if ( this.constructor.static.cancelButtonMouseDownEvents ) {
return false;
}
};
/**
* Handles mouse up events.
*
* @protected
* @param {jQuery.Event} e Mouse up event
*/
OO.ui.ButtonElement.prototype.onMouseUp = function ( e ) {
if ( this.isDisabled() || e.which !== 1 ) {
return;
}
this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
// Stop listening for mouseup, since we only needed this once
this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
};
/**
* Handles mouse click events.
*
* @protected
* @param {jQuery.Event} e Mouse click event
* @fires click
*/
OO.ui.ButtonElement.prototype.onClick = function ( e ) {
if ( !this.isDisabled() && e.which === 1 ) {
if ( this.emit( 'click' ) ) {
return false;
}
}
};
/**
* Handles key down events.
*
* @protected
* @param {jQuery.Event} e Key down event
*/
OO.ui.ButtonElement.prototype.onKeyDown = function ( e ) {
if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
return;
}
this.$element.addClass( 'oo-ui-buttonElement-pressed' );
// Run the keyup handler no matter where the key is when the button is let go, so we can
// reliably remove the pressed class
this.getElementDocument().addEventListener( 'keyup', this.onKeyUpHandler, true );
};
/**
* Handles key up events.
*
* @protected
* @param {jQuery.Event} e Key up event
*/
OO.ui.ButtonElement.prototype.onKeyUp = function ( e ) {
if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
return;
}
this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
// Stop listening for keyup, since we only needed this once
this.getElementDocument().removeEventListener( 'keyup', this.onKeyUpHandler, true );
};
/**
* Handles key press events.
*
* @protected
* @param {jQuery.Event} e Key press event
* @fires click
*/
OO.ui.ButtonElement.prototype.onKeyPress = function ( e ) {
if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
if ( this.emit( 'click' ) ) {
return false;
}
}
};
/**
* Check if button has a frame.
*
* @return {boolean} Button is framed
*/
OO.ui.ButtonElement.prototype.isFramed = function () {
return this.framed;
};
/**
* Render the button with or without a frame. Omit the `framed` parameter to toggle the button frame on and off.
*
* @param {boolean} [framed] Make button framed, omit to toggle
* @chainable
*/
OO.ui.ButtonElement.prototype.toggleFramed = function ( framed ) {
framed = framed === undefined ? !this.framed : !!framed;
if ( framed !== this.framed ) {
this.framed = framed;
this.$element
.toggleClass( 'oo-ui-buttonElement-frameless', !framed )
.toggleClass( 'oo-ui-buttonElement-framed', framed );
this.updateThemeClasses();
}
return this;
};
/**
* Set the button's access key.
*
* @param {string} accessKey Button's access key, use empty string to remove
* @chainable
*/
OO.ui.ButtonElement.prototype.setAccessKey = function ( accessKey ) {
accessKey = typeof accessKey === 'string' && accessKey.length ? accessKey : null;
if ( this.accessKey !== accessKey ) {
if ( this.$button ) {
if ( accessKey !== null ) {
this.$button.attr( 'accesskey', accessKey );
} else {
this.$button.removeAttr( 'accesskey' );
}
}
this.accessKey = accessKey;
}
return this;
};
/**
* Set the button to its 'active' state.
*
* The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
* a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
* for other button types.
*
* @param {boolean} [value] Make button active
* @chainable
*/
OO.ui.ButtonElement.prototype.setActive = function ( value ) {
this.$element.toggleClass( 'oo-ui-buttonElement-active', !!value );
return this;
};