3 * https://www.mediawiki.org/wiki/OOUI
5 * Copyright 2011–2019 OOUI Team and other contributors.
6 * Released under the MIT license
7 * http://oojs.mit-license.org
9 * Date: 2019-06-05T16:24:08Z
16 * Toolbars are complex interface components that permit users to easily access a variety
17 * of {@link OO.ui.Tool tools} (e.g., formatting commands) and actions, which are additional
18 * commands that are part of the toolbar, but not configured as tools.
20 * Individual tools are customized and then registered with a
21 * {@link OO.ui.ToolFactory tool factory}, which creates the tools on demand. Each tool has a
22 * symbolic name (used when registering the tool), a title (e.g., ‘Insert image’), and an icon.
24 * Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be
25 * {@link OO.ui.MenuToolGroup menus} of tools, {@link OO.ui.ListToolGroup lists} of tools, or a
26 * single {@link OO.ui.BarToolGroup bar} of tools. The arrangement and order of the toolgroups is
27 * customized when the toolbar is set up. Tools can be presented in any order, but each can only
28 * appear once in the toolbar.
30 * The toolbar can be synchronized with the state of the external "application", like a text
31 * editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as
32 * active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption
33 * tool would be disabled while the user is not editing a table). A state change is signalled by
34 * emitting the {@link #event-updateState 'updateState' event}, which calls Tools'
35 * {@link OO.ui.Tool#onUpdateState onUpdateState method}.
37 * The following is an example of a basic toolbar.
40 * // Example of a toolbar
41 * // Create the toolbar
42 * var toolFactory = new OO.ui.ToolFactory();
43 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
44 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
46 * // We will be placing status text in this element when tools are used
47 * var $area = $( '<p>' ).text( 'Toolbar example' );
49 * // Define the tools that we're going to place in our toolbar
51 * // Create a class inheriting from OO.ui.Tool
52 * function SearchTool() {
53 * SearchTool.parent.apply( this, arguments );
55 * OO.inheritClass( SearchTool, OO.ui.Tool );
56 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
57 * // of 'icon' and 'title' (displayed icon and text).
58 * SearchTool.static.name = 'search';
59 * SearchTool.static.icon = 'search';
60 * SearchTool.static.title = 'Search...';
61 * // Defines the action that will happen when this tool is selected (clicked).
62 * SearchTool.prototype.onSelect = function () {
63 * $area.text( 'Search tool clicked!' );
64 * // Never display this tool as "active" (selected).
65 * this.setActive( false );
67 * SearchTool.prototype.onUpdateState = function () {};
68 * // Make this tool available in our toolFactory and thus our toolbar
69 * toolFactory.register( SearchTool );
71 * // Register two more tools, nothing interesting here
72 * function SettingsTool() {
73 * SettingsTool.parent.apply( this, arguments );
75 * OO.inheritClass( SettingsTool, OO.ui.Tool );
76 * SettingsTool.static.name = 'settings';
77 * SettingsTool.static.icon = 'settings';
78 * SettingsTool.static.title = 'Change settings';
79 * SettingsTool.prototype.onSelect = function () {
80 * $area.text( 'Settings tool clicked!' );
81 * this.setActive( false );
83 * SettingsTool.prototype.onUpdateState = function () {};
84 * toolFactory.register( SettingsTool );
86 * // Register two more tools, nothing interesting here
87 * function StuffTool() {
88 * StuffTool.parent.apply( this, arguments );
90 * OO.inheritClass( StuffTool, OO.ui.Tool );
91 * StuffTool.static.name = 'stuff';
92 * StuffTool.static.icon = 'ellipsis';
93 * StuffTool.static.title = 'More stuff';
94 * StuffTool.prototype.onSelect = function () {
95 * $area.text( 'More stuff tool clicked!' );
96 * this.setActive( false );
98 * StuffTool.prototype.onUpdateState = function () {};
99 * toolFactory.register( StuffTool );
101 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
102 * // little popup window (a PopupWidget).
103 * function HelpTool( toolGroup, config ) {
104 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
109 * this.popup.$body.append( '<p>I am helpful!</p>' );
111 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
112 * HelpTool.static.name = 'help';
113 * HelpTool.static.icon = 'help';
114 * HelpTool.static.title = 'Help';
115 * toolFactory.register( HelpTool );
117 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
118 * // used once (but not all defined tools must be used).
121 * // 'bar' tool groups display tools' icons only, side-by-side.
123 * include: [ 'search', 'help' ]
126 * // 'list' tool groups display both the titles and icons, in a dropdown list.
130 * include: [ 'settings', 'stuff' ]
132 * // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed
133 * // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here,
134 * // since it's more complicated to use. (See the next example snippet on this page.)
137 * // Create some UI around the toolbar and place it in the document
138 * var frame = new OO.ui.PanelLayout( {
142 * var contentFrame = new OO.ui.PanelLayout( {
146 * frame.$element.append(
148 * contentFrame.$element.append( $area )
150 * $( document.body ).append( frame.$element );
152 * // Here is where the toolbar is actually built. This must be done after inserting it into the
154 * toolbar.initialize();
155 * toolbar.emit( 'updateState' );
157 * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
158 * {@link #event-updateState 'updateState' event}.
161 * // Create the toolbar
162 * var toolFactory = new OO.ui.ToolFactory();
163 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
164 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
166 * // We will be placing status text in this element when tools are used
167 * var $area = $( '<p>' ).text( 'Toolbar example' );
169 * // Define the tools that we're going to place in our toolbar
171 * // Create a class inheriting from OO.ui.Tool
172 * function SearchTool() {
173 * SearchTool.parent.apply( this, arguments );
175 * OO.inheritClass( SearchTool, OO.ui.Tool );
176 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
177 * // of 'icon' and 'title' (displayed icon and text).
178 * SearchTool.static.name = 'search';
179 * SearchTool.static.icon = 'search';
180 * SearchTool.static.title = 'Search...';
181 * // Defines the action that will happen when this tool is selected (clicked).
182 * SearchTool.prototype.onSelect = function () {
183 * $area.text( 'Search tool clicked!' );
184 * // Never display this tool as "active" (selected).
185 * this.setActive( false );
187 * SearchTool.prototype.onUpdateState = function () {};
188 * // Make this tool available in our toolFactory and thus our toolbar
189 * toolFactory.register( SearchTool );
191 * // Register two more tools, nothing interesting here
192 * function SettingsTool() {
193 * SettingsTool.parent.apply( this, arguments );
194 * this.reallyActive = false;
196 * OO.inheritClass( SettingsTool, OO.ui.Tool );
197 * SettingsTool.static.name = 'settings';
198 * SettingsTool.static.icon = 'settings';
199 * SettingsTool.static.title = 'Change settings';
200 * SettingsTool.prototype.onSelect = function () {
201 * $area.text( 'Settings tool clicked!' );
202 * // Toggle the active state on each click
203 * this.reallyActive = !this.reallyActive;
204 * this.setActive( this.reallyActive );
205 * // To update the menu label
206 * this.toolbar.emit( 'updateState' );
208 * SettingsTool.prototype.onUpdateState = function () {};
209 * toolFactory.register( SettingsTool );
211 * // Register two more tools, nothing interesting here
212 * function StuffTool() {
213 * StuffTool.parent.apply( this, arguments );
214 * this.reallyActive = false;
216 * OO.inheritClass( StuffTool, OO.ui.Tool );
217 * StuffTool.static.name = 'stuff';
218 * StuffTool.static.icon = 'ellipsis';
219 * StuffTool.static.title = 'More stuff';
220 * StuffTool.prototype.onSelect = function () {
221 * $area.text( 'More stuff tool clicked!' );
222 * // Toggle the active state on each click
223 * this.reallyActive = !this.reallyActive;
224 * this.setActive( this.reallyActive );
225 * // To update the menu label
226 * this.toolbar.emit( 'updateState' );
228 * StuffTool.prototype.onUpdateState = function () {};
229 * toolFactory.register( StuffTool );
231 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
232 * // little popup window (a PopupWidget). 'onUpdateState' is also already implemented.
233 * function HelpTool( toolGroup, config ) {
234 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
239 * this.popup.$body.append( '<p>I am helpful!</p>' );
241 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
242 * HelpTool.static.name = 'help';
243 * HelpTool.static.icon = 'help';
244 * HelpTool.static.title = 'Help';
245 * toolFactory.register( HelpTool );
247 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
248 * // used once (but not all defined tools must be used).
251 * // 'bar' tool groups display tools' icons only, side-by-side.
253 * include: [ 'search', 'help' ]
256 * // 'menu' tool groups display both the titles and icons, in a dropdown menu.
257 * // Menu label indicates which items are selected.
260 * include: [ 'settings', 'stuff' ]
264 * // Create some UI around the toolbar and place it in the document
265 * var frame = new OO.ui.PanelLayout( {
269 * var contentFrame = new OO.ui.PanelLayout( {
273 * frame.$element.append(
275 * contentFrame.$element.append( $area )
277 * $( document.body ).append( frame.$element );
279 * // Here is where the toolbar is actually built. This must be done after inserting it into the
281 * toolbar.initialize();
282 * toolbar.emit( 'updateState' );
285 * @extends OO.ui.Element
286 * @mixins OO.EventEmitter
287 * @mixins OO.ui.mixin.GroupElement
290 * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools
291 * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups
292 * @param {Object} [config] Configuration options
293 * @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are
294 * included in the toolbar, but are not configured as tools. By default, actions are displayed on
295 * the right side of the toolbar.
296 * @cfg {string} [position='top'] Whether the toolbar is positioned above ('top') or below
297 * ('bottom') content.
298 * @cfg {jQuery} [$overlay] An overlay for the popup.
299 * See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
301 OO
.ui
.Toolbar
= function OoUiToolbar( toolFactory
, toolGroupFactory
, config
) {
302 // Allow passing positional parameters inside the config object
303 if ( OO
.isPlainObject( toolFactory
) && config
=== undefined ) {
304 config
= toolFactory
;
305 toolFactory
= config
.toolFactory
;
306 toolGroupFactory
= config
.toolGroupFactory
;
309 // Configuration initialization
310 config
= config
|| {};
312 // Parent constructor
313 OO
.ui
.Toolbar
.parent
.call( this, config
);
315 // Mixin constructors
316 OO
.EventEmitter
.call( this );
317 OO
.ui
.mixin
.GroupElement
.call( this, config
);
320 this.toolFactory
= toolFactory
;
321 this.toolGroupFactory
= toolGroupFactory
;
322 this.groupsByName
= {};
323 this.activeToolGroups
= 0;
325 this.position
= config
.position
|| 'top';
326 this.$bar
= $( '<div>' );
327 this.$actions
= $( '<div>' );
328 this.$popups
= $( '<div>' );
329 this.initialized
= false;
330 this.narrowThreshold
= null;
331 this.onWindowResizeHandler
= this.onWindowResize
.bind( this );
332 this.$overlay
= ( config
.$overlay
=== true ? OO
.ui
.getDefaultOverlay() : config
.$overlay
) ||
337 .add( this.$bar
).add( this.$group
).add( this.$actions
)
338 .on( 'mousedown keydown', this.onPointerDown
.bind( this ) );
341 this.$group
.addClass( 'oo-ui-toolbar-tools' );
342 if ( config
.actions
) {
343 this.$bar
.append( this.$actions
.addClass( 'oo-ui-toolbar-actions' ) );
345 this.$popups
.addClass( 'oo-ui-toolbar-popups' );
347 .addClass( 'oo-ui-toolbar-bar' )
348 .append( this.$group
, '<div style="clear:both"></div>' );
349 // Possible classes: oo-ui-toolbar-position-top, oo-ui-toolbar-position-bottom
351 .addClass( 'oo-ui-toolbar oo-ui-toolbar-position-' + this.position
)
352 .append( this.$bar
);
353 this.$overlay
.append( this.$popups
);
358 OO
.inheritClass( OO
.ui
.Toolbar
, OO
.ui
.Element
);
359 OO
.mixinClass( OO
.ui
.Toolbar
, OO
.EventEmitter
);
360 OO
.mixinClass( OO
.ui
.Toolbar
, OO
.ui
.mixin
.GroupElement
);
367 * An 'updateState' event must be emitted on the Toolbar (by calling
368 * `toolbar.emit( 'updateState' )`) every time the state of the application using the toolbar
369 * changes, and an update to the state of tools is required.
371 * @param {...Mixed} data Application-defined parameters
377 * An 'active' event is emitted when the number of active toolgroups increases from 0, or
380 * @param {boolean} There are active toolgroups in this toolbar
386 * Get the tool factory.
388 * @return {OO.ui.ToolFactory} Tool factory
390 OO
.ui
.Toolbar
.prototype.getToolFactory = function () {
391 return this.toolFactory
;
395 * Get the toolgroup factory.
397 * @return {OO.Factory} Toolgroup factory
399 OO
.ui
.Toolbar
.prototype.getToolGroupFactory = function () {
400 return this.toolGroupFactory
;
404 * Handles mouse down events.
407 * @param {jQuery.Event} e Mouse down event
408 * @return {undefined|boolean} False to prevent default if event is handled
410 OO
.ui
.Toolbar
.prototype.onPointerDown = function ( e
) {
411 var $closestWidgetToEvent
= $( e
.target
).closest( '.oo-ui-widget' ),
412 $closestWidgetToToolbar
= this.$element
.closest( '.oo-ui-widget' );
414 !$closestWidgetToEvent
.length
||
415 $closestWidgetToEvent
[ 0 ] ===
416 $closestWidgetToToolbar
[ 0 ]
423 * Handle window resize event.
426 * @param {jQuery.Event} e Window resize event
428 OO
.ui
.Toolbar
.prototype.onWindowResize = function () {
429 this.$element
.add( this.$popups
).toggleClass(
430 'oo-ui-toolbar-narrow',
431 this.$bar
[ 0 ].clientWidth
<= this.getNarrowThreshold()
436 * Get the (lazily-computed) width threshold for applying the oo-ui-toolbar-narrow
440 * @return {number} Width threshold in pixels
442 OO
.ui
.Toolbar
.prototype.getNarrowThreshold = function () {
443 if ( this.narrowThreshold
=== null ) {
444 this.narrowThreshold
= this.$group
[ 0 ].offsetWidth
+ this.$actions
[ 0 ].offsetWidth
;
446 return this.narrowThreshold
;
450 * Sets up handles and preloads required information for the toolbar to work.
451 * This must be called after it is attached to a visible document and before doing anything else.
453 OO
.ui
.Toolbar
.prototype.initialize = function () {
454 if ( !this.initialized
) {
455 this.initialized
= true;
456 $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler
);
457 this.onWindowResize();
462 * Set up the toolbar.
464 * The toolbar is set up with a list of toolgroup configurations that specify the type of
465 * toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or
466 * {@link OO.ui.ListToolGroup list}) to add and which tools to include, exclude, promote, or demote
467 * within that toolgroup. Please see {@link OO.ui.ToolGroup toolgroups} for more information about
468 * including tools in toolgroups.
470 * @param {Object.<string,Array>} groups List of toolgroup configurations
471 * @param {string} [groups.name] Symbolic name for this toolgroup
472 * @param {string} [groups.type] Toolgroup type, should exist in the toolgroup factory
473 * @param {Array|string} [groups.include] Tools to include in the toolgroup
474 * @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup
475 * @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup
476 * @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup
478 OO
.ui
.Toolbar
.prototype.setup = function ( groups
) {
479 var i
, len
, type
, toolGroup
, groupConfig
,
483 // Cleanup previous groups
486 // Build out new groups
487 for ( i
= 0, len
= groups
.length
; i
< len
; i
++ ) {
488 groupConfig
= groups
[ i
];
489 if ( groupConfig
.include
=== '*' ) {
490 // Apply defaults to catch-all groups
491 if ( groupConfig
.type
=== undefined ) {
492 groupConfig
.type
= 'list';
494 if ( groupConfig
.label
=== undefined ) {
495 groupConfig
.label
= OO
.ui
.msg( 'ooui-toolbar-more' );
498 // Check type has been registered
499 type
= this.getToolGroupFactory().lookup( groupConfig
.type
) ?
500 groupConfig
.type
: defaultType
;
501 toolGroup
= this.getToolGroupFactory().create( type
, this, groupConfig
);
502 items
.push( toolGroup
);
503 if ( groupConfig
.name
) {
504 this.groupsByName
[ groupConfig
.name
] = toolGroup
;
506 // Groups without name are deprecated
507 OO
.ui
.warnDeprecation( 'Toolgroups must have a \'name\' property' );
509 toolGroup
.connect( this, {
510 active
: 'onToolGroupActive'
513 this.addItems( items
);
517 * Handle active events from tool groups
519 * @param {boolean} active Tool group has become active, inactive if false
522 OO
.ui
.Toolbar
.prototype.onToolGroupActive = function ( active
) {
524 this.activeToolGroups
++;
525 if ( this.activeToolGroups
=== 1 ) {
526 this.emit( 'active', true );
529 this.activeToolGroups
--;
530 if ( this.activeToolGroups
=== 0 ) {
531 this.emit( 'active', false );
537 * Get a toolgroup by name
539 * @param {string} name Group name
540 * @return {OO.ui.ToolGroup|null} Tool group, or null if none found by that name
542 OO
.ui
.Toolbar
.prototype.getToolGroupByName = function ( name
) {
543 return this.groupsByName
[ name
] || null;
547 * Remove all tools and toolgroups from the toolbar.
549 OO
.ui
.Toolbar
.prototype.reset = function () {
552 this.groupsByName
= {};
554 for ( i
= 0, len
= this.items
.length
; i
< len
; i
++ ) {
555 this.items
[ i
].destroy();
561 * Destroy the toolbar.
563 * Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar.
564 * Call this method whenever you are done using a toolbar.
566 OO
.ui
.Toolbar
.prototype.destroy = function () {
567 $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler
);
569 this.$element
.remove();
573 * Check if the tool is available.
575 * Available tools are ones that have not yet been added to the toolbar.
577 * @param {string} name Symbolic name of tool
578 * @return {boolean} Tool is available
580 OO
.ui
.Toolbar
.prototype.isToolAvailable = function ( name
) {
581 return !this.tools
[ name
];
585 * Prevent tool from being used again.
587 * @param {OO.ui.Tool} tool Tool to reserve
589 OO
.ui
.Toolbar
.prototype.reserveTool = function ( tool
) {
590 this.tools
[ tool
.getName() ] = tool
;
594 * Allow tool to be used again.
596 * @param {OO.ui.Tool} tool Tool to release
598 OO
.ui
.Toolbar
.prototype.releaseTool = function ( tool
) {
599 delete this.tools
[ tool
.getName() ];
603 * Get accelerator label for tool.
605 * The OOUI library does not contain an accelerator system, but this is the hook for one. To
606 * use an accelerator system, subclass the toolbar and override this method, which is meant to
607 * return a label that describes the accelerator keys for the tool passed (by symbolic name) to
610 * @param {string} name Symbolic name of tool
611 * @return {string|undefined} Tool accelerator label if available
613 OO
.ui
.Toolbar
.prototype.getToolAccelerator = function () {
618 * Tools, together with {@link OO.ui.ToolGroup toolgroups}, constitute
619 * {@link OO.ui.Toolbar toolbars}.
620 * Each tool is configured with a static name, title, and icon and is customized with the command
621 * to carry out when the tool is selected. Tools must also be registered with a
622 * {@link OO.ui.ToolFactory tool factory}, which creates the tools on demand.
624 * Every Tool subclass must implement two methods:
626 * - {@link #onUpdateState}
627 * - {@link #onSelect}
629 * Tools are added to toolgroups ({@link OO.ui.ListToolGroup ListToolGroup},
630 * {@link OO.ui.BarToolGroup BarToolGroup}, or {@link OO.ui.MenuToolGroup MenuToolGroup}), which
631 * determine how the tool is displayed in the toolbar. See {@link OO.ui.Toolbar toolbars} for an
634 * For more information, please see the [OOUI documentation on MediaWiki][1].
635 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
639 * @extends OO.ui.Widget
640 * @mixins OO.ui.mixin.IconElement
641 * @mixins OO.ui.mixin.FlaggedElement
642 * @mixins OO.ui.mixin.TabIndexedElement
645 * @param {OO.ui.ToolGroup} toolGroup
646 * @param {Object} [config] Configuration options
647 * @cfg {string|Function} [title] Title text or a function that returns text. If this config is
648 * omitted, the value of the {@link #static-title static title} property is used.
650 * The title is used in different ways depending on the type of toolgroup that contains the tool.
651 * The title is used as a tooltip if the tool is part of a {@link OO.ui.BarToolGroup bar}
652 * toolgroup, or as the label text if the tool is part of a {@link OO.ui.ListToolGroup list} or
653 * {@link OO.ui.MenuToolGroup menu} toolgroup.
655 * For bar toolgroups, a description of the accelerator key is appended to the title if an
656 * accelerator key is associated with an action by the same name as the tool and accelerator
657 * functionality has been added to the application.
658 * To add accelerator key functionality, you must subclass OO.ui.Toolbar and override the
659 * {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method.
661 OO
.ui
.Tool
= function OoUiTool( toolGroup
, config
) {
662 // Allow passing positional parameters inside the config object
663 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
665 toolGroup
= config
.toolGroup
;
668 // Configuration initialization
669 config
= config
|| {};
671 // Parent constructor
672 OO
.ui
.Tool
.parent
.call( this, config
);
675 this.toolGroup
= toolGroup
;
676 this.toolbar
= this.toolGroup
.getToolbar();
678 this.$title
= $( '<span>' );
679 this.$accel
= $( '<span>' );
680 this.$link
= $( '<a>' );
682 this.checkIcon
= new OO
.ui
.IconWidget( {
684 classes
: [ 'oo-ui-tool-checkIcon' ]
687 // Mixin constructors
688 OO
.ui
.mixin
.IconElement
.call( this, config
);
689 OO
.ui
.mixin
.FlaggedElement
.call( this, config
);
690 OO
.ui
.mixin
.TabIndexedElement
.call( this, $.extend( {
691 $tabIndexed
: this.$link
695 this.toolbar
.connect( this, {
696 updateState
: 'onUpdateState'
700 this.$title
.addClass( 'oo-ui-tool-title' );
702 .addClass( 'oo-ui-tool-accel' )
704 // This may need to be changed if the key names are ever localized,
705 // but for now they are essentially written in English
710 .addClass( 'oo-ui-tool-link' )
711 .append( this.checkIcon
.$element
, this.$icon
, this.$title
, this.$accel
)
712 .attr( 'role', 'button' );
714 .data( 'oo-ui-tool', this )
715 .addClass( 'oo-ui-tool' )
716 .addClass( 'oo-ui-tool-name-' +
717 this.constructor.static.name
.replace( /^([^/]+)\/([^/]+).*$/, '$1-$2' ) )
718 .toggleClass( 'oo-ui-tool-with-label', this.constructor.static.displayBothIconAndLabel
)
719 .append( this.$link
);
720 this.setTitle( config
.title
|| this.constructor.static.title
);
725 OO
.inheritClass( OO
.ui
.Tool
, OO
.ui
.Widget
);
726 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.IconElement
);
727 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.FlaggedElement
);
728 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.TabIndexedElement
);
730 /* Static Properties */
736 OO
.ui
.Tool
.static.tagName
= 'span';
739 * Symbolic name of tool.
741 * The symbolic name is used internally to register the tool with a
742 * {@link OO.ui.ToolFactory ToolFactory}. It can also be used when adding tools to toolgroups.
749 OO
.ui
.Tool
.static.name
= '';
752 * Symbolic name of the group.
754 * The group name is used to associate tools with each other so that they can be selected later by
755 * a {@link OO.ui.ToolGroup toolgroup}.
762 OO
.ui
.Tool
.static.group
= '';
765 * Tool title text or a function that returns title text. The value of the static property is
766 * overridden if the #title config option is used.
771 * @property {string|Function}
773 OO
.ui
.Tool
.static.title
= '';
776 * Display both icon and label when the tool is used in a {@link OO.ui.BarToolGroup bar} toolgroup.
777 * Normally only the icon is displayed, or only the label if no icon is given.
781 * @property {boolean}
783 OO
.ui
.Tool
.static.displayBothIconAndLabel
= false;
786 * Add tool to catch-all groups automatically.
788 * A catch-all group, which contains all tools that do not currently belong to a toolgroup,
789 * can be included in a toolgroup using the wildcard selector, an asterisk (*).
793 * @property {boolean}
795 OO
.ui
.Tool
.static.autoAddToCatchall
= true;
798 * Add tool to named groups automatically.
800 * By default, tools that are configured with a static ‘group’ property are added
801 * to that group and will be selected when the symbolic name of the group is specified (e.g., when
802 * toolgroups include tools by group name).
805 * @property {boolean}
808 OO
.ui
.Tool
.static.autoAddToGroup
= true;
811 * Check if this tool is compatible with given data.
813 * This is a stub that can be overridden to provide support for filtering tools based on an
814 * arbitrary piece of information (e.g., where the cursor is in a document). The implementation
815 * must also call this method so that the compatibility check can be performed.
819 * @param {Mixed} data Data to check
820 * @return {boolean} Tool can be used with data
822 OO
.ui
.Tool
.static.isCompatibleWith = function () {
829 * Handle the toolbar state being updated. This method is called when the
830 * {@link OO.ui.Toolbar#event-updateState 'updateState' event} is emitted on the
831 * {@link OO.ui.Toolbar Toolbar} that uses this tool, and should set the state of this tool
832 * depending on application state (usually by calling #setDisabled to enable or disable the tool,
833 * or #setActive to mark is as currently in-use or not).
835 * This is an abstract method that must be overridden in a concrete subclass.
841 OO
.ui
.Tool
.prototype.onUpdateState
= null;
844 * Handle the tool being selected. This method is called when the user triggers this tool,
845 * usually by clicking on its label/icon.
847 * This is an abstract method that must be overridden in a concrete subclass.
853 OO
.ui
.Tool
.prototype.onSelect
= null;
856 * Check if the tool is active.
858 * Tools become active when their #onSelect or #onUpdateState handlers change them to appear pressed
859 * with the #setActive method. Additional CSS is applied to the tool to reflect the active state.
861 * @return {boolean} Tool is active
863 OO
.ui
.Tool
.prototype.isActive = function () {
868 * Make the tool appear active or inactive.
870 * This method should be called within #onSelect or #onUpdateState event handlers to make the tool
871 * appear pressed or not.
873 * @param {boolean} state Make tool appear active
875 OO
.ui
.Tool
.prototype.setActive = function ( state
) {
876 this.active
= !!state
;
877 this.$element
.toggleClass( 'oo-ui-tool-active', this.active
);
878 this.updateThemeClasses();
882 * Set the tool #title.
884 * @param {string|Function} title Title text or a function that returns text
886 * @return {OO.ui.Tool} The tool, for chaining
888 OO
.ui
.Tool
.prototype.setTitle = function ( title
) {
889 this.title
= OO
.ui
.resolveMsg( title
);
895 * Get the tool #title.
897 * @return {string} Title text
899 OO
.ui
.Tool
.prototype.getTitle = function () {
904 * Get the tool's symbolic name.
906 * @return {string} Symbolic name of tool
908 OO
.ui
.Tool
.prototype.getName = function () {
909 return this.constructor.static.name
;
915 OO
.ui
.Tool
.prototype.updateTitle = function () {
916 var titleTooltips
= this.toolGroup
.constructor.static.titleTooltips
,
917 accelTooltips
= this.toolGroup
.constructor.static.accelTooltips
,
918 accel
= this.toolbar
.getToolAccelerator( this.constructor.static.name
),
921 this.$title
.text( this.title
);
922 this.$accel
.text( accel
);
924 if ( titleTooltips
&& typeof this.title
=== 'string' && this.title
.length
) {
925 tooltipParts
.push( this.title
);
927 if ( accelTooltips
&& typeof accel
=== 'string' && accel
.length
) {
928 tooltipParts
.push( accel
);
930 if ( tooltipParts
.length
) {
931 this.$link
.attr( 'title', tooltipParts
.join( ' ' ) );
933 this.$link
.removeAttr( 'title' );
938 * @inheritdoc OO.ui.mixin.IconElement
940 OO
.ui
.Tool
.prototype.setIcon = function ( icon
) {
942 OO
.ui
.mixin
.IconElement
.prototype.setIcon
.call( this, icon
);
944 this.$element
.toggleClass( 'oo-ui-tool-with-icon', !!this.icon
);
952 * Destroying the tool removes all event handlers and the tool’s DOM elements.
953 * Call this method whenever you are done using a tool.
955 OO
.ui
.Tool
.prototype.destroy = function () {
956 this.toolbar
.disconnect( this );
957 this.$element
.remove();
961 * ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a
962 * {@link OO.ui.Toolbar toolbar}.
963 * The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or
964 * {@link OO.ui.MenuToolGroup menu}) to which a tool belongs determines how the tool is arranged
965 * and displayed in the toolbar. Toolgroups themselves are created on demand with a
966 * {@link OO.ui.ToolGroupFactory toolgroup factory}.
968 * Toolgroups can contain individual tools, groups of tools, or all available tools, as specified
969 * using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format.
970 * The options `exclude`, `promote`, and `demote` support the same formats.
972 * See {@link OO.ui.Toolbar toolbars} for a full example. For more information about toolbars in
973 * general, please see the [OOUI documentation on MediaWiki][1].
975 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
979 * @extends OO.ui.Widget
980 * @mixins OO.ui.mixin.GroupElement
983 * @param {OO.ui.Toolbar} toolbar
984 * @param {Object} [config] Configuration options
985 * @cfg {Array|string} [include] List of tools to include in the toolgroup, see above.
986 * @cfg {Array|string} [exclude] List of tools to exclude from the toolgroup, see above.
987 * @cfg {Array|string} [promote] List of tools to promote to the beginning of the toolgroup,
989 * @cfg {Array|string} [demote] List of tools to demote to the end of the toolgroup, see above.
990 * This setting is particularly useful when tools have been added to the toolgroup
991 * en masse (e.g., via the catch-all selector).
993 OO
.ui
.ToolGroup
= function OoUiToolGroup( toolbar
, config
) {
994 // Allow passing positional parameters inside the config object
995 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
997 toolbar
= config
.toolbar
;
1000 // Configuration initialization
1001 config
= config
|| {};
1003 // Parent constructor
1004 OO
.ui
.ToolGroup
.parent
.call( this, config
);
1006 // Mixin constructors
1007 OO
.ui
.mixin
.GroupElement
.call( this, config
);
1010 this.toolbar
= toolbar
;
1012 this.pressed
= null;
1013 this.autoDisabled
= false;
1014 this.include
= config
.include
|| [];
1015 this.exclude
= config
.exclude
|| [];
1016 this.promote
= config
.promote
|| [];
1017 this.demote
= config
.demote
|| [];
1018 this.onDocumentMouseKeyUpHandler
= this.onDocumentMouseKeyUp
.bind( this );
1022 mousedown
: this.onMouseKeyDown
.bind( this ),
1023 mouseup
: this.onMouseKeyUp
.bind( this ),
1024 keydown
: this.onMouseKeyDown
.bind( this ),
1025 keyup
: this.onMouseKeyUp
.bind( this ),
1026 focus
: this.onMouseOverFocus
.bind( this ),
1027 blur
: this.onMouseOutBlur
.bind( this ),
1028 mouseover
: this.onMouseOverFocus
.bind( this ),
1029 mouseout
: this.onMouseOutBlur
.bind( this )
1031 this.toolbar
.getToolFactory().connect( this, {
1032 register
: 'onToolFactoryRegister'
1035 disable
: 'itemDisable'
1037 this.connect( this, {
1038 itemDisable
: 'updateDisabled',
1039 disable
: 'onDisable'
1043 this.$group
.addClass( 'oo-ui-toolGroup-tools' );
1045 .addClass( 'oo-ui-toolGroup' )
1046 .append( this.$group
);
1047 this.onDisable( this.isDisabled() );
1053 OO
.inheritClass( OO
.ui
.ToolGroup
, OO
.ui
.Widget
);
1054 OO
.mixinClass( OO
.ui
.ToolGroup
, OO
.ui
.mixin
.GroupElement
);
1065 * An 'active' event is emitted when any popup is shown/hidden.
1067 * @param {boolean} The popup is visible
1070 /* Static Properties */
1073 * Show labels in tooltips.
1077 * @property {boolean}
1079 OO
.ui
.ToolGroup
.static.titleTooltips
= false;
1082 * Show acceleration labels in tooltips.
1084 * Note: The OOUI library does not include an accelerator system, but does contain
1085 * a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and
1086 * override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is
1087 * meant to return a label that describes the accelerator keys for a given tool (e.g., Control+M
1092 * @property {boolean}
1094 OO
.ui
.ToolGroup
.static.accelTooltips
= false;
1097 * Automatically disable the toolgroup when all tools are disabled
1101 * @property {boolean}
1103 OO
.ui
.ToolGroup
.static.autoDisable
= true;
1109 * @property {string}
1111 OO
.ui
.ToolGroup
.static.name
= null;
1118 OO
.ui
.ToolGroup
.prototype.isDisabled = function () {
1119 return this.autoDisabled
||
1120 OO
.ui
.ToolGroup
.parent
.prototype.isDisabled
.apply( this, arguments
);
1126 OO
.ui
.ToolGroup
.prototype.updateDisabled = function () {
1127 var i
, item
, allDisabled
= true;
1129 if ( this.constructor.static.autoDisable
) {
1130 for ( i
= this.items
.length
- 1; i
>= 0; i
-- ) {
1131 item
= this.items
[ i
];
1132 if ( !item
.isDisabled() ) {
1133 allDisabled
= false;
1137 this.autoDisabled
= allDisabled
;
1139 OO
.ui
.ToolGroup
.parent
.prototype.updateDisabled
.apply( this, arguments
);
1143 * Handle disable events.
1146 * @param {boolean} isDisabled
1148 OO
.ui
.ToolGroup
.prototype.onDisable = function ( isDisabled
) {
1149 this.$group
.toggleClass( 'oo-ui-toolGroup-disabled-tools', isDisabled
);
1150 this.$group
.toggleClass( 'oo-ui-toolGroup-enabled-tools', !isDisabled
);
1154 * Handle mouse down and key down events.
1157 * @param {jQuery.Event} e Mouse down or key down event
1158 * @return {undefined|boolean} False to prevent default if event is handled
1160 OO
.ui
.ToolGroup
.prototype.onMouseKeyDown = function ( e
) {
1162 !this.isDisabled() && (
1163 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
1164 e
.which
=== OO
.ui
.Keys
.SPACE
||
1165 e
.which
=== OO
.ui
.Keys
.ENTER
1168 this.pressed
= this.findTargetTool( e
);
1169 if ( this.pressed
) {
1170 this.pressed
.setActive( true );
1171 this.getElementDocument().addEventListener(
1173 this.onDocumentMouseKeyUpHandler
,
1176 this.getElementDocument().addEventListener(
1178 this.onDocumentMouseKeyUpHandler
,
1187 * Handle document mouse up and key up events.
1190 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1192 OO
.ui
.ToolGroup
.prototype.onDocumentMouseKeyUp = function ( e
) {
1193 this.getElementDocument().removeEventListener(
1195 this.onDocumentMouseKeyUpHandler
,
1198 this.getElementDocument().removeEventListener(
1200 this.onDocumentMouseKeyUpHandler
,
1203 // onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
1204 // released, but since `this.pressed` will no longer be true, the second call will be ignored.
1205 this.onMouseKeyUp( e
);
1209 * Handle mouse up and key up events.
1212 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1214 OO
.ui
.ToolGroup
.prototype.onMouseKeyUp = function ( e
) {
1215 var tool
= this.findTargetTool( e
);
1218 !this.isDisabled() && this.pressed
&& this.pressed
=== tool
&& (
1219 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
1220 e
.which
=== OO
.ui
.Keys
.SPACE
||
1221 e
.which
=== OO
.ui
.Keys
.ENTER
1224 this.pressed
.onSelect();
1225 this.pressed
= null;
1227 e
.stopPropagation();
1230 this.pressed
= null;
1234 * Handle mouse over and focus events.
1237 * @param {jQuery.Event} e Mouse over or focus event
1239 OO
.ui
.ToolGroup
.prototype.onMouseOverFocus = function ( e
) {
1240 var tool
= this.findTargetTool( e
);
1242 if ( this.pressed
&& this.pressed
=== tool
) {
1243 this.pressed
.setActive( true );
1248 * Handle mouse out and blur events.
1251 * @param {jQuery.Event} e Mouse out or blur event
1253 OO
.ui
.ToolGroup
.prototype.onMouseOutBlur = function ( e
) {
1254 var tool
= this.findTargetTool( e
);
1256 if ( this.pressed
&& this.pressed
=== tool
) {
1257 this.pressed
.setActive( false );
1262 * Get the closest tool to a jQuery.Event.
1264 * Only tool links are considered, which prevents other elements in the tool such as popups from
1265 * triggering tool group interactions.
1268 * @param {jQuery.Event} e
1269 * @return {OO.ui.Tool|null} Tool, `null` if none was found
1271 OO
.ui
.ToolGroup
.prototype.findTargetTool = function ( e
) {
1273 $item
= $( e
.target
).closest( '.oo-ui-tool-link' );
1275 if ( $item
.length
) {
1276 tool
= $item
.parent().data( 'oo-ui-tool' );
1279 return tool
&& !tool
.isDisabled() ? tool
: null;
1283 * Handle tool registry register events.
1285 * If a tool is registered after the group is created, we must repopulate the list to account for:
1287 * - a tool being added that may be included
1288 * - a tool already included being overridden
1291 * @param {string} name Symbolic name of tool
1293 OO
.ui
.ToolGroup
.prototype.onToolFactoryRegister = function () {
1298 * Get the toolbar that contains the toolgroup.
1300 * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
1302 OO
.ui
.ToolGroup
.prototype.getToolbar = function () {
1303 return this.toolbar
;
1307 * Add and remove tools based on configuration.
1309 OO
.ui
.ToolGroup
.prototype.populate = function () {
1310 var i
, len
, name
, tool
,
1311 toolFactory
= this.toolbar
.getToolFactory(),
1315 list
= this.toolbar
.getToolFactory().getTools(
1316 this.include
, this.exclude
, this.promote
, this.demote
1319 // Build a list of needed tools
1320 for ( i
= 0, len
= list
.length
; i
< len
; i
++ ) {
1324 toolFactory
.lookup( name
) &&
1325 // Tool is available or is already in this group
1326 ( this.toolbar
.isToolAvailable( name
) || this.tools
[ name
] )
1328 // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool
1329 // before creating it, but we can't call reserveTool() yet because we haven't created
1331 this.toolbar
.tools
[ name
] = true;
1332 tool
= this.tools
[ name
];
1334 // Auto-initialize tools on first use
1335 this.tools
[ name
] = tool
= toolFactory
.create( name
, this );
1338 this.toolbar
.reserveTool( tool
);
1340 names
[ name
] = true;
1343 // Remove tools that are no longer needed
1344 for ( name
in this.tools
) {
1345 if ( !names
[ name
] ) {
1346 this.tools
[ name
].destroy();
1347 this.toolbar
.releaseTool( this.tools
[ name
] );
1348 remove
.push( this.tools
[ name
] );
1349 delete this.tools
[ name
];
1352 if ( remove
.length
) {
1353 this.removeItems( remove
);
1355 // Update emptiness state
1357 this.$element
.removeClass( 'oo-ui-toolGroup-empty' );
1359 this.$element
.addClass( 'oo-ui-toolGroup-empty' );
1361 // Re-add tools (moving existing ones to new locations)
1362 this.addItems( add
);
1363 // Disabled state may depend on items
1364 this.updateDisabled();
1368 * Destroy toolgroup.
1370 OO
.ui
.ToolGroup
.prototype.destroy = function () {
1374 this.toolbar
.getToolFactory().disconnect( this );
1375 for ( name
in this.tools
) {
1376 this.toolbar
.releaseTool( this.tools
[ name
] );
1377 this.tools
[ name
].disconnect( this ).destroy();
1378 delete this.tools
[ name
];
1380 this.$element
.remove();
1384 * A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools},
1385 * {@link OO.ui.PopupTool PopupTools}, and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be
1386 * registered with a tool factory. Tools are registered by their symbolic name. See
1387 * {@link OO.ui.Toolbar toolbars} for an example.
1389 * For more information about toolbars in general, please see the
1390 * [OOUI documentation on MediaWiki][1].
1392 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1395 * @extends OO.Factory
1398 OO
.ui
.ToolFactory
= function OoUiToolFactory() {
1399 // Parent constructor
1400 OO
.ui
.ToolFactory
.parent
.call( this );
1405 OO
.inheritClass( OO
.ui
.ToolFactory
, OO
.Factory
);
1410 * Get tools from the factory.
1412 * @param {Array|string} [include] Included tools, see #extract for format
1413 * @param {Array|string} [exclude] Excluded tools, see #extract for format
1414 * @param {Array|string} [promote] Promoted tools, see #extract for format
1415 * @param {Array|string} [demote] Demoted tools, see #extract for format
1416 * @return {string[]} List of tools
1418 OO
.ui
.ToolFactory
.prototype.getTools = function ( include
, exclude
, promote
, demote
) {
1419 var i
, len
, included
, promoted
, demoted
,
1423 // Collect included and not excluded tools
1424 included
= OO
.simpleArrayDifference( this.extract( include
), this.extract( exclude
) );
1427 promoted
= this.extract( promote
, used
);
1428 demoted
= this.extract( demote
, used
);
1431 for ( i
= 0, len
= included
.length
; i
< len
; i
++ ) {
1432 if ( !used
[ included
[ i
] ] ) {
1433 auto
.push( included
[ i
] );
1437 return promoted
.concat( auto
).concat( demoted
);
1441 * Get a flat list of names from a list of names or groups.
1443 * Normally, `collection` is an array of tool specifications. Tools can be specified in the
1446 * - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
1447 * - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
1448 * tool to a group, use OO.ui.Tool.static.group.)
1450 * Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
1451 * catch-all selector `'*'`.
1453 * If `used` is passed, tool names that appear as properties in this object will be considered
1454 * already assigned, and will not be returned even if specified otherwise. The tool names extracted
1455 * by this function call will be added as new properties in the object.
1458 * @param {Array|string} collection List of tools, see above
1459 * @param {Object} [used] Object containing information about used tools, see above
1460 * @return {string[]} List of extracted tool names
1462 OO
.ui
.ToolFactory
.prototype.extract = function ( collection
, used
) {
1463 var i
, len
, item
, name
, tool
,
1466 collection
= !Array
.isArray( collection
) ? [ collection
] : collection
;
1468 for ( i
= 0, len
= collection
.length
; i
< len
; i
++ ) {
1469 item
= collection
[ i
];
1470 if ( item
=== '*' ) {
1471 for ( name
in this.registry
) {
1472 tool
= this.registry
[ name
];
1474 // Only add tools by group name when auto-add is enabled
1475 tool
.static.autoAddToCatchall
&&
1476 // Exclude already used tools
1477 ( !used
|| !used
[ name
] )
1481 used
[ name
] = true;
1486 // Allow plain strings as shorthand for named tools
1487 if ( typeof item
=== 'string' ) {
1488 item
= { name
: item
};
1490 if ( OO
.isPlainObject( item
) ) {
1492 for ( name
in this.registry
) {
1493 tool
= this.registry
[ name
];
1495 // Include tools with matching group
1496 tool
.static.group
=== item
.group
&&
1497 // Only add tools by group name when auto-add is enabled
1498 tool
.static.autoAddToGroup
&&
1499 // Exclude already used tools
1500 ( !used
|| !used
[ name
] )
1504 used
[ name
] = true;
1508 // Include tools with matching name and exclude already used tools
1509 } else if ( item
.name
&& ( !used
|| !used
[ item
.name
] ) ) {
1510 names
.push( item
.name
);
1512 used
[ item
.name
] = true;
1522 * ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes
1523 * must specify a symbolic name and be registered with the factory. The following classes are
1524 * registered by default:
1526 * - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
1527 * - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
1528 * - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
1530 * See {@link OO.ui.Toolbar toolbars} for an example.
1532 * For more information about toolbars in general, please see the
1533 * [OOUI documentation on MediaWiki][1].
1535 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1538 * @extends OO.Factory
1541 OO
.ui
.ToolGroupFactory
= function OoUiToolGroupFactory() {
1542 var i
, l
, defaultClasses
;
1543 // Parent constructor
1544 OO
.Factory
.call( this );
1546 defaultClasses
= this.constructor.static.getDefaultClasses();
1548 // Register default toolgroups
1549 for ( i
= 0, l
= defaultClasses
.length
; i
< l
; i
++ ) {
1550 this.register( defaultClasses
[ i
] );
1556 OO
.inheritClass( OO
.ui
.ToolGroupFactory
, OO
.Factory
);
1558 /* Static Methods */
1561 * Get a default set of classes to be registered on construction.
1563 * @return {Function[]} Default classes
1565 OO
.ui
.ToolGroupFactory
.static.getDefaultClasses = function () {
1568 OO
.ui
.ListToolGroup
,
1574 * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}.
1575 * Each popup tool is configured with a static name, title, and icon, as well with as any popup
1576 * configurations. Unlike other tools, popup tools do not require that developers specify an
1577 * #onSelect or #onUpdateState method, as these methods have been implemented already.
1579 * // Example of a popup tool. When selected, a popup tool displays
1580 * // a popup window.
1581 * function HelpTool( toolGroup, config ) {
1582 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1587 * this.popup.$body.append( '<p>I am helpful!</p>' );
1589 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1590 * HelpTool.static.name = 'help';
1591 * HelpTool.static.icon = 'help';
1592 * HelpTool.static.title = 'Help';
1593 * toolFactory.register( HelpTool );
1595 * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}.
1596 * For more information about toolbars in general, please see the
1597 * [OOUI documentation on MediaWiki][1].
1599 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1603 * @extends OO.ui.Tool
1604 * @mixins OO.ui.mixin.PopupElement
1607 * @param {OO.ui.ToolGroup} toolGroup
1608 * @param {Object} [config] Configuration options
1610 OO
.ui
.PopupTool
= function OoUiPopupTool( toolGroup
, config
) {
1611 // Allow passing positional parameters inside the config object
1612 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
1614 toolGroup
= config
.toolGroup
;
1617 // Parent constructor
1618 OO
.ui
.PopupTool
.parent
.call( this, toolGroup
, config
);
1620 // Mixin constructors
1621 OO
.ui
.mixin
.PopupElement
.call( this, config
);
1624 this.popup
.connect( this, {
1625 toggle
: 'onPopupToggle'
1629 this.popup
.setAutoFlip( false );
1630 this.popup
.setPosition( toolGroup
.getToolbar().position
=== 'bottom' ? 'above' : 'below' );
1631 this.$element
.addClass( 'oo-ui-popupTool' );
1632 this.popup
.$element
.addClass( 'oo-ui-popupTool-popup' );
1633 this.toolbar
.$popups
.append( this.popup
.$element
);
1638 OO
.inheritClass( OO
.ui
.PopupTool
, OO
.ui
.Tool
);
1639 OO
.mixinClass( OO
.ui
.PopupTool
, OO
.ui
.mixin
.PopupElement
);
1644 * Handle the tool being selected.
1648 OO
.ui
.PopupTool
.prototype.onSelect = function () {
1649 if ( !this.isDisabled() ) {
1650 this.popup
.toggle();
1656 * Handle the toolbar state being updated.
1660 OO
.ui
.PopupTool
.prototype.onUpdateState = function () {
1664 * Handle popup visibility being toggled.
1666 * @param {boolean} isVisible
1668 OO
.ui
.PopupTool
.prototype.onPopupToggle = function ( isVisible
) {
1669 this.setActive( isVisible
);
1670 this.toolGroup
.emit( 'active', isVisible
);
1674 * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools}
1675 * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used
1676 * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from
1677 * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list}
1678 * when the ToolGroupTool is selected.
1680 * // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2',
1681 * // defined elsewhere.
1683 * function SettingsTool() {
1684 * SettingsTool.parent.apply( this, arguments );
1686 * OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
1687 * SettingsTool.static.name = 'settings';
1688 * SettingsTool.static.title = 'Change settings';
1689 * SettingsTool.static.groupConfig = {
1691 * label: 'ToolGroupTool',
1692 * include: [ 'setting1', 'setting2' ]
1694 * toolFactory.register( SettingsTool );
1696 * For more information, please see the [OOUI documentation on MediaWiki][1].
1698 * Please note that this implementation is subject to change per [T74159] [2].
1700 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars#ToolGroupTool
1701 * [2]: https://phabricator.wikimedia.org/T74159
1705 * @extends OO.ui.Tool
1708 * @param {OO.ui.ToolGroup} toolGroup
1709 * @param {Object} [config] Configuration options
1711 OO
.ui
.ToolGroupTool
= function OoUiToolGroupTool( toolGroup
, config
) {
1712 // Allow passing positional parameters inside the config object
1713 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
1715 toolGroup
= config
.toolGroup
;
1718 // Parent constructor
1719 OO
.ui
.ToolGroupTool
.parent
.call( this, toolGroup
, config
);
1722 this.innerToolGroup
= this.createGroup( this.constructor.static.groupConfig
);
1725 this.innerToolGroup
.connect( this, {
1726 disable
: 'onToolGroupDisable',
1727 // Re-emit active events from the innerToolGroup on the parent toolGroup
1728 active
: this.toolGroup
.emit
.bind( this.toolGroup
, 'active' )
1732 this.$link
.remove();
1734 .addClass( 'oo-ui-toolGroupTool' )
1735 .append( this.innerToolGroup
.$element
);
1740 OO
.inheritClass( OO
.ui
.ToolGroupTool
, OO
.ui
.Tool
);
1742 /* Static Properties */
1745 * Toolgroup configuration.
1747 * The toolgroup configuration consists of the tools to include, as well as an icon and label
1748 * to use for the bar item. Tools can be included by symbolic name, group, or with the
1749 * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information.
1751 * @property {Object.<string,Array>}
1753 OO
.ui
.ToolGroupTool
.static.groupConfig
= {};
1758 * Handle the tool being selected.
1762 OO
.ui
.ToolGroupTool
.prototype.onSelect = function () {
1763 this.innerToolGroup
.setActive( !this.innerToolGroup
.active
);
1768 * Synchronize disabledness state of the tool with the inner toolgroup.
1771 * @param {boolean} disabled Element is disabled
1773 OO
.ui
.ToolGroupTool
.prototype.onToolGroupDisable = function ( disabled
) {
1774 this.setDisabled( disabled
);
1778 * Handle the toolbar state being updated.
1782 OO
.ui
.ToolGroupTool
.prototype.onUpdateState = function () {
1783 this.setActive( false );
1787 * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
1789 * @param {Object.<string,Array>} group Toolgroup configuration. Please see
1790 * {@link OO.ui.ToolGroup toolgroup} for more information.
1791 * @return {OO.ui.ListToolGroup}
1793 OO
.ui
.ToolGroupTool
.prototype.createGroup = function ( group
) {
1794 if ( group
.include
=== '*' ) {
1795 // Apply defaults to catch-all groups
1796 if ( group
.label
=== undefined ) {
1797 group
.label
= OO
.ui
.msg( 'ooui-toolbar-more' );
1801 return this.toolbar
.getToolGroupFactory().create( 'list', this.toolbar
, group
);
1805 * BarToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
1806 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are
1807 * {@link OO.ui.MenuToolGroup MenuToolGroup} and {@link OO.ui.ListToolGroup ListToolGroup}).
1808 * The {@link OO.ui.Tool tools} in a BarToolGroup are displayed by icon in a single row. The
1809 * title of the tool is displayed when users move the mouse over the tool.
1811 * BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
1815 * // Example of a BarToolGroup with two tools
1816 * var toolFactory = new OO.ui.ToolFactory();
1817 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
1818 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
1820 * // We will be placing status text in this element when tools are used
1821 * var $area = $( '<p>' ).text( 'Example of a BarToolGroup with two tools.' );
1823 * // Define the tools that we're going to place in our toolbar
1825 * // Create a class inheriting from OO.ui.Tool
1826 * function SearchTool() {
1827 * SearchTool.parent.apply( this, arguments );
1829 * OO.inheritClass( SearchTool, OO.ui.Tool );
1830 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
1831 * // of 'icon' and 'title' (displayed icon and text).
1832 * SearchTool.static.name = 'search';
1833 * SearchTool.static.icon = 'search';
1834 * SearchTool.static.title = 'Search...';
1835 * // Defines the action that will happen when this tool is selected (clicked).
1836 * SearchTool.prototype.onSelect = function () {
1837 * $area.text( 'Search tool clicked!' );
1838 * // Never display this tool as "active" (selected).
1839 * this.setActive( false );
1841 * SearchTool.prototype.onUpdateState = function () {};
1842 * // Make this tool available in our toolFactory and thus our toolbar
1843 * toolFactory.register( SearchTool );
1845 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
1846 * // little popup window (a PopupWidget).
1847 * function HelpTool( toolGroup, config ) {
1848 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1853 * this.popup.$body.append( '<p>I am helpful!</p>' );
1855 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1856 * HelpTool.static.name = 'help';
1857 * HelpTool.static.icon = 'help';
1858 * HelpTool.static.title = 'Help';
1859 * toolFactory.register( HelpTool );
1861 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
1862 * // used once (but not all defined tools must be used).
1865 * // 'bar' tool groups display tools by icon only
1867 * include: [ 'search', 'help' ]
1871 * // Create some UI around the toolbar and place it in the document
1872 * var frame = new OO.ui.PanelLayout( {
1876 * var contentFrame = new OO.ui.PanelLayout( {
1880 * frame.$element.append(
1882 * contentFrame.$element.append( $area )
1884 * $( document.body ).append( frame.$element );
1886 * // Here is where the toolbar is actually built. This must be done after inserting it into the
1888 * toolbar.initialize();
1890 * For more information about how to add tools to a bar tool group, please see
1891 * {@link OO.ui.ToolGroup toolgroup}.
1892 * For more information about toolbars in general, please see the
1893 * [OOUI documentation on MediaWiki][1].
1895 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1898 * @extends OO.ui.ToolGroup
1901 * @param {OO.ui.Toolbar} toolbar
1902 * @param {Object} [config] Configuration options
1904 OO
.ui
.BarToolGroup
= function OoUiBarToolGroup( toolbar
, config
) {
1905 // Allow passing positional parameters inside the config object
1906 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
1908 toolbar
= config
.toolbar
;
1911 // Parent constructor
1912 OO
.ui
.BarToolGroup
.parent
.call( this, toolbar
, config
);
1915 this.$element
.addClass( 'oo-ui-barToolGroup' );
1916 this.$group
.addClass( 'oo-ui-barToolGroup-tools' );
1921 OO
.inheritClass( OO
.ui
.BarToolGroup
, OO
.ui
.ToolGroup
);
1923 /* Static Properties */
1929 OO
.ui
.BarToolGroup
.static.titleTooltips
= true;
1935 OO
.ui
.BarToolGroup
.static.accelTooltips
= true;
1941 OO
.ui
.BarToolGroup
.static.name
= 'bar';
1944 * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup}
1945 * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup (an overlaid menu or list of
1946 * tools with an optional icon and label). This class can be used for other base classes that
1947 * also use this functionality.
1951 * @extends OO.ui.ToolGroup
1952 * @mixins OO.ui.mixin.IconElement
1953 * @mixins OO.ui.mixin.IndicatorElement
1954 * @mixins OO.ui.mixin.LabelElement
1955 * @mixins OO.ui.mixin.TitledElement
1956 * @mixins OO.ui.mixin.FlaggedElement
1957 * @mixins OO.ui.mixin.ClippableElement
1958 * @mixins OO.ui.mixin.FloatableElement
1959 * @mixins OO.ui.mixin.TabIndexedElement
1962 * @param {OO.ui.Toolbar} toolbar
1963 * @param {Object} [config] Configuration options
1964 * @cfg {string} [header] Text to display at the top of the popup
1966 OO
.ui
.PopupToolGroup
= function OoUiPopupToolGroup( toolbar
, config
) {
1967 // Allow passing positional parameters inside the config object
1968 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
1970 toolbar
= config
.toolbar
;
1973 // Configuration initialization
1974 config
= $.extend( {
1975 indicator
: config
.indicator
=== undefined ?
1976 ( toolbar
.position
=== 'bottom' ? 'up' : 'down' ) : config
.indicator
1979 // Parent constructor
1980 OO
.ui
.PopupToolGroup
.parent
.call( this, toolbar
, config
);
1983 this.active
= false;
1984 this.dragging
= false;
1985 // Don't conflict with parent method of the same name
1986 this.onPopupDocumentMouseKeyUpHandler
= this.onPopupDocumentMouseKeyUp
.bind( this );
1987 this.$handle
= $( '<span>' );
1989 // Mixin constructors
1990 OO
.ui
.mixin
.IconElement
.call( this, config
);
1991 OO
.ui
.mixin
.IndicatorElement
.call( this, config
);
1992 OO
.ui
.mixin
.LabelElement
.call( this, config
);
1993 OO
.ui
.mixin
.TitledElement
.call( this, config
);
1994 OO
.ui
.mixin
.FlaggedElement
.call( this, config
);
1995 OO
.ui
.mixin
.ClippableElement
.call( this, $.extend( {
1996 $clippable
: this.$group
1998 OO
.ui
.mixin
.FloatableElement
.call( this, $.extend( {
1999 $floatable
: this.$group
,
2000 $floatableContainer
: this.$handle
,
2001 hideWhenOutOfView
: false,
2002 verticalPosition
: this.toolbar
.position
=== 'bottom' ? 'above' : 'below'
2004 OO
.ui
.mixin
.TabIndexedElement
.call( this, $.extend( {
2005 $tabIndexed
: this.$handle
2010 keydown
: this.onHandleMouseKeyDown
.bind( this ),
2011 keyup
: this.onHandleMouseKeyUp
.bind( this ),
2012 mousedown
: this.onHandleMouseKeyDown
.bind( this ),
2013 mouseup
: this.onHandleMouseKeyUp
.bind( this )
2018 .addClass( 'oo-ui-popupToolGroup-handle' )
2019 .attr( 'role', 'button' )
2020 .append( this.$icon
, this.$label
, this.$indicator
);
2021 // If the pop-up should have a header, add it to the top of the toolGroup.
2022 // Note: If this feature is useful for other widgets, we could abstract it into an
2023 // OO.ui.HeaderedElement mixin constructor.
2024 if ( config
.header
!== undefined ) {
2026 .prepend( $( '<span>' )
2027 .addClass( 'oo-ui-popupToolGroup-header' )
2028 .text( config
.header
)
2032 .addClass( 'oo-ui-popupToolGroup' )
2033 .prepend( this.$handle
);
2034 this.$group
.addClass( 'oo-ui-popupToolGroup-tools' );
2035 this.toolbar
.$popups
.append( this.$group
);
2040 OO
.inheritClass( OO
.ui
.PopupToolGroup
, OO
.ui
.ToolGroup
);
2041 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.IconElement
);
2042 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.IndicatorElement
);
2043 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.LabelElement
);
2044 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.TitledElement
);
2045 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.FlaggedElement
);
2046 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.ClippableElement
);
2047 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.FloatableElement
);
2048 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.TabIndexedElement
);
2055 OO
.ui
.PopupToolGroup
.prototype.setDisabled = function () {
2057 OO
.ui
.PopupToolGroup
.parent
.prototype.setDisabled
.apply( this, arguments
);
2059 if ( this.isDisabled() && this.isElementAttached() ) {
2060 this.setActive( false );
2065 * Handle document mouse up and key up events.
2068 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
2070 OO
.ui
.PopupToolGroup
.prototype.onPopupDocumentMouseKeyUp = function ( e
) {
2071 var $target
= $( e
.target
);
2072 // Only deactivate when clicking outside the dropdown element
2073 if ( $target
.closest( '.oo-ui-popupToolGroup' )[ 0 ] === this.$element
[ 0 ] ) {
2076 if ( $target
.closest( '.oo-ui-popupToolGroup-tools' )[ 0 ] === this.$group
[ 0 ] ) {
2079 this.setActive( false );
2085 OO
.ui
.PopupToolGroup
.prototype.onMouseKeyUp = function ( e
) {
2086 // Only close toolgroup when a tool was actually selected
2088 !this.isDisabled() && this.pressed
&& this.pressed
=== this.findTargetTool( e
) && (
2089 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
2090 e
.which
=== OO
.ui
.Keys
.SPACE
||
2091 e
.which
=== OO
.ui
.Keys
.ENTER
2094 this.setActive( false );
2096 return OO
.ui
.PopupToolGroup
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2102 OO
.ui
.PopupToolGroup
.prototype.onMouseKeyDown = function ( e
) {
2103 var $focused
, $firstFocusable
, $lastFocusable
;
2104 // Shift-Tab on the first tool in the group jumps to the handle.
2105 // Tab on the last tool in the group jumps to the next group.
2106 if ( !this.isDisabled() && e
.which
=== OO
.ui
.Keys
.TAB
) {
2107 // We can't use this.items because ListToolGroup inserts the extra fake
2108 // expand/collapse tool.
2109 $focused
= $( document
.activeElement
);
2110 $firstFocusable
= OO
.ui
.findFocusable( this.$group
);
2111 if ( $focused
[ 0 ] === $firstFocusable
[ 0 ] && e
.shiftKey
) {
2112 this.$handle
.trigger( 'focus' );
2115 $lastFocusable
= OO
.ui
.findFocusable( this.$group
, true );
2116 if ( $focused
[ 0 ] === $lastFocusable
[ 0 ] && !e
.shiftKey
) {
2117 // Focus this group's handle and let the browser's tab handling happen
2118 // (no 'return false').
2119 // This way we don't have to fiddle with other ToolGroups' business, or worry what to do
2120 // if the next group is not a PopupToolGroup or doesn't exist at all.
2121 this.$handle
.trigger( 'focus' );
2122 // Close the popup so that we don't move back inside it (if this is the last group).
2123 this.setActive( false );
2126 return OO
.ui
.PopupToolGroup
.parent
.prototype.onMouseKeyDown
.call( this, e
);
2130 * Handle mouse up and key up events.
2133 * @param {jQuery.Event} e Mouse up or key up event
2134 * @return {undefined|boolean} False to prevent default if event is handled
2136 OO
.ui
.PopupToolGroup
.prototype.onHandleMouseKeyUp = function ( e
) {
2138 !this.isDisabled() && (
2139 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
2140 e
.which
=== OO
.ui
.Keys
.SPACE
||
2141 e
.which
=== OO
.ui
.Keys
.ENTER
2149 * Handle mouse down and key down events.
2152 * @param {jQuery.Event} e Mouse down or key down event
2153 * @return {undefined|boolean} False to prevent default if event is handled
2155 OO
.ui
.PopupToolGroup
.prototype.onHandleMouseKeyDown = function ( e
) {
2157 if ( !this.isDisabled() ) {
2158 // Tab on the handle jumps to the first tool in the group (if the popup is open).
2159 if ( e
.which
=== OO
.ui
.Keys
.TAB
&& !e
.shiftKey
) {
2160 $focusable
= OO
.ui
.findFocusable( this.$group
);
2161 if ( $focusable
.length
) {
2162 $focusable
.trigger( 'focus' );
2167 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
2168 e
.which
=== OO
.ui
.Keys
.SPACE
||
2169 e
.which
=== OO
.ui
.Keys
.ENTER
2171 this.setActive( !this.active
);
2178 * Check if the tool group is active.
2180 * @return {boolean} Tool group is active
2182 OO
.ui
.PopupToolGroup
.prototype.isActive = function () {
2187 * Switch into 'active' mode.
2189 * When active, the popup is visible. A mouseup event anywhere in the document will trigger
2192 * @param {boolean} value The active state to set
2195 OO
.ui
.PopupToolGroup
.prototype.setActive = function ( value
) {
2196 var containerWidth
, containerLeft
;
2198 if ( this.active
!== value
) {
2199 this.active
= value
;
2201 this.getElementDocument().addEventListener(
2203 this.onPopupDocumentMouseKeyUpHandler
,
2206 this.getElementDocument().addEventListener(
2208 this.onPopupDocumentMouseKeyUpHandler
,
2212 this.$clippable
.css( 'left', '' );
2213 this.$element
.addClass( 'oo-ui-popupToolGroup-active' );
2214 this.$group
.addClass( 'oo-ui-popupToolGroup-active-tools' );
2215 this.togglePositioning( true );
2216 this.toggleClipping( true );
2218 // Try anchoring the popup to the left first
2219 this.setHorizontalPosition( 'start' );
2221 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2222 // Anchoring to the left caused the popup to clip, so anchor it to the
2224 this.setHorizontalPosition( 'end' );
2226 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2227 // Anchoring to the right also caused the popup to clip, so just make it fill the
2229 containerWidth
= this.$clippableScrollableContainer
.width();
2230 containerLeft
= this.$clippableScrollableContainer
[ 0 ] ===
2231 document
.documentElement
?
2233 this.$clippableScrollableContainer
.offset().left
;
2235 this.toggleClipping( false );
2236 this.setHorizontalPosition( 'start' );
2238 this.$clippable
.css( {
2239 'margin-left': -( this.$element
.offset().left
- containerLeft
),
2240 width
: containerWidth
2244 this.getElementDocument().removeEventListener(
2246 this.onPopupDocumentMouseKeyUpHandler
,
2249 this.getElementDocument().removeEventListener(
2251 this.onPopupDocumentMouseKeyUpHandler
,
2254 this.$element
.removeClass( 'oo-ui-popupToolGroup-active' );
2255 this.$group
.removeClass( 'oo-ui-popupToolGroup-active-tools' );
2256 this.togglePositioning( false );
2257 this.toggleClipping( false );
2259 this.emit( 'active', this.active
);
2260 this.updateThemeClasses();
2265 * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2266 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are
2267 * {@link OO.ui.MenuToolGroup MenuToolGroup} and {@link OO.ui.BarToolGroup BarToolGroup}).
2268 * The {@link OO.ui.Tool tools} in a ListToolGroup are displayed by label in a dropdown menu.
2269 * The title of the tool is used as the label text. The menu itself can be configured with a label,
2270 * icon, indicator, header, and title.
2272 * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a
2273 * ‘More’ option that users can select to see the full list of tools. If a collapsed toolgroup is
2274 * expanded, a ‘Fewer’ option permits users to collapse the list again.
2276 * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the
2277 * toolbar is set up. The factory requires the ListToolGroup's symbolic name, 'list', which is
2278 * specified along with the other configurations. For more information about how to add tools to a
2279 * ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
2282 * // Example of a ListToolGroup
2283 * var toolFactory = new OO.ui.ToolFactory();
2284 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2285 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2287 * // Configure and register two tools
2288 * function SettingsTool() {
2289 * SettingsTool.parent.apply( this, arguments );
2291 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2292 * SettingsTool.static.name = 'settings';
2293 * SettingsTool.static.icon = 'settings';
2294 * SettingsTool.static.title = 'Change settings';
2295 * SettingsTool.prototype.onSelect = function () {
2296 * this.setActive( false );
2298 * SettingsTool.prototype.onUpdateState = function () {};
2299 * toolFactory.register( SettingsTool );
2300 * // Register two more tools, nothing interesting here
2301 * function StuffTool() {
2302 * StuffTool.parent.apply( this, arguments );
2304 * OO.inheritClass( StuffTool, OO.ui.Tool );
2305 * StuffTool.static.name = 'stuff';
2306 * StuffTool.static.icon = 'search';
2307 * StuffTool.static.title = 'Change the world';
2308 * StuffTool.prototype.onSelect = function () {
2309 * this.setActive( false );
2311 * StuffTool.prototype.onUpdateState = function () {};
2312 * toolFactory.register( StuffTool );
2315 * // Configurations for list toolgroup.
2317 * label: 'ListToolGroup',
2319 * title: 'This is the title, displayed when user moves the mouse over the list ' +
2321 * header: 'This is the header',
2322 * include: [ 'settings', 'stuff' ],
2323 * allowCollapse: ['stuff']
2327 * // Create some UI around the toolbar and place it in the document
2328 * var frame = new OO.ui.PanelLayout( {
2332 * frame.$element.append(
2335 * $( document.body ).append( frame.$element );
2336 * // Build the toolbar. This must be done after the toolbar has been appended to the document.
2337 * toolbar.initialize();
2339 * For more information about toolbars in general, please see the
2340 * [OOUI documentation on MediaWiki][1].
2342 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2345 * @extends OO.ui.PopupToolGroup
2348 * @param {OO.ui.Toolbar} toolbar
2349 * @param {Object} [config] Configuration options
2350 * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible
2351 * tools will only be displayed if users click the ‘More’ option displayed at the bottom of the
2352 * list. If the list is expanded, a ‘Fewer’ option permits users to collapse the list again.
2353 * Any tools that are included in the toolgroup, but are not designated as collapsible, will always
2355 * To open a collapsible list in its expanded state, set #expanded to 'true'.
2356 * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as
2357 * collapsible. Unless #expanded is set to true, the collapsible tools will be collapsed when the
2358 * list is first opened.
2359 * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools
2360 * have been designated as collapsible. When expanded is set to true, all tools in the group will
2361 * be displayed when the list is first opened. Users can collapse the list with a ‘Fewer’ option at
2364 OO
.ui
.ListToolGroup
= function OoUiListToolGroup( toolbar
, config
) {
2365 // Allow passing positional parameters inside the config object
2366 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
2368 toolbar
= config
.toolbar
;
2371 // Configuration initialization
2372 config
= config
|| {};
2374 // Properties (must be set before parent constructor, which calls #populate)
2375 this.allowCollapse
= config
.allowCollapse
;
2376 this.forceExpand
= config
.forceExpand
;
2377 this.expanded
= config
.expanded
!== undefined ? config
.expanded
: false;
2378 this.collapsibleTools
= [];
2380 // Parent constructor
2381 OO
.ui
.ListToolGroup
.parent
.call( this, toolbar
, config
);
2384 this.$element
.addClass( 'oo-ui-listToolGroup' );
2385 this.$group
.addClass( 'oo-ui-listToolGroup-tools' );
2390 OO
.inheritClass( OO
.ui
.ListToolGroup
, OO
.ui
.PopupToolGroup
);
2392 /* Static Properties */
2398 OO
.ui
.ListToolGroup
.static.name
= 'list';
2405 OO
.ui
.ListToolGroup
.prototype.populate = function () {
2406 var i
, len
, allowCollapse
= [];
2408 OO
.ui
.ListToolGroup
.parent
.prototype.populate
.call( this );
2410 // Update the list of collapsible tools
2411 if ( this.allowCollapse
!== undefined ) {
2412 allowCollapse
= this.allowCollapse
;
2413 } else if ( this.forceExpand
!== undefined ) {
2414 allowCollapse
= OO
.simpleArrayDifference( Object
.keys( this.tools
), this.forceExpand
);
2417 this.collapsibleTools
= [];
2418 for ( i
= 0, len
= allowCollapse
.length
; i
< len
; i
++ ) {
2419 if ( this.tools
[ allowCollapse
[ i
] ] !== undefined ) {
2420 this.collapsibleTools
.push( this.tools
[ allowCollapse
[ i
] ] );
2424 // Keep at the end, even when tools are added
2425 this.$group
.append( this.getExpandCollapseTool().$element
);
2427 this.getExpandCollapseTool().toggle( this.collapsibleTools
.length
!== 0 );
2428 this.updateCollapsibleState();
2432 * Get the expand/collapse tool for this group
2434 * @return {OO.ui.Tool} Expand collapse tool
2436 OO
.ui
.ListToolGroup
.prototype.getExpandCollapseTool = function () {
2437 var ExpandCollapseTool
;
2438 if ( this.expandCollapseTool
=== undefined ) {
2439 ExpandCollapseTool = function () {
2440 ExpandCollapseTool
.parent
.apply( this, arguments
);
2443 OO
.inheritClass( ExpandCollapseTool
, OO
.ui
.Tool
);
2445 ExpandCollapseTool
.prototype.onSelect = function () {
2446 this.toolGroup
.expanded
= !this.toolGroup
.expanded
;
2447 this.toolGroup
.updateCollapsibleState();
2448 this.setActive( false );
2450 ExpandCollapseTool
.prototype.onUpdateState = function () {
2451 // Do nothing. Tool interface requires an implementation of this function.
2454 ExpandCollapseTool
.static.name
= 'more-fewer';
2456 this.expandCollapseTool
= new ExpandCollapseTool( this );
2458 return this.expandCollapseTool
;
2464 OO
.ui
.ListToolGroup
.prototype.onMouseKeyUp = function ( e
) {
2465 // Do not close the popup when the user wants to show more/fewer tools
2467 $( e
.target
).closest( '.oo-ui-tool-name-more-fewer' ).length
&& (
2468 e
.which
=== OO
.ui
.MouseButtons
.LEFT
||
2469 e
.which
=== OO
.ui
.Keys
.SPACE
||
2470 e
.which
=== OO
.ui
.Keys
.ENTER
2473 // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation
2474 // (which hides the popup list when a tool is selected) and call ToolGroup's implementation
2476 return OO
.ui
.ListToolGroup
.parent
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2478 return OO
.ui
.ListToolGroup
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2482 OO
.ui
.ListToolGroup
.prototype.updateCollapsibleState = function () {
2485 if ( this.toolbar
.position
!== 'bottom' ) {
2486 icon
= this.expanded
? 'collapse' : 'expand';
2488 icon
= this.expanded
? 'expand' : 'collapse';
2491 this.getExpandCollapseTool()
2493 .setTitle( OO
.ui
.msg( this.expanded
? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
2495 for ( i
= 0, len
= this.collapsibleTools
.length
; i
< len
; i
++ ) {
2496 this.collapsibleTools
[ i
].toggle( this.expanded
);
2499 // Re-evaluate clipping, because our height has changed
2504 * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2505 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are
2506 * {@link OO.ui.BarToolGroup BarToolGroup} and {@link OO.ui.ListToolGroup ListToolGroup}).
2507 * MenuToolGroups contain selectable {@link OO.ui.Tool tools}, which are displayed by label in a
2508 * dropdown menu. The tool's title is used as the label text, and the menu label is updated to
2509 * reflect which tool or tools are currently selected. If no tools are selected, the menu label
2510 * is empty. The menu can be configured with an indicator, icon, title, and/or header.
2512 * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the
2513 * toolbar is set up.
2516 * // Example of a MenuToolGroup
2517 * var toolFactory = new OO.ui.ToolFactory();
2518 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2519 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2521 * // We will be placing status text in this element when tools are used
2522 * var $area = $( '<p>' ).text( 'An example of a MenuToolGroup. Select a tool from the '
2523 * + 'dropdown menu.' );
2525 * // Define the tools that we're going to place in our toolbar
2527 * function SettingsTool() {
2528 * SettingsTool.parent.apply( this, arguments );
2529 * this.reallyActive = false;
2531 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2532 * SettingsTool.static.name = 'settings';
2533 * SettingsTool.static.icon = 'settings';
2534 * SettingsTool.static.title = 'Change settings';
2535 * SettingsTool.prototype.onSelect = function () {
2536 * $area.text( 'Settings tool clicked!' );
2537 * // Toggle the active state on each click
2538 * this.reallyActive = !this.reallyActive;
2539 * this.setActive( this.reallyActive );
2540 * // To update the menu label
2541 * this.toolbar.emit( 'updateState' );
2543 * SettingsTool.prototype.onUpdateState = function () {};
2544 * toolFactory.register( SettingsTool );
2546 * function StuffTool() {
2547 * StuffTool.parent.apply( this, arguments );
2548 * this.reallyActive = false;
2550 * OO.inheritClass( StuffTool, OO.ui.Tool );
2551 * StuffTool.static.name = 'stuff';
2552 * StuffTool.static.icon = 'ellipsis';
2553 * StuffTool.static.title = 'More stuff';
2554 * StuffTool.prototype.onSelect = function () {
2555 * $area.text( 'More stuff tool clicked!' );
2556 * // Toggle the active state on each click
2557 * this.reallyActive = !this.reallyActive;
2558 * this.setActive( this.reallyActive );
2559 * // To update the menu label
2560 * this.toolbar.emit( 'updateState' );
2562 * StuffTool.prototype.onUpdateState = function () {};
2563 * toolFactory.register( StuffTool );
2565 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
2566 * // used once (but not all defined tools must be used).
2570 * header: 'This is the (optional) header',
2571 * title: 'This is the (optional) title',
2572 * include: [ 'settings', 'stuff' ]
2576 * // Create some UI around the toolbar and place it in the document
2577 * var frame = new OO.ui.PanelLayout( {
2581 * var contentFrame = new OO.ui.PanelLayout( {
2585 * frame.$element.append(
2587 * contentFrame.$element.append( $area )
2589 * $( document.body ).append( frame.$element );
2591 * // Here is where the toolbar is actually built. This must be done after inserting it into the
2593 * toolbar.initialize();
2594 * toolbar.emit( 'updateState' );
2596 * For more information about how to add tools to a MenuToolGroup, please see
2597 * {@link OO.ui.ToolGroup toolgroup}.
2598 * For more information about toolbars in general, please see the
2599 * [OOUI documentation on MediaWiki] [1].
2601 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2604 * @extends OO.ui.PopupToolGroup
2607 * @param {OO.ui.Toolbar} toolbar
2608 * @param {Object} [config] Configuration options
2610 OO
.ui
.MenuToolGroup
= function OoUiMenuToolGroup( toolbar
, config
) {
2611 // Allow passing positional parameters inside the config object
2612 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
2614 toolbar
= config
.toolbar
;
2617 // Configuration initialization
2618 config
= config
|| {};
2620 // Parent constructor
2621 OO
.ui
.MenuToolGroup
.parent
.call( this, toolbar
, config
);
2624 this.toolbar
.connect( this, {
2625 updateState
: 'onUpdateState'
2629 this.$element
.addClass( 'oo-ui-menuToolGroup' );
2630 this.$group
.addClass( 'oo-ui-menuToolGroup-tools' );
2635 OO
.inheritClass( OO
.ui
.MenuToolGroup
, OO
.ui
.PopupToolGroup
);
2637 /* Static Properties */
2643 OO
.ui
.MenuToolGroup
.static.name
= 'menu';
2648 * Handle the toolbar state being updated.
2650 * When the state changes, the title of each active item in the menu will be joined together and
2651 * used as a label for the group. The label will be empty if none of the items are active.
2655 OO
.ui
.MenuToolGroup
.prototype.onUpdateState = function () {
2659 for ( name
in this.tools
) {
2660 if ( this.tools
[ name
].isActive() ) {
2661 labelTexts
.push( this.tools
[ name
].getTitle() );
2665 this.setLabel( labelTexts
.join( ', ' ) || ' ' );
2670 //# sourceMappingURL=oojs-ui-toolbars.js.map.json