2 * OOjs UI v0.18.4-fix (d4045dee45)
3 * https://www.mediawiki.org/wiki/OOjs_UI
5 * Copyright 2011–2017 OOjs UI Team and other contributors.
6 * Released under the MIT license
7 * http://oojs.mit-license.org
9 * Date: 2017-01-19T20:22:26Z
16 * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
17 * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
20 * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
21 * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
24 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
27 * @extends OO.ui.ButtonWidget
28 * @mixins OO.ui.mixin.PendingElement
31 * @param {Object} [config] Configuration options
32 * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
33 * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
34 * should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
35 * for more information about setting modes.
36 * @cfg {boolean} [framed=false] Render the action button with a frame
38 OO.ui.ActionWidget = function OoUiActionWidget( config ) {
39 // Configuration initialization
40 config = $.extend( { framed: false }, config );
43 OO.ui.ActionWidget.parent.call( this, config );
46 OO.ui.mixin.PendingElement.call( this, config );
49 this.action = config.action || '';
50 this.modes = config.modes || [];
55 this.$element.addClass( 'oo-ui-actionWidget' );
60 OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
61 OO.mixinClass( OO.ui.ActionWidget, OO.ui.mixin.PendingElement );
66 * A resize event is emitted when the size of the widget changes.
74 * Check if the action is configured to be available in the specified `mode`.
76 * @param {string} mode Name of mode
77 * @return {boolean} The action is configured with the mode
79 OO.ui.ActionWidget.prototype.hasMode = function ( mode ) {
80 return this.modes.indexOf( mode ) !== -1;
84 * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
88 OO.ui.ActionWidget.prototype.getAction = function () {
93 * Get the symbolic name of the mode or modes for which the action is configured to be available.
95 * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
96 * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
101 OO.ui.ActionWidget.prototype.getModes = function () {
102 return this.modes.slice();
106 * Emit a resize event if the size has changed.
111 OO.ui.ActionWidget.prototype.propagateResize = function () {
114 if ( this.isElementAttached() ) {
115 width = this.$element.width();
116 height = this.$element.height();
118 if ( width !== this.width || height !== this.height ) {
120 this.height = height;
121 this.emit( 'resize' );
131 OO.ui.ActionWidget.prototype.setIcon = function () {
133 OO.ui.mixin.IconElement.prototype.setIcon.apply( this, arguments );
134 this.propagateResize();
142 OO.ui.ActionWidget.prototype.setLabel = function () {
144 OO.ui.mixin.LabelElement.prototype.setLabel.apply( this, arguments );
145 this.propagateResize();
153 OO.ui.ActionWidget.prototype.setFlags = function () {
155 OO.ui.mixin.FlaggedElement.prototype.setFlags.apply( this, arguments );
156 this.propagateResize();
164 OO.ui.ActionWidget.prototype.clearFlags = function () {
166 OO.ui.mixin.FlaggedElement.prototype.clearFlags.apply( this, arguments );
167 this.propagateResize();
173 * Toggle the visibility of the action button.
175 * @param {boolean} [show] Show button, omit to toggle visibility
178 OO.ui.ActionWidget.prototype.toggle = function () {
180 OO.ui.ActionWidget.parent.prototype.toggle.apply( this, arguments );
181 this.propagateResize();
186 /* eslint-disable no-unused-vars */
188 * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
189 * Actions can be made available for specific contexts (modes) and circumstances
190 * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
192 * ActionSets contain two types of actions:
194 * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
195 * - Other: Other actions include all non-special visible actions.
197 * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
200 * // Example: An action set used in a process dialog
201 * function MyProcessDialog( config ) {
202 * MyProcessDialog.parent.call( this, config );
204 * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
205 * MyProcessDialog.static.title = 'An action set in a process dialog';
206 * // An action set that uses modes ('edit' and 'help' mode, in this example).
207 * MyProcessDialog.static.actions = [
208 * { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
209 * { action: 'help', modes: 'edit', label: 'Help' },
210 * { modes: 'edit', label: 'Cancel', flags: 'safe' },
211 * { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
214 * MyProcessDialog.prototype.initialize = function () {
215 * MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
216 * this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
217 * this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
218 * this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
219 * this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
220 * this.stackLayout = new OO.ui.StackLayout( {
221 * items: [ this.panel1, this.panel2 ]
223 * this.$body.append( this.stackLayout.$element );
225 * MyProcessDialog.prototype.getSetupProcess = function ( data ) {
226 * return MyProcessDialog.parent.prototype.getSetupProcess.call( this, data )
227 * .next( function () {
228 * this.actions.setMode( 'edit' );
231 * MyProcessDialog.prototype.getActionProcess = function ( action ) {
232 * if ( action === 'help' ) {
233 * this.actions.setMode( 'help' );
234 * this.stackLayout.setItem( this.panel2 );
235 * } else if ( action === 'back' ) {
236 * this.actions.setMode( 'edit' );
237 * this.stackLayout.setItem( this.panel1 );
238 * } else if ( action === 'continue' ) {
240 * return new OO.ui.Process( function () {
244 * return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
246 * MyProcessDialog.prototype.getBodyHeight = function () {
247 * return this.panel1.$element.outerHeight( true );
249 * var windowManager = new OO.ui.WindowManager();
250 * $( 'body' ).append( windowManager.$element );
251 * var dialog = new MyProcessDialog( {
254 * windowManager.addWindows( [ dialog ] );
255 * windowManager.openWindow( dialog );
257 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
261 * @mixins OO.EventEmitter
264 * @param {Object} [config] Configuration options
266 OO.ui.ActionSet = function OoUiActionSet( config ) {
267 // Configuration initialization
268 config = config || {};
270 // Mixin constructors
271 OO.EventEmitter.call( this );
276 actions: 'getAction',
280 this.categorized = {};
283 this.organized = false;
284 this.changing = false;
285 this.changed = false;
287 /* eslint-enable no-unused-vars */
291 OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );
293 /* Static Properties */
296 * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
297 * header of a {@link OO.ui.ProcessDialog process dialog}.
298 * See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
300 * [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
307 OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];
314 * A 'click' event is emitted when an action is clicked.
316 * @param {OO.ui.ActionWidget} action Action that was clicked
322 * A 'resize' event is emitted when an action widget is resized.
324 * @param {OO.ui.ActionWidget} action Action that was resized
330 * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
332 * @param {OO.ui.ActionWidget[]} added Actions added
338 * A 'remove' event is emitted when actions are {@link #method-remove removed}
339 * or {@link #clear cleared}.
341 * @param {OO.ui.ActionWidget[]} added Actions removed
347 * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
348 * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
355 * Handle action change events.
360 OO.ui.ActionSet.prototype.onActionChange = function () {
361 this.organized = false;
362 if ( this.changing ) {
365 this.emit( 'change' );
370 * Check if an action is one of the special actions.
372 * @param {OO.ui.ActionWidget} action Action to check
373 * @return {boolean} Action is special
375 OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
378 for ( flag in this.special ) {
379 if ( action === this.special[ flag ] ) {
388 * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
391 * @param {Object} [filters] Filters to use, omit to get all actions
392 * @param {string|string[]} [filters.actions] Actions that action widgets must have
393 * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
394 * @param {string|string[]} [filters.modes] Modes that action widgets must have
395 * @param {boolean} [filters.visible] Action widgets must be visible
396 * @param {boolean} [filters.disabled] Action widgets must be disabled
397 * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
399 OO.ui.ActionSet.prototype.get = function ( filters ) {
400 var i, len, list, category, actions, index, match, matches;
405 // Collect category candidates
407 for ( category in this.categorized ) {
408 list = filters[ category ];
410 if ( !Array.isArray( list ) ) {
413 for ( i = 0, len = list.length; i < len; i++ ) {
414 actions = this.categorized[ category ][ list[ i ] ];
415 if ( Array.isArray( actions ) ) {
416 matches.push.apply( matches, actions );
421 // Remove by boolean filters
422 for ( i = 0, len = matches.length; i < len; i++ ) {
423 match = matches[ i ];
425 ( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||
426 ( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )
428 matches.splice( i, 1 );
434 for ( i = 0, len = matches.length; i < len; i++ ) {
435 match = matches[ i ];
436 index = matches.lastIndexOf( match );
437 while ( index !== i ) {
438 matches.splice( index, 1 );
440 index = matches.lastIndexOf( match );
445 return this.list.slice();
449 * Get 'special' actions.
451 * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
452 * Special flags can be configured in subclasses by changing the static #specialFlags property.
454 * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
456 OO.ui.ActionSet.prototype.getSpecial = function () {
458 return $.extend( {}, this.special );
462 * Get 'other' actions.
464 * Other actions include all non-special visible action widgets.
466 * @return {OO.ui.ActionWidget[]} 'Other' action widgets
468 OO.ui.ActionSet.prototype.getOthers = function () {
470 return this.others.slice();
474 * Set the mode (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
475 * to be available in the specified mode will be made visible. All other actions will be hidden.
477 * @param {string} mode The mode. Only actions configured to be available in the specified
478 * mode will be made visible.
483 OO.ui.ActionSet.prototype.setMode = function ( mode ) {
486 this.changing = true;
487 for ( i = 0, len = this.list.length; i < len; i++ ) {
488 action = this.list[ i ];
489 action.toggle( action.hasMode( mode ) );
492 this.organized = false;
493 this.changing = false;
494 this.emit( 'change' );
500 * Set the abilities of the specified actions.
502 * Action widgets that are configured with the specified actions will be enabled
503 * or disabled based on the boolean values specified in the `actions`
506 * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
507 * values that indicate whether or not the action should be enabled.
510 OO.ui.ActionSet.prototype.setAbilities = function ( actions ) {
511 var i, len, action, item;
513 for ( i = 0, len = this.list.length; i < len; i++ ) {
514 item = this.list[ i ];
515 action = item.getAction();
516 if ( actions[ action ] !== undefined ) {
517 item.setDisabled( !actions[ action ] );
525 * Executes a function once per action.
527 * When making changes to multiple actions, use this method instead of iterating over the actions
528 * manually to defer emitting a #change event until after all actions have been changed.
530 * @param {Object|null} filter Filters to use to determine which actions to iterate over; see #get
531 * @param {Function} callback Callback to run for each action; callback is invoked with three
532 * arguments: the action, the action's index, the list of actions being iterated over
535 OO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {
536 this.changed = false;
537 this.changing = true;
538 this.get( filter ).forEach( callback );
539 this.changing = false;
540 if ( this.changed ) {
541 this.emit( 'change' );
548 * Add action widgets to the action set.
550 * @param {OO.ui.ActionWidget[]} actions Action widgets to add
555 OO.ui.ActionSet.prototype.add = function ( actions ) {
558 this.changing = true;
559 for ( i = 0, len = actions.length; i < len; i++ ) {
560 action = actions[ i ];
561 action.connect( this, {
562 click: [ 'emit', 'click', action ],
563 resize: [ 'emit', 'resize', action ],
564 toggle: [ 'onActionChange' ]
566 this.list.push( action );
568 this.organized = false;
569 this.emit( 'add', actions );
570 this.changing = false;
571 this.emit( 'change' );
577 * Remove action widgets from the set.
579 * To remove all actions, you may wish to use the #clear method instead.
581 * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
586 OO.ui.ActionSet.prototype.remove = function ( actions ) {
587 var i, len, index, action;
589 this.changing = true;
590 for ( i = 0, len = actions.length; i < len; i++ ) {
591 action = actions[ i ];
592 index = this.list.indexOf( action );
593 if ( index !== -1 ) {
594 action.disconnect( this );
595 this.list.splice( index, 1 );
598 this.organized = false;
599 this.emit( 'remove', actions );
600 this.changing = false;
601 this.emit( 'change' );
607 * Remove all action widets from the set.
609 * To remove only specified actions, use the {@link #method-remove remove} method instead.
615 OO.ui.ActionSet.prototype.clear = function () {
617 removed = this.list.slice();
619 this.changing = true;
620 for ( i = 0, len = this.list.length; i < len; i++ ) {
621 action = this.list[ i ];
622 action.disconnect( this );
627 this.organized = false;
628 this.emit( 'remove', removed );
629 this.changing = false;
630 this.emit( 'change' );
638 * This is called whenever organized information is requested. It will only reorganize the actions
639 * if something has changed since the last time it ran.
644 OO.ui.ActionSet.prototype.organize = function () {
645 var i, iLen, j, jLen, flag, action, category, list, item, special,
646 specialFlags = this.constructor.static.specialFlags;
648 if ( !this.organized ) {
649 this.categorized = {};
652 for ( i = 0, iLen = this.list.length; i < iLen; i++ ) {
653 action = this.list[ i ];
654 if ( action.isVisible() ) {
655 // Populate categories
656 for ( category in this.categories ) {
657 if ( !this.categorized[ category ] ) {
658 this.categorized[ category ] = {};
660 list = action[ this.categories[ category ] ]();
661 if ( !Array.isArray( list ) ) {
664 for ( j = 0, jLen = list.length; j < jLen; j++ ) {
666 if ( !this.categorized[ category ][ item ] ) {
667 this.categorized[ category ][ item ] = [];
669 this.categorized[ category ][ item ].push( action );
672 // Populate special/others
674 for ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) {
675 flag = specialFlags[ j ];
676 if ( !this.special[ flag ] && action.hasFlag( flag ) ) {
677 this.special[ flag ] = action;
683 this.others.push( action );
687 this.organized = true;
694 * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
695 * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
696 * appearance and functionality of the error interface.
698 * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
699 * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
700 * that initiated the failed process will be disabled.
702 * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
705 * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
707 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
712 * @param {string|jQuery} message Description of error
713 * @param {Object} [config] Configuration options
714 * @cfg {boolean} [recoverable=true] Error is recoverable.
715 * By default, errors are recoverable, and users can try the process again.
716 * @cfg {boolean} [warning=false] Error is a warning.
717 * If the error is a warning, the error interface will include a
718 * 'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
719 * is not triggered a second time if the user chooses to continue.
721 OO.ui.Error = function OoUiError( message, config ) {
722 // Allow passing positional parameters inside the config object
723 if ( OO.isPlainObject( message ) && config === undefined ) {
725 message = config.message;
728 // Configuration initialization
729 config = config || {};
732 this.message = message instanceof jQuery ? message : String( message );
733 this.recoverable = config.recoverable === undefined || !!config.recoverable;
734 this.warning = !!config.warning;
739 OO.initClass( OO.ui.Error );
744 * Check if the error is recoverable.
746 * If the error is recoverable, users are able to try the process again.
748 * @return {boolean} Error is recoverable
750 OO.ui.Error.prototype.isRecoverable = function () {
751 return this.recoverable;
755 * Check if the error is a warning.
757 * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
759 * @return {boolean} Error is warning
761 OO.ui.Error.prototype.isWarning = function () {
766 * Get error message as DOM nodes.
768 * @return {jQuery} Error message in DOM nodes
770 OO.ui.Error.prototype.getMessage = function () {
771 return this.message instanceof jQuery ?
772 this.message.clone() :
773 $( '<div>' ).text( this.message ).contents();
777 * Get the error message text.
779 * @return {string} Error message
781 OO.ui.Error.prototype.getMessageText = function () {
782 return this.message instanceof jQuery ? this.message.text() : this.message;
786 * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
789 * - **number**: the process will wait for the specified number of milliseconds before proceeding.
790 * - **promise**: the process will continue to the next step when the promise is successfully resolved
791 * or stop if the promise is rejected.
792 * - **function**: the process will execute the function. The process will stop if the function returns
793 * either a boolean `false` or a promise that is rejected; if the function returns a number, the process
794 * will wait for that number of milliseconds before proceeding.
796 * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
797 * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
798 * its remaining steps will not be performed.
803 * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
804 * that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
805 * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
806 * a number or promise.
808 OO.ui.Process = function ( step, context ) {
813 if ( step !== undefined ) {
814 this.next( step, context );
820 OO.initClass( OO.ui.Process );
827 * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
828 * If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
829 * and any remaining steps are not performed.
831 OO.ui.Process.prototype.execute = function () {
835 * Continue execution.
838 * @param {Array} step A function and the context it should be called in
839 * @return {Function} Function that continues the process
841 function proceed( step ) {
843 // Execute step in the correct context
845 result = step.callback.call( step.context );
847 if ( result === false ) {
848 // Use rejected promise for boolean false results
849 return $.Deferred().reject( [] ).promise();
851 if ( typeof result === 'number' ) {
853 throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
855 // Use a delayed promise for numbers, expecting them to be in milliseconds
856 deferred = $.Deferred();
857 setTimeout( deferred.resolve, result );
858 return deferred.promise();
860 if ( result instanceof OO.ui.Error ) {
861 // Use rejected promise for error
862 return $.Deferred().reject( [ result ] ).promise();
864 if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
865 // Use rejected promise for list of errors
866 return $.Deferred().reject( result ).promise();
868 // Duck-type the object to see if it can produce a promise
869 if ( result && $.isFunction( result.promise ) ) {
870 // Use a promise generated from the result
871 return result.promise();
873 // Use resolved promise for other results
874 return $.Deferred().resolve().promise();
878 if ( this.steps.length ) {
879 // Generate a chain reaction of promises
880 promise = proceed( this.steps[ 0 ] )();
881 for ( i = 1, len = this.steps.length; i < len; i++ ) {
882 promise = promise.then( proceed( this.steps[ i ] ) );
885 promise = $.Deferred().resolve().promise();
892 * Create a process step.
895 * @param {number|jQuery.Promise|Function} step
897 * - Number of milliseconds to wait before proceeding
898 * - Promise that must be resolved before proceeding
899 * - Function to execute
900 * - If the function returns a boolean false the process will stop
901 * - If the function returns a promise, the process will continue to the next
902 * step when the promise is resolved or stop if the promise is rejected
903 * - If the function returns a number, the process will wait for that number of
904 * milliseconds before proceeding
905 * @param {Object} [context=null] Execution context of the function. The context is
906 * ignored if the step is a number or promise.
907 * @return {Object} Step object, with `callback` and `context` properties
909 OO.ui.Process.prototype.createStep = function ( step, context ) {
910 if ( typeof step === 'number' || $.isFunction( step.promise ) ) {
912 callback: function () {
918 if ( $.isFunction( step ) ) {
924 throw new Error( 'Cannot create process step: number, promise or function expected' );
928 * Add step to the beginning of the process.
930 * @inheritdoc #createStep
931 * @return {OO.ui.Process} this
934 OO.ui.Process.prototype.first = function ( step, context ) {
935 this.steps.unshift( this.createStep( step, context ) );
940 * Add step to the end of the process.
942 * @inheritdoc #createStep
943 * @return {OO.ui.Process} this
946 OO.ui.Process.prototype.next = function ( step, context ) {
947 this.steps.push( this.createStep( step, context ) );
952 * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
953 * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
954 * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
955 * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
956 * pertinent data and reused.
958 * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
959 * `opened`, and `closing`, which represent the primary stages of the cycle:
961 * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
962 * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
964 * - an `opening` event is emitted with an `opening` promise
965 * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before
966 * the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the
967 * window and its result executed
968 * - a `setup` progress notification is emitted from the `opening` promise
969 * - the #getReadyDelay method is called the returned value is used to time a pause in execution before
970 * the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the
971 * window and its result executed
972 * - a `ready` progress notification is emitted from the `opening` promise
973 * - the `opening` promise is resolved with an `opened` promise
975 * **Opened**: the window is now open.
977 * **Closing**: the closing stage begins when the window manager's #closeWindow or the
978 * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
979 * to close the window.
981 * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
982 * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
983 * the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
984 * window and its result executed
985 * - a `hold` progress notification is emitted from the `closing` promise
986 * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
987 * the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
988 * window and its result executed
989 * - a `teardown` progress notification is emitted from the `closing` promise
990 * - the `closing` promise is resolved. The window is now closed
992 * See the [OOjs UI documentation on MediaWiki][1] for more information.
994 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
997 * @extends OO.ui.Element
998 * @mixins OO.EventEmitter
1001 * @param {Object} [config] Configuration options
1002 * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
1003 * Note that window classes that are instantiated with a factory must have
1004 * a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
1005 * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
1007 OO.ui.WindowManager = function OoUiWindowManager( config ) {
1008 // Configuration initialization
1009 config = config || {};
1011 // Parent constructor
1012 OO.ui.WindowManager.parent.call( this, config );
1014 // Mixin constructors
1015 OO.EventEmitter.call( this );
1018 this.factory = config.factory;
1019 this.modal = config.modal === undefined || !!config.modal;
1021 this.opening = null;
1023 this.closing = null;
1024 this.preparingToOpen = null;
1025 this.preparingToClose = null;
1026 this.currentWindow = null;
1027 this.globalEvents = false;
1028 this.$returnFocusTo = null;
1029 this.$ariaHidden = null;
1030 this.onWindowResizeTimeout = null;
1031 this.onWindowResizeHandler = this.onWindowResize.bind( this );
1032 this.afterWindowResizeHandler = this.afterWindowResize.bind( this );
1036 .addClass( 'oo-ui-windowManager' )
1037 .toggleClass( 'oo-ui-windowManager-modal', this.modal );
1042 OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
1043 OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
1048 * An 'opening' event is emitted when the window begins to be opened.
1051 * @param {OO.ui.Window} win Window that's being opened
1052 * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully.
1053 * When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument
1054 * is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete.
1055 * @param {Object} data Window opening data
1059 * A 'closing' event is emitted when the window begins to be closed.
1062 * @param {OO.ui.Window} win Window that's being closed
1063 * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window
1064 * is closed successfully. The promise emits `hold` and `teardown` notifications when those
1065 * processes are complete. When the `closing` promise is resolved, the first argument of its value
1066 * is the closing data.
1067 * @param {Object} data Window closing data
1071 * A 'resize' event is emitted when a window is resized.
1074 * @param {OO.ui.Window} win Window that was resized
1077 /* Static Properties */
1080 * Map of the symbolic name of each window size and its CSS properties.
1084 * @property {Object}
1086 OO.ui.WindowManager.static.sizes = {
1100 // These can be non-numeric because they are never used in calculations
1107 * Symbolic name of the default window size.
1109 * The default size is used if the window's requested size is not recognized.
1113 * @property {string}
1115 OO.ui.WindowManager.static.defaultSize = 'medium';
1120 * Handle window resize events.
1123 * @param {jQuery.Event} e Window resize event
1125 OO.ui.WindowManager.prototype.onWindowResize = function () {
1126 clearTimeout( this.onWindowResizeTimeout );
1127 this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
1131 * Handle window resize events.
1134 * @param {jQuery.Event} e Window resize event
1136 OO.ui.WindowManager.prototype.afterWindowResize = function () {
1137 if ( this.currentWindow ) {
1138 this.updateWindowSize( this.currentWindow );
1143 * Check if window is opening.
1145 * @param {OO.ui.Window} win Window to check
1146 * @return {boolean} Window is opening
1148 OO.ui.WindowManager.prototype.isOpening = function ( win ) {
1149 return win === this.currentWindow && !!this.opening && this.opening.state() === 'pending';
1153 * Check if window is closing.
1155 * @param {OO.ui.Window} win Window to check
1156 * @return {boolean} Window is closing
1158 OO.ui.WindowManager.prototype.isClosing = function ( win ) {
1159 return win === this.currentWindow && !!this.closing && this.closing.state() === 'pending';
1163 * Check if window is opened.
1165 * @param {OO.ui.Window} win Window to check
1166 * @return {boolean} Window is opened
1168 OO.ui.WindowManager.prototype.isOpened = function ( win ) {
1169 return win === this.currentWindow && !!this.opened && this.opened.state() === 'pending';
1173 * Check if a window is being managed.
1175 * @param {OO.ui.Window} win Window to check
1176 * @return {boolean} Window is being managed
1178 OO.ui.WindowManager.prototype.hasWindow = function ( win ) {
1181 for ( name in this.windows ) {
1182 if ( this.windows[ name ] === win ) {
1191 * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
1193 * @param {OO.ui.Window} win Window being opened
1194 * @param {Object} [data] Window opening data
1195 * @return {number} Milliseconds to wait
1197 OO.ui.WindowManager.prototype.getSetupDelay = function () {
1202 * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
1204 * @param {OO.ui.Window} win Window being opened
1205 * @param {Object} [data] Window opening data
1206 * @return {number} Milliseconds to wait
1208 OO.ui.WindowManager.prototype.getReadyDelay = function () {
1213 * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
1215 * @param {OO.ui.Window} win Window being closed
1216 * @param {Object} [data] Window closing data
1217 * @return {number} Milliseconds to wait
1219 OO.ui.WindowManager.prototype.getHoldDelay = function () {
1224 * Get the number of milliseconds to wait after the ‘hold’ process has finished before
1225 * executing the ‘teardown’ process.
1227 * @param {OO.ui.Window} win Window being closed
1228 * @param {Object} [data] Window closing data
1229 * @return {number} Milliseconds to wait
1231 OO.ui.WindowManager.prototype.getTeardownDelay = function () {
1232 return this.modal ? 250 : 0;
1236 * Get a window by its symbolic name.
1238 * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
1239 * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
1240 * for more information about using factories.
1241 * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
1243 * @param {string} name Symbolic name of the window
1244 * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
1245 * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
1246 * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
1248 OO.ui.WindowManager.prototype.getWindow = function ( name ) {
1249 var deferred = $.Deferred(),
1250 win = this.windows[ name ];
1252 if ( !( win instanceof OO.ui.Window ) ) {
1253 if ( this.factory ) {
1254 if ( !this.factory.lookup( name ) ) {
1255 deferred.reject( new OO.ui.Error(
1256 'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
1259 win = this.factory.create( name );
1260 this.addWindows( [ win ] );
1261 deferred.resolve( win );
1264 deferred.reject( new OO.ui.Error(
1265 'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
1269 deferred.resolve( win );
1272 return deferred.promise();
1276 * Get current window.
1278 * @return {OO.ui.Window|null} Currently opening/opened/closing window
1280 OO.ui.WindowManager.prototype.getCurrentWindow = function () {
1281 return this.currentWindow;
1287 * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
1288 * @param {Object} [data] Window opening data
1289 * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed.
1290 * Defaults the current activeElement. If set to null, focus isn't changed on close.
1291 * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
1292 * See {@link #event-opening 'opening' event} for more information about `opening` promises.
1295 OO.ui.WindowManager.prototype.openWindow = function ( win, data ) {
1297 opening = $.Deferred();
1300 // Argument handling
1301 if ( typeof win === 'string' ) {
1302 return this.getWindow( win ).then( function ( win ) {
1303 return manager.openWindow( win, data );
1308 if ( !this.hasWindow( win ) ) {
1309 opening.reject( new OO.ui.Error(
1310 'Cannot open window: window is not attached to manager'
1312 } else if ( this.preparingToOpen || this.opening || this.opened ) {
1313 opening.reject( new OO.ui.Error(
1314 'Cannot open window: another window is opening or open'
1319 if ( opening.state() !== 'rejected' ) {
1320 // If a window is currently closing, wait for it to complete
1321 this.preparingToOpen = $.when( this.closing );
1322 // Ensure handlers get called after preparingToOpen is set
1323 this.preparingToOpen.done( function () {
1324 if ( manager.modal ) {
1325 manager.toggleGlobalEvents( true );
1326 manager.toggleAriaIsolation( true );
1328 manager.$returnFocusTo = data.$returnFocusTo || $( document.activeElement );
1329 manager.currentWindow = win;
1330 manager.opening = opening;
1331 manager.preparingToOpen = null;
1332 manager.emit( 'opening', win, opening, data );
1333 setTimeout( function () {
1334 win.setup( data ).then( function () {
1335 manager.updateWindowSize( win );
1336 manager.opening.notify( { state: 'setup' } );
1337 setTimeout( function () {
1338 win.ready( data ).then( function () {
1339 manager.opening.notify( { state: 'ready' } );
1340 manager.opening = null;
1341 manager.opened = $.Deferred();
1342 opening.resolve( manager.opened.promise(), data );
1344 manager.opening = null;
1345 manager.opened = $.Deferred();
1347 manager.closeWindow( win );
1349 }, manager.getReadyDelay() );
1351 manager.opening = null;
1352 manager.opened = $.Deferred();
1354 manager.closeWindow( win );
1356 }, manager.getSetupDelay() );
1360 return opening.promise();
1366 * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
1367 * @param {Object} [data] Window closing data
1368 * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing.
1369 * See {@link #event-closing 'closing' event} for more information about closing promises.
1370 * @throws {Error} An error is thrown if the window is not managed by the window manager.
1373 OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
1375 closing = $.Deferred(),
1378 // Argument handling
1379 if ( typeof win === 'string' ) {
1380 win = this.windows[ win ];
1381 } else if ( !this.hasWindow( win ) ) {
1387 closing.reject( new OO.ui.Error(
1388 'Cannot close window: window is not attached to manager'
1390 } else if ( win !== this.currentWindow ) {
1391 closing.reject( new OO.ui.Error(
1392 'Cannot close window: window already closed with different data'
1394 } else if ( this.preparingToClose || this.closing ) {
1395 closing.reject( new OO.ui.Error(
1396 'Cannot close window: window already closing with different data'
1401 if ( closing.state() !== 'rejected' ) {
1402 // If the window is currently opening, close it when it's done
1403 this.preparingToClose = $.when( this.opening );
1404 // Ensure handlers get called after preparingToClose is set
1405 this.preparingToClose.always( function () {
1406 manager.closing = closing;
1407 manager.preparingToClose = null;
1408 manager.emit( 'closing', win, closing, data );
1409 opened = manager.opened;
1410 manager.opened = null;
1411 opened.resolve( closing.promise(), data );
1412 setTimeout( function () {
1413 win.hold( data ).then( function () {
1414 closing.notify( { state: 'hold' } );
1415 setTimeout( function () {
1416 win.teardown( data ).then( function () {
1417 closing.notify( { state: 'teardown' } );
1418 if ( manager.modal ) {
1419 manager.toggleGlobalEvents( false );
1420 manager.toggleAriaIsolation( false );
1422 if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {
1423 manager.$returnFocusTo[ 0 ].focus();
1425 manager.closing = null;
1426 manager.currentWindow = null;
1427 closing.resolve( data );
1429 }, manager.getTeardownDelay() );
1431 }, manager.getHoldDelay() );
1435 return closing.promise();
1439 * Add windows to the window manager.
1441 * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
1442 * See the [OOjs ui documentation on MediaWiki] [2] for examples.
1443 * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
1445 * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
1446 * by reference, symbolic name, or explicitly defined symbolic names.
1447 * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
1448 * explicit nor a statically configured symbolic name.
1450 OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
1451 var i, len, win, name, list;
1453 if ( Array.isArray( windows ) ) {
1454 // Convert to map of windows by looking up symbolic names from static configuration
1456 for ( i = 0, len = windows.length; i < len; i++ ) {
1457 name = windows[ i ].constructor.static.name;
1458 if ( typeof name !== 'string' ) {
1459 throw new Error( 'Cannot add window' );
1462 OO.ui.warnDeprecation( 'OO.ui.WindowManager#addWindows: Windows must have a `name` static property defined.' );
1464 list[ name ] = windows[ i ];
1466 } else if ( OO.isPlainObject( windows ) ) {
1471 for ( name in list ) {
1473 this.windows[ name ] = win.toggle( false );
1474 this.$element.append( win.$element );
1475 win.setManager( this );
1480 * Remove the specified windows from the windows manager.
1482 * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
1483 * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
1484 * longer listens to events, use the #destroy method.
1486 * @param {string[]} names Symbolic names of windows to remove
1487 * @return {jQuery.Promise} Promise resolved when window is closed and removed
1488 * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
1490 OO.ui.WindowManager.prototype.removeWindows = function ( names ) {
1491 var i, len, win, name, cleanupWindow,
1494 cleanup = function ( name, win ) {
1495 delete manager.windows[ name ];
1496 win.$element.detach();
1499 for ( i = 0, len = names.length; i < len; i++ ) {
1501 win = this.windows[ name ];
1503 throw new Error( 'Cannot remove window' );
1505 cleanupWindow = cleanup.bind( null, name, win );
1506 promises.push( this.closeWindow( name ).then( cleanupWindow, cleanupWindow ) );
1509 return $.when.apply( $, promises );
1513 * Remove all windows from the window manager.
1515 * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
1516 * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
1517 * To remove just a subset of windows, use the #removeWindows method.
1519 * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
1521 OO.ui.WindowManager.prototype.clearWindows = function () {
1522 return this.removeWindows( Object.keys( this.windows ) );
1526 * Set dialog size. In general, this method should not be called directly.
1528 * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
1530 * @param {OO.ui.Window} win Window to update, should be the current window
1533 OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
1536 // Bypass for non-current, and thus invisible, windows
1537 if ( win !== this.currentWindow ) {
1541 isFullscreen = win.getSize() === 'full';
1543 this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', isFullscreen );
1544 this.$element.toggleClass( 'oo-ui-windowManager-floating', !isFullscreen );
1545 win.setDimensions( win.getSizeProperties() );
1547 this.emit( 'resize', win );
1553 * Bind or unbind global events for scrolling.
1556 * @param {boolean} [on] Bind global events
1559 OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
1560 var scrollWidth, bodyMargin,
1561 $body = $( this.getElementDocument().body ),
1562 // We could have multiple window managers open so only modify
1563 // the body css at the bottom of the stack
1564 stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;
1566 on = on === undefined ? !!this.globalEvents : !!on;
1569 if ( !this.globalEvents ) {
1570 $( this.getElementWindow() ).on( {
1571 // Start listening for top-level window dimension changes
1572 'orientationchange resize': this.onWindowResizeHandler
1574 if ( stackDepth === 0 ) {
1575 scrollWidth = window.innerWidth - document.documentElement.clientWidth;
1576 bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;
1579 'margin-right': bodyMargin + scrollWidth
1583 this.globalEvents = true;
1585 } else if ( this.globalEvents ) {
1586 $( this.getElementWindow() ).off( {
1587 // Stop listening for top-level window dimension changes
1588 'orientationchange resize': this.onWindowResizeHandler
1591 if ( stackDepth === 0 ) {
1597 this.globalEvents = false;
1599 $body.data( 'windowManagerGlobalEvents', stackDepth );
1605 * Toggle screen reader visibility of content other than the window manager.
1608 * @param {boolean} [isolate] Make only the window manager visible to screen readers
1611 OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
1612 isolate = isolate === undefined ? !this.$ariaHidden : !!isolate;
1615 if ( !this.$ariaHidden ) {
1616 // Hide everything other than the window manager from screen readers
1617 this.$ariaHidden = $( 'body' )
1619 .not( this.$element.parentsUntil( 'body' ).last() )
1620 .attr( 'aria-hidden', '' );
1622 } else if ( this.$ariaHidden ) {
1623 // Restore screen reader visibility
1624 this.$ariaHidden.removeAttr( 'aria-hidden' );
1625 this.$ariaHidden = null;
1632 * Destroy the window manager.
1634 * Destroying the window manager ensures that it will no longer listen to events. If you would like to
1635 * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
1638 OO.ui.WindowManager.prototype.destroy = function () {
1639 this.toggleGlobalEvents( false );
1640 this.toggleAriaIsolation( false );
1641 this.clearWindows();
1642 this.$element.remove();
1646 * A window is a container for elements that are in a child frame. They are used with
1647 * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
1648 * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
1649 * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
1650 * the window manager will choose a sensible fallback.
1652 * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
1653 * different processes are executed:
1655 * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
1656 * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
1659 * - {@link #getSetupProcess} method is called and its result executed
1660 * - {@link #getReadyProcess} method is called and its result executed
1662 * **opened**: The window is now open
1664 * **closing**: The closing stage begins when the window manager's
1665 * {@link OO.ui.WindowManager#closeWindow closeWindow}
1666 * or the window's {@link #close} methods are used, and the window manager begins to close the window.
1668 * - {@link #getHoldProcess} method is called and its result executed
1669 * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
1671 * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
1672 * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
1673 * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
1674 * processing can complete. Always assume window processes are executed asynchronously.
1676 * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
1678 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
1682 * @extends OO.ui.Element
1683 * @mixins OO.EventEmitter
1686 * @param {Object} [config] Configuration options
1687 * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
1688 * `full`. If omitted, the value of the {@link #static-size static size} property will be used.
1690 OO.ui.Window = function OoUiWindow( config ) {
1691 // Configuration initialization
1692 config = config || {};
1694 // Parent constructor
1695 OO.ui.Window.parent.call( this, config );
1697 // Mixin constructors
1698 OO.EventEmitter.call( this );
1701 this.manager = null;
1702 this.size = config.size || this.constructor.static.size;
1703 this.$frame = $( '<div>' );
1704 this.$overlay = $( '<div>' );
1705 this.$content = $( '<div>' );
1707 this.$focusTrapBefore = $( '<div>' ).prop( 'tabIndex', 0 );
1708 this.$focusTrapAfter = $( '<div>' ).prop( 'tabIndex', 0 );
1709 this.$focusTraps = this.$focusTrapBefore.add( this.$focusTrapAfter );
1712 this.$overlay.addClass( 'oo-ui-window-overlay' );
1714 .addClass( 'oo-ui-window-content' )
1715 .attr( 'tabindex', 0 );
1717 .addClass( 'oo-ui-window-frame' )
1718 .append( this.$focusTrapBefore, this.$content, this.$focusTrapAfter );
1721 .addClass( 'oo-ui-window' )
1722 .append( this.$frame, this.$overlay );
1724 // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
1725 // that reference properties not initialized at that time of parent class construction
1726 // TODO: Find a better way to handle post-constructor setup
1727 this.visible = false;
1728 this.$element.addClass( 'oo-ui-element-hidden' );
1733 OO.inheritClass( OO.ui.Window, OO.ui.Element );
1734 OO.mixinClass( OO.ui.Window, OO.EventEmitter );
1736 /* Static Properties */
1739 * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
1741 * The static size is used if no #size is configured during construction.
1745 * @property {string}
1747 OO.ui.Window.static.size = 'medium';
1752 * Handle mouse down events.
1755 * @param {jQuery.Event} e Mouse down event
1757 OO.ui.Window.prototype.onMouseDown = function ( e ) {
1758 // Prevent clicking on the click-block from stealing focus
1759 if ( e.target === this.$element[ 0 ] ) {
1765 * Check if the window has been initialized.
1767 * Initialization occurs when a window is added to a manager.
1769 * @return {boolean} Window has been initialized
1771 OO.ui.Window.prototype.isInitialized = function () {
1772 return !!this.manager;
1776 * Check if the window is visible.
1778 * @return {boolean} Window is visible
1780 OO.ui.Window.prototype.isVisible = function () {
1781 return this.visible;
1785 * Check if the window is opening.
1787 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
1790 * @return {boolean} Window is opening
1792 OO.ui.Window.prototype.isOpening = function () {
1793 return this.manager.isOpening( this );
1797 * Check if the window is closing.
1799 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
1801 * @return {boolean} Window is closing
1803 OO.ui.Window.prototype.isClosing = function () {
1804 return this.manager.isClosing( this );
1808 * Check if the window is opened.
1810 * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
1812 * @return {boolean} Window is opened
1814 OO.ui.Window.prototype.isOpened = function () {
1815 return this.manager.isOpened( this );
1819 * Get the window manager.
1821 * All windows must be attached to a window manager, which is used to open
1822 * and close the window and control its presentation.
1824 * @return {OO.ui.WindowManager} Manager of window
1826 OO.ui.Window.prototype.getManager = function () {
1827 return this.manager;
1831 * Get the symbolic name of the window size (e.g., `small` or `medium`).
1833 * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
1835 OO.ui.Window.prototype.getSize = function () {
1836 var viewport = OO.ui.Element.static.getDimensions( this.getElementWindow() ),
1837 sizes = this.manager.constructor.static.sizes,
1840 if ( !sizes[ size ] ) {
1841 size = this.manager.constructor.static.defaultSize;
1843 if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {
1851 * Get the size properties associated with the current window size
1853 * @return {Object} Size properties
1855 OO.ui.Window.prototype.getSizeProperties = function () {
1856 return this.manager.constructor.static.sizes[ this.getSize() ];
1860 * Disable transitions on window's frame for the duration of the callback function, then enable them
1864 * @param {Function} callback Function to call while transitions are disabled
1866 OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {
1867 // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
1868 // Disable transitions first, otherwise we'll get values from when the window was animating.
1869 // We need to build the transition CSS properties using these specific properties since
1870 // Firefox doesn't return anything useful when asked just for 'transition'.
1871 var oldTransition = this.$frame.css( 'transition-property' ) + ' ' +
1872 this.$frame.css( 'transition-duration' ) + ' ' +
1873 this.$frame.css( 'transition-timing-function' ) + ' ' +
1874 this.$frame.css( 'transition-delay' );
1876 this.$frame.css( 'transition', 'none' );
1879 // Force reflow to make sure the style changes done inside callback
1880 // really are not transitioned
1881 this.$frame.height();
1882 this.$frame.css( 'transition', oldTransition );
1886 * Get the height of the full window contents (i.e., the window head, body and foot together).
1888 * What consistitutes the head, body, and foot varies depending on the window type.
1889 * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
1890 * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
1891 * and special actions in the head, and dialog content in the body.
1893 * To get just the height of the dialog body, use the #getBodyHeight method.
1895 * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
1897 OO.ui.Window.prototype.getContentHeight = function () {
1900 bodyStyleObj = this.$body[ 0 ].style,
1901 frameStyleObj = this.$frame[ 0 ].style;
1903 // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
1904 // Disable transitions first, otherwise we'll get values from when the window was animating.
1905 this.withoutSizeTransitions( function () {
1906 var oldHeight = frameStyleObj.height,
1907 oldPosition = bodyStyleObj.position;
1908 frameStyleObj.height = '1px';
1909 // Force body to resize to new width
1910 bodyStyleObj.position = 'relative';
1911 bodyHeight = win.getBodyHeight();
1912 frameStyleObj.height = oldHeight;
1913 bodyStyleObj.position = oldPosition;
1917 // Add buffer for border
1918 ( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
1919 // Use combined heights of children
1920 ( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
1925 * Get the height of the window body.
1927 * To get the height of the full window contents (the window body, head, and foot together),
1928 * use #getContentHeight.
1930 * When this function is called, the window will temporarily have been resized
1931 * to height=1px, so .scrollHeight measurements can be taken accurately.
1933 * @return {number} Height of the window body in pixels
1935 OO.ui.Window.prototype.getBodyHeight = function () {
1936 return this.$body[ 0 ].scrollHeight;
1940 * Get the directionality of the frame (right-to-left or left-to-right).
1942 * @return {string} Directionality: `'ltr'` or `'rtl'`
1944 OO.ui.Window.prototype.getDir = function () {
1945 return OO.ui.Element.static.getDir( this.$content ) || 'ltr';
1949 * Get the 'setup' process.
1951 * The setup process is used to set up a window for use in a particular context,
1952 * based on the `data` argument. This method is called during the opening phase of the window’s
1955 * Override this method to add additional steps to the ‘setup’ process the parent method provides
1956 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
1959 * To add window content that persists between openings, you may wish to use the #initialize method
1962 * @param {Object} [data] Window opening data
1963 * @return {OO.ui.Process} Setup process
1965 OO.ui.Window.prototype.getSetupProcess = function () {
1966 return new OO.ui.Process();
1970 * Get the ‘ready’ process.
1972 * The ready process is used to ready a window for use in a particular
1973 * context, based on the `data` argument. This method is called during the opening phase of
1974 * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
1976 * Override this method to add additional steps to the ‘ready’ process the parent method
1977 * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
1978 * methods of OO.ui.Process.
1980 * @param {Object} [data] Window opening data
1981 * @return {OO.ui.Process} Ready process
1983 OO.ui.Window.prototype.getReadyProcess = function () {
1984 return new OO.ui.Process();
1988 * Get the 'hold' process.
1990 * The hold process is used to keep a window from being used in a particular context,
1991 * based on the `data` argument. This method is called during the closing phase of the window’s
1994 * Override this method to add additional steps to the 'hold' process the parent method provides
1995 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
1998 * @param {Object} [data] Window closing data
1999 * @return {OO.ui.Process} Hold process
2001 OO.ui.Window.prototype.getHoldProcess = function () {
2002 return new OO.ui.Process();
2006 * Get the ‘teardown’ process.
2008 * The teardown process is used to teardown a window after use. During teardown,
2009 * user interactions within the window are conveyed and the window is closed, based on the `data`
2010 * argument. This method is called during the closing phase of the window’s lifecycle.
2012 * Override this method to add additional steps to the ‘teardown’ process the parent method provides
2013 * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
2016 * @param {Object} [data] Window closing data
2017 * @return {OO.ui.Process} Teardown process
2019 OO.ui.Window.prototype.getTeardownProcess = function () {
2020 return new OO.ui.Process();
2024 * Set the window manager.
2026 * This will cause the window to initialize. Calling it more than once will cause an error.
2028 * @param {OO.ui.WindowManager} manager Manager for this window
2029 * @throws {Error} An error is thrown if the method is called more than once
2032 OO.ui.Window.prototype.setManager = function ( manager ) {
2033 if ( this.manager ) {
2034 throw new Error( 'Cannot set window manager, window already has a manager' );
2037 this.manager = manager;
2044 * Set the window size by symbolic name (e.g., 'small' or 'medium')
2046 * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
2050 OO.ui.Window.prototype.setSize = function ( size ) {
2057 * Update the window size.
2059 * @throws {Error} An error is thrown if the window is not attached to a window manager
2062 OO.ui.Window.prototype.updateSize = function () {
2063 if ( !this.manager ) {
2064 throw new Error( 'Cannot update window size, must be attached to a manager' );
2067 this.manager.updateWindowSize( this );
2073 * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
2074 * when the window is opening. In general, setDimensions should not be called directly.
2076 * To set the size of the window, use the #setSize method.
2078 * @param {Object} dim CSS dimension properties
2079 * @param {string|number} [dim.width] Width
2080 * @param {string|number} [dim.minWidth] Minimum width
2081 * @param {string|number} [dim.maxWidth] Maximum width
2082 * @param {string|number} [dim.height] Height, omit to set based on height of contents
2083 * @param {string|number} [dim.minHeight] Minimum height
2084 * @param {string|number} [dim.maxHeight] Maximum height
2087 OO.ui.Window.prototype.setDimensions = function ( dim ) {
2090 styleObj = this.$frame[ 0 ].style;
2092 // Calculate the height we need to set using the correct width
2093 if ( dim.height === undefined ) {
2094 this.withoutSizeTransitions( function () {
2095 var oldWidth = styleObj.width;
2096 win.$frame.css( 'width', dim.width || '' );
2097 height = win.getContentHeight();
2098 styleObj.width = oldWidth;
2101 height = dim.height;
2105 width: dim.width || '',
2106 minWidth: dim.minWidth || '',
2107 maxWidth: dim.maxWidth || '',
2108 height: height || '',
2109 minHeight: dim.minHeight || '',
2110 maxHeight: dim.maxHeight || ''
2117 * Initialize window contents.
2119 * Before the window is opened for the first time, #initialize is called so that content that
2120 * persists between openings can be added to the window.
2122 * To set up a window with new content each time the window opens, use #getSetupProcess.
2124 * @throws {Error} An error is thrown if the window is not attached to a window manager
2127 OO.ui.Window.prototype.initialize = function () {
2128 if ( !this.manager ) {
2129 throw new Error( 'Cannot initialize window, must be attached to a manager' );
2133 this.$head = $( '<div>' );
2134 this.$body = $( '<div>' );
2135 this.$foot = $( '<div>' );
2136 this.$document = $( this.getElementDocument() );
2139 this.$element.on( 'mousedown', this.onMouseDown.bind( this ) );
2142 this.$head.addClass( 'oo-ui-window-head' );
2143 this.$body.addClass( 'oo-ui-window-body' );
2144 this.$foot.addClass( 'oo-ui-window-foot' );
2145 this.$content.append( this.$head, this.$body, this.$foot );
2151 * Called when someone tries to focus the hidden element at the end of the dialog.
2152 * Sends focus back to the start of the dialog.
2154 * @param {jQuery.Event} event Focus event
2156 OO.ui.Window.prototype.onFocusTrapFocused = function ( event ) {
2157 var backwards = this.$focusTrapBefore.is( event.target ),
2158 element = OO.ui.findFocusable( this.$content, backwards );
2160 // There's a focusable element inside the content, at the front or
2161 // back depending on which focus trap we hit; select it.
2164 // There's nothing focusable inside the content. As a fallback,
2165 // this.$content is focusable, and focusing it will keep our focus
2166 // properly trapped. It's not a *meaningful* focus, since it's just
2167 // the content-div for the Window, but it's better than letting focus
2168 // escape into the page.
2169 this.$content.focus();
2176 * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
2177 * method, which returns a promise resolved when the window is done opening.
2179 * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
2181 * @param {Object} [data] Window opening data
2182 * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
2183 * if the window fails to open. When the promise is resolved successfully, the first argument of the
2184 * value is a new promise, which is resolved when the window begins closing.
2185 * @throws {Error} An error is thrown if the window is not attached to a window manager
2187 OO.ui.Window.prototype.open = function ( data ) {
2188 if ( !this.manager ) {
2189 throw new Error( 'Cannot open window, must be attached to a manager' );
2192 return this.manager.openWindow( this, data );
2198 * This method is a wrapper around a call to the window
2199 * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
2200 * which returns a closing promise resolved when the window is done closing.
2202 * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
2203 * phase of the window’s lifecycle and can be used to specify closing behavior each time
2204 * the window closes.
2206 * @param {Object} [data] Window closing data
2207 * @return {jQuery.Promise} Promise resolved when window is closed
2208 * @throws {Error} An error is thrown if the window is not attached to a window manager
2210 OO.ui.Window.prototype.close = function ( data ) {
2211 if ( !this.manager ) {
2212 throw new Error( 'Cannot close window, must be attached to a manager' );
2215 return this.manager.closeWindow( this, data );
2221 * This is called by OO.ui.WindowManager during window opening, and should not be called directly
2224 * @param {Object} [data] Window opening data
2225 * @return {jQuery.Promise} Promise resolved when window is setup
2227 OO.ui.Window.prototype.setup = function ( data ) {
2230 this.toggle( true );
2232 this.focusTrapHandler = OO.ui.bind( this.onFocusTrapFocused, this );
2233 this.$focusTraps.on( 'focus', this.focusTrapHandler );
2235 return this.getSetupProcess( data ).execute().then( function () {
2236 // Force redraw by asking the browser to measure the elements' widths
2237 win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
2238 win.$content.addClass( 'oo-ui-window-content-setup' ).width();
2245 * This is called by OO.ui.WindowManager during window opening, and should not be called directly
2248 * @param {Object} [data] Window opening data
2249 * @return {jQuery.Promise} Promise resolved when window is ready
2251 OO.ui.Window.prototype.ready = function ( data ) {
2254 this.$content.focus();
2255 return this.getReadyProcess( data ).execute().then( function () {
2256 // Force redraw by asking the browser to measure the elements' widths
2257 win.$element.addClass( 'oo-ui-window-ready' ).width();
2258 win.$content.addClass( 'oo-ui-window-content-ready' ).width();
2265 * This is called by OO.ui.WindowManager during window closing, and should not be called directly
2268 * @param {Object} [data] Window closing data
2269 * @return {jQuery.Promise} Promise resolved when window is held
2271 OO.ui.Window.prototype.hold = function ( data ) {
2274 return this.getHoldProcess( data ).execute().then( function () {
2275 // Get the focused element within the window's content
2276 var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement );
2278 // Blur the focused element
2279 if ( $focus.length ) {
2283 // Force redraw by asking the browser to measure the elements' widths
2284 win.$element.removeClass( 'oo-ui-window-ready' ).width();
2285 win.$content.removeClass( 'oo-ui-window-content-ready' ).width();
2292 * This is called by OO.ui.WindowManager during window closing, and should not be called directly
2295 * @param {Object} [data] Window closing data
2296 * @return {jQuery.Promise} Promise resolved when window is torn down
2298 OO.ui.Window.prototype.teardown = function ( data ) {
2301 return this.getTeardownProcess( data ).execute().then( function () {
2302 // Force redraw by asking the browser to measure the elements' widths
2303 win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
2304 win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
2305 win.$focusTraps.off( 'focus', win.focusTrapHandler );
2306 win.toggle( false );
2311 * The Dialog class serves as the base class for the other types of dialogs.
2312 * Unless extended to include controls, the rendered dialog box is a simple window
2313 * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
2314 * which opens, closes, and controls the presentation of the window. See the
2315 * [OOjs UI documentation on MediaWiki] [1] for more information.
2318 * // A simple dialog window.
2319 * function MyDialog( config ) {
2320 * MyDialog.parent.call( this, config );
2322 * OO.inheritClass( MyDialog, OO.ui.Dialog );
2323 * MyDialog.prototype.initialize = function () {
2324 * MyDialog.parent.prototype.initialize.call( this );
2325 * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
2326 * this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
2327 * this.$body.append( this.content.$element );
2329 * MyDialog.prototype.getBodyHeight = function () {
2330 * return this.content.$element.outerHeight( true );
2332 * var myDialog = new MyDialog( {
2335 * // Create and append a window manager, which opens and closes the window.
2336 * var windowManager = new OO.ui.WindowManager();
2337 * $( 'body' ).append( windowManager.$element );
2338 * windowManager.addWindows( [ myDialog ] );
2339 * // Open the window!
2340 * windowManager.openWindow( myDialog );
2342 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
2346 * @extends OO.ui.Window
2347 * @mixins OO.ui.mixin.PendingElement
2350 * @param {Object} [config] Configuration options
2352 OO.ui.Dialog = function OoUiDialog( config ) {
2353 // Parent constructor
2354 OO.ui.Dialog.parent.call( this, config );
2356 // Mixin constructors
2357 OO.ui.mixin.PendingElement.call( this );
2360 this.actions = new OO.ui.ActionSet();
2361 this.attachedActions = [];
2362 this.currentAction = null;
2363 this.onDialogKeyDownHandler = this.onDialogKeyDown.bind( this );
2366 this.actions.connect( this, {
2367 click: 'onActionClick',
2368 resize: 'onActionResize',
2369 change: 'onActionsChange'
2374 .addClass( 'oo-ui-dialog' )
2375 .attr( 'role', 'dialog' );
2380 OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
2381 OO.mixinClass( OO.ui.Dialog, OO.ui.mixin.PendingElement );
2383 /* Static Properties */
2386 * Symbolic name of dialog.
2388 * The dialog class must have a symbolic name in order to be registered with OO.Factory.
2389 * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
2391 * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
2396 * @property {string}
2398 OO.ui.Dialog.static.name = '';
2403 * The title can be specified as a plaintext string, a {@link OO.ui.mixin.LabelElement Label} node, or a function
2404 * that will produce a Label node or string. The title can also be specified with data passed to the
2405 * constructor (see #getSetupProcess). In this case, the static value will be overridden.
2410 * @property {jQuery|string|Function}
2412 OO.ui.Dialog.static.title = '';
2415 * An array of configured {@link OO.ui.ActionWidget action widgets}.
2417 * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
2418 * value will be overridden.
2420 * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
2424 * @property {Object[]}
2426 OO.ui.Dialog.static.actions = [];
2429 * Close the dialog when the 'Esc' key is pressed.
2434 * @property {boolean}
2436 OO.ui.Dialog.static.escapable = true;
2441 * Handle frame document key down events.
2444 * @param {jQuery.Event} e Key down event
2446 OO.ui.Dialog.prototype.onDialogKeyDown = function ( e ) {
2448 if ( e.which === OO.ui.Keys.ESCAPE && this.constructor.static.escapable ) {
2449 this.executeAction( '' );
2451 e.stopPropagation();
2452 } else if ( e.which === OO.ui.Keys.ENTER && e.ctrlKey ) {
2453 actions = this.actions.get( { flags: 'primary', visible: true, disabled: false } );
2454 if ( actions.length > 0 ) {
2455 this.executeAction( actions[ 0 ].getAction() );
2457 e.stopPropagation();
2463 * Handle action resized events.
2466 * @param {OO.ui.ActionWidget} action Action that was resized
2468 OO.ui.Dialog.prototype.onActionResize = function () {
2469 // Override in subclass
2473 * Handle action click events.
2476 * @param {OO.ui.ActionWidget} action Action that was clicked
2478 OO.ui.Dialog.prototype.onActionClick = function ( action ) {
2479 if ( !this.isPending() ) {
2480 this.executeAction( action.getAction() );
2485 * Handle actions change event.
2489 OO.ui.Dialog.prototype.onActionsChange = function () {
2490 this.detachActions();
2491 if ( !this.isClosing() ) {
2492 this.attachActions();
2497 * Get the set of actions used by the dialog.
2499 * @return {OO.ui.ActionSet}
2501 OO.ui.Dialog.prototype.getActions = function () {
2502 return this.actions;
2506 * Get a process for taking action.
2508 * When you override this method, you can create a new OO.ui.Process and return it, or add additional
2509 * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
2510 * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
2512 * @param {string} [action] Symbolic name of action
2513 * @return {OO.ui.Process} Action process
2515 OO.ui.Dialog.prototype.getActionProcess = function ( action ) {
2516 return new OO.ui.Process()
2517 .next( function () {
2519 // An empty action always closes the dialog without data, which should always be
2520 // safe and make no changes
2529 * @param {Object} [data] Dialog opening data
2530 * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
2531 * the {@link #static-title static title}
2532 * @param {Object[]} [data.actions] List of configuration options for each
2533 * {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
2535 OO.ui.Dialog.prototype.getSetupProcess = function ( data ) {
2539 return OO.ui.Dialog.parent.prototype.getSetupProcess.call( this, data )
2540 .next( function () {
2541 var config = this.constructor.static,
2542 actions = data.actions !== undefined ? data.actions : config.actions,
2543 title = data.title !== undefined ? data.title : config.title;
2545 this.title.setLabel( title ).setTitle( title );
2546 this.actions.add( this.getActionWidgets( actions ) );
2548 this.$element.on( 'keydown', this.onDialogKeyDownHandler );
2555 OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {
2557 return OO.ui.Dialog.parent.prototype.getTeardownProcess.call( this, data )
2558 .first( function () {
2559 this.$element.off( 'keydown', this.onDialogKeyDownHandler );
2561 this.actions.clear();
2562 this.currentAction = null;
2569 OO.ui.Dialog.prototype.initialize = function () {
2573 OO.ui.Dialog.parent.prototype.initialize.call( this );
2575 titleId = OO.ui.generateElementId();
2578 this.title = new OO.ui.LabelWidget( {
2583 this.$content.addClass( 'oo-ui-dialog-content' );
2584 this.$element.attr( 'aria-labelledby', titleId );
2585 this.setPendingElement( this.$head );
2589 * Get action widgets from a list of configs
2591 * @param {Object[]} actions Action widget configs
2592 * @return {OO.ui.ActionWidget[]} Action widgets
2594 OO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {
2595 var i, len, widgets = [];
2596 for ( i = 0, len = actions.length; i < len; i++ ) {
2598 new OO.ui.ActionWidget( actions[ i ] )
2605 * Attach action actions.
2609 OO.ui.Dialog.prototype.attachActions = function () {
2610 // Remember the list of potentially attached actions
2611 this.attachedActions = this.actions.get();
2615 * Detach action actions.
2620 OO.ui.Dialog.prototype.detachActions = function () {
2623 // Detach all actions that may have been previously attached
2624 for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
2625 this.attachedActions[ i ].$element.detach();
2627 this.attachedActions = [];
2631 * Execute an action.
2633 * @param {string} action Symbolic name of action to execute
2634 * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
2636 OO.ui.Dialog.prototype.executeAction = function ( action ) {
2638 this.currentAction = action;
2639 return this.getActionProcess( action ).execute()
2640 .always( this.popPending.bind( this ) );
2644 * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
2645 * consists of a header that contains the dialog title, a body with the message, and a footer that
2646 * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
2647 * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
2649 * There are two basic types of message dialogs, confirmation and alert:
2651 * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
2652 * more details about the consequences.
2653 * - **alert**: the dialog title describes which event occurred and the message provides more information
2654 * about why the event occurred.
2656 * The MessageDialog class specifies two actions: ‘accept’, the primary
2657 * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
2658 * passing along the selected action.
2660 * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
2663 * // Example: Creating and opening a message dialog window.
2664 * var messageDialog = new OO.ui.MessageDialog();
2666 * // Create and append a window manager.
2667 * var windowManager = new OO.ui.WindowManager();
2668 * $( 'body' ).append( windowManager.$element );
2669 * windowManager.addWindows( [ messageDialog ] );
2670 * // Open the window.
2671 * windowManager.openWindow( messageDialog, {
2672 * title: 'Basic message dialog',
2673 * message: 'This is the message'
2676 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
2679 * @extends OO.ui.Dialog
2682 * @param {Object} [config] Configuration options
2684 OO.ui.MessageDialog = function OoUiMessageDialog( config ) {
2685 // Parent constructor
2686 OO.ui.MessageDialog.parent.call( this, config );
2689 this.verticalActionLayout = null;
2692 this.$element.addClass( 'oo-ui-messageDialog' );
2697 OO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );
2699 /* Static Properties */
2701 OO.ui.MessageDialog.static.name = 'message';
2703 OO.ui.MessageDialog.static.size = 'small';
2705 // @deprecated since v0.18.4 as default; TODO: Remove
2706 OO.ui.MessageDialog.static.verbose = true;
2711 * The title of a confirmation dialog describes what a progressive action will do. The
2712 * title of an alert dialog describes which event occurred.
2716 * @property {jQuery|string|Function|null}
2718 OO.ui.MessageDialog.static.title = null;
2721 * The message displayed in the dialog body.
2723 * A confirmation message describes the consequences of a progressive action. An alert
2724 * message describes why an event occurred.
2728 * @property {jQuery|string|Function|null}
2730 OO.ui.MessageDialog.static.message = null;
2732 // Note that OO.ui.alert() and OO.ui.confirm() rely on these.
2733 OO.ui.MessageDialog.static.actions = [
2734 { action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },
2735 { action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }
2743 OO.ui.MessageDialog.prototype.setManager = function ( manager ) {
2744 OO.ui.MessageDialog.parent.prototype.setManager.call( this, manager );
2747 this.manager.connect( this, {
2757 OO.ui.MessageDialog.prototype.onActionResize = function ( action ) {
2759 return OO.ui.MessageDialog.parent.prototype.onActionResize.call( this, action );
2763 * Handle window resized events.
2767 OO.ui.MessageDialog.prototype.onResize = function () {
2769 dialog.fitActions();
2770 // Wait for CSS transition to finish and do it again :(
2771 setTimeout( function () {
2772 dialog.fitActions();
2777 * Toggle action layout between vertical and horizontal.
2780 * @param {boolean} [value] Layout actions vertically, omit to toggle
2783 OO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {
2784 value = value === undefined ? !this.verticalActionLayout : !!value;
2786 if ( value !== this.verticalActionLayout ) {
2787 this.verticalActionLayout = value;
2789 .toggleClass( 'oo-ui-messageDialog-actions-vertical', value )
2790 .toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );
2799 OO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {
2801 return new OO.ui.Process( function () {
2802 this.close( { action: action } );
2805 return OO.ui.MessageDialog.parent.prototype.getActionProcess.call( this, action );
2811 * @param {Object} [data] Dialog opening data
2812 * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
2813 * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
2814 * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
2817 OO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {
2821 return OO.ui.MessageDialog.parent.prototype.getSetupProcess.call( this, data )
2822 .next( function () {
2823 this.title.setLabel(
2824 data.title !== undefined ? data.title : this.constructor.static.title
2826 this.message.setLabel(
2827 data.message !== undefined ? data.message : this.constructor.static.message
2829 // @deprecated since v0.18.4 as default; TODO: Remove and make default instead.
2830 this.message.$element.toggleClass(
2831 'oo-ui-messageDialog-message-verbose',
2832 data.verbose !== undefined ? data.verbose : this.constructor.static.verbose
2840 OO.ui.MessageDialog.prototype.getReadyProcess = function ( data ) {
2844 return OO.ui.MessageDialog.parent.prototype.getReadyProcess.call( this, data )
2845 .next( function () {
2846 // Focus the primary action button
2847 var actions = this.actions.get();
2848 actions = actions.filter( function ( action ) {
2849 return action.getFlags().indexOf( 'primary' ) > -1;
2851 if ( actions.length > 0 ) {
2852 actions[ 0 ].$button.focus();
2860 OO.ui.MessageDialog.prototype.getBodyHeight = function () {
2861 var bodyHeight, oldOverflow,
2862 $scrollable = this.container.$element;
2864 oldOverflow = $scrollable[ 0 ].style.overflow;
2865 $scrollable[ 0 ].style.overflow = 'hidden';
2867 OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
2869 bodyHeight = this.text.$element.outerHeight( true );
2870 $scrollable[ 0 ].style.overflow = oldOverflow;
2878 OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
2879 var $scrollable = this.container.$element;
2880 OO.ui.MessageDialog.parent.prototype.setDimensions.call( this, dim );
2882 // Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.
2883 // Need to do it after transition completes (250ms), add 50ms just in case.
2884 setTimeout( function () {
2885 var oldOverflow = $scrollable[ 0 ].style.overflow,
2886 activeElement = document.activeElement;
2888 $scrollable[ 0 ].style.overflow = 'hidden';
2890 OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
2892 // Check reconsiderScrollbars didn't destroy our focus, as we
2893 // are doing this after the ready process.
2894 if ( activeElement && activeElement !== document.activeElement && activeElement.focus ) {
2895 activeElement.focus();
2898 $scrollable[ 0 ].style.overflow = oldOverflow;
2907 OO.ui.MessageDialog.prototype.initialize = function () {
2909 OO.ui.MessageDialog.parent.prototype.initialize.call( this );
2912 this.$actions = $( '<div>' );
2913 this.container = new OO.ui.PanelLayout( {
2914 scrollable: true, classes: [ 'oo-ui-messageDialog-container' ]
2916 this.text = new OO.ui.PanelLayout( {
2917 padded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]
2919 this.message = new OO.ui.LabelWidget( {
2920 classes: [ 'oo-ui-messageDialog-message' ]
2924 this.title.$element.addClass( 'oo-ui-messageDialog-title' );
2925 this.$content.addClass( 'oo-ui-messageDialog-content' );
2926 this.container.$element.append( this.text.$element );
2927 this.text.$element.append( this.title.$element, this.message.$element );
2928 this.$body.append( this.container.$element );
2929 this.$actions.addClass( 'oo-ui-messageDialog-actions' );
2930 this.$foot.append( this.$actions );
2936 OO.ui.MessageDialog.prototype.attachActions = function () {
2937 var i, len, other, special, others;
2940 OO.ui.MessageDialog.parent.prototype.attachActions.call( this );
2942 special = this.actions.getSpecial();
2943 others = this.actions.getOthers();
2945 if ( special.safe ) {
2946 this.$actions.append( special.safe.$element );
2947 special.safe.toggleFramed( false );
2949 if ( others.length ) {
2950 for ( i = 0, len = others.length; i < len; i++ ) {
2951 other = others[ i ];
2952 this.$actions.append( other.$element );
2953 other.toggleFramed( false );
2956 if ( special.primary ) {
2957 this.$actions.append( special.primary.$element );
2958 special.primary.toggleFramed( false );
2961 if ( !this.isOpening() ) {
2962 // If the dialog is currently opening, this will be called automatically soon.
2963 // This also calls #fitActions.
2969 * Fit action actions into columns or rows.
2971 * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
2975 OO.ui.MessageDialog.prototype.fitActions = function () {
2977 previous = this.verticalActionLayout,
2978 actions = this.actions.get();
2981 this.toggleVerticalActionLayout( false );
2982 for ( i = 0, len = actions.length; i < len; i++ ) {
2983 action = actions[ i ];
2984 if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) {
2985 this.toggleVerticalActionLayout( true );
2990 // Move the body out of the way of the foot
2991 this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
2993 if ( this.verticalActionLayout !== previous ) {
2994 // We changed the layout, window height might need to be updated.
3000 * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
3001 * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
3002 * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
3003 * relevant. The ProcessDialog class is always extended and customized with the actions and content
3004 * required for each process.
3006 * The process dialog box consists of a header that visually represents the ‘working’ state of long
3007 * processes with an animation. The header contains the dialog title as well as
3008 * two {@link OO.ui.ActionWidget action widgets}: a ‘safe’ action on the left (e.g., ‘Cancel’) and
3009 * a ‘primary’ action on the right (e.g., ‘Done’).
3011 * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
3012 * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
3015 * // Example: Creating and opening a process dialog window.
3016 * function MyProcessDialog( config ) {
3017 * MyProcessDialog.parent.call( this, config );
3019 * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
3021 * MyProcessDialog.static.title = 'Process dialog';
3022 * MyProcessDialog.static.actions = [
3023 * { action: 'save', label: 'Done', flags: 'primary' },
3024 * { label: 'Cancel', flags: 'safe' }
3027 * MyProcessDialog.prototype.initialize = function () {
3028 * MyProcessDialog.parent.prototype.initialize.apply( this, arguments );
3029 * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
3030 * this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action) on the right.</p>' );
3031 * this.$body.append( this.content.$element );
3033 * MyProcessDialog.prototype.getActionProcess = function ( action ) {
3034 * var dialog = this;
3036 * return new OO.ui.Process( function () {
3037 * dialog.close( { action: action } );
3040 * return MyProcessDialog.parent.prototype.getActionProcess.call( this, action );
3043 * var windowManager = new OO.ui.WindowManager();
3044 * $( 'body' ).append( windowManager.$element );
3046 * var dialog = new MyProcessDialog();
3047 * windowManager.addWindows( [ dialog ] );
3048 * windowManager.openWindow( dialog );
3050 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
3054 * @extends OO.ui.Dialog
3057 * @param {Object} [config] Configuration options
3059 OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
3060 // Parent constructor
3061 OO.ui.ProcessDialog.parent.call( this, config );
3064 this.fitOnOpen = false;
3067 this.$element.addClass( 'oo-ui-processDialog' );
3072 OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );
3077 * Handle dismiss button click events.
3083 OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
3088 * Handle retry button click events.
3090 * Hides errors and then tries again.
3094 OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
3096 this.executeAction( this.currentAction );
3102 OO.ui.ProcessDialog.prototype.onActionResize = function ( action ) {
3103 if ( this.actions.isSpecial( action ) ) {
3106 return OO.ui.ProcessDialog.parent.prototype.onActionResize.call( this, action );
3112 OO.ui.ProcessDialog.prototype.initialize = function () {
3114 OO.ui.ProcessDialog.parent.prototype.initialize.call( this );
3117 this.$navigation = $( '<div>' );
3118 this.$location = $( '<div>' );
3119 this.$safeActions = $( '<div>' );
3120 this.$primaryActions = $( '<div>' );
3121 this.$otherActions = $( '<div>' );
3122 this.dismissButton = new OO.ui.ButtonWidget( {
3123 label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
3125 this.retryButton = new OO.ui.ButtonWidget();
3126 this.$errors = $( '<div>' );
3127 this.$errorsTitle = $( '<div>' );
3130 this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } );
3131 this.retryButton.connect( this, { click: 'onRetryButtonClick' } );
3134 this.title.$element.addClass( 'oo-ui-processDialog-title' );
3136 .append( this.title.$element )
3137 .addClass( 'oo-ui-processDialog-location' );
3138 this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
3139 this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
3140 this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
3142 .addClass( 'oo-ui-processDialog-errors-title' )
3143 .text( OO.ui.msg( 'ooui-dialog-process-error' ) );
3145 .addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
3146 .append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
3148 .addClass( 'oo-ui-processDialog-content' )
3149 .append( this.$errors );
3151 .addClass( 'oo-ui-processDialog-navigation' )
3152 // Note: Order of appends below is important. These are in the order
3153 // we want tab to go through them. Display-order is handled entirely
3154 // by CSS absolute-positioning. As such, primary actions like "done"
3156 .append( this.$primaryActions, this.$location, this.$safeActions );
3157 this.$head.append( this.$navigation );
3158 this.$foot.append( this.$otherActions );
3164 OO.ui.ProcessDialog.prototype.getActionWidgets = function ( actions ) {
3166 isMobile = OO.ui.isMobile(),
3169 for ( i = 0, len = actions.length; i < len; i++ ) {
3170 config = $.extend( { framed: !OO.ui.isMobile() }, actions[ i ] );
3172 ( config.flags === 'back' || ( Array.isArray( config.flags ) && config.flags.indexOf( 'back' ) !== -1 ) )
3180 new OO.ui.ActionWidget( config )
3189 OO.ui.ProcessDialog.prototype.attachActions = function () {
3190 var i, len, other, special, others;
3193 OO.ui.ProcessDialog.parent.prototype.attachActions.call( this );
3195 special = this.actions.getSpecial();
3196 others = this.actions.getOthers();
3197 if ( special.primary ) {
3198 this.$primaryActions.append( special.primary.$element );
3200 for ( i = 0, len = others.length; i < len; i++ ) {
3201 other = others[ i ];
3202 this.$otherActions.append( other.$element );
3204 if ( special.safe ) {
3205 this.$safeActions.append( special.safe.$element );
3209 this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
3215 OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
3217 return OO.ui.ProcessDialog.parent.prototype.executeAction.call( this, action )
3218 .fail( function ( errors ) {
3219 process.showErrors( errors || [] );
3226 OO.ui.ProcessDialog.prototype.setDimensions = function () {
3228 OO.ui.ProcessDialog.parent.prototype.setDimensions.apply( this, arguments );
3234 * Fit label between actions.
3239 OO.ui.ProcessDialog.prototype.fitLabel = function () {
3240 var safeWidth, primaryWidth, biggerWidth, labelWidth, navigationWidth, leftWidth, rightWidth,
3241 size = this.getSizeProperties();
3243 if ( typeof size.width !== 'number' ) {
3244 if ( this.isOpened() ) {
3245 navigationWidth = this.$head.width() - 20;
3246 } else if ( this.isOpening() ) {
3247 if ( !this.fitOnOpen ) {
3248 // Size is relative and the dialog isn't open yet, so wait.
3249 this.manager.opening.done( this.fitLabel.bind( this ) );
3250 this.fitOnOpen = true;
3257 navigationWidth = size.width - 20;
3260 safeWidth = this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0;
3261 primaryWidth = this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0;
3262 biggerWidth = Math.max( safeWidth, primaryWidth );
3264 labelWidth = this.title.$element.width();
3266 if ( 2 * biggerWidth + labelWidth < navigationWidth ) {
3267 // We have enough space to center the label
3268 leftWidth = rightWidth = biggerWidth;
3270 // Let's hope we at least have enough space not to overlap, because we can't wrap the label…
3271 if ( this.getDir() === 'ltr' ) {
3272 leftWidth = safeWidth;
3273 rightWidth = primaryWidth;
3275 leftWidth = primaryWidth;
3276 rightWidth = safeWidth;
3280 this.$location.css( { paddingLeft: leftWidth, paddingRight: rightWidth } );
3286 * Handle errors that occurred during accept or reject processes.
3289 * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
3291 OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
3292 var i, len, $item, actions,
3298 if ( errors instanceof OO.ui.Error ) {
3299 errors = [ errors ];
3302 for ( i = 0, len = errors.length; i < len; i++ ) {
3303 if ( !errors[ i ].isRecoverable() ) {
3304 recoverable = false;
3306 if ( errors[ i ].isWarning() ) {
3309 $item = $( '<div>' )
3310 .addClass( 'oo-ui-processDialog-error' )
3311 .append( errors[ i ].getMessage() );
3312 items.push( $item[ 0 ] );
3314 this.$errorItems = $( items );
3315 if ( recoverable ) {
3316 abilities[ this.currentAction ] = true;
3317 // Copy the flags from the first matching action
3318 actions = this.actions.get( { actions: this.currentAction } );
3319 if ( actions.length ) {
3320 this.retryButton.clearFlags().setFlags( actions[ 0 ].getFlags() );
3323 abilities[ this.currentAction ] = false;
3324 this.actions.setAbilities( abilities );
3327 this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
3329 this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
3331 this.retryButton.toggle( recoverable );
3332 this.$errorsTitle.after( this.$errorItems );
3333 this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
3341 OO.ui.ProcessDialog.prototype.hideErrors = function () {
3342 this.$errors.addClass( 'oo-ui-element-hidden' );
3343 if ( this.$errorItems ) {
3344 this.$errorItems.remove();
3345 this.$errorItems = null;
3352 OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
3354 return OO.ui.ProcessDialog.parent.prototype.getTeardownProcess.call( this, data )
3355 .first( function () {
3356 // Make sure to hide errors
3358 this.fitOnOpen = false;
3367 * Lazy-initialize and return a global OO.ui.WindowManager instance, used by OO.ui.alert and
3371 * @return {OO.ui.WindowManager}
3373 OO.ui.getWindowManager = function () {
3374 if ( !OO.ui.windowManager ) {
3375 OO.ui.windowManager = new OO.ui.WindowManager();
3376 $( 'body' ).append( OO.ui.windowManager.$element );
3377 OO.ui.windowManager.addWindows( {
3378 messageDialog: new OO.ui.MessageDialog()
3381 return OO.ui.windowManager;
3385 * Display a quick modal alert dialog, using a OO.ui.MessageDialog. While the dialog is open, the
3386 * rest of the page will be dimmed out and the user won't be able to interact with it. The dialog
3387 * has only one action button, labelled "OK", clicking it will simply close the dialog.
3389 * A window manager is created automatically when this function is called for the first time.
3392 * OO.ui.alert( 'Something happened!' ).done( function () {
3393 * console.log( 'User closed the dialog.' );
3396 * @param {jQuery|string} text Message text to display
3397 * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
3398 * @return {jQuery.Promise} Promise resolved when the user closes the dialog
3400 OO.ui.alert = function ( text, options ) {
3401 return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
3403 actions: [ OO.ui.MessageDialog.static.actions[ 0 ] ]
3404 }, options ) ).then( function ( opened ) {
3405 return opened.then( function ( closing ) {
3406 return closing.then( function () {
3407 return $.Deferred().resolve();
3414 * Display a quick modal confirmation dialog, using a OO.ui.MessageDialog. While the dialog is open,
3415 * the rest of the page will be dimmed out and the user won't be able to interact with it. The
3416 * dialog has two action buttons, one to confirm an operation (labelled "OK") and one to cancel it
3417 * (labelled "Cancel").
3419 * A window manager is created automatically when this function is called for the first time.
3422 * OO.ui.confirm( 'Are you sure?' ).done( function ( confirmed ) {
3423 * if ( confirmed ) {
3424 * console.log( 'User clicked "OK"!' );
3426 * console.log( 'User clicked "Cancel" or closed the dialog.' );
3430 * @param {jQuery|string} text Message text to display
3431 * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
3432 * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
3433 * confirm, the promise will resolve to boolean `true`; otherwise, it will resolve to boolean
3436 OO.ui.confirm = function ( text, options ) {
3437 return OO.ui.getWindowManager().openWindow( 'messageDialog', $.extend( {
3439 }, options ) ).then( function ( opened ) {
3440 return opened.then( function ( closing ) {
3441 return closing.then( function ( data ) {
3442 return $.Deferred().resolve( !!( data && data.action === 'accept' ) );
3449 * Display a quick modal prompt dialog, using a OO.ui.MessageDialog. While the dialog is open,
3450 * the rest of the page will be dimmed out and the user won't be able to interact with it. The
3451 * dialog has a text input widget and two action buttons, one to confirm an operation (labelled "OK")
3452 * and one to cancel it (labelled "Cancel").
3454 * A window manager is created automatically when this function is called for the first time.
3457 * OO.ui.prompt( 'Choose a line to go to', { textInput: { placeholder: 'Line number' } } ).done( function ( result ) {
3458 * if ( result !== null ) {
3459 * console.log( 'User typed "' + result + '" then clicked "OK".' );
3461 * console.log( 'User clicked "Cancel" or closed the dialog.' );
3465 * @param {jQuery|string} text Message text to display
3466 * @param {Object} [options] Additional options, see OO.ui.MessageDialog#getSetupProcess
3467 * @cfg {Object} [textInput] Additional options for text input widget, see OO.ui.TextInputWidget
3468 * @return {jQuery.Promise} Promise resolved when the user closes the dialog. If the user chose to
3469 * confirm, the promise will resolve with the value of the text input widget; otherwise, it will
3470 * resolve to `null`.
3472 OO.ui.prompt = function ( text, options ) {
3473 var manager = OO.ui.getWindowManager(),
3474 textInput = new OO.ui.TextInputWidget( ( options && options.textInput ) || {} ),
3475 textField = new OO.ui.FieldLayout( textInput, {
3480 // TODO: This is a little hacky, and could be done by extending MessageDialog instead.
3482 return manager.openWindow( 'messageDialog', $.extend( {
3483 message: textField.$element
3484 }, options ) ).then( function ( opened ) {
3486 textInput.on( 'enter', function () {
3487 manager.getCurrentWindow().close( { action: 'accept' } );
3490 return opened.then( function ( closing ) {
3491 return closing.then( function ( data ) {
3492 return $.Deferred().resolve( data && data.action === 'accept' ? textInput.getValue() : null );
3500 //# sourceMappingURL=oojs-ui-windows.js.map