text: /**
   * FieldLayouts are used with OO.ui.FieldsetLayout. Each FieldLayout requires a field-widget,
   * which is a widget that is specified by reference before any optional configuration settings.
   *
   * Field layouts can be configured with help text and/or labels. Labels are aligned in one of
   * four ways:
   *
   * - **left**: The label is placed before the field-widget and aligned with the left margin.
   *   A left-alignment is used for forms with many fields.
   * - **right**: The label is placed before the field-widget and aligned to the right margin.
   *   A right-alignment is used for long but familiar forms which users tab through,
   *   verifying the current field with a quick glance at the label.
   * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
   *   that users fill out from top to bottom.
   * - **inline**: The label is placed after the field-widget and aligned to the left.
   *   An inline-alignment is best used with checkboxes or radio buttons.
   *
   * Help text can either be:
   *
   * - accessed via a help icon that appears in the upper right corner of the rendered field layout,
   *   or
   * - shown as a subtle explanation below the label.
   *
   * If the help text is brief, or is essential to always expose it, set `helpInline` to `true`.
   * If it is long or not essential, leave `helpInline` to its default, `false`.
   *
   * Please see the [OOUI documentation on MediaWiki] [1] for examples and more information.
   *
   * [1]: https://www.mediawiki.org/wiki/OOUI/Layouts/Fields_and_Fieldsets
   *
   * @class
   * @extends OO.ui.Layout
   * @mixins OO.ui.mixin.LabelElement
   * @mixins OO.ui.mixin.TitledElement
   *
   * @constructor
   * @param {OO.ui.Widget} fieldWidget Field widget
   * @param {Object} [config] Configuration options
   * @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top'
   *  or 'inline'
   * @cfg {Array} [errors] Error messages about the widget, which will be
   *  displayed below the widget.
   * @cfg {Array} [warnings] Warning messages about the widget, which will be
   *  displayed below the widget.
   * @cfg {Array} [successMessages] Success messages on user interactions with the widget,
   *  which will be displayed below the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   * @cfg {Array} [notices] Notices about the widget, which will be displayed
   *  below the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   *  These are more visible than `help` messages when `helpInline` is set, and so
   *  might be good for transient messages.
   * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified
   *  and `helpInline` is `false`, a "help" icon will appear in the upper-right
   *  corner of the rendered field; clicking it will display the text in a popup.
   *  If `helpInline` is `true`, then a subtle description will be shown after the
   *  label.
   * @cfg {boolean} [helpInline=false] Whether or not the help should be inline,
   *  or shown when the "help" icon is clicked.
   * @cfg {jQuery} [$overlay] Passed to OO.ui.PopupButtonWidget for help popup, if
   * `help` is given.
   *  See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
   *
   * @throws {Error} An error is thrown if no widget is specified
   */
  OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
  	var id;
  
  	// Allow passing positional parameters inside the config object
  	if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
  		config = fieldWidget;
  		fieldWidget = config.fieldWidget;
  	}
  
  	// Make sure we have required constructor arguments
  	if ( fieldWidget === undefined ) {
  		throw new Error( 'Widget not found' );
  	}
  
  	// Configuration initialization
  	config = $.extend( { align: 'left', helpInline: false }, config );
  
  	// Parent constructor
  	OO.ui.FieldLayout.super.call( this, config );
  
  	// Mixin constructors
  	OO.ui.mixin.LabelElement.call( this, $.extend( {
  		$label: $( '<label>' )
  	}, config ) );
  	OO.ui.mixin.TitledElement.call( this, $.extend( { $titled: this.$label }, config ) );
  
  	// Properties
  	this.fieldWidget = fieldWidget;
  	this.errors = [];
  	this.warnings = [];
  	this.successMessages = [];
  	this.notices = [];
  	this.$field = this.isFieldInline() ? $( '<span>' ) : $( '<div>' );
  	this.$messages = $( '<div>' );
  	this.$header = $( '<span>' );
  	this.$body = $( '<div>' );
  	this.align = null;
  	this.helpInline = config.helpInline;
  
  	// Events
  	this.fieldWidget.connect( this, {
  		disable: 'onFieldDisable'
  	} );
  
  	// Initialization
  	this.$help = config.help ?
  		this.createHelpElement( config.help, config.$overlay ) :
  		$( [] );
  	if ( this.fieldWidget.getInputId() ) {
  		this.$label.attr( 'for', this.fieldWidget.getInputId() );
  		if ( this.helpInline ) {
  			this.$help.attr( 'for', this.fieldWidget.getInputId() );
  		}
  	} else {
  		// We can't use `label for` with non-form elements, use `aria-labelledby` instead
  		id = OO.ui.generateElementId();
  		this.$label.attr( 'id', id );
  		this.fieldWidget.setLabelledBy( id );
  
  		// Forward clicks on the label to the widget, like `label for` would do
  		this.$label.on( 'click', function () {
  			this.fieldWidget.simulateLabelClick();
  		}.bind( this ) );
  		if ( this.helpInline ) {
  			this.$help.on( 'click', function () {
  				this.fieldWidget.simulateLabelClick();
  			}.bind( this ) );
  		}
  	}
  	this.$element
  		.addClass( 'oo-ui-fieldLayout' )
  		.toggleClass( 'oo-ui-fieldLayout-disabled', this.fieldWidget.isDisabled() )
  		.append( this.$body );
  	this.$body.addClass( 'oo-ui-fieldLayout-body' );
  	this.$header.addClass( 'oo-ui-fieldLayout-header' );
  	this.$messages.addClass( 'oo-ui-fieldLayout-messages' );
  	this.$field
  		.addClass( 'oo-ui-fieldLayout-field' )
  		.append( this.fieldWidget.$element );
  
  	this.setErrors( config.errors || [] );
  	this.setWarnings( config.warnings || [] );
  	this.setSuccess( config.successMessages || [] );
  	this.setNotices( config.notices || [] );
  	this.setAlignment( config.align );
  	// Call this again to take into account the widget's accessKey
  	this.updateTitle();
  };
  
  /* Setup */
  
  OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
  OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.LabelElement );
  OO.mixinClass( OO.ui.FieldLayout, OO.ui.mixin.TitledElement );
  
  /* Methods */
  
  /**
   * Handle field disable events.
   *
   * @private
   * @param {boolean} value Field is disabled
   */
  OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) {
  	this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value );
  };
  
  /**
   * Get the widget contained by the field.
   *
   * @return {OO.ui.Widget} Field widget
   */
  OO.ui.FieldLayout.prototype.getField = function () {
  	return this.fieldWidget;
  };
  
  /**
   * Return `true` if the given field widget can be used with `'inline'` alignment (see
   * #setAlignment). Return `false` if it can't or if this can't be determined.
   *
   * @return {boolean}
   */
  OO.ui.FieldLayout.prototype.isFieldInline = function () {
  	// This is very simplistic, but should be good enough.
  	return this.getField().$element.prop( 'tagName' ).toLowerCase() === 'span';
  };
  
  /**
   * @protected
   * @param {string} kind 'error' or 'notice'
   * @param {string|OO.ui.HtmlSnippet} text
   * @return {jQuery}
   */
  OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
  	return new OO.ui.MessageWidget( {
  		type: kind,
  		inline: true,
  		label: text
  	} ).$element;
  };
  
  /**
   * Set the field alignment mode.
   *
   * @private
   * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
   * @chainable
   * @return {OO.ui.BookletLayout} The layout, for chaining
   */
  OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
  	if ( value !== this.align ) {
  		// Default to 'left'
  		if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) {
  			value = 'left';
  		}
  		// Validate
  		if ( value === 'inline' && !this.isFieldInline() ) {
  			value = 'top';
  		}
  		// Reorder elements
  
  		if ( this.helpInline ) {
  			if ( value === 'top' ) {
  				this.$header.append( this.$label );
  				this.$body.append( this.$header, this.$field, this.$help );
  			} else if ( value === 'inline' ) {
  				this.$header.append( this.$label, this.$help );
  				this.$body.append( this.$field, this.$header );
  			} else {
  				this.$header.append( this.$label, this.$help );
  				this.$body.append( this.$header, this.$field );
  			}
  		} else {
  			if ( value === 'top' ) {
  				this.$header.append( this.$help, this.$label );
  				this.$body.append( this.$header, this.$field );
  			} else if ( value === 'inline' ) {
  				this.$header.append( this.$help, this.$label );
  				this.$body.append( this.$field, this.$header );
  			} else {
  				this.$header.append( this.$label );
  				this.$body.append( this.$header, this.$help, this.$field );
  			}
  		}
  		// Set classes. The following classes can be used here:
  		// * oo-ui-fieldLayout-align-left
  		// * oo-ui-fieldLayout-align-right
  		// * oo-ui-fieldLayout-align-top
  		// * oo-ui-fieldLayout-align-inline
  		if ( this.align ) {
  			this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align );
  		}
  		this.$element.addClass( 'oo-ui-fieldLayout-align-' + value );
  		this.align = value;
  	}
  
  	return this;
  };
  
  /**
   * Set the list of error messages.
   *
   * @param {Array} errors Error messages about the widget, which will be displayed below the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   * @chainable
   * @return {OO.ui.BookletLayout} The layout, for chaining
   */
  OO.ui.FieldLayout.prototype.setErrors = function ( errors ) {
  	this.errors = errors.slice();
  	this.updateMessages();
  	return this;
  };
  
  /**
   * Set the list of warning messages.
   *
   * @param {Array} warnings Warning messages about the widget, which will be displayed below
   *  the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   * @chainable
   * @return {OO.ui.BookletLayout} The layout, for chaining
   */
  OO.ui.FieldLayout.prototype.setWarnings = function ( warnings ) {
  	this.warnings = warnings.slice();
  	this.updateMessages();
  	return this;
  };
  
  /**
   * Set the list of success messages.
   *
   * @param {Array} successMessages Success messages about the widget, which will be displayed below
   *  the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   * @chainable
   * @return {OO.ui.BookletLayout} The layout, for chaining
   */
  OO.ui.FieldLayout.prototype.setSuccess = function ( successMessages ) {
  	this.successMessages = successMessages.slice();
  	this.updateMessages();
  	return this;
  };
  
  /**
   * Set the list of notice messages.
   *
   * @param {Array} notices Notices about the widget, which will be displayed below the widget.
   *  The array may contain strings or OO.ui.HtmlSnippet instances.
   * @chainable
   * @return {OO.ui.BookletLayout} The layout, for chaining
   */
  OO.ui.FieldLayout.prototype.setNotices = function ( notices ) {
  	this.notices = notices.slice();
  	this.updateMessages();
  	return this;
  };
  
  /**
   * Update the rendering of error, warning, success and notice messages.
   *
   * @private
   */
  OO.ui.FieldLayout.prototype.updateMessages = function () {
  	var i;
  	this.$messages.empty();
  
  	if (
  		this.errors.length ||
  		this.warnings.length ||
  		this.successMessages.length ||
  		this.notices.length
  	) {
  		this.$body.after( this.$messages );
  	} else {
  		this.$messages.remove();
  		return;
  	}
  
  	for ( i = 0; i < this.errors.length; i++ ) {
  		this.$messages.append( this.makeMessage( 'error', this.errors[ i ] ) );
  	}
  	for ( i = 0; i < this.warnings.length; i++ ) {
  		this.$messages.append( this.makeMessage( 'warning', this.warnings[ i ] ) );
  	}
  	for ( i = 0; i < this.successMessages.length; i++ ) {
  		this.$messages.append( this.makeMessage( 'success', this.successMessages[ i ] ) );
  	}
  	for ( i = 0; i < this.notices.length; i++ ) {
  		this.$messages.append( this.makeMessage( 'notice', this.notices[ i ] ) );
  	}
  };
  
  /**
   * Include information about the widget's accessKey in our title. TitledElement calls this method.
   * (This is a bit of a hack.)
   *
   * @protected
   * @param {string} title Tooltip label for 'title' attribute
   * @return {string}
   */
  OO.ui.FieldLayout.prototype.formatTitleWithAccessKey = function ( title ) {
  	if ( this.fieldWidget && this.fieldWidget.formatTitleWithAccessKey ) {
  		return this.fieldWidget.formatTitleWithAccessKey( title );
  	}
  	return title;
  };
  
  /**
   * Creates and returns the help element. Also sets the `aria-describedby`
   * attribute on the main element of the `fieldWidget`.
   *
   * @private
   * @param {string|OO.ui.HtmlSnippet} [help] Help text.
   * @param {jQuery} [$overlay] Passed to OO.ui.PopupButtonWidget for help popup.
   * @return {jQuery} The element that should become `this.$help`.
   */
  OO.ui.FieldLayout.prototype.createHelpElement = function ( help, $overlay ) {
  	var helpId, helpWidget;
  
  	if ( this.helpInline ) {
  		helpWidget = new OO.ui.LabelWidget( {
  			label: help,
  			classes: [ 'oo-ui-inline-help' ]
  		} );
  
  		helpId = helpWidget.getElementId();
  	} else {
  		helpWidget = new OO.ui.PopupButtonWidget( {
  			$overlay: $overlay,
  			popup: {
  				padded: true
  			},
  			classes: [ 'oo-ui-fieldLayout-help' ],
  			framed: false,
  			icon: 'info',
  			label: OO.ui.msg( 'ooui-field-help' ),
  			invisibleLabel: true
  		} );
  
  		helpWidget.popup.on( 'ready', function () {
  			var $popupElement = helpWidget.popup.$element;
  			$popupElement.attr( 'tabindex', 0 );
  			$popupElement.trigger( 'focus' );
  		} );
  
  		helpWidget.popup.on( 'closing', function () {
  			helpWidget.$button.trigger( 'focus' );
  		} );
  
  		if ( help instanceof OO.ui.HtmlSnippet ) {
  			helpWidget.getPopup().$body.html( help.toString() );
  		} else {
  			helpWidget.getPopup().$body.text( help );
  		}
  
  		helpId = helpWidget.getPopup().getBodyId();
  	}
  
  	// Set the 'aria-describedby' attribute on the fieldWidget
  	// Preference given to an input or a button
  	(
  		this.fieldWidget.$input ||
  		this.fieldWidget.$button ||
  		this.fieldWidget.$element
  	).attr( 'aria-describedby', helpId );
  
  	return helpWidget.$element;
  };
  