X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Flib%2Foojs-ui%2Foojs-ui.js;h=d42139e8921e742c2818603160012b9d740a38d6;hb=ffdd99bef1838e8ad79a367205a550957844ba6e;hp=96befc538b87b155163a670cc72654ca7ea1414e;hpb=79168e0f8b0d20d2ffdfbbb51d1acf42ecea05a4;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/oojs-ui/oojs-ui.js b/resources/lib/oojs-ui/oojs-ui.js index 96befc538b..d42139e892 100644 --- a/resources/lib/oojs-ui/oojs-ui.js +++ b/resources/lib/oojs-ui/oojs-ui.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.1.0-pre (deccd11549) + * OOjs UI v0.2.3 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2014 OOjs Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2014-10-28T16:52:18Z + * Date: 2014-11-26T23:37:00Z */ ( function ( OO ) { @@ -93,6 +93,30 @@ OO.ui.getLocalValue = function ( obj, lang, fallback ) { return undefined; }; +/** + * Check if a node is contained within another node + * + * Similar to jQuery#contains except a list of containers can be supplied + * and a boolean argument allows you to include the container in the match list + * + * @param {HTMLElement|HTMLElement[]} containers Container node(s) to search in + * @param {HTMLElement} contained Node to find + * @param {boolean} [matchContainers] Include the container(s) in the list of nodes to match, otherwise only match descendants + * @return {boolean} The node is in the list of target nodes + */ +OO.ui.contains = function ( containers, contained, matchContainers ) { + var i; + if ( !Array.isArray( containers ) ) { + containers = [ containers ]; + } + for ( i = containers.length - 1; i >= 0; i-- ) { + if ( ( matchContainers && contained === containers[i] ) || $.contains( containers[i], contained ) ) { + return true; + } + } + return false; +}; + ( function () { /** * Message store for the default implementation of OO.ui.msg @@ -123,8 +147,10 @@ OO.ui.getLocalValue = function ( obj, lang, fallback ) { 'ooui-dialog-process-error': 'Something went wrong', // Label for process dialog dismiss error button, visible when describing errors 'ooui-dialog-process-dismiss': 'Dismiss', - // Label for process dialog retry action button, visible when describing recoverable errors - 'ooui-dialog-process-retry': 'Try again' + // Label for process dialog retry action button, visible when describing only recoverable errors + 'ooui-dialog-process-retry': 'Try again', + // Label for process dialog retry action button, visible when describing only warnings + 'ooui-dialog-process-continue': 'Continue' }; /** @@ -200,9 +226,10 @@ OO.ui.getLocalValue = function ( obj, lang, fallback ) { * * @constructor * @param {Object} [config] Configuration options + * @cfg {jQuery} [$pending] Element to mark as pending, defaults to this.$element */ OO.ui.PendingElement = function OoUiPendingElement( config ) { - // Config initialisation + // Configuration initialization config = config || {}; // Properties @@ -287,7 +314,7 @@ OO.ui.PendingElement.prototype.popPending = function () { * @param {Object} [config] Configuration options */ OO.ui.ActionSet = function OoUiActionSet( config ) { - // Configuration intialization + // Configuration initialization config = config || {}; // Mixin constructors @@ -629,7 +656,7 @@ OO.ui.ActionSet.prototype.clear = function () { /** * Organize actions. * - * This is called whenver organized information is requested. It will only reorganize the actions + * This is called whenever organized information is requested. It will only reorganize the actions * if something has changed since the last time it ran. * * @private @@ -646,7 +673,7 @@ OO.ui.ActionSet.prototype.organize = function () { for ( i = 0, iLen = this.list.length; i < iLen; i++ ) { action = this.list[i]; if ( action.isVisible() ) { - // Populate catgeories + // Populate categories for ( category in this.categories ) { if ( !this.categorized[category] ) { this.categorized[category] = {}; @@ -693,9 +720,10 @@ OO.ui.ActionSet.prototype.organize = function () { * @constructor * @param {Object} [config] Configuration options * @cfg {Function} [$] jQuery for the frame the widget is in - * @cfg {string[]} [classes] CSS class names + * @cfg {string[]} [classes] CSS class names to add * @cfg {string} [text] Text to insert * @cfg {jQuery} [$content] Content elements to append (after text) + * @cfg {Mixed} [data] Element data */ OO.ui.Element = function OoUiElement( config ) { // Configuration initialization @@ -703,6 +731,7 @@ OO.ui.Element = function OoUiElement( config ) { // Properties this.$ = config.$ || OO.ui.Element.getJQuery( document ); + this.data = config.data; this.$element = this.$( this.$.context.createElement( this.getTagName() ) ); this.elementGroup = null; this.debouncedUpdateThemeClassesHandler = this.debouncedUpdateThemeClasses.bind( this ); @@ -800,7 +829,7 @@ OO.ui.Element.getWindow = function ( obj ) { * * @static * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for - * @return {string} Text direction, either `ltr` or `rtl` + * @return {string} Text direction, either 'ltr' or 'rtl' */ OO.ui.Element.getDir = function ( obj ) { var isDoc, isWin; @@ -1011,7 +1040,7 @@ OO.ui.Element.getClosestScrollableContainer = function ( el, dimension ) { * * @static * @param {HTMLElement} el Element to scroll into view - * @param {Object} [config={}] Configuration config + * @param {Object} [config] Configuration options * @param {string} [config.duration] jQuery animation duration value * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit * to scroll in both directions @@ -1108,11 +1137,31 @@ OO.ui.Element.offDOMEvent = function ( el, event, callback ) { /* Methods */ +/** + * Get element data. + * + * @return {Mixed} Element data + */ +OO.ui.Element.prototype.getData = function () { + return this.data; +}; + +/** + * Set element data. + * + * @param {Mixed} Element data + * @chainable + */ +OO.ui.Element.prototype.setData = function ( data ) { + this.data = data; + return this; +}; + /** * Check if element supports one or more methods. * * @param {string|string[]} methods Method or list of methods to check - * @return boolean All methods are supported + * @return {boolean} All methods are supported */ OO.ui.Element.prototype.supports = function ( methods ) { var i, len, @@ -1131,7 +1180,7 @@ OO.ui.Element.prototype.supports = function ( methods ) { /** * Update the theme-provided classes. * - * @localdoc This is called in element mixins and widget classes anytime state changes. + * @localdoc This is called in element mixins and widget classes any time state changes. * Updating is debounced, minimizing overhead of changing multiple attributes and * guaranteeing that theme updates do not occur within an element's constructor */ @@ -1217,7 +1266,7 @@ OO.ui.Element.prototype.setElementGroup = function ( group ) { /** * Scroll element into view. * - * @param {Object} [config={}] + * @param {Object} [config] Configuration options */ OO.ui.Element.prototype.scrollElementIntoView = function ( config ) { return OO.ui.Element.scrollIntoView( this.$element[0], config ); @@ -1257,7 +1306,7 @@ OO.ui.Element.prototype.offDOMEvent = function ( event, callback ) { * @param {Object} [config] Configuration options */ OO.ui.Layout = function OoUiLayout( config ) { - // Initialize config + // Configuration initialization config = config || {}; // Parent constructor @@ -1424,8 +1473,8 @@ OO.ui.Widget.prototype.updateDisabled = function () { * * Each process (setup, ready, hold and teardown) can be extended in subclasses by overriding * {@link #getSetupProcess}, {@link #getReadyProcess}, {@link #getHoldProcess} and - * {@link #getTeardownProcess} respectively. Each process is executed in series, so asynchonous - * processing can complete. Always assume window processes are executed asychronously. See + * {@link #getTeardownProcess} respectively. Each process is executed in series, so asynchronous + * processing can complete. Always assume window processes are executed asynchronously. See * OO.ui.Process for more details about how to work with processes. Some events, as well as the * #open and #close methods, provide promises which are resolved when the window enters a new state. * @@ -1436,7 +1485,6 @@ OO.ui.Widget.prototype.updateDisabled = function () { * @param {Object} [config] Configuration options * @cfg {string} [size] Symbolic name of dialog size, `small`, `medium`, `large` or `full`; omit to * use #static-size - * @fires initialize */ OO.ui.Window = function OoUiWindow( config ) { // Configuration initialization @@ -1468,7 +1516,7 @@ OO.ui.Window = function OoUiWindow( config ) { this.$frame.addClass( 'oo-ui-window-frame' ); this.$overlay.addClass( 'oo-ui-window-overlay' ); - // NOTE: Additional intitialization will occur when #setManager is called + // NOTE: Additional initialization will occur when #setManager is called }; /* Setup */ @@ -1708,11 +1756,20 @@ OO.ui.Window.prototype.getSize = function () { * @return {number} Content height */ OO.ui.Window.prototype.getContentHeight = function () { - // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements - var bodyHeight, oldHeight = this.$frame[0].style.height; - this.$frame[0].style.height = '1px'; + // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements. + // Disable transitions first, otherwise we'll get values from when the window was animating. + var bodyHeight, oldHeight, oldTransition, + styleObj = this.$frame[0].style; + oldTransition = styleObj.transition || styleObj.OTransition || styleObj.MsTransition || + styleObj.MozTransition || styleObj.WebkitTransition; + styleObj.transition = styleObj.OTransition = styleObj.MsTransition = + styleObj.MozTransition = styleObj.WebkitTransition = 'none'; + oldHeight = styleObj.height; + styleObj.height = '1px'; bodyHeight = this.getBodyHeight(); - this.$frame[0].style.height = oldHeight; + styleObj.height = oldHeight; + styleObj.transition = styleObj.OTransition = styleObj.MsTransition = + styleObj.MozTransition = styleObj.WebkitTransition = oldTransition; return Math.round( // Add buffer for border @@ -1814,11 +1871,11 @@ OO.ui.Window.prototype.getTeardownProcess = function () { /** * Toggle visibility of window. * - * If the window is isolated and hasn't fully loaded yet, the visiblity property will be used + * If the window is isolated and hasn't fully loaded yet, the visibility property will be used * instead of display. * * @param {boolean} [show] Make window visible, omit to toggle visibility - * @fires visible + * @fires toggle * @chainable */ OO.ui.Window.prototype.toggle = function ( show ) { @@ -1987,7 +2044,7 @@ OO.ui.Window.prototype.close = function ( data ) { /** * Setup window. * - * This is called by OO.ui.WindowManager durring window opening, and should not be called directly + * This is called by OO.ui.WindowManager during window opening, and should not be called directly * by other systems. * * @param {Object} [data] Window opening data @@ -2012,7 +2069,7 @@ OO.ui.Window.prototype.setup = function ( data ) { /** * Ready window. * - * This is called by OO.ui.WindowManager durring window opening, and should not be called directly + * This is called by OO.ui.WindowManager during window opening, and should not be called directly * by other systems. * * @param {Object} [data] Window opening data @@ -2036,7 +2093,7 @@ OO.ui.Window.prototype.ready = function ( data ) { /** * Hold window. * - * This is called by OO.ui.WindowManager durring window closing, and should not be called directly + * This is called by OO.ui.WindowManager during window closing, and should not be called directly * by other systems. * * @param {Object} [data] Window closing data @@ -2067,7 +2124,7 @@ OO.ui.Window.prototype.hold = function ( data ) { /** * Teardown window. * - * This is called by OO.ui.WindowManager durring window closing, and should not be called directly + * This is called by OO.ui.WindowManager during window closing, and should not be called directly * by other systems. * * @param {Object} [data] Window closing data @@ -2079,7 +2136,7 @@ OO.ui.Window.prototype.teardown = function ( data ) { this.getTeardownProcess( data ).execute().done( function () { // Force redraw by asking the browser to measure the elements' widths - win.$element.removeClass( 'oo-ui-window-setup' ).width(); + win.$element.removeClass( 'oo-ui-window-load oo-ui-window-setup' ).width(); win.$content.removeClass( 'oo-ui-window-content-setup' ).width(); win.$element.hide(); win.visible = false; @@ -2092,10 +2149,9 @@ OO.ui.Window.prototype.teardown = function ( data ) { /** * Load the frame contents. * - * Once the iframe's stylesheets are loaded, the `load` event will be emitted and the returned - * promise will be resolved. Calling while loading will return a promise but not trigger a new - * loading cycle. Calling after loading is complete will return a promise that's already been - * resolved. + * Once the iframe's stylesheets are loaded the returned promise will be resolved. Calling while + * loading will return a promise but not trigger a new loading cycle. Calling after loading is + * complete will return a promise that's already been resolved. * * Sounds simple right? Read on... * @@ -2124,12 +2180,13 @@ OO.ui.Window.prototype.teardown = function ( data ) { * All this stylesheet injection and polling magic is in #transplantStyles. * * @return {jQuery.Promise} Promise resolved when loading is complete - * @fires load */ OO.ui.Window.prototype.load = function () { var sub, doc, loading, win = this; + this.$element.addClass( 'oo-ui-window-load' ); + // Non-isolated windows are already "loaded" if ( !this.loading && !this.isolated ) { this.loading = $.Deferred().resolve(); @@ -2227,6 +2284,7 @@ OO.ui.Dialog = function OoUiDialog( config ) { this.actions = new OO.ui.ActionSet(); this.attachedActions = []; this.currentAction = null; + this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); // Events this.actions.connect( this, { @@ -2390,6 +2448,10 @@ OO.ui.Dialog.prototype.getSetupProcess = function ( data ) { ); } this.actions.add( items ); + + if ( this.constructor.static.escapable ) { + this.$document.on( 'keydown', this.onDocumentKeyDownHandler ); + } }, this ); }; @@ -2400,6 +2462,10 @@ OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) { // Parent method return OO.ui.Dialog.super.prototype.getTeardownProcess.call( this, data ) .first( function () { + if ( this.constructor.static.escapable ) { + this.$document.off( 'keydown', this.onDocumentKeyDownHandler ); + } + this.actions.clear(); this.currentAction = null; }, this ); @@ -2415,11 +2481,6 @@ OO.ui.Dialog.prototype.initialize = function () { // Properties this.title = new OO.ui.LabelWidget( { $: this.$ } ); - // Events - if ( this.constructor.static.escapable ) { - this.$document.on( 'keydown', this.onDocumentKeyDown.bind( this ) ); - } - // Initialization this.$content.addClass( 'oo-ui-dialog-content' ); this.setPendingElement( this.$head ); @@ -2853,13 +2914,10 @@ OO.ui.WindowManager.prototype.openWindow = function ( win, data ) { // Window opening if ( opening.state() !== 'rejected' ) { - // Begin loading the window if it's not loading or loaded already - may take noticable time - // and we want to do this in paralell with any other preparatory actions - if ( !win.isLoading() && !win.isLoaded() ) { - // Finish initializing the window (must be done after manager is attached to DOM) + if ( !win.getManager() ) { win.setManager( this ); - preparing.push( win.load() ); } + preparing.push( win.load() ); if ( this.closing ) { // If a window is currently closing, wait for it to complete @@ -3139,7 +3197,7 @@ OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) { .attr( 'aria-hidden', '' ); } } else if ( this.$ariaHidden ) { - // Restore screen reader visiblity + // Restore screen reader visibility this.$ariaHidden.removeAttr( 'aria-hidden' ); this.$ariaHidden = null; } @@ -3166,6 +3224,7 @@ OO.ui.WindowManager.prototype.destroy = function () { * @param {string|jQuery} message Description of error * @param {Object} [config] Configuration options * @cfg {boolean} [recoverable=true] Error is recoverable + * @cfg {boolean} [warning=false] Whether this error is a warning or not. */ OO.ui.Error = function OoUiElement( message, config ) { // Configuration initialization @@ -3174,6 +3233,7 @@ OO.ui.Error = function OoUiElement( message, config ) { // Properties this.message = message instanceof jQuery ? message : String( message ); this.recoverable = config.recoverable === undefined || !!config.recoverable; + this.warning = !!config.warning; }; /* Setup */ @@ -3191,6 +3251,15 @@ OO.ui.Error.prototype.isRecoverable = function () { return this.recoverable; }; +/** + * Check if the error is a warning + * + * @return {boolean} Error is warning + */ +OO.ui.Error.prototype.isWarning = function () { + return this.warning; +}; + /** * Get error message as DOM nodes. * @@ -3388,7 +3457,15 @@ OO.inheritClass( OO.ui.ToolFactory, OO.Factory ); /* Methods */ -/** */ +/** + * Get tools from the factory + * + * @param {Array} include Included tools + * @param {Array} exclude Excluded tools + * @param {Array} promote Promoted tools + * @param {Array} demote Demoted tools + * @return {string[]} List of tools + */ OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, demote ) { var i, len, included, promoted, demoted, auto = [], @@ -3532,7 +3609,7 @@ OO.ui.ToolGroupFactory.static.getDefaultClasses = function () { * @param {Object} [config] Configuration options */ OO.ui.Theme = function OoUiTheme( config ) { - // Initialize config + // Configuration initialization config = config || {}; }; @@ -3545,8 +3622,8 @@ OO.initClass( OO.ui.Theme ); /** * Get a list of classes to be applied to a widget. * - * @localdoc The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or - * removes, otherwise state transitions will not work properly. + * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes, + * otherwise state transitions will not work properly. * * @param {OO.ui.Element} element Element for which to get classes * @return {Object.} Categorized class names with `on` and `off` lists @@ -3558,9 +3635,9 @@ OO.ui.Theme.prototype.getElementClasses = function ( /* element */ ) { /** * Update CSS classes provided by the theme. * - * For elements with theme logic hooks, this should be called anytime there's a state change. + * For elements with theme logic hooks, this should be called any time there's a state change. * - * @param {OO.ui.Element} Element for which to update classes + * @param {OO.ui.Element} element Element for which to update classes * @return {Object.} Categorized class names with `on` and `off` lists */ OO.ui.Theme.prototype.updateElementClasses = function ( element ) { @@ -3584,7 +3661,8 @@ OO.ui.Theme.prototype.updateElementClasses = function ( element ) { * @param {Object} [config] Configuration options * @cfg {jQuery} [$button] Button node, assigned to #$button, omit to use a generated `` * @cfg {boolean} [framed=true] Render button with a frame - * @cfg {number} [tabIndex=0] Button's tab index, use null to have no tabIndex + * @cfg {number} [tabIndex=0] Button's tab index. Use 0 to use default ordering, use -1 to prevent + * tab focusing. * @cfg {string} [accessKey] Button's access key */ OO.ui.ButtonElement = function OoUiButtonElement( config ) { @@ -3676,7 +3754,7 @@ OO.ui.ButtonElement.prototype.onMouseUp = function ( e ) { if ( this.isDisabled() || e.which !== 1 ) { return false; } - // Restore the tab-index after the button is up to restore the button's accesssibility + // Restore the tab-index after the button is up to restore the button's accessibility this.$button.attr( 'tabindex', this.tabIndex ); this.$element.removeClass( 'oo-ui-buttonElement-pressed' ); // Stop listening for mouseup, since we only needed this once @@ -3779,7 +3857,7 @@ OO.ui.ButtonElement.prototype.setActive = function ( value ) { * @cfg {jQuery} [$group] Container node, assigned to #$group, omit to use a generated `
` */ OO.ui.GroupElement = function OoUiGroupElement( config ) { - // Configuration + // Configuration initialization config = config || {}; // Properties @@ -3827,6 +3905,51 @@ OO.ui.GroupElement.prototype.getItems = function () { return this.items.slice( 0 ); }; +/** + * Get an item by its data. + * + * Data is compared by a hash of its value. Only the first item with matching data will be returned. + * + * @param {Object} data Item data to search for + * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists + */ +OO.ui.GroupElement.prototype.getItemFromData = function ( data ) { + var i, len, item, + hash = OO.getHash( data ); + + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[i]; + if ( hash === OO.getHash( item.getData() ) ) { + return item; + } + } + + return null; +}; + +/** + * Get items by their data. + * + * Data is compared by a hash of its value. All items with matching data will be returned. + * + * @param {Object} data Item data to search for + * @return {OO.ui.Element[]} Items with equivalent data + */ +OO.ui.GroupElement.prototype.getItemsFromData = function ( data ) { + var i, len, item, + hash = OO.getHash( data ), + items = []; + + for ( i = 0, len = this.items.length; i < len; i++ ) { + item = this.items[i]; + if ( hash === OO.getHash( item.getData() ) ) { + items.push( item ); + } + } + + return items; +}; + /** * Add an aggregate item event. * @@ -3845,7 +3968,7 @@ OO.ui.GroupElement.prototype.aggregate = function ( events ) { groupEvent = events[itemEvent]; // Remove existing aggregated event - if ( itemEvent in this.aggregateItemEvents ) { + if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { // Don't allow duplicate aggregations if ( groupEvent ) { throw new Error( 'Duplicate item event aggregation for ' + itemEvent ); @@ -3883,7 +4006,7 @@ OO.ui.GroupElement.prototype.aggregate = function ( events ) { /** * Add items. * - * Adding an existing item (by value) will move it. + * Adding an existing item will move it. * * @param {OO.ui.Element[]} items Items * @param {number} [index] Index to insert items at @@ -3952,7 +4075,7 @@ OO.ui.GroupElement.prototype.removeItems = function ( items ) { !$.isEmptyObject( this.aggregateItemEvents ) ) { remove = {}; - if ( itemEvent in this.aggregateItemEvents ) { + if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; } item.disconnect( this, remove ); @@ -3984,7 +4107,7 @@ OO.ui.GroupElement.prototype.clearItems = function () { !$.isEmptyObject( this.aggregateItemEvents ) ) { remove = {}; - if ( itemEvent in this.aggregateItemEvents ) { + if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) { remove[itemEvent] = [ 'emit', this.aggregateItemEvents[itemEvent], item ]; } item.disconnect( this, remove ); @@ -4017,7 +4140,7 @@ OO.ui.GroupElement.prototype.clearItems = function () { * @cfg {string} [iconTitle] Icon title text or a function that returns text */ OO.ui.IconElement = function OoUiIconElement( config ) { - // Config intialization + // Configuration initialization config = config || {}; // Properties @@ -4091,7 +4214,7 @@ OO.ui.IconElement.prototype.setIconElement = function ( $icon ) { }; /** - * Set icon. + * Set icon name. * * @param {Object|string|null} icon Symbolic icon name, or map of icon names keyed by language ID; * use the 'default' key to specify the icon to be used when there is no icon in the user's @@ -4147,9 +4270,9 @@ OO.ui.IconElement.prototype.setIconTitle = function ( iconTitle ) { }; /** - * Get icon. + * Get icon name. * - * @return {string} Icon + * @return {string} Icon name */ OO.ui.IconElement.prototype.getIcon = function () { return this.icon; @@ -4174,7 +4297,7 @@ OO.ui.IconElement.prototype.getIcon = function () { * @cfg {string} [indicatorTitle] Indicator title text or a function that returns text */ OO.ui.IndicatorElement = function OoUiIndicatorElement( config ) { - // Config intialization + // Configuration initialization config = config || {}; // Properties @@ -4199,7 +4322,7 @@ OO.initClass( OO.ui.IndicatorElement ); * * @static * @inheritable - * @property {string|null} Symbolic indicator name or null for no indicator + * @property {string|null} Symbolic indicator name */ OO.ui.IndicatorElement.static.indicator = null; @@ -4238,7 +4361,7 @@ OO.ui.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) { }; /** - * Set indicator. + * Set indicator name. * * @param {string|null} indicator Symbolic name of indicator to use or null for no indicator * @chainable @@ -4291,9 +4414,9 @@ OO.ui.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) }; /** - * Get indicator. + * Get indicator name. * - * @return {string} title Symbolic name of indicator + * @return {string} Symbolic name of indicator */ OO.ui.IndicatorElement.prototype.getIndicator = function () { return this.indicator; @@ -4321,7 +4444,7 @@ OO.ui.IndicatorElement.prototype.getIndicatorTitle = function () { * @cfg {boolean} [autoFitLabel=true] Whether to fit the label or not. */ OO.ui.LabelElement = function OoUiLabelElement( config ) { - // Config intialization + // Configuration initialization config = config || {}; // Properties @@ -4372,7 +4495,7 @@ OO.ui.LabelElement.prototype.setLabelElement = function ( $label ) { * Set the label. * * An empty string will result in the label being hidden. A string containing only whitespace will - * be converted to a single   + * be converted to a single ` `. * * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or * text; or null for no label @@ -4397,7 +4520,7 @@ OO.ui.LabelElement.prototype.setLabel = function ( label ) { /** * Get the label. * - * @return {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or + * @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or * text; or null for no label */ OO.ui.LabelElement.prototype.getLabel = function () { @@ -4439,7 +4562,6 @@ OO.ui.LabelElement.prototype.setLabelContent = function ( label ) { } else { this.$label.empty(); } - this.$label.css( 'display', !label ? 'none' : '' ); }; /** @@ -4487,11 +4609,11 @@ OO.ui.PopupElement.prototype.getPopup = function () { * * @constructor * @param {Object} [config] Configuration options - * @cfg {string[]} [flags=[]] Styling flags, e.g. 'primary', 'destructive' or 'constructive' + * @cfg {string|string[]} [flags] Styling flags, e.g. 'primary', 'destructive' or 'constructive' * @cfg {jQuery} [$flagged] Flagged node, assigned to #$flagged, omit to use #$element */ OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) { - // Config initialization + // Configuration initialization config = config || {}; // Properties @@ -4545,7 +4667,7 @@ OO.ui.FlaggedElement.prototype.hasFlag = function ( flag ) { /** * Get the names of all flags set. * - * @return {string[]} flags Flag names + * @return {string[]} Flag names */ OO.ui.FlaggedElement.prototype.getFlags = function () { return Object.keys( this.flags ); @@ -4658,10 +4780,11 @@ OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) { * @constructor * @param {Object} [config] Configuration options * @cfg {jQuery} [$titled] Titled node, assigned to #$titled, omit to use #$element - * @cfg {string|Function} [title] Title text or a function that returns text + * @cfg {string|Function} [title] Title text or a function that returns text. If not provided, the + * static property 'title' is used. */ OO.ui.TitledElement = function OoUiTitledElement( config ) { - // Config intialization + // Configuration initialization config = config || {}; // Properties @@ -4956,7 +5079,7 @@ OO.ui.ClippableElement.prototype.clip = function () { * @cfg {string|Function} [title] Title text or a function that returns text */ OO.ui.Tool = function OoUiTool( toolGroup, config ) { - // Config intialization + // Configuration initialization config = config || {}; // Parent constructor @@ -4981,7 +5104,14 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) { // Initialization this.$titleText.addClass( 'oo-ui-tool-title-text' ); - this.$accel.addClass( 'oo-ui-tool-accel' ); + this.$accel + .addClass( 'oo-ui-tool-accel' ) + .prop( { + // This may need to be changed if the key names are ever localized, + // but for now they are essentially written in English + dir: 'ltr', + lang: 'en' + } ); this.$title .addClass( 'oo-ui-tool-title' ) .append( this.$titleText, this.$accel ); @@ -5241,12 +5371,12 @@ OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) { // Initialization this.$group.addClass( 'oo-ui-toolbar-tools' ); - this.$bar.addClass( 'oo-ui-toolbar-bar' ).append( this.$group ); if ( config.actions ) { - this.$actions.addClass( 'oo-ui-toolbar-actions' ); - this.$bar.append( this.$actions ); + this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) ); } - this.$bar.append( '
' ); + this.$bar + .addClass( 'oo-ui-toolbar-bar' ) + .append( this.$group, '
' ); if ( config.shadow ) { this.$bar.append( '
' ); } @@ -5769,7 +5899,7 @@ OO.ui.MessageDialog.static.verbose = false; * Dialog title. * * A confirmation dialog's title should describe what the progressive action will do. An alert - * dialog's title should describe what event occured. + * dialog's title should describe what event occurred. * * @static * inheritable @@ -5779,7 +5909,7 @@ OO.ui.MessageDialog.static.title = null; /** * A confirmation dialog's message should describe the consequences of the progressive action. An - * alert dialog's message should describe why the event occured. + * alert dialog's message should describe why the event occurred. * * @static * inheritable @@ -5799,7 +5929,7 @@ OO.ui.MessageDialog.static.actions = [ */ OO.ui.MessageDialog.prototype.onActionResize = function ( action ) { this.fitActions(); - return OO.ui.ProcessDialog.super.prototype.onActionResize.call( this, action ); + return OO.ui.MessageDialog.super.prototype.onActionResize.call( this, action ); }; /** @@ -5866,7 +5996,45 @@ OO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) { * @inheritdoc */ OO.ui.MessageDialog.prototype.getBodyHeight = function () { - return Math.round( this.text.$element.outerHeight( true ) ); + var bodyHeight, oldOverflow, + $scrollable = this.container.$element; + + oldOverflow = $scrollable[0].style.overflow; + $scrollable[0].style.overflow = 'hidden'; + + // Force… ugh… something to happen + $scrollable.contents().hide(); + $scrollable.height(); + $scrollable.contents().show(); + + bodyHeight = Math.round( this.text.$element.outerHeight( true ) ); + $scrollable[0].style.overflow = oldOverflow; + + return bodyHeight; +}; + +/** + * @inheritdoc + */ +OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) { + var $scrollable = this.container.$element; + OO.ui.MessageDialog.super.prototype.setDimensions.call( this, dim ); + + // Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced. + // Need to do it after transition completes (250ms), add 50ms just in case. + setTimeout( function () { + var oldOverflow = $scrollable[0].style.overflow; + $scrollable[0].style.overflow = 'hidden'; + + // Force… ugh… something to happen + $scrollable.contents().hide(); + $scrollable.height(); + $scrollable.contents().show(); + + $scrollable[0].style.overflow = oldOverflow; + }, 300 ); + + return this; }; /** @@ -5925,10 +6093,9 @@ OO.ui.MessageDialog.prototype.attachActions = function () { special.primary.toggleFramed( false ); } + this.manager.updateWindowSize( this ); this.fitActions(); - if ( !this.isOpening() ) { - this.manager.updateWindowSize( this ); - } + this.$body.css( 'bottom', this.$foot.outerHeight( true ) ); }; @@ -6038,10 +6205,7 @@ OO.ui.ProcessDialog.prototype.initialize = function () { $: this.$, label: OO.ui.msg( 'ooui-dialog-process-dismiss' ) } ); - this.retryButton = new OO.ui.ButtonWidget( { - $: this.$, - label: OO.ui.msg( 'ooui-dialog-process-retry' ) - } ); + this.retryButton = new OO.ui.ButtonWidget( { $: this.$ } ); this.$errors = this.$( '
' ); this.$errorsTitle = this.$( '
' ); @@ -6128,19 +6292,23 @@ OO.ui.ProcessDialog.prototype.fitLabel = function () { }; /** - * Handle errors that occured durring accept or reject processes. + * Handle errors that occurred during accept or reject processes. * * @param {OO.ui.Error[]} errors Errors to be handled */ OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) { var i, len, $item, items = [], - recoverable = true; + recoverable = true, + warning = false; for ( i = 0, len = errors.length; i < len; i++ ) { if ( !errors[i].isRecoverable() ) { recoverable = false; } + if ( errors[i].isWarning() ) { + warning = true; + } $item = this.$( '
' ) .addClass( 'oo-ui-processDialog-error' ) .append( errors[i].getMessage() ); @@ -6152,6 +6320,11 @@ OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) { } else { this.currentAction.setDisabled( true ); } + if ( warning ) { + this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) ); + } else { + this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) ); + } this.retryButton.toggle( recoverable ); this.$errorsTitle.after( this.$errorItems ); this.$errors.show().scrollTop( 0 ); @@ -6180,7 +6353,7 @@ OO.ui.ProcessDialog.prototype.hideErrors = function () { * @cfg {boolean} [editable=false] Show controls for adding, removing and reordering pages */ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { - // Initialize configuration + // Configuration initialization config = config || {}; // Parent constructor @@ -6197,7 +6370,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { if ( this.outlined ) { this.editable = !!config.editable; this.outlineControlsWidget = null; - this.outlineWidget = new OO.ui.OutlineWidget( { $: this.$ } ); + this.outlineSelectWidget = new OO.ui.OutlineSelectWidget( { $: this.$ } ); this.outlinePanel = new OO.ui.PanelLayout( { $: this.$, scrollable: true } ); this.gridLayout = new OO.ui.GridLayout( [ this.outlinePanel, this.stackLayout ], @@ -6206,7 +6379,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { this.outlineVisible = true; if ( this.editable ) { this.outlineControlsWidget = new OO.ui.OutlineControlsWidget( - this.outlineWidget, { $: this.$ } + this.outlineSelectWidget, { $: this.$ } ); } } @@ -6214,7 +6387,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { // Events this.stackLayout.connect( this, { set: 'onStackLayoutSet' } ); if ( this.outlined ) { - this.outlineWidget.connect( this, { select: 'onOutlineWidgetSelect' } ); + this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } ); } if ( this.autoFocus ) { // Event 'focus' does not bubble, but 'focusin' does @@ -6227,7 +6400,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { if ( this.outlined ) { this.outlinePanel.$element .addClass( 'oo-ui-bookletLayout-outlinePanel' ) - .append( this.outlineWidget.$element ); + .append( this.outlineSelectWidget.$element ); if ( this.editable ) { this.outlinePanel.$element .addClass( 'oo-ui-bookletLayout-outlinePanel-editable' ) @@ -6288,28 +6461,46 @@ OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) { * @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel */ OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) { - var $input, layout = this; + var layout = this; if ( page ) { page.scrollElementIntoView( { complete: function () { if ( layout.autoFocus ) { - // Set focus to the first input if nothing on the page is focused yet - if ( !page.$element.find( ':focus' ).length ) { - $input = page.$element.find( ':input:first' ); - if ( $input.length ) { - $input[0].focus(); - } - } + layout.focus(); } } } ); } }; +/** + * Focus the first input in the current page. + * + * If no page is selected, the first selectable page will be selected. + * If the focus is already in an element on the current page, nothing will happen. + */ +OO.ui.BookletLayout.prototype.focus = function () { + var $input, page = this.stackLayout.getCurrentItem(); + if ( !page && this.outlined ) { + this.selectFirstSelectablePage(); + page = this.stackLayout.getCurrentItem(); + if ( !page ) { + return; + } + } + // Only change the focus if is not already in the current page + if ( !page.$element.find( ':focus' ).length ) { + $input = page.$element.find( ':input:first' ); + if ( $input.length ) { + $input[0].focus(); + } + } +}; + /** * Handle outline widget select events. * * @param {OO.ui.OptionWidget|null} item Selected item */ -OO.ui.BookletLayout.prototype.onOutlineWidgetSelect = function ( item ) { +OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) { if ( item ) { this.setPage( item.getData() ); } @@ -6374,16 +6565,16 @@ OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) { prev = pages[index - 1]; // Prefer adjacent pages at the same level if ( this.outlined ) { - level = this.outlineWidget.getItemFromData( page.getName() ).getLevel(); + level = this.outlineSelectWidget.getItemFromData( page.getName() ).getLevel(); if ( prev && - level === this.outlineWidget.getItemFromData( prev.getName() ).getLevel() + level === this.outlineSelectWidget.getItemFromData( prev.getName() ).getLevel() ) { return prev; } if ( next && - level === this.outlineWidget.getItemFromData( next.getName() ).getLevel() + level === this.outlineSelectWidget.getItemFromData( next.getName() ).getLevel() ) { return next; } @@ -6395,10 +6586,10 @@ OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) { /** * Get the outline widget. * - * @return {OO.ui.OutlineWidget|null} Outline widget, or null if boolet has no outline + * @return {OO.ui.OutlineSelectWidget|null} Outline widget, or null if booklet has no outline */ OO.ui.BookletLayout.prototype.getOutline = function () { - return this.outlineWidget; + return this.outlineSelectWidget; }; /** @@ -6470,15 +6661,15 @@ OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) { name = page.getName(); this.pages[page.getName()] = page; if ( this.outlined ) { - item = new OO.ui.OutlineItemWidget( name, page, { $: this.$ } ); + item = new OO.ui.OutlineOptionWidget( { $: this.$, data: name } ); page.setOutlineItem( item ); items.push( item ); } } if ( this.outlined && items.length ) { - this.outlineWidget.addItems( items, index ); - this.updateOutlineWidget(); + this.outlineSelectWidget.addItems( items, index ); + this.selectFirstSelectablePage(); } this.stackLayout.addItems( pages, index ); this.emit( 'add', pages, index ); @@ -6501,13 +6692,13 @@ OO.ui.BookletLayout.prototype.removePages = function ( pages ) { name = page.getName(); delete this.pages[name]; if ( this.outlined ) { - items.push( this.outlineWidget.getItemFromData( name ) ); + items.push( this.outlineSelectWidget.getItemFromData( name ) ); page.setOutlineItem( null ); } } if ( this.outlined && items.length ) { - this.outlineWidget.removeItems( items ); - this.updateOutlineWidget(); + this.outlineSelectWidget.removeItems( items ); + this.selectFirstSelectablePage(); } this.stackLayout.removeItems( pages ); this.emit( 'remove', pages ); @@ -6528,7 +6719,7 @@ OO.ui.BookletLayout.prototype.clearPages = function () { this.pages = {}; this.currentPageName = null; if ( this.outlined ) { - this.outlineWidget.clearItems(); + this.outlineSelectWidget.clearItems(); for ( i = 0, len = pages.length; i < len; i++ ) { pages[i].setOutlineItem( null ); } @@ -6553,9 +6744,9 @@ OO.ui.BookletLayout.prototype.setPage = function ( name ) { if ( name !== this.currentPageName ) { if ( this.outlined ) { - selectedItem = this.outlineWidget.getSelectedItem(); + selectedItem = this.outlineSelectWidget.getSelectedItem(); if ( selectedItem && selectedItem.getData() !== name ) { - this.outlineWidget.selectItem( this.outlineWidget.getItemFromData( name ) ); + this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getItemFromData( name ) ); } } if ( page ) { @@ -6580,14 +6771,13 @@ OO.ui.BookletLayout.prototype.setPage = function ( name ) { }; /** - * Call this after adding or removing items from the OutlineWidget. + * Select the first selectable page. * * @chainable */ -OO.ui.BookletLayout.prototype.updateOutlineWidget = function () { - // Auto-select first item when nothing is selected anymore - if ( !this.outlineWidget.getSelectedItem() ) { - this.outlineWidget.selectItem( this.outlineWidget.getFirstSelectableItem() ); +OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () { + if ( !this.outlineSelectWidget.getSelectedItem() ) { + this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getFirstSelectableItem() ); } return this; @@ -6596,10 +6786,6 @@ OO.ui.BookletLayout.prototype.updateOutlineWidget = function () { /** * Layout made of a field and optional label. * - * @class - * @extends OO.ui.Layout - * @mixins OO.ui.LabelElement - * * Available label alignment modes include: * - left: Label is before the field and aligned away from it, best for when the user will be * scanning for a specific label in a form with many fields @@ -6610,6 +6796,10 @@ OO.ui.BookletLayout.prototype.updateOutlineWidget = function () { * - inline: Label is after the field and aligned toward it, best for small boolean fields like * checkboxes or radio buttons * + * @class + * @extends OO.ui.Layout + * @mixins OO.ui.LabelElement + * * @constructor * @param {OO.ui.Widget} fieldWidget Field widget * @param {Object} [config] Configuration options @@ -6617,9 +6807,12 @@ OO.ui.BookletLayout.prototype.updateOutlineWidget = function () { * @cfg {string} [help] Explanatory text shown as a '?' icon. */ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) { - // Config initialization + // Configuration initialization config = $.extend( { align: 'left' }, config ); + // Properties (must be set before parent constructor, which calls #getTagName) + this.fieldWidget = fieldWidget; + // Parent constructor OO.ui.FieldLayout.super.call( this, config ); @@ -6628,7 +6821,6 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) { // Properties this.$field = this.$( '
' ); - this.fieldWidget = fieldWidget; this.align = null; if ( config.help ) { this.popupButtonWidget = new OO.ui.PopupButtonWidget( { @@ -6670,6 +6862,17 @@ OO.mixinClass( OO.ui.FieldLayout, OO.ui.LabelElement ); /* Methods */ +/** + * @inheritdoc + */ +OO.ui.FieldLayout.prototype.getTagName = function () { + if ( this.fieldWidget instanceof OO.ui.InputWidget ) { + return 'label'; + } else { + return 'div'; + } +}; + /** * Handle field disable events. * @@ -6701,6 +6904,7 @@ OO.ui.FieldLayout.prototype.getField = function () { /** * Set the field alignment mode. * + * @private * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline' * @chainable */ @@ -6738,8 +6942,8 @@ OO.ui.FieldLayout.prototype.setAlignment = function ( value ) { * * @class * @extends OO.ui.Layout - * @mixins OO.ui.LabelElement * @mixins OO.ui.IconElement + * @mixins OO.ui.LabelElement * @mixins OO.ui.GroupElement * * @constructor @@ -6747,7 +6951,7 @@ OO.ui.FieldLayout.prototype.setAlignment = function ( value ) { * @cfg {OO.ui.FieldLayout[]} [items] Items to add */ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) { - // Config initialization + // Configuration initialization config = config || {}; // Parent constructor @@ -6848,7 +7052,7 @@ OO.ui.FormLayout.prototype.onFormSubmit = function () { OO.ui.GridLayout = function OoUiGridLayout( panels, config ) { var i, len, widths; - // Config initialization + // Configuration initialization config = config || {}; // Parent constructor @@ -6984,7 +7188,7 @@ OO.ui.GridLayout.prototype.update = function () { * * @param {number} x Horizontal position * @param {number} y Vertical position - * @return {OO.ui.PanelLayout} The panel at the given postion + * @return {OO.ui.PanelLayout} The panel at the given position */ OO.ui.GridLayout.prototype.getPanel = function ( x, y ) { return this.panels[ ( x * this.widths.length ) + y ]; @@ -7003,7 +7207,7 @@ OO.ui.GridLayout.prototype.getPanel = function ( x, y ) { * @cfg {boolean} [expanded=true] Expand size to fill the entire parent element */ OO.ui.PanelLayout = function OoUiPanelLayout( config ) { - // Config initialization + // Configuration initialization config = $.extend( { scrollable: false, padded: false, @@ -7091,7 +7295,7 @@ OO.ui.PageLayout.prototype.isActive = function () { /** * Get outline item. * - * @return {OO.ui.OutlineItemWidget|null} Outline item widget + * @return {OO.ui.OutlineOptionWidget|null} Outline item widget */ OO.ui.PageLayout.prototype.getOutlineItem = function () { return this.outlineItem; @@ -7103,9 +7307,9 @@ OO.ui.PageLayout.prototype.getOutlineItem = function () { * @localdoc Subclasses should override #setupOutlineItem instead of this method to adjust the * outline item as desired; this method is called for setting (with an object) and unsetting * (with null) and overriding methods would have to check the value of `outlineItem` to avoid - * operating on null instead of an OO.ui.OutlineItemWidget object. + * operating on null instead of an OO.ui.OutlineOptionWidget object. * - * @param {OO.ui.OutlineItemWidget|null} outlineItem Outline item widget, null to clear + * @param {OO.ui.OutlineOptionWidget|null} outlineItem Outline item widget, null to clear * @chainable */ OO.ui.PageLayout.prototype.setOutlineItem = function ( outlineItem ) { @@ -7121,7 +7325,7 @@ OO.ui.PageLayout.prototype.setOutlineItem = function ( outlineItem ) { * * @localdoc Subclasses should override this method to adjust the outline item as desired. * - * @param {OO.ui.OutlineItemWidget} outlineItem Outline item widget to setup + * @param {OO.ui.OutlineOptionWidget} outlineItem Outline item widget to setup * @chainable */ OO.ui.PageLayout.prototype.setupOutlineItem = function () { @@ -7158,7 +7362,7 @@ OO.ui.PageLayout.prototype.setActive = function ( active ) { * @cfg {OO.ui.Layout[]} [items] Layouts to add */ OO.ui.StackLayout = function OoUiStackLayout( config ) { - // Config initialization + // Configuration initialization config = $.extend( { scrollable: true }, config ); // Parent constructor @@ -7451,7 +7655,9 @@ OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) { */ OO.ui.PopupToolGroup.prototype.onPointerUp = function ( e ) { // e.which is 0 for touch events, 1 for left mouse button - if ( !this.isDisabled() && e.which <= 1 ) { + // Only close toolgroup when a tool was actually selected + // FIXME: this duplicates logic from the parent class + if ( !this.isDisabled() && e.which <= 1 && this.pressed && this.pressed === this.getTargetTool( e ) ) { this.setActive( false ); } return OO.ui.PopupToolGroup.super.prototype.onPointerUp.call( this, e ); @@ -7532,6 +7738,9 @@ OO.ui.PopupToolGroup.prototype.setActive = function ( value ) { * @cfg {boolean} [expanded=false] Whether the collapsible tools are expanded by default */ OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) { + // Configuration initialization + config = config || {}; + // Properties (must be set before parent constructor, which calls #populate) this.allowCollapse = config.allowCollapse; this.forceExpand = config.forceExpand; @@ -7806,7 +8015,7 @@ OO.ui.GroupWidget.prototype.setDisabled = function ( disabled ) { * Mixin for widgets used as items in widgets that inherit OO.ui.GroupWidget. * * Item widgets have a reference to a OO.ui.GroupWidget while they are attached to the group. This - * allows bidrectional communication. + * allows bidirectional communication. * * Use together with OO.ui.GroupWidget to make disabled state inheritable. * @@ -7855,6 +8064,10 @@ OO.ui.ItemWidget.prototype.setElementGroup = function ( group ) { * * Subclasses must handle `select` and `choose` events on #lookupMenu to make use of selections. * + * Subclasses that set the value of #lookupInput from their `choose` or `select` handler should + * be aware that this will cause new suggestions to be looked up for the new value. If this is + * not desired, disable lookups with #setLookupsDisabled, then set the value, then re-enable lookups. + * * @class * @abstract * @@ -7862,15 +8075,16 @@ OO.ui.ItemWidget.prototype.setElementGroup = function ( group ) { * @param {OO.ui.TextInputWidget} input Input widget * @param {Object} [config] Configuration options * @cfg {jQuery} [$overlay] Overlay for dropdown; defaults to relative positioning + * @cfg {jQuery} [$container=input.$element] Element to render menu under */ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) { - // Config intialization + // Configuration initialization config = config || {}; // Properties this.lookupInput = input; this.$overlay = config.$overlay || this.$element; - this.lookupMenu = new OO.ui.TextInputMenuWidget( this, { + this.lookupMenu = new OO.ui.TextInputMenuSelectWidget( this, { $: OO.ui.Element.getJQuery( this.$overlay ), input: this.lookupInput, $container: config.$container @@ -7878,7 +8092,8 @@ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) { this.lookupCache = {}; this.lookupQuery = null; this.lookupRequest = null; - this.populating = false; + this.lookupsDisabled = false; + this.lookupInputFocused = false; // Events this.lookupInput.$input.on( { @@ -7887,6 +8102,7 @@ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) { mousedown: this.onLookupInputMouseDown.bind( this ) } ); this.lookupInput.connect( this, { change: 'onLookupInputChange' } ); + this.lookupMenu.connect( this, { toggle: 'onLookupMenuToggle' } ); // Initialization this.$element.addClass( 'oo-ui-lookupWidget' ); @@ -7902,7 +8118,8 @@ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) { * @param {jQuery.Event} e Input focus event */ OO.ui.LookupInputWidget.prototype.onLookupInputFocus = function () { - this.openLookupMenu(); + this.lookupInputFocused = true; + this.populateLookupMenu(); }; /** @@ -7911,7 +8128,8 @@ OO.ui.LookupInputWidget.prototype.onLookupInputFocus = function () { * @param {jQuery.Event} e Input blur event */ OO.ui.LookupInputWidget.prototype.onLookupInputBlur = function () { - this.lookupMenu.toggle( false ); + this.closeLookupMenu(); + this.lookupInputFocused = false; }; /** @@ -7920,7 +8138,13 @@ OO.ui.LookupInputWidget.prototype.onLookupInputBlur = function () { * @param {jQuery.Event} e Input mouse down event */ OO.ui.LookupInputWidget.prototype.onLookupInputMouseDown = function () { - this.openLookupMenu(); + // Only open the menu if the input was already focused. + // This way we allow the user to open the menu again after closing it with Esc + // by clicking in the input. Opening (and populating) the menu when initially + // clicking into the input is handled by the focus handler. + if ( this.lookupInputFocused && !this.lookupMenu.isVisible() ) { + this.populateLookupMenu(); + } }; /** @@ -7929,48 +8153,90 @@ OO.ui.LookupInputWidget.prototype.onLookupInputMouseDown = function () { * @param {string} value New input value */ OO.ui.LookupInputWidget.prototype.onLookupInputChange = function () { - this.openLookupMenu(); + if ( this.lookupInputFocused ) { + this.populateLookupMenu(); + } +}; + +/** + * Handle the lookup menu being shown/hidden. + * @param {boolean} visible Whether the lookup menu is now visible. + */ +OO.ui.LookupInputWidget.prototype.onLookupMenuToggle = function ( visible ) { + if ( !visible ) { + // When the menu is hidden, abort any active request and clear the menu. + // This has to be done here in addition to closeLookupMenu(), because + // MenuSelectWidget will close itself when the user presses Esc. + this.abortLookupRequest(); + this.lookupMenu.clearItems(); + } }; /** * Get lookup menu. * - * @return {OO.ui.TextInputMenuWidget} + * @return {OO.ui.TextInputMenuSelectWidget} */ OO.ui.LookupInputWidget.prototype.getLookupMenu = function () { return this.lookupMenu; }; /** - * Open the menu. + * Disable or re-enable lookups. + * + * When lookups are disabled, calls to #populateLookupMenu will be ignored. + * + * @param {boolean} disabled Disable lookups + */ +OO.ui.LookupInputWidget.prototype.setLookupsDisabled = function ( disabled ) { + this.lookupsDisabled = !!disabled; +}; + +/** + * Open the menu. If there are no entries in the menu, this does nothing. * * @chainable */ OO.ui.LookupInputWidget.prototype.openLookupMenu = function () { - var value = this.lookupInput.getValue(); - - if ( this.lookupMenu.$input.is( ':focus' ) && $.trim( value ) !== '' ) { - this.populateLookupMenu(); + if ( !this.lookupMenu.isEmpty() ) { this.lookupMenu.toggle( true ); - } else { - this.lookupMenu - .clearItems() - .toggle( false ); } + return this; +}; +/** + * Close the menu, empty it, and abort any pending request. + * + * @chainable + */ +OO.ui.LookupInputWidget.prototype.closeLookupMenu = function () { + this.lookupMenu.toggle( false ); + this.abortLookupRequest(); + this.lookupMenu.clearItems(); return this; }; /** - * Populate lookup menu with current information. + * Request menu items based on the input's current value, and when they arrive, + * populate the menu with these items and show the menu. + * + * If lookups have been disabled with #setLookupsDisabled, this function does nothing. * * @chainable */ OO.ui.LookupInputWidget.prototype.populateLookupMenu = function () { - var widget = this; + var widget = this, + value = this.lookupInput.getValue(); + + if ( this.lookupsDisabled ) { + return; + } - if ( !this.populating ) { - this.populating = true; + // If the input is empty, clear the menu + if ( value === '' ) { + this.closeLookupMenu(); + // Skip population if there is already a request pending for the current value + } else if ( value !== this.lookupQuery ) { this.getLookupMenuItems() .done( function ( items ) { widget.lookupMenu.clearItems(); @@ -7979,15 +8245,12 @@ OO.ui.LookupInputWidget.prototype.populateLookupMenu = function () { .addItems( items ) .toggle( true ); widget.initializeLookupMenuSelection(); - widget.openLookupMenu(); } else { - widget.lookupMenu.toggle( true ); + widget.lookupMenu.toggle( false ); } - widget.populating = false; } ) .fail( function () { widget.lookupMenu.clearItems(); - widget.populating = false; } ); } @@ -7995,7 +8258,7 @@ OO.ui.LookupInputWidget.prototype.populateLookupMenu = function () { }; /** - * Set selection in the lookup menu with current information. + * Select and highlight the first selectable item in the menu. * * @chainable */ @@ -8010,50 +8273,74 @@ OO.ui.LookupInputWidget.prototype.initializeLookupMenuSelection = function () { * Get lookup menu items for the current query. * * @return {jQuery.Promise} Promise object which will be passed menu items as the first argument - * of the done event + * of the done event. If the request was aborted to make way for a subsequent request, + * this promise will not be rejected: it will remain pending forever. */ OO.ui.LookupInputWidget.prototype.getLookupMenuItems = function () { var widget = this, value = this.lookupInput.getValue(), - deferred = $.Deferred(); + deferred = $.Deferred(), + ourRequest; - 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 ) { - deferred.resolve( this.getLookupMenuItemsFromData( this.lookupCache[value] ) ); - } else { - this.lookupQuery = value; - this.lookupRequest = this.getLookupRequest() - .always( function () { + this.abortLookupRequest(); + if ( Object.prototype.hasOwnProperty.call( this.lookupCache, value ) ) { + deferred.resolve( this.getLookupMenuItemsFromData( this.lookupCache[value] ) ); + } else { + this.lookupInput.pushPending(); + this.lookupQuery = value; + ourRequest = this.lookupRequest = this.getLookupRequest(); + ourRequest + .always( function () { + // We need to pop pending even if this is an old request, otherwise + // the widget will remain pending forever. + // TODO: this assumes that an aborted request will fail or succeed soon after + // being aborted, or at least eventually. It would be nice if we could popPending() + // at abort time, but only if we knew that we hadn't already called popPending() + // for that request. + widget.lookupInput.popPending(); + } ) + .done( function ( data ) { + // If this is an old request (and aborting it somehow caused it to still succeed), + // ignore its success completely + if ( ourRequest === widget.lookupRequest ) { widget.lookupQuery = null; widget.lookupRequest = null; - } ) - .done( function ( data ) { widget.lookupCache[value] = widget.getLookupCacheItemFromData( data ); deferred.resolve( widget.getLookupMenuItemsFromData( widget.lookupCache[value] ) ); - } ) - .fail( function () { + } + } ) + .fail( function () { + // If this is an old request (or a request failing because it's being aborted), + // ignore its failure completely + if ( ourRequest === widget.lookupRequest ) { + widget.lookupQuery = null; + widget.lookupRequest = null; deferred.reject(); - } ); - this.pushPending(); - this.lookupRequest.always( function () { - widget.popPending(); + } } ); - } } return deferred.promise(); }; +/** + * Abort the currently pending lookup request, if any. + */ +OO.ui.LookupInputWidget.prototype.abortLookupRequest = function () { + var oldRequest = this.lookupRequest; + if ( oldRequest ) { + // First unset this.lookupRequest to the fail handler will notice + // that the request is no longer current + this.lookupRequest = null; + this.lookupQuery = null; + oldRequest.abort(); + } +}; + /** * Get a new request object of the current lookup query value. * * @abstract - * @return {jqXHR} jQuery AJAX object, or promise object with an .abort() method + * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method */ OO.ui.LookupInputWidget.prototype.getLookupRequest = function () { // Stub, implemented in subclass @@ -8061,32 +8348,31 @@ OO.ui.LookupInputWidget.prototype.getLookupRequest = function () { }; /** - * 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 #OO.ui.MenuItemWidget objects. + * Get a list of menu item widgets from the data stored by the lookup request's done handler. * * @abstract - * @param {Mixed} data Response from server + * @param {Mixed} data Cached result data, usually an array + * @return {OO.ui.MenuOptionWidget[]} Menu items */ -OO.ui.LookupInputWidget.prototype.onLookupRequestDone = function () { +OO.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () { // Stub, implemented in subclass + return []; }; /** - * Get a list of menu item widgets from the data stored by the lookup request's done handler. + * Get lookup cache item from server response data. * * @abstract - * @param {Mixed} data Cached result data, usually an array - * @return {OO.ui.MenuItemWidget[]} Menu items + * @param {Mixed} data Response from server + * @return {Mixed} Cached result data */ -OO.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () { +OO.ui.LookupInputWidget.prototype.getLookupCacheItemFromData = function () { // Stub, implemented in subclass return []; }; /** - * Set of controls for an OO.ui.OutlineWidget. + * Set of controls for an OO.ui.OutlineSelectWidget. * * Controls include moving items up and down, removing items, and adding different kinds of items. * @@ -8096,7 +8382,7 @@ OO.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () { * @mixins OO.ui.IconElement * * @constructor - * @param {OO.ui.OutlineWidget} outline Outline to control + * @param {OO.ui.OutlineSelectWidget} outline Outline to control * @param {Object} [config] Configuration options */ OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) { @@ -8271,9 +8557,12 @@ OO.ui.ToggleWidget.prototype.setValue = function ( value ) { * * @constructor * @param {Object} [config] Configuration options - * @cfg {OO.ui.ButtonWidget} [items] Buttons to add + * @cfg {OO.ui.ButtonWidget[]} [items] Buttons to add */ OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) { + // Configuration initialization + config = config || {}; + // Parent constructor OO.ui.ButtonGroupWidget.super.call( this, config ); @@ -8468,7 +8757,7 @@ OO.ui.ButtonWidget.prototype.setTarget = function ( target ) { * @cfg {boolean} [framed=false] Render button with a frame */ OO.ui.ActionWidget = function OoUiActionWidget( config ) { - // Config intialization + // Configuration initialization config = $.extend( { framed: false }, config ); // Parent constructor @@ -8717,191 +9006,191 @@ OO.ui.ToggleButtonWidget.prototype.setValue = function ( value ) { }; /** - * Icon widget. + * Dropdown menu of options. * - * See OO.ui.IconElement for more information. + * Dropdown menus provide a control for accessing a menu and compose a menu within the widget, which + * can be accessed using the #getMenu method. + * + * Use with OO.ui.MenuOptionWidget. * * @class * @extends OO.ui.Widget * @mixins OO.ui.IconElement + * @mixins OO.ui.IndicatorElement + * @mixins OO.ui.LabelElement * @mixins OO.ui.TitledElement * * @constructor * @param {Object} [config] Configuration options + * @cfg {Object} [menu] Configuration options to pass to menu widget */ -OO.ui.IconWidget = function OoUiIconWidget( config ) { - // Config intialization - config = config || {}; +OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) { + // Configuration initialization + config = $.extend( { indicator: 'down' }, config ); // Parent constructor - OO.ui.IconWidget.super.call( this, config ); + OO.ui.DropdownWidget.super.call( this, config ); // Mixin constructors - OO.ui.IconElement.call( this, $.extend( {}, config, { $icon: this.$element } ) ); - OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) ); + OO.ui.IconElement.call( this, config ); + OO.ui.IndicatorElement.call( this, config ); + OO.ui.LabelElement.call( this, config ); + OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) ); + + // Properties + this.menu = new OO.ui.MenuSelectWidget( $.extend( { $: this.$, widget: this }, config.menu ) ); + this.$handle = this.$( '' ); + + // Events + this.$element.on( { click: this.onClick.bind( this ) } ); + this.menu.connect( this, { select: 'onMenuSelect' } ); // Initialization - this.$element.addClass( 'oo-ui-iconWidget' ); + this.$handle + .addClass( 'oo-ui-dropdownWidget-handle' ) + .append( this.$icon, this.$label, this.$indicator ); + this.$element + .addClass( 'oo-ui-dropdownWidget' ) + .append( this.$handle, this.menu.$element ); }; /* Setup */ -OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget ); -OO.mixinClass( OO.ui.IconWidget, OO.ui.IconElement ); -OO.mixinClass( OO.ui.IconWidget, OO.ui.TitledElement ); +OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget ); +OO.mixinClass( OO.ui.DropdownWidget, OO.ui.IconElement ); +OO.mixinClass( OO.ui.DropdownWidget, OO.ui.IndicatorElement ); +OO.mixinClass( OO.ui.DropdownWidget, OO.ui.LabelElement ); +OO.mixinClass( OO.ui.DropdownWidget, OO.ui.TitledElement ); -/* Static Properties */ +/* Methods */ -OO.ui.IconWidget.static.tagName = 'span'; +/** + * Get the menu. + * + * @return {OO.ui.MenuSelectWidget} Menu of widget + */ +OO.ui.DropdownWidget.prototype.getMenu = function () { + return this.menu; +}; /** - * Indicator widget. + * Handles menu select events. * - * See OO.ui.IndicatorElement for more information. + * @param {OO.ui.MenuOptionWidget} item Selected menu item + */ +OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) { + var selectedLabel; + + if ( !item ) { + return; + } + + selectedLabel = item.getLabel(); + + // If the label is a DOM element, clone it, because setLabel will append() it + if ( selectedLabel instanceof jQuery ) { + selectedLabel = selectedLabel.clone(); + } + + this.setLabel( selectedLabel ); +}; + +/** + * Handles mouse click events. + * + * @param {jQuery.Event} e Mouse click event + */ +OO.ui.DropdownWidget.prototype.onClick = function ( e ) { + // Skip clicks within the menu + if ( $.contains( this.menu.$element[0], e.target ) ) { + return; + } + + if ( !this.isDisabled() ) { + if ( this.menu.isVisible() ) { + this.menu.toggle( false ); + } else { + this.menu.toggle( true ); + } + } + return false; +}; + +/** + * Icon widget. + * + * See OO.ui.IconElement for more information. * * @class * @extends OO.ui.Widget - * @mixins OO.ui.IndicatorElement + * @mixins OO.ui.IconElement * @mixins OO.ui.TitledElement * * @constructor * @param {Object} [config] Configuration options */ -OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) { - // Config intialization +OO.ui.IconWidget = function OoUiIconWidget( config ) { + // Configuration initialization config = config || {}; // Parent constructor - OO.ui.IndicatorWidget.super.call( this, config ); + OO.ui.IconWidget.super.call( this, config ); // Mixin constructors - OO.ui.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) ); + OO.ui.IconElement.call( this, $.extend( {}, config, { $icon: this.$element } ) ); OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) ); // Initialization - this.$element.addClass( 'oo-ui-indicatorWidget' ); + this.$element.addClass( 'oo-ui-iconWidget' ); }; /* Setup */ -OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget ); -OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.IndicatorElement ); -OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.TitledElement ); +OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget ); +OO.mixinClass( OO.ui.IconWidget, OO.ui.IconElement ); +OO.mixinClass( OO.ui.IconWidget, OO.ui.TitledElement ); /* Static Properties */ -OO.ui.IndicatorWidget.static.tagName = 'span'; +OO.ui.IconWidget.static.tagName = 'span'; /** - * Inline menu of options. - * - * Inline menus provide a control for accessing a menu and compose a menu within the widget, which - * can be accessed using the #getMenu method. + * Indicator widget. * - * Use with OO.ui.MenuItemWidget. + * See OO.ui.IndicatorElement for more information. * * @class * @extends OO.ui.Widget - * @mixins OO.ui.IconElement * @mixins OO.ui.IndicatorElement - * @mixins OO.ui.LabelElement * @mixins OO.ui.TitledElement * * @constructor * @param {Object} [config] Configuration options - * @cfg {Object} [menu] Configuration options to pass to menu widget */ -OO.ui.InlineMenuWidget = function OoUiInlineMenuWidget( config ) { +OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) { // Configuration initialization - config = $.extend( { indicator: 'down' }, config ); + config = config || {}; // Parent constructor - OO.ui.InlineMenuWidget.super.call( this, config ); + OO.ui.IndicatorWidget.super.call( this, config ); // Mixin constructors - OO.ui.IconElement.call( this, config ); - OO.ui.IndicatorElement.call( this, config ); - OO.ui.LabelElement.call( this, config ); - OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) ); - - // Properties - this.menu = new OO.ui.MenuWidget( $.extend( { $: this.$, widget: this }, config.menu ) ); - this.$handle = this.$( '' ); - - // Events - this.$element.on( { click: this.onClick.bind( this ) } ); - this.menu.connect( this, { select: 'onMenuSelect' } ); + OO.ui.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) ); + OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) ); // Initialization - this.$handle - .addClass( 'oo-ui-inlineMenuWidget-handle' ) - .append( this.$icon, this.$label, this.$indicator ); - this.$element - .addClass( 'oo-ui-inlineMenuWidget' ) - .append( this.$handle, this.menu.$element ); + this.$element.addClass( 'oo-ui-indicatorWidget' ); }; /* Setup */ -OO.inheritClass( OO.ui.InlineMenuWidget, OO.ui.Widget ); -OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.IconElement ); -OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.IndicatorElement ); -OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.LabelElement ); -OO.mixinClass( OO.ui.InlineMenuWidget, OO.ui.TitledElement ); - -/* Methods */ - -/** - * Get the menu. - * - * @return {OO.ui.MenuWidget} Menu of widget - */ -OO.ui.InlineMenuWidget.prototype.getMenu = function () { - return this.menu; -}; - -/** - * Handles menu select events. - * - * @param {OO.ui.MenuItemWidget} item Selected menu item - */ -OO.ui.InlineMenuWidget.prototype.onMenuSelect = function ( item ) { - var selectedLabel; - - if ( !item ) { - return; - } - - selectedLabel = item.getLabel(); - - // If the label is a DOM element, clone it, because setLabel will append() it - if ( selectedLabel instanceof jQuery ) { - selectedLabel = selectedLabel.clone(); - } - - this.setLabel( selectedLabel ); -}; +OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget ); +OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.IndicatorElement ); +OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.TitledElement ); -/** - * Handles mouse click events. - * - * @param {jQuery.Event} e Mouse click event - */ -OO.ui.InlineMenuWidget.prototype.onClick = function ( e ) { - // Skip clicks within the menu - if ( $.contains( this.menu.$element[0], e.target ) ) { - return; - } +/* Static Properties */ - if ( !this.isDisabled() ) { - if ( this.menu.isVisible() ) { - this.menu.toggle( false ); - } else { - this.menu.toggle( true ); - } - } - return false; -}; +OO.ui.IndicatorWidget.static.tagName = 'span'; /** * Base class for input widgets. @@ -8915,12 +9204,11 @@ OO.ui.InlineMenuWidget.prototype.onClick = function ( e ) { * @param {Object} [config] Configuration options * @cfg {string} [name=''] HTML input name * @cfg {string} [value=''] Input value - * @cfg {boolean} [readOnly=false] Prevent changes * @cfg {Function} [inputFilter] Filter function to apply to the input. Takes a string argument and returns a string. */ OO.ui.InputWidget = function OoUiInputWidget( config ) { - // Config intialization - config = $.extend( { readOnly: false }, config ); + // Configuration initialization + config = config || {}; // Parent constructor OO.ui.InputWidget.super.call( this, config ); @@ -8931,7 +9219,6 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) { // Properties this.$input = this.getInputElement( config ); this.value = ''; - this.readOnly = false; this.inputFilter = config.inputFilter; // Events @@ -8941,8 +9228,7 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) { this.$input .attr( 'name', config.name ) .prop( 'disabled', this.isDisabled() ); - this.setReadOnly( config.readOnly ); - this.$element.addClass( 'oo-ui-inputWidget' ).append( this.$input ); + this.$element.addClass( 'oo-ui-inputWidget' ).append( this.$input, $( '' ) ); this.setValue( config.value ); }; @@ -8963,6 +9249,7 @@ OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement ); /** * Get input element. * + * @private * @param {Object} [config] Configuration options * @return {jQuery} Input element */ @@ -9017,12 +9304,12 @@ OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) { * @chainable */ OO.ui.InputWidget.prototype.setValue = function ( value ) { - value = this.sanitizeValue( value ); + value = this.cleanUpValue( value ); if ( this.value !== value ) { this.value = value; this.emit( 'change', this.value ); } - // Update the DOM if it has changed. Note that with sanitizeValue, it + // Update the DOM if it has changed. Note that with cleanUpValue, it // is possible for the DOM value to change without this.value changing. if ( this.$input.val() !== this.value ) { this.$input.val( this.value ); @@ -9031,14 +9318,15 @@ OO.ui.InputWidget.prototype.setValue = function ( value ) { }; /** - * Sanitize incoming value. + * Clean up incoming value. * - * Ensures value is a string, and converts undefined and null to empty strings. + * Ensures value is a string, and converts undefined and null to empty string. * + * @private * @param {string} value Original value - * @return {string} Sanitized value + * @return {string} Cleaned up value */ -OO.ui.InputWidget.prototype.sanitizeValue = function ( value ) { +OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) { if ( value === undefined || value === null ) { return ''; } else if ( this.inputFilter ) { @@ -9061,29 +9349,6 @@ OO.ui.InputWidget.prototype.simulateLabelClick = function () { } }; -/** - * Check if the widget is read-only. - * - * @return {boolean} - */ -OO.ui.InputWidget.prototype.isReadOnly = function () { - return this.readOnly; -}; - -/** - * Set the read-only state of the widget. - * - * This should probably change the widgets's appearance and prevent it from being used. - * - * @param {boolean} state Make input read-only - * @chainable - */ -OO.ui.InputWidget.prototype.setReadOnly = function ( state ) { - this.readOnly = !!state; - this.$input.prop( 'readOnly', this.readOnly ); - return this; -}; - /** * @inheritdoc */ @@ -9116,7 +9381,7 @@ OO.ui.InputWidget.prototype.blur = function () { }; /** - * A button that is an input widget. Intended to be used within FormLayouts. + * A button that is an input widget. Intended to be used within a OO.ui.FormLayout. * * @class * @extends OO.ui.InputWidget @@ -9131,14 +9396,17 @@ OO.ui.InputWidget.prototype.blur = function () { * @param {Object} [config] Configuration options * @cfg {string} [type='button'] HTML tag `type` attribute, may be 'button', 'submit' or 'reset' * @cfg {boolean} [useInputTag=false] Whether to use `` rather than `