/*!
- * OOjs UI v0.1.0-pre (bd008e8aed)
+ * OOjs UI v0.1.0-pre (880100c45e)
* 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-09-11T23:10:10Z
+ * Date: 2014-09-23T22:28:43Z
*/
( function ( OO ) {
} )();
+/**
+ * Element that can be marked as pending.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.PendingElement = function OoUiPendingElement( config ) {
+ // Config initialisation
+ config = config || {};
+
+ // Properties
+ this.pending = 0;
+ this.$pending = null;
+
+ // Initialisation
+ this.setPendingElement( config.$pending || this.$element );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.PendingElement );
+
+/* Methods */
+
+/**
+ * Set the pending element (and clean up any existing one).
+ *
+ * @param {jQuery} $pending The element to set to pending.
+ */
+OO.ui.PendingElement.prototype.setPendingElement = function ( $pending ) {
+ if ( this.$pending ) {
+ this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+ }
+
+ this.$pending = $pending;
+ if ( this.pending > 0 ) {
+ this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+ }
+};
+
+/**
+ * Check if input is pending.
+ *
+ * @return {boolean}
+ */
+OO.ui.PendingElement.prototype.isPending = function () {
+ return !!this.pending;
+};
+
+/**
+ * Increase the pending stack.
+ *
+ * @chainable
+ */
+OO.ui.PendingElement.prototype.pushPending = function () {
+ if ( this.pending === 0 ) {
+ this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+ }
+ this.pending++;
+
+ return this;
+};
+
+/**
+ * Reduce the pending stack.
+ *
+ * Clamped at zero.
+ *
+ * @chainable
+ */
+OO.ui.PendingElement.prototype.popPending = function () {
+ if ( this.pending === 1 ) {
+ this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+ }
+ this.pending = Math.max( 0, this.pending - 1 );
+
+ return this;
+};
+
/**
* List of actions.
*
* @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';
+ bodyHeight = this.getBodyHeight();
+ this.$frame[0].style.height = oldHeight;
+
return Math.round(
// Add buffer for border
( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
// Use combined heights of children
- ( this.$head.outerHeight( true ) + this.getBodyHeight() + this.$foot.outerHeight( true ) )
+ ( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
);
};
/**
* Get the height of the dialog contents.
*
+ * When this function is called, the dialog will temporarily have been resized
+ * to height=1px, so .scrollHeight measurements can be taken accurately.
+ *
* @return {number} Height of content
*/
OO.ui.Window.prototype.getBodyHeight = function () {
* @abstract
* @class
* @extends OO.ui.Window
+ * @mixins OO.ui.PendingElement
*
* @constructor
* @param {Object} [config] Configuration options
// Parent constructor
OO.ui.Dialog.super.call( this, config );
+ // Mixin constructors
+ OO.ui.PendingElement.call( this );
+
// Properties
this.actions = new OO.ui.ActionSet();
this.attachedActions = [];
this.currentAction = null;
- this.pending = 0;
// Events
this.actions.connect( this, {
/* Setup */
OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
+OO.mixinClass( OO.ui.Dialog, OO.ui.PendingElement );
/* Static Properties */
}
};
-/**
- * Check if input is pending.
- *
- * @return {boolean}
- */
-OO.ui.Dialog.prototype.isPending = function () {
- return !!this.pending;
-};
-
/**
* Get set of actions.
*
* @inheritdoc
*
* @param {Object} [data] Dialog opening data
- * @param {jQuery|string|Function|null} [data.label] Dialog label, omit to use #static-label
+ * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use #static-title
* @param {Object[]} [data.actions] List of OO.ui.ActionWidget configuration options for each
* action item, omit to use #static-actions
*/
// Initialization
this.$content.addClass( 'oo-ui-dialog-content' );
+ this.setPendingElement( this.$head );
};
/**
.always( OO.ui.bind( this.popPending, this ) );
};
-/**
- * Increase the pending stack.
- *
- * @chainable
- */
-OO.ui.Dialog.prototype.pushPending = function () {
- if ( this.pending === 0 ) {
- this.$content.addClass( 'oo-ui-actionDialog-content-pending' );
- this.$head.addClass( 'oo-ui-texture-pending' );
- }
- this.pending++;
-
- return this;
-};
-
-/**
- * Reduce the pending stack.
- *
- * Clamped at zero.
- *
- * @chainable
- */
-OO.ui.Dialog.prototype.popPending = function () {
- if ( this.pending === 1 ) {
- this.$content.removeClass( 'oo-ui-actionDialog-content-pending' );
- this.$head.removeClass( 'oo-ui-texture-pending' );
- }
- this.pending = Math.max( 0, this.pending - 1 );
-
- return this;
-};
-
/**
* Collection of windows.
*
manager.opening.notify( { state: 'setup' } );
setTimeout( function () {
win.ready( data ).then( function () {
- manager.updateWindowSize( win );
manager.opening.notify( { state: 'ready' } );
manager.opening = null;
manager.opened = $.Deferred();
};
/**
- * Set the ideal size.
+ * Set the ideal size. These are the dimensions the element will have when it's not being clipped.
*
* @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix
* @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix
OO.ui.ClippableElement.prototype.setIdealSize = function ( width, height ) {
this.idealWidth = width;
this.idealHeight = height;
+
+ if ( !this.clipping ) {
+ // Update dimensions
+ this.$clippable.css( { width: width, height: height } );
+ }
+ // While clipping, idealWidth and idealHeight are not considered
};
/**
if ( config.help ) {
this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
$: this.$,
+ classes: [ 'oo-ui-fieldLayout-help' ],
framed: false,
- icon: 'info',
- title: config.help
+ icon: 'info'
} );
- this.popupButtonWidget.getPopup().$body.append( this.$( '<span>' ).text( config.help ) );
+ this.popupButtonWidget.getPopup().$body.append(
+ this.$( '<div>' )
+ .text( config.help )
+ .addClass( 'oo-ui-fieldLayout-help-content' )
+ );
this.$help = this.popupButtonWidget.$element;
} else {
- this.$help = this.$( '<div>' );
+ this.$help = this.$( [] );
}
// Events
*
* @class
* @extends OO.ui.ButtonWidget
+ * @mixins OO.ui.PendingElement
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [action] Symbolic action name
* @cfg {string[]} [modes] Symbolic mode names
+ * @cfg {boolean} [framed=false] Render button with a frame
*/
OO.ui.ActionWidget = function OoUiActionWidget( config ) {
// Config intialization
// Parent constructor
OO.ui.ActionWidget.super.call( this, config );
+ // Mixin constructors
+ OO.ui.PendingElement.call( this, config );
+
// Properties
this.action = config.action || '';
this.modes = config.modes || [];
/* Setup */
OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
+OO.mixinClass( OO.ui.ActionWidget, OO.ui.PendingElement );
/* Events */
* @abstract
* @class
* @extends OO.ui.Widget
+ * @mixins OO.ui.FlaggedElement
*
* @constructor
* @param {Object} [config] Configuration options
// Parent constructor
OO.ui.InputWidget.super.call( this, config );
+ // Mixin constructors
+ OO.ui.FlaggedElement.call( this, config );
+
// Properties
this.$input = this.getInputElement( config );
this.value = '';
/* Setup */
OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement );
/* Events */
* @extends OO.ui.InputWidget
* @mixins OO.ui.IconElement
* @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.PendingElement
*
* @constructor
* @param {Object} [config] Configuration options
* @cfg {boolean} [multiline=false] Allow multiple lines of text
* @cfg {boolean} [autosize=false] Automatically resize to fit content
* @cfg {boolean} [maxRows=10] Maximum number of rows to make visible when autosizing
+ * @cfg {RegExp|string} [validate] Regular expression (or symbolic name referencing
+ * one, see #static-validationPatterns)
*/
OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
// Configuration initialization
// Mixin constructors
OO.ui.IconElement.call( this, config );
OO.ui.IndicatorElement.call( this, config );
+ OO.ui.PendingElement.call( this, config );
// Properties
- this.pending = 0;
this.multiline = !!config.multiline;
this.autosize = !!config.autosize;
this.maxRows = config.maxRows !== undefined ? config.maxRows : 10;
+ this.validate = config.validate || null;
// Events
- this.$input.on( 'keypress', OO.ui.bind( this.onKeyPress, this ) );
+ this.$input.on( {
+ keypress: OO.ui.bind( this.onKeyPress, this ),
+ blur: OO.ui.bind( this.setValidityFlag, this )
+ } );
this.$element.on( 'DOMNodeInsertedIntoDocument', OO.ui.bind( this.onElementAttach, this ) );
this.$icon.on( 'mousedown', OO.ui.bind( this.onIconMouseDown, this ) );
this.$indicator.on( 'mousedown', OO.ui.bind( this.onIndicatorMouseDown, this ) );
OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IconElement );
OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.PendingElement );
+
+/* Static properties */
+
+OO.ui.TextInputWidget.static.validationPatterns = {
+ 'non-empty': /.+/,
+ integer: /^\d+$/
+};
/* Events */
// Parent method
OO.ui.TextInputWidget.super.prototype.setValue.call( this, value );
+ this.setValidityFlag();
this.adjustSize();
return this;
};
};
/**
- * Check if input is pending.
- *
- * @return {boolean}
- */
-OO.ui.TextInputWidget.prototype.isPending = function () {
- return !!this.pending;
-};
-
-/**
- * Increase the pending stack.
+ * Select the contents of the input.
*
* @chainable
*/
-OO.ui.TextInputWidget.prototype.pushPending = function () {
- if ( this.pending === 0 ) {
- this.$element.addClass( 'oo-ui-textInputWidget-pending' );
- this.$input.addClass( 'oo-ui-texture-pending' );
- }
- this.pending++;
-
+OO.ui.TextInputWidget.prototype.select = function () {
+ this.$input.select();
return this;
};
/**
- * Reduce the pending stack.
- *
- * Clamped at zero.
- *
- * @chainable
+ * Sets the 'invalid' flag appropriately.
*/
-OO.ui.TextInputWidget.prototype.popPending = function () {
- if ( this.pending === 1 ) {
- this.$element.removeClass( 'oo-ui-textInputWidget-pending' );
- this.$input.removeClass( 'oo-ui-texture-pending' );
- }
- this.pending = Math.max( 0, this.pending - 1 );
-
- return this;
+OO.ui.TextInputWidget.prototype.setValidityFlag = function () {
+ this.isValid().done( OO.ui.bind( function ( valid ) {
+ this.setFlags( { invalid: !valid } );
+ }, this ) );
};
/**
- * Select the contents of the input.
+ * Returns whether or not the current value is considered valid, according to the
+ * supplied validation pattern.
*
- * @chainable
+ * @return {jQuery.Deferred}
*/
-OO.ui.TextInputWidget.prototype.select = function () {
- this.$input.select();
- return this;
+OO.ui.TextInputWidget.prototype.isValid = function () {
+ var validationRegexp;
+ if ( this.validate instanceof RegExp ) {
+ validationRegexp = this.validate;
+ } else {
+ validationRegexp = this.constructor.static.validationPatterns[this.validate];
+ }
+ return $.Deferred().resolve( !!this.getValue().match( validationRegexp ) ).promise();
};
/**
this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
}
+ // Reevaluate clipping state since we've relocated and resized the popup
+ this.clip();
+
return this;
};
var change = visible !== this.isVisible();
+ if ( change && visible ) {
+ // Make sure the width is set before the parent method runs.
+ // After this we have to call this.position(); again to actually
+ // position ourselves correctly.
+ this.position();
+ }
+
// Parent method
OO.ui.TextInputMenuWidget.super.prototype.toggle.call( this, visible );
this.$( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
}
}
+
return this;
};
}
this.$element.css( dimensions );
this.setIdealSize( $container.width() );
+ // We updated the position, so re-evaluate the clipping state
+ this.clip();
return this;
};