/** * 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; };