3 * https://www.mediawiki.org/wiki/OOUI
5 * Copyright 2011–2018 OOUI Team and other contributors.
6 * Released under the MIT license
7 * http://oojs.mit-license.org
9 * Date: 2018-10-08T22:42:55Z
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 commands that are
18 * part of the toolbar, but not configured as tools.
20 * Individual tools are customized and then registered with a {@link OO.ui.ToolFactory tool factory}, which creates
21 * the tools on demand. Each tool has a symbolic name (used when registering the tool), a title (e.g., ‘Insert
22 * image’), and an icon.
24 * Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be {@link OO.ui.MenuToolGroup menus}
25 * of tools, {@link OO.ui.ListToolGroup lists} of tools, or a single {@link OO.ui.BarToolGroup bar} of tools.
26 * The arrangement and order of the toolgroups is customized when the toolbar is set up. Tools can be presented in
27 * any order, but each can only appear once in the toolbar.
29 * The toolbar can be synchronized with the state of the external "application", like a text
30 * editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as
31 * active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption
32 * tool would be disabled while the user is not editing a table). A state change is signalled by
33 * emitting the {@link #event-updateState 'updateState' event}, which calls Tools'
34 * {@link OO.ui.Tool#onUpdateState onUpdateState method}.
36 * The following is an example of a basic toolbar.
39 * // Example of a toolbar
40 * // Create the toolbar
41 * var toolFactory = new OO.ui.ToolFactory();
42 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
43 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
45 * // We will be placing status text in this element when tools are used
46 * var $area = $( '<p>' ).text( 'Toolbar example' );
48 * // Define the tools that we're going to place in our toolbar
50 * // Create a class inheriting from OO.ui.Tool
51 * function SearchTool() {
52 * SearchTool.parent.apply( this, arguments );
54 * OO.inheritClass( SearchTool, OO.ui.Tool );
55 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
56 * // of 'icon' and 'title' (displayed icon and text).
57 * SearchTool.static.name = 'search';
58 * SearchTool.static.icon = 'search';
59 * SearchTool.static.title = 'Search...';
60 * // Defines the action that will happen when this tool is selected (clicked).
61 * SearchTool.prototype.onSelect = function () {
62 * $area.text( 'Search tool clicked!' );
63 * // Never display this tool as "active" (selected).
64 * this.setActive( false );
66 * SearchTool.prototype.onUpdateState = function () {};
67 * // Make this tool available in our toolFactory and thus our toolbar
68 * toolFactory.register( SearchTool );
70 * // Register two more tools, nothing interesting here
71 * function SettingsTool() {
72 * SettingsTool.parent.apply( this, arguments );
74 * OO.inheritClass( SettingsTool, OO.ui.Tool );
75 * SettingsTool.static.name = 'settings';
76 * SettingsTool.static.icon = 'settings';
77 * SettingsTool.static.title = 'Change settings';
78 * SettingsTool.prototype.onSelect = function () {
79 * $area.text( 'Settings tool clicked!' );
80 * this.setActive( false );
82 * SettingsTool.prototype.onUpdateState = function () {};
83 * toolFactory.register( SettingsTool );
85 * // Register two more tools, nothing interesting here
86 * function StuffTool() {
87 * StuffTool.parent.apply( this, arguments );
89 * OO.inheritClass( StuffTool, OO.ui.Tool );
90 * StuffTool.static.name = 'stuff';
91 * StuffTool.static.icon = 'ellipsis';
92 * StuffTool.static.title = 'More stuff';
93 * StuffTool.prototype.onSelect = function () {
94 * $area.text( 'More stuff tool clicked!' );
95 * this.setActive( false );
97 * StuffTool.prototype.onUpdateState = function () {};
98 * toolFactory.register( StuffTool );
100 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
101 * // little popup window (a PopupWidget).
102 * function HelpTool( toolGroup, config ) {
103 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
108 * this.popup.$body.append( '<p>I am helpful!</p>' );
110 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
111 * HelpTool.static.name = 'help';
112 * HelpTool.static.icon = 'help';
113 * HelpTool.static.title = 'Help';
114 * toolFactory.register( HelpTool );
116 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
117 * // used once (but not all defined tools must be used).
120 * // 'bar' tool groups display tools' icons only, side-by-side.
122 * include: [ 'search', 'help' ]
125 * // 'list' tool groups display both the titles and icons, in a dropdown list.
129 * include: [ 'settings', 'stuff' ]
131 * // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed
132 * // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here,
133 * // since it's more complicated to use. (See the next example snippet on this page.)
136 * // Create some UI around the toolbar and place it in the document
137 * var frame = new OO.ui.PanelLayout( {
141 * var contentFrame = new OO.ui.PanelLayout( {
145 * frame.$element.append(
147 * contentFrame.$element.append( $area )
149 * $( 'body' ).append( frame.$element );
151 * // Here is where the toolbar is actually built. This must be done after inserting it into the
153 * toolbar.initialize();
154 * toolbar.emit( 'updateState' );
156 * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
157 * {@link #event-updateState 'updateState' event}.
160 * // Create the toolbar
161 * var toolFactory = new OO.ui.ToolFactory();
162 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
163 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
165 * // We will be placing status text in this element when tools are used
166 * var $area = $( '<p>' ).text( 'Toolbar example' );
168 * // Define the tools that we're going to place in our toolbar
170 * // Create a class inheriting from OO.ui.Tool
171 * function SearchTool() {
172 * SearchTool.parent.apply( this, arguments );
174 * OO.inheritClass( SearchTool, OO.ui.Tool );
175 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
176 * // of 'icon' and 'title' (displayed icon and text).
177 * SearchTool.static.name = 'search';
178 * SearchTool.static.icon = 'search';
179 * SearchTool.static.title = 'Search...';
180 * // Defines the action that will happen when this tool is selected (clicked).
181 * SearchTool.prototype.onSelect = function () {
182 * $area.text( 'Search tool clicked!' );
183 * // Never display this tool as "active" (selected).
184 * this.setActive( false );
186 * SearchTool.prototype.onUpdateState = function () {};
187 * // Make this tool available in our toolFactory and thus our toolbar
188 * toolFactory.register( SearchTool );
190 * // Register two more tools, nothing interesting here
191 * function SettingsTool() {
192 * SettingsTool.parent.apply( this, arguments );
193 * this.reallyActive = false;
195 * OO.inheritClass( SettingsTool, OO.ui.Tool );
196 * SettingsTool.static.name = 'settings';
197 * SettingsTool.static.icon = 'settings';
198 * SettingsTool.static.title = 'Change settings';
199 * SettingsTool.prototype.onSelect = function () {
200 * $area.text( 'Settings tool clicked!' );
201 * // Toggle the active state on each click
202 * this.reallyActive = !this.reallyActive;
203 * this.setActive( this.reallyActive );
204 * // To update the menu label
205 * this.toolbar.emit( 'updateState' );
207 * SettingsTool.prototype.onUpdateState = function () {};
208 * toolFactory.register( SettingsTool );
210 * // Register two more tools, nothing interesting here
211 * function StuffTool() {
212 * StuffTool.parent.apply( this, arguments );
213 * this.reallyActive = false;
215 * OO.inheritClass( StuffTool, OO.ui.Tool );
216 * StuffTool.static.name = 'stuff';
217 * StuffTool.static.icon = 'ellipsis';
218 * StuffTool.static.title = 'More stuff';
219 * StuffTool.prototype.onSelect = function () {
220 * $area.text( 'More stuff tool clicked!' );
221 * // Toggle the active state on each click
222 * this.reallyActive = !this.reallyActive;
223 * this.setActive( this.reallyActive );
224 * // To update the menu label
225 * this.toolbar.emit( 'updateState' );
227 * StuffTool.prototype.onUpdateState = function () {};
228 * toolFactory.register( StuffTool );
230 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
231 * // little popup window (a PopupWidget). 'onUpdateState' is also already implemented.
232 * function HelpTool( toolGroup, config ) {
233 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
238 * this.popup.$body.append( '<p>I am helpful!</p>' );
240 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
241 * HelpTool.static.name = 'help';
242 * HelpTool.static.icon = 'help';
243 * HelpTool.static.title = 'Help';
244 * toolFactory.register( HelpTool );
246 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
247 * // used once (but not all defined tools must be used).
250 * // 'bar' tool groups display tools' icons only, side-by-side.
252 * include: [ 'search', 'help' ]
255 * // 'menu' tool groups display both the titles and icons, in a dropdown menu.
256 * // Menu label indicates which items are selected.
259 * include: [ 'settings', 'stuff' ]
263 * // Create some UI around the toolbar and place it in the document
264 * var frame = new OO.ui.PanelLayout( {
268 * var contentFrame = new OO.ui.PanelLayout( {
272 * frame.$element.append(
274 * contentFrame.$element.append( $area )
276 * $( 'body' ).append( frame.$element );
278 * // Here is where the toolbar is actually built. This must be done after inserting it into the
280 * toolbar.initialize();
281 * toolbar.emit( 'updateState' );
284 * @extends OO.ui.Element
285 * @mixins OO.EventEmitter
286 * @mixins OO.ui.mixin.GroupElement
289 * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools
290 * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups
291 * @param {Object} [config] Configuration options
292 * @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are included
293 * in the toolbar, but are not configured as tools. By default, actions are displayed on the right side of
295 * @cfg {string} [position='top'] Whether the toolbar is positioned above ('top') or below ('bottom') content.
296 * @cfg {jQuery} [$overlay] An overlay for the popup.
297 * See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
299 OO
.ui
.Toolbar
= function OoUiToolbar( toolFactory
, toolGroupFactory
, config
) {
300 // Allow passing positional parameters inside the config object
301 if ( OO
.isPlainObject( toolFactory
) && config
=== undefined ) {
302 config
= toolFactory
;
303 toolFactory
= config
.toolFactory
;
304 toolGroupFactory
= config
.toolGroupFactory
;
307 // Configuration initialization
308 config
= config
|| {};
310 // Parent constructor
311 OO
.ui
.Toolbar
.parent
.call( this, config
);
313 // Mixin constructors
314 OO
.EventEmitter
.call( this );
315 OO
.ui
.mixin
.GroupElement
.call( this, config
);
318 this.toolFactory
= toolFactory
;
319 this.toolGroupFactory
= toolGroupFactory
;
320 this.groupsByName
= {};
321 this.activeToolGroups
= 0;
323 this.position
= config
.position
|| 'top';
324 this.$bar
= $( '<div>' );
325 this.$actions
= $( '<div>' );
326 this.$popups
= $( '<div>' );
327 this.initialized
= false;
328 this.narrowThreshold
= null;
329 this.onWindowResizeHandler
= this.onWindowResize
.bind( this );
330 this.$overlay
= ( config
.$overlay
=== true ? OO
.ui
.getDefaultOverlay() : config
.$overlay
) || this.$element
;
334 .add( this.$bar
).add( this.$group
).add( this.$actions
)
335 .on( 'mousedown keydown', this.onPointerDown
.bind( this ) );
338 this.$group
.addClass( 'oo-ui-toolbar-tools' );
339 if ( config
.actions
) {
340 this.$bar
.append( this.$actions
.addClass( 'oo-ui-toolbar-actions' ) );
342 this.$popups
.addClass( 'oo-ui-toolbar-popups' );
344 .addClass( 'oo-ui-toolbar-bar' )
345 .append( this.$group
, '<div style="clear:both"></div>' );
346 // Possible classes: oo-ui-toolbar-position-top, oo-ui-toolbar-position-bottom
347 this.$element
.addClass( 'oo-ui-toolbar oo-ui-toolbar-position-' + this.position
).append( this.$bar
);
348 this.$overlay
.append( this.$popups
);
353 OO
.inheritClass( OO
.ui
.Toolbar
, OO
.ui
.Element
);
354 OO
.mixinClass( OO
.ui
.Toolbar
, OO
.EventEmitter
);
355 OO
.mixinClass( OO
.ui
.Toolbar
, OO
.ui
.mixin
.GroupElement
);
362 * An 'updateState' event must be emitted on the Toolbar (by calling `toolbar.emit( 'updateState' )`)
363 * every time the state of the application using the toolbar changes, and an update to the state of
366 * @param {...Mixed} data Application-defined parameters
372 * An 'active' event is emitted when the number of active toolgroups increases from 0, or
375 * @param {boolean} There are active toolgroups in this toolbar
381 * Get the tool factory.
383 * @return {OO.ui.ToolFactory} Tool factory
385 OO
.ui
.Toolbar
.prototype.getToolFactory = function () {
386 return this.toolFactory
;
390 * Get the toolgroup factory.
392 * @return {OO.Factory} Toolgroup factory
394 OO
.ui
.Toolbar
.prototype.getToolGroupFactory = function () {
395 return this.toolGroupFactory
;
399 * Handles mouse down events.
402 * @param {jQuery.Event} e Mouse down event
404 OO
.ui
.Toolbar
.prototype.onPointerDown = function ( e
) {
405 var $closestWidgetToEvent
= $( e
.target
).closest( '.oo-ui-widget' ),
406 $closestWidgetToToolbar
= this.$element
.closest( '.oo-ui-widget' );
407 if ( !$closestWidgetToEvent
.length
|| $closestWidgetToEvent
[ 0 ] === $closestWidgetToToolbar
[ 0 ] ) {
413 * Handle window resize event.
416 * @param {jQuery.Event} e Window resize event
418 OO
.ui
.Toolbar
.prototype.onWindowResize = function () {
419 this.$element
.add( this.$popups
).toggleClass(
420 'oo-ui-toolbar-narrow',
421 this.$bar
[ 0 ].clientWidth
<= this.getNarrowThreshold()
426 * Get the (lazily-computed) width threshold for applying the oo-ui-toolbar-narrow
430 * @return {number} Width threshold in pixels
432 OO
.ui
.Toolbar
.prototype.getNarrowThreshold = function () {
433 if ( this.narrowThreshold
=== null ) {
434 this.narrowThreshold
= this.$group
[ 0 ].offsetWidth
+ this.$actions
[ 0 ].offsetWidth
;
436 return this.narrowThreshold
;
440 * Sets up handles and preloads required information for the toolbar to work.
441 * This must be called after it is attached to a visible document and before doing anything else.
443 OO
.ui
.Toolbar
.prototype.initialize = function () {
444 if ( !this.initialized
) {
445 this.initialized
= true;
446 $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler
);
447 this.onWindowResize();
452 * Set up the toolbar.
454 * The toolbar is set up with a list of toolgroup configurations that specify the type of
455 * toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or {@link OO.ui.ListToolGroup list})
456 * to add and which tools to include, exclude, promote, or demote within that toolgroup. Please
457 * see {@link OO.ui.ToolGroup toolgroups} for more information about including tools in toolgroups.
459 * @param {Object.<string,Array>} groups List of toolgroup configurations
460 * @param {string} [groups.name] Symbolic name for this toolgroup
461 * @param {string} [groups.type] Toolgroup type, should exist in the toolgroup factory
462 * @param {Array|string} [groups.include] Tools to include in the toolgroup
463 * @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup
464 * @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup
465 * @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup
467 OO
.ui
.Toolbar
.prototype.setup = function ( groups
) {
468 var i
, len
, type
, toolGroup
, groupConfig
,
472 // Cleanup previous groups
475 // Build out new groups
476 for ( i
= 0, len
= groups
.length
; i
< len
; i
++ ) {
477 groupConfig
= groups
[ i
];
478 if ( groupConfig
.include
=== '*' ) {
479 // Apply defaults to catch-all groups
480 if ( groupConfig
.type
=== undefined ) {
481 groupConfig
.type
= 'list';
483 if ( groupConfig
.label
=== undefined ) {
484 groupConfig
.label
= OO
.ui
.msg( 'ooui-toolbar-more' );
487 // Check type has been registered
488 type
= this.getToolGroupFactory().lookup( groupConfig
.type
) ? groupConfig
.type
: defaultType
;
489 toolGroup
= this.getToolGroupFactory().create( type
, this, groupConfig
);
490 items
.push( toolGroup
);
491 if ( groupConfig
.name
) {
492 this.groupsByName
[ groupConfig
.name
] = toolGroup
;
494 // Groups without name are deprecated
495 OO
.ui
.warnDeprecation( 'Toolgroups must have a \'name\' property' );
497 toolGroup
.connect( this, { active
: 'onToolGroupActive' } );
499 this.addItems( items
);
503 * Handle active events from tool groups
505 * @param {boolean} active Tool group has become active, inactive if false
508 OO
.ui
.Toolbar
.prototype.onToolGroupActive = function ( active
) {
510 this.activeToolGroups
++;
511 if ( this.activeToolGroups
=== 1 ) {
512 this.emit( 'active', true );
515 this.activeToolGroups
--;
516 if ( this.activeToolGroups
=== 0 ) {
517 this.emit( 'active', false );
523 * Get a toolgroup by name
525 * @param {string} name Group name
526 * @return {OO.ui.ToolGroup|null} Tool group, or null if none found by that name
528 OO
.ui
.Toolbar
.prototype.getToolGroupByName = function ( name
) {
529 return this.groupsByName
[ name
] || null;
533 * Remove all tools and toolgroups from the toolbar.
535 OO
.ui
.Toolbar
.prototype.reset = function () {
538 this.groupsByName
= {};
540 for ( i
= 0, len
= this.items
.length
; i
< len
; i
++ ) {
541 this.items
[ i
].destroy();
547 * Destroy the toolbar.
549 * Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar. Call
550 * this method whenever you are done using a toolbar.
552 OO
.ui
.Toolbar
.prototype.destroy = function () {
553 $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler
);
555 this.$element
.remove();
559 * Check if the tool is available.
561 * Available tools are ones that have not yet been added to the toolbar.
563 * @param {string} name Symbolic name of tool
564 * @return {boolean} Tool is available
566 OO
.ui
.Toolbar
.prototype.isToolAvailable = function ( name
) {
567 return !this.tools
[ name
];
571 * Prevent tool from being used again.
573 * @param {OO.ui.Tool} tool Tool to reserve
575 OO
.ui
.Toolbar
.prototype.reserveTool = function ( tool
) {
576 this.tools
[ tool
.getName() ] = tool
;
580 * Allow tool to be used again.
582 * @param {OO.ui.Tool} tool Tool to release
584 OO
.ui
.Toolbar
.prototype.releaseTool = function ( tool
) {
585 delete this.tools
[ tool
.getName() ];
589 * Get accelerator label for tool.
591 * The OOUI library does not contain an accelerator system, but this is the hook for one. To
592 * use an accelerator system, subclass the toolbar and override this method, which is meant to return a label
593 * that describes the accelerator keys for the tool passed (by symbolic name) to the method.
595 * @param {string} name Symbolic name of tool
596 * @return {string|undefined} Tool accelerator label if available
598 OO
.ui
.Toolbar
.prototype.getToolAccelerator = function () {
603 * Tools, together with {@link OO.ui.ToolGroup toolgroups}, constitute {@link OO.ui.Toolbar toolbars}.
604 * Each tool is configured with a static name, title, and icon and is customized with the command to carry
605 * out when the tool is selected. Tools must also be registered with a {@link OO.ui.ToolFactory tool factory},
606 * which creates the tools on demand.
608 * Every Tool subclass must implement two methods:
610 * - {@link #onUpdateState}
611 * - {@link #onSelect}
613 * Tools are added to toolgroups ({@link OO.ui.ListToolGroup ListToolGroup},
614 * {@link OO.ui.BarToolGroup BarToolGroup}, or {@link OO.ui.MenuToolGroup MenuToolGroup}), which determine how
615 * the tool is displayed in the toolbar. See {@link OO.ui.Toolbar toolbars} for an example.
617 * For more information, please see the [OOUI documentation on MediaWiki][1].
618 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
622 * @extends OO.ui.Widget
623 * @mixins OO.ui.mixin.IconElement
624 * @mixins OO.ui.mixin.FlaggedElement
625 * @mixins OO.ui.mixin.TabIndexedElement
628 * @param {OO.ui.ToolGroup} toolGroup
629 * @param {Object} [config] Configuration options
630 * @cfg {string|Function} [title] Title text or a function that returns text. If this config is omitted, the value of
631 * the {@link #static-title static title} property is used.
633 * The title is used in different ways depending on the type of toolgroup that contains the tool. The
634 * title is used as a tooltip if the tool is part of a {@link OO.ui.BarToolGroup bar} toolgroup, or as the label text if the tool is
635 * part of a {@link OO.ui.ListToolGroup list} or {@link OO.ui.MenuToolGroup menu} toolgroup.
637 * For bar toolgroups, a description of the accelerator key is appended to the title if an accelerator key
638 * is associated with an action by the same name as the tool and accelerator functionality has been added to the application.
639 * To add accelerator key functionality, you must subclass OO.ui.Toolbar and override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method.
641 OO
.ui
.Tool
= function OoUiTool( toolGroup
, config
) {
642 // Allow passing positional parameters inside the config object
643 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
645 toolGroup
= config
.toolGroup
;
648 // Configuration initialization
649 config
= config
|| {};
651 // Parent constructor
652 OO
.ui
.Tool
.parent
.call( this, config
);
655 this.toolGroup
= toolGroup
;
656 this.toolbar
= this.toolGroup
.getToolbar();
658 this.$title
= $( '<span>' );
659 this.$accel
= $( '<span>' );
660 this.$link
= $( '<a>' );
662 this.checkIcon
= new OO
.ui
.IconWidget( {
664 classes
: [ 'oo-ui-tool-checkIcon' ]
667 // Mixin constructors
668 OO
.ui
.mixin
.IconElement
.call( this, config
);
669 OO
.ui
.mixin
.FlaggedElement
.call( this, config
);
670 OO
.ui
.mixin
.TabIndexedElement
.call( this, $.extend( {}, config
, { $tabIndexed
: this.$link
} ) );
673 this.toolbar
.connect( this, { updateState
: 'onUpdateState' } );
676 this.$title
.addClass( 'oo-ui-tool-title' );
678 .addClass( 'oo-ui-tool-accel' )
680 // This may need to be changed if the key names are ever localized,
681 // but for now they are essentially written in English
686 .addClass( 'oo-ui-tool-link' )
687 .append( this.checkIcon
.$element
, this.$icon
, this.$title
, this.$accel
)
688 .attr( 'role', 'button' );
690 .data( 'oo-ui-tool', this )
691 .addClass( 'oo-ui-tool' )
692 .addClass( 'oo-ui-tool-name-' + this.constructor.static.name
.replace( /^([^/]+)\/([^/]+).*$/, '$1-$2' ) )
693 .toggleClass( 'oo-ui-tool-with-label', this.constructor.static.displayBothIconAndLabel
)
694 .append( this.$link
);
695 this.setTitle( config
.title
|| this.constructor.static.title
);
700 OO
.inheritClass( OO
.ui
.Tool
, OO
.ui
.Widget
);
701 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.IconElement
);
702 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.FlaggedElement
);
703 OO
.mixinClass( OO
.ui
.Tool
, OO
.ui
.mixin
.TabIndexedElement
);
705 /* Static Properties */
711 OO
.ui
.Tool
.static.tagName
= 'span';
714 * Symbolic name of tool.
716 * The symbolic name is used internally to register the tool with a {@link OO.ui.ToolFactory ToolFactory}. It can
717 * also be used when adding tools to toolgroups.
724 OO
.ui
.Tool
.static.name
= '';
727 * Symbolic name of the group.
729 * The group name is used to associate tools with each other so that they can be selected later by
730 * a {@link OO.ui.ToolGroup toolgroup}.
737 OO
.ui
.Tool
.static.group
= '';
740 * Tool title text or a function that returns title text. The value of the static property is overridden if the #title config option is used.
745 * @property {string|Function}
747 OO
.ui
.Tool
.static.title
= '';
750 * Display both icon and label when the tool is used in a {@link OO.ui.BarToolGroup bar} toolgroup.
751 * Normally only the icon is displayed, or only the label if no icon is given.
755 * @property {boolean}
757 OO
.ui
.Tool
.static.displayBothIconAndLabel
= false;
760 * Add tool to catch-all groups automatically.
762 * A catch-all group, which contains all tools that do not currently belong to a toolgroup,
763 * can be included in a toolgroup using the wildcard selector, an asterisk (*).
767 * @property {boolean}
769 OO
.ui
.Tool
.static.autoAddToCatchall
= true;
772 * Add tool to named groups automatically.
774 * By default, tools that are configured with a static ‘group’ property are added
775 * to that group and will be selected when the symbolic name of the group is specified (e.g., when
776 * toolgroups include tools by group name).
779 * @property {boolean}
782 OO
.ui
.Tool
.static.autoAddToGroup
= true;
785 * Check if this tool is compatible with given data.
787 * This is a stub that can be overridden to provide support for filtering tools based on an
788 * arbitrary piece of information (e.g., where the cursor is in a document). The implementation
789 * must also call this method so that the compatibility check can be performed.
793 * @param {Mixed} data Data to check
794 * @return {boolean} Tool can be used with data
796 OO
.ui
.Tool
.static.isCompatibleWith = function () {
803 * Handle the toolbar state being updated. This method is called when the
804 * {@link OO.ui.Toolbar#event-updateState 'updateState' event} is emitted on the
805 * {@link OO.ui.Toolbar Toolbar} that uses this tool, and should set the state of this tool
806 * depending on application state (usually by calling #setDisabled to enable or disable the tool,
807 * or #setActive to mark is as currently in-use or not).
809 * This is an abstract method that must be overridden in a concrete subclass.
815 OO
.ui
.Tool
.prototype.onUpdateState
= null;
818 * Handle the tool being selected. This method is called when the user triggers this tool,
819 * usually by clicking on its label/icon.
821 * This is an abstract method that must be overridden in a concrete subclass.
827 OO
.ui
.Tool
.prototype.onSelect
= null;
830 * Check if the tool is active.
832 * Tools become active when their #onSelect or #onUpdateState handlers change them to appear pressed
833 * with the #setActive method. Additional CSS is applied to the tool to reflect the active state.
835 * @return {boolean} Tool is active
837 OO
.ui
.Tool
.prototype.isActive = function () {
842 * Make the tool appear active or inactive.
844 * This method should be called within #onSelect or #onUpdateState event handlers to make the tool
845 * appear pressed or not.
847 * @param {boolean} state Make tool appear active
849 OO
.ui
.Tool
.prototype.setActive = function ( state
) {
850 this.active
= !!state
;
851 this.$element
.toggleClass( 'oo-ui-tool-active', this.active
);
852 this.updateThemeClasses();
856 * Set the tool #title.
858 * @param {string|Function} title Title text or a function that returns text
861 OO
.ui
.Tool
.prototype.setTitle = function ( title
) {
862 this.title
= OO
.ui
.resolveMsg( title
);
868 * Get the tool #title.
870 * @return {string} Title text
872 OO
.ui
.Tool
.prototype.getTitle = function () {
877 * Get the tool's symbolic name.
879 * @return {string} Symbolic name of tool
881 OO
.ui
.Tool
.prototype.getName = function () {
882 return this.constructor.static.name
;
888 OO
.ui
.Tool
.prototype.updateTitle = function () {
889 var titleTooltips
= this.toolGroup
.constructor.static.titleTooltips
,
890 accelTooltips
= this.toolGroup
.constructor.static.accelTooltips
,
891 accel
= this.toolbar
.getToolAccelerator( this.constructor.static.name
),
894 this.$title
.text( this.title
);
895 this.$accel
.text( accel
);
897 if ( titleTooltips
&& typeof this.title
=== 'string' && this.title
.length
) {
898 tooltipParts
.push( this.title
);
900 if ( accelTooltips
&& typeof accel
=== 'string' && accel
.length
) {
901 tooltipParts
.push( accel
);
903 if ( tooltipParts
.length
) {
904 this.$link
.attr( 'title', tooltipParts
.join( ' ' ) );
906 this.$link
.removeAttr( 'title' );
913 * Destroying the tool removes all event handlers and the tool’s DOM elements.
914 * Call this method whenever you are done using a tool.
916 OO
.ui
.Tool
.prototype.destroy = function () {
917 this.toolbar
.disconnect( this );
918 this.$element
.remove();
922 * ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a {@link OO.ui.Toolbar toolbar}.
923 * The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or {@link OO.ui.MenuToolGroup menu})
924 * to which a tool belongs determines how the tool is arranged and displayed in the toolbar. Toolgroups
925 * themselves are created on demand with a {@link OO.ui.ToolGroupFactory toolgroup factory}.
927 * Toolgroups can contain individual tools, groups of tools, or all available tools, as specified
928 * using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format.
929 * The options `exclude`, `promote`, and `demote` support the same formats.
931 * See {@link OO.ui.Toolbar toolbars} for a full example. For more information about toolbars in general,
932 * please see the [OOUI documentation on MediaWiki][1].
934 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
938 * @extends OO.ui.Widget
939 * @mixins OO.ui.mixin.GroupElement
942 * @param {OO.ui.Toolbar} toolbar
943 * @param {Object} [config] Configuration options
944 * @cfg {Array|string} [include] List of tools to include in the toolgroup, see above.
945 * @cfg {Array|string} [exclude] List of tools to exclude from the toolgroup, see above.
946 * @cfg {Array|string} [promote] List of tools to promote to the beginning of the toolgroup, see above.
947 * @cfg {Array|string} [demote] List of tools to demote to the end of the toolgroup, see above.
948 * This setting is particularly useful when tools have been added to the toolgroup
949 * en masse (e.g., via the catch-all selector).
951 OO
.ui
.ToolGroup
= function OoUiToolGroup( toolbar
, config
) {
952 // Allow passing positional parameters inside the config object
953 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
955 toolbar
= config
.toolbar
;
958 // Configuration initialization
959 config
= config
|| {};
961 // Parent constructor
962 OO
.ui
.ToolGroup
.parent
.call( this, config
);
964 // Mixin constructors
965 OO
.ui
.mixin
.GroupElement
.call( this, config
);
968 this.toolbar
= toolbar
;
971 this.autoDisabled
= false;
972 this.include
= config
.include
|| [];
973 this.exclude
= config
.exclude
|| [];
974 this.promote
= config
.promote
|| [];
975 this.demote
= config
.demote
|| [];
976 this.onDocumentMouseKeyUpHandler
= this.onDocumentMouseKeyUp
.bind( this );
980 mousedown
: this.onMouseKeyDown
.bind( this ),
981 mouseup
: this.onMouseKeyUp
.bind( this ),
982 keydown
: this.onMouseKeyDown
.bind( this ),
983 keyup
: this.onMouseKeyUp
.bind( this ),
984 focus
: this.onMouseOverFocus
.bind( this ),
985 blur
: this.onMouseOutBlur
.bind( this ),
986 mouseover
: this.onMouseOverFocus
.bind( this ),
987 mouseout
: this.onMouseOutBlur
.bind( this )
989 this.toolbar
.getToolFactory().connect( this, { register
: 'onToolFactoryRegister' } );
990 this.aggregate( { disable
: 'itemDisable' } );
991 this.connect( this, {
992 itemDisable
: 'updateDisabled',
997 this.$group
.addClass( 'oo-ui-toolGroup-tools' );
999 .addClass( 'oo-ui-toolGroup' )
1000 .append( this.$group
);
1001 this.onDisable( this.isDisabled() );
1007 OO
.inheritClass( OO
.ui
.ToolGroup
, OO
.ui
.Widget
);
1008 OO
.mixinClass( OO
.ui
.ToolGroup
, OO
.ui
.mixin
.GroupElement
);
1019 * An 'active' event is emitted when any popup is shown/hidden.
1021 * @param {boolean} The popup is visible
1024 /* Static Properties */
1027 * Show labels in tooltips.
1031 * @property {boolean}
1033 OO
.ui
.ToolGroup
.static.titleTooltips
= false;
1036 * Show acceleration labels in tooltips.
1038 * Note: The OOUI library does not include an accelerator system, but does contain
1039 * a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and
1040 * override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is
1041 * meant to return a label that describes the accelerator keys for a given tool (e.g., 'Ctrl + M').
1045 * @property {boolean}
1047 OO
.ui
.ToolGroup
.static.accelTooltips
= false;
1050 * Automatically disable the toolgroup when all tools are disabled
1054 * @property {boolean}
1056 OO
.ui
.ToolGroup
.static.autoDisable
= true;
1062 * @property {string}
1064 OO
.ui
.ToolGroup
.static.name
= null;
1071 OO
.ui
.ToolGroup
.prototype.isDisabled = function () {
1072 return this.autoDisabled
|| OO
.ui
.ToolGroup
.parent
.prototype.isDisabled
.apply( this, arguments
);
1078 OO
.ui
.ToolGroup
.prototype.updateDisabled = function () {
1079 var i
, item
, allDisabled
= true;
1081 if ( this.constructor.static.autoDisable
) {
1082 for ( i
= this.items
.length
- 1; i
>= 0; i
-- ) {
1083 item
= this.items
[ i
];
1084 if ( !item
.isDisabled() ) {
1085 allDisabled
= false;
1089 this.autoDisabled
= allDisabled
;
1091 OO
.ui
.ToolGroup
.parent
.prototype.updateDisabled
.apply( this, arguments
);
1095 * Handle disable events.
1098 * @param {boolean} isDisabled
1100 OO
.ui
.ToolGroup
.prototype.onDisable = function ( isDisabled
) {
1101 this.$group
.toggleClass( 'oo-ui-toolGroup-disabled-tools', isDisabled
);
1102 this.$group
.toggleClass( 'oo-ui-toolGroup-enabled-tools', !isDisabled
);
1106 * Handle mouse down and key down events.
1109 * @param {jQuery.Event} e Mouse down or key down event
1111 OO
.ui
.ToolGroup
.prototype.onMouseKeyDown = function ( e
) {
1113 !this.isDisabled() &&
1114 ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
)
1116 this.pressed
= this.findTargetTool( e
);
1117 if ( this.pressed
) {
1118 this.pressed
.setActive( true );
1119 this.getElementDocument().addEventListener( 'mouseup', this.onDocumentMouseKeyUpHandler
, true );
1120 this.getElementDocument().addEventListener( 'keyup', this.onDocumentMouseKeyUpHandler
, true );
1127 * Handle document mouse up and key up events.
1130 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1132 OO
.ui
.ToolGroup
.prototype.onDocumentMouseKeyUp = function ( e
) {
1133 this.getElementDocument().removeEventListener( 'mouseup', this.onDocumentMouseKeyUpHandler
, true );
1134 this.getElementDocument().removeEventListener( 'keyup', this.onDocumentMouseKeyUpHandler
, true );
1135 // onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
1136 // released, but since `this.pressed` will no longer be true, the second call will be ignored.
1137 this.onMouseKeyUp( e
);
1140 // Deprecated alias since 0.28.3
1141 OO
.ui
.ToolGroup
.prototype.onCapturedMouseKeyUp = function () {
1142 OO
.ui
.warnDeprecation( 'onCapturedMouseKeyUp is deprecated, use onDocumentMouseKeyUp instead' );
1143 this.onDocumentMouseKeyUp
.apply( this, arguments
);
1147 * Handle mouse up and key up events.
1150 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1152 OO
.ui
.ToolGroup
.prototype.onMouseKeyUp = function ( e
) {
1153 var tool
= this.findTargetTool( e
);
1156 !this.isDisabled() && this.pressed
&& this.pressed
=== tool
&&
1157 ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
)
1159 this.pressed
.onSelect();
1160 this.pressed
= null;
1162 e
.stopPropagation();
1165 this.pressed
= null;
1169 * Handle mouse over and focus events.
1172 * @param {jQuery.Event} e Mouse over or focus event
1174 OO
.ui
.ToolGroup
.prototype.onMouseOverFocus = function ( e
) {
1175 var tool
= this.findTargetTool( e
);
1177 if ( this.pressed
&& this.pressed
=== tool
) {
1178 this.pressed
.setActive( true );
1183 * Handle mouse out and blur events.
1186 * @param {jQuery.Event} e Mouse out or blur event
1188 OO
.ui
.ToolGroup
.prototype.onMouseOutBlur = function ( e
) {
1189 var tool
= this.findTargetTool( e
);
1191 if ( this.pressed
&& this.pressed
=== tool
) {
1192 this.pressed
.setActive( false );
1197 * Get the closest tool to a jQuery.Event.
1199 * Only tool links are considered, which prevents other elements in the tool such as popups from
1200 * triggering tool group interactions.
1203 * @param {jQuery.Event} e
1204 * @return {OO.ui.Tool|null} Tool, `null` if none was found
1206 OO
.ui
.ToolGroup
.prototype.findTargetTool = function ( e
) {
1208 $item
= $( e
.target
).closest( '.oo-ui-tool-link' );
1210 if ( $item
.length
) {
1211 tool
= $item
.parent().data( 'oo-ui-tool' );
1214 return tool
&& !tool
.isDisabled() ? tool
: null;
1218 * Handle tool registry register events.
1220 * If a tool is registered after the group is created, we must repopulate the list to account for:
1222 * - a tool being added that may be included
1223 * - a tool already included being overridden
1226 * @param {string} name Symbolic name of tool
1228 OO
.ui
.ToolGroup
.prototype.onToolFactoryRegister = function () {
1233 * Get the toolbar that contains the toolgroup.
1235 * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
1237 OO
.ui
.ToolGroup
.prototype.getToolbar = function () {
1238 return this.toolbar
;
1242 * Add and remove tools based on configuration.
1244 OO
.ui
.ToolGroup
.prototype.populate = function () {
1245 var i
, len
, name
, tool
,
1246 toolFactory
= this.toolbar
.getToolFactory(),
1250 list
= this.toolbar
.getToolFactory().getTools(
1251 this.include
, this.exclude
, this.promote
, this.demote
1254 // Build a list of needed tools
1255 for ( i
= 0, len
= list
.length
; i
< len
; i
++ ) {
1259 toolFactory
.lookup( name
) &&
1260 // Tool is available or is already in this group
1261 ( this.toolbar
.isToolAvailable( name
) || this.tools
[ name
] )
1263 // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before
1264 // creating it, but we can't call reserveTool() yet because we haven't created the tool.
1265 this.toolbar
.tools
[ name
] = true;
1266 tool
= this.tools
[ name
];
1268 // Auto-initialize tools on first use
1269 this.tools
[ name
] = tool
= toolFactory
.create( name
, this );
1272 this.toolbar
.reserveTool( tool
);
1274 names
[ name
] = true;
1277 // Remove tools that are no longer needed
1278 for ( name
in this.tools
) {
1279 if ( !names
[ name
] ) {
1280 this.tools
[ name
].destroy();
1281 this.toolbar
.releaseTool( this.tools
[ name
] );
1282 remove
.push( this.tools
[ name
] );
1283 delete this.tools
[ name
];
1286 if ( remove
.length
) {
1287 this.removeItems( remove
);
1289 // Update emptiness state
1291 this.$element
.removeClass( 'oo-ui-toolGroup-empty' );
1293 this.$element
.addClass( 'oo-ui-toolGroup-empty' );
1295 // Re-add tools (moving existing ones to new locations)
1296 this.addItems( add
);
1297 // Disabled state may depend on items
1298 this.updateDisabled();
1302 * Destroy toolgroup.
1304 OO
.ui
.ToolGroup
.prototype.destroy = function () {
1308 this.toolbar
.getToolFactory().disconnect( this );
1309 for ( name
in this.tools
) {
1310 this.toolbar
.releaseTool( this.tools
[ name
] );
1311 this.tools
[ name
].disconnect( this ).destroy();
1312 delete this.tools
[ name
];
1314 this.$element
.remove();
1318 * A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools}, {@link OO.ui.PopupTool PopupTools},
1319 * and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be registered with a tool factory. Tools are
1320 * registered by their symbolic name. See {@link OO.ui.Toolbar toolbars} for an example.
1322 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1324 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1327 * @extends OO.Factory
1330 OO
.ui
.ToolFactory
= function OoUiToolFactory() {
1331 // Parent constructor
1332 OO
.ui
.ToolFactory
.parent
.call( this );
1337 OO
.inheritClass( OO
.ui
.ToolFactory
, OO
.Factory
);
1342 * Get tools from the factory
1344 * @param {Array|string} [include] Included tools, see #extract for format
1345 * @param {Array|string} [exclude] Excluded tools, see #extract for format
1346 * @param {Array|string} [promote] Promoted tools, see #extract for format
1347 * @param {Array|string} [demote] Demoted tools, see #extract for format
1348 * @return {string[]} List of tools
1350 OO
.ui
.ToolFactory
.prototype.getTools = function ( include
, exclude
, promote
, demote
) {
1351 var i
, len
, included
, promoted
, demoted
,
1355 // Collect included and not excluded tools
1356 included
= OO
.simpleArrayDifference( this.extract( include
), this.extract( exclude
) );
1359 promoted
= this.extract( promote
, used
);
1360 demoted
= this.extract( demote
, used
);
1363 for ( i
= 0, len
= included
.length
; i
< len
; i
++ ) {
1364 if ( !used
[ included
[ i
] ] ) {
1365 auto
.push( included
[ i
] );
1369 return promoted
.concat( auto
).concat( demoted
);
1373 * Get a flat list of names from a list of names or groups.
1375 * Normally, `collection` is an array of tool specifications. Tools can be specified in the
1378 * - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
1379 * - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
1380 * tool to a group, use OO.ui.Tool.static.group.)
1382 * Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
1383 * catch-all selector `'*'`.
1385 * If `used` is passed, tool names that appear as properties in this object will be considered
1386 * already assigned, and will not be returned even if specified otherwise. The tool names extracted
1387 * by this function call will be added as new properties in the object.
1390 * @param {Array|string} collection List of tools, see above
1391 * @param {Object} [used] Object containing information about used tools, see above
1392 * @return {string[]} List of extracted tool names
1394 OO
.ui
.ToolFactory
.prototype.extract = function ( collection
, used
) {
1395 var i
, len
, item
, name
, tool
,
1398 collection
= !Array
.isArray( collection
) ? [ collection
] : collection
;
1400 for ( i
= 0, len
= collection
.length
; i
< len
; i
++ ) {
1401 item
= collection
[ i
];
1402 if ( item
=== '*' ) {
1403 for ( name
in this.registry
) {
1404 tool
= this.registry
[ name
];
1406 // Only add tools by group name when auto-add is enabled
1407 tool
.static.autoAddToCatchall
&&
1408 // Exclude already used tools
1409 ( !used
|| !used
[ name
] )
1413 used
[ name
] = true;
1418 // Allow plain strings as shorthand for named tools
1419 if ( typeof item
=== 'string' ) {
1420 item
= { name
: item
};
1422 if ( OO
.isPlainObject( item
) ) {
1424 for ( name
in this.registry
) {
1425 tool
= this.registry
[ name
];
1427 // Include tools with matching group
1428 tool
.static.group
=== item
.group
&&
1429 // Only add tools by group name when auto-add is enabled
1430 tool
.static.autoAddToGroup
&&
1431 // Exclude already used tools
1432 ( !used
|| !used
[ name
] )
1436 used
[ name
] = true;
1440 // Include tools with matching name and exclude already used tools
1441 } else if ( item
.name
&& ( !used
|| !used
[ item
.name
] ) ) {
1442 names
.push( item
.name
);
1444 used
[ item
.name
] = true;
1454 * ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes must
1455 * specify a symbolic name and be registered with the factory. The following classes are registered by
1458 * - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
1459 * - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
1460 * - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
1462 * See {@link OO.ui.Toolbar toolbars} for an example.
1464 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1466 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1469 * @extends OO.Factory
1472 OO
.ui
.ToolGroupFactory
= function OoUiToolGroupFactory() {
1473 var i
, l
, defaultClasses
;
1474 // Parent constructor
1475 OO
.Factory
.call( this );
1477 defaultClasses
= this.constructor.static.getDefaultClasses();
1479 // Register default toolgroups
1480 for ( i
= 0, l
= defaultClasses
.length
; i
< l
; i
++ ) {
1481 this.register( defaultClasses
[ i
] );
1487 OO
.inheritClass( OO
.ui
.ToolGroupFactory
, OO
.Factory
);
1489 /* Static Methods */
1492 * Get a default set of classes to be registered on construction.
1494 * @return {Function[]} Default classes
1496 OO
.ui
.ToolGroupFactory
.static.getDefaultClasses = function () {
1499 OO
.ui
.ListToolGroup
,
1505 * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}. Each popup tool is configured
1506 * with a static name, title, and icon, as well with as any popup configurations. Unlike other tools, popup tools do not require that developers specify
1507 * an #onSelect or #onUpdateState method, as these methods have been implemented already.
1509 * // Example of a popup tool. When selected, a popup tool displays
1510 * // a popup window.
1511 * function HelpTool( toolGroup, config ) {
1512 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1517 * this.popup.$body.append( '<p>I am helpful!</p>' );
1519 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1520 * HelpTool.static.name = 'help';
1521 * HelpTool.static.icon = 'help';
1522 * HelpTool.static.title = 'Help';
1523 * toolFactory.register( HelpTool );
1525 * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about
1526 * toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1528 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1532 * @extends OO.ui.Tool
1533 * @mixins OO.ui.mixin.PopupElement
1536 * @param {OO.ui.ToolGroup} toolGroup
1537 * @param {Object} [config] Configuration options
1539 OO
.ui
.PopupTool
= function OoUiPopupTool( toolGroup
, config
) {
1540 // Allow passing positional parameters inside the config object
1541 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
1543 toolGroup
= config
.toolGroup
;
1546 // Parent constructor
1547 OO
.ui
.PopupTool
.parent
.call( this, toolGroup
, config
);
1549 // Mixin constructors
1550 OO
.ui
.mixin
.PopupElement
.call( this, config
);
1553 this.popup
.connect( this, { toggle
: 'onPopupToggle' } );
1556 this.popup
.setPosition( toolGroup
.getToolbar().position
=== 'bottom' ? 'above' : 'below' );
1557 this.$element
.addClass( 'oo-ui-popupTool' );
1558 this.popup
.$element
.addClass( 'oo-ui-popupTool-popup' );
1559 this.toolbar
.$popups
.append( this.popup
.$element
);
1564 OO
.inheritClass( OO
.ui
.PopupTool
, OO
.ui
.Tool
);
1565 OO
.mixinClass( OO
.ui
.PopupTool
, OO
.ui
.mixin
.PopupElement
);
1570 * Handle the tool being selected.
1574 OO
.ui
.PopupTool
.prototype.onSelect = function () {
1575 if ( !this.isDisabled() ) {
1576 this.popup
.toggle();
1582 * Handle the toolbar state being updated.
1586 OO
.ui
.PopupTool
.prototype.onUpdateState = function () {
1590 * Handle popup visibility being toggled.
1592 * @param {boolean} isVisible
1594 OO
.ui
.PopupTool
.prototype.onPopupToggle = function ( isVisible
) {
1595 this.setActive( isVisible
);
1596 this.toolGroup
.emit( 'active', isVisible
);
1600 * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools}
1601 * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used
1602 * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from
1603 * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list}
1604 * when the ToolGroupTool is selected.
1606 * // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2', defined elsewhere.
1608 * function SettingsTool() {
1609 * SettingsTool.parent.apply( this, arguments );
1611 * OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
1612 * SettingsTool.static.name = 'settings';
1613 * SettingsTool.static.title = 'Change settings';
1614 * SettingsTool.static.groupConfig = {
1616 * label: 'ToolGroupTool',
1617 * include: [ 'setting1', 'setting2' ]
1619 * toolFactory.register( SettingsTool );
1621 * For more information, please see the [OOUI documentation on MediaWiki][1].
1623 * Please note that this implementation is subject to change per [T74159] [2].
1625 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars#ToolGroupTool
1626 * [2]: https://phabricator.wikimedia.org/T74159
1630 * @extends OO.ui.Tool
1633 * @param {OO.ui.ToolGroup} toolGroup
1634 * @param {Object} [config] Configuration options
1636 OO
.ui
.ToolGroupTool
= function OoUiToolGroupTool( toolGroup
, config
) {
1637 // Allow passing positional parameters inside the config object
1638 if ( OO
.isPlainObject( toolGroup
) && config
=== undefined ) {
1640 toolGroup
= config
.toolGroup
;
1643 // Parent constructor
1644 OO
.ui
.ToolGroupTool
.parent
.call( this, toolGroup
, config
);
1647 this.innerToolGroup
= this.createGroup( this.constructor.static.groupConfig
);
1650 this.innerToolGroup
.connect( this, {
1651 disable
: 'onToolGroupDisable',
1652 // Re-emit active events from the innerToolGroup on the parent toolGroup
1653 active
: this.toolGroup
.emit
.bind( this.toolGroup
, 'active' )
1657 this.$link
.remove();
1659 .addClass( 'oo-ui-toolGroupTool' )
1660 .append( this.innerToolGroup
.$element
);
1665 OO
.inheritClass( OO
.ui
.ToolGroupTool
, OO
.ui
.Tool
);
1667 /* Static Properties */
1670 * Toolgroup configuration.
1672 * The toolgroup configuration consists of the tools to include, as well as an icon and label
1673 * to use for the bar item. Tools can be included by symbolic name, group, or with the
1674 * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information.
1676 * @property {Object.<string,Array>}
1678 OO
.ui
.ToolGroupTool
.static.groupConfig
= {};
1683 * Handle the tool being selected.
1687 OO
.ui
.ToolGroupTool
.prototype.onSelect = function () {
1688 this.innerToolGroup
.setActive( !this.innerToolGroup
.active
);
1693 * Synchronize disabledness state of the tool with the inner toolgroup.
1696 * @param {boolean} disabled Element is disabled
1698 OO
.ui
.ToolGroupTool
.prototype.onToolGroupDisable = function ( disabled
) {
1699 this.setDisabled( disabled
);
1703 * Handle the toolbar state being updated.
1707 OO
.ui
.ToolGroupTool
.prototype.onUpdateState = function () {
1708 this.setActive( false );
1712 * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
1714 * @param {Object.<string,Array>} group Toolgroup configuration. Please see {@link OO.ui.ToolGroup toolgroup} for
1716 * @return {OO.ui.ListToolGroup}
1718 OO
.ui
.ToolGroupTool
.prototype.createGroup = function ( group
) {
1719 if ( group
.include
=== '*' ) {
1720 // Apply defaults to catch-all groups
1721 if ( group
.label
=== undefined ) {
1722 group
.label
= OO
.ui
.msg( 'ooui-toolbar-more' );
1726 return this.toolbar
.getToolGroupFactory().create( 'list', this.toolbar
, group
);
1730 * BarToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
1731 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
1732 * and {@link OO.ui.ListToolGroup ListToolGroup}). The {@link OO.ui.Tool tools} in a BarToolGroup are
1733 * displayed by icon in a single row. The title of the tool is displayed when users move the mouse over
1736 * BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar is
1740 * // Example of a BarToolGroup with two tools
1741 * var toolFactory = new OO.ui.ToolFactory();
1742 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
1743 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
1745 * // We will be placing status text in this element when tools are used
1746 * var $area = $( '<p>' ).text( 'Example of a BarToolGroup with two tools.' );
1748 * // Define the tools that we're going to place in our toolbar
1750 * // Create a class inheriting from OO.ui.Tool
1751 * function SearchTool() {
1752 * SearchTool.parent.apply( this, arguments );
1754 * OO.inheritClass( SearchTool, OO.ui.Tool );
1755 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
1756 * // of 'icon' and 'title' (displayed icon and text).
1757 * SearchTool.static.name = 'search';
1758 * SearchTool.static.icon = 'search';
1759 * SearchTool.static.title = 'Search...';
1760 * // Defines the action that will happen when this tool is selected (clicked).
1761 * SearchTool.prototype.onSelect = function () {
1762 * $area.text( 'Search tool clicked!' );
1763 * // Never display this tool as "active" (selected).
1764 * this.setActive( false );
1766 * SearchTool.prototype.onUpdateState = function () {};
1767 * // Make this tool available in our toolFactory and thus our toolbar
1768 * toolFactory.register( SearchTool );
1770 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
1771 * // little popup window (a PopupWidget).
1772 * function HelpTool( toolGroup, config ) {
1773 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1778 * this.popup.$body.append( '<p>I am helpful!</p>' );
1780 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1781 * HelpTool.static.name = 'help';
1782 * HelpTool.static.icon = 'help';
1783 * HelpTool.static.title = 'Help';
1784 * toolFactory.register( HelpTool );
1786 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
1787 * // used once (but not all defined tools must be used).
1790 * // 'bar' tool groups display tools by icon only
1792 * include: [ 'search', 'help' ]
1796 * // Create some UI around the toolbar and place it in the document
1797 * var frame = new OO.ui.PanelLayout( {
1801 * var contentFrame = new OO.ui.PanelLayout( {
1805 * frame.$element.append(
1807 * contentFrame.$element.append( $area )
1809 * $( 'body' ).append( frame.$element );
1811 * // Here is where the toolbar is actually built. This must be done after inserting it into the
1813 * toolbar.initialize();
1815 * For more information about how to add tools to a bar tool group, please see {@link OO.ui.ToolGroup toolgroup}.
1816 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1818 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1821 * @extends OO.ui.ToolGroup
1824 * @param {OO.ui.Toolbar} toolbar
1825 * @param {Object} [config] Configuration options
1827 OO
.ui
.BarToolGroup
= function OoUiBarToolGroup( toolbar
, config
) {
1828 // Allow passing positional parameters inside the config object
1829 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
1831 toolbar
= config
.toolbar
;
1834 // Parent constructor
1835 OO
.ui
.BarToolGroup
.parent
.call( this, toolbar
, config
);
1838 this.$element
.addClass( 'oo-ui-barToolGroup' );
1839 this.$group
.addClass( 'oo-ui-barToolGroup-tools' );
1844 OO
.inheritClass( OO
.ui
.BarToolGroup
, OO
.ui
.ToolGroup
);
1846 /* Static Properties */
1852 OO
.ui
.BarToolGroup
.static.titleTooltips
= true;
1858 OO
.ui
.BarToolGroup
.static.accelTooltips
= true;
1864 OO
.ui
.BarToolGroup
.static.name
= 'bar';
1867 * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup}
1868 * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup--an overlaid menu or list of tools with an
1869 * optional icon and label. This class can be used for other base classes that also use this functionality.
1873 * @extends OO.ui.ToolGroup
1874 * @mixins OO.ui.mixin.IconElement
1875 * @mixins OO.ui.mixin.IndicatorElement
1876 * @mixins OO.ui.mixin.LabelElement
1877 * @mixins OO.ui.mixin.TitledElement
1878 * @mixins OO.ui.mixin.FlaggedElement
1879 * @mixins OO.ui.mixin.ClippableElement
1880 * @mixins OO.ui.mixin.FloatableElement
1881 * @mixins OO.ui.mixin.TabIndexedElement
1884 * @param {OO.ui.Toolbar} toolbar
1885 * @param {Object} [config] Configuration options
1886 * @cfg {string} [header] Text to display at the top of the popup
1888 OO
.ui
.PopupToolGroup
= function OoUiPopupToolGroup( toolbar
, config
) {
1889 // Allow passing positional parameters inside the config object
1890 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
1892 toolbar
= config
.toolbar
;
1895 // Configuration initialization
1896 config
= $.extend( {
1897 indicator
: config
.indicator
=== undefined ? ( toolbar
.position
=== 'bottom' ? 'up' : 'down' ) : config
.indicator
1900 // Parent constructor
1901 OO
.ui
.PopupToolGroup
.parent
.call( this, toolbar
, config
);
1904 this.active
= false;
1905 this.dragging
= false;
1906 // Don't conflict with parent method of the same name
1907 this.onPopupDocumentMouseKeyUpHandler
= this.onPopupDocumentMouseKeyUp
.bind( this );
1908 this.$handle
= $( '<span>' );
1910 // Mixin constructors
1911 OO
.ui
.mixin
.IconElement
.call( this, config
);
1912 OO
.ui
.mixin
.IndicatorElement
.call( this, config
);
1913 OO
.ui
.mixin
.LabelElement
.call( this, config
);
1914 OO
.ui
.mixin
.TitledElement
.call( this, config
);
1915 OO
.ui
.mixin
.FlaggedElement
.call( this, config
);
1916 OO
.ui
.mixin
.ClippableElement
.call( this, $.extend( {}, config
, { $clippable
: this.$group
} ) );
1917 OO
.ui
.mixin
.FloatableElement
.call( this, $.extend( {}, config
, {
1918 $floatable
: this.$group
,
1919 $floatableContainer
: this.$handle
,
1920 hideWhenOutOfView
: false,
1921 verticalPosition
: this.toolbar
.position
=== 'bottom' ? 'above' : 'below'
1923 OO
.ui
.mixin
.TabIndexedElement
.call( this, $.extend( {}, config
, { $tabIndexed
: this.$handle
} ) );
1927 keydown
: this.onHandleMouseKeyDown
.bind( this ),
1928 keyup
: this.onHandleMouseKeyUp
.bind( this ),
1929 mousedown
: this.onHandleMouseKeyDown
.bind( this ),
1930 mouseup
: this.onHandleMouseKeyUp
.bind( this )
1935 .addClass( 'oo-ui-popupToolGroup-handle' )
1936 .attr( 'role', 'button' )
1937 .append( this.$icon
, this.$label
, this.$indicator
);
1938 // If the pop-up should have a header, add it to the top of the toolGroup.
1939 // Note: If this feature is useful for other widgets, we could abstract it into an
1940 // OO.ui.HeaderedElement mixin constructor.
1941 if ( config
.header
!== undefined ) {
1943 .prepend( $( '<span>' )
1944 .addClass( 'oo-ui-popupToolGroup-header' )
1945 .text( config
.header
)
1949 .addClass( 'oo-ui-popupToolGroup' )
1950 .prepend( this.$handle
);
1951 this.$group
.addClass( 'oo-ui-popupToolGroup-tools' );
1952 this.toolbar
.$popups
.append( this.$group
);
1957 OO
.inheritClass( OO
.ui
.PopupToolGroup
, OO
.ui
.ToolGroup
);
1958 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.IconElement
);
1959 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.IndicatorElement
);
1960 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.LabelElement
);
1961 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.TitledElement
);
1962 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.FlaggedElement
);
1963 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.ClippableElement
);
1964 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.FloatableElement
);
1965 OO
.mixinClass( OO
.ui
.PopupToolGroup
, OO
.ui
.mixin
.TabIndexedElement
);
1972 OO
.ui
.PopupToolGroup
.prototype.setDisabled = function () {
1974 OO
.ui
.PopupToolGroup
.parent
.prototype.setDisabled
.apply( this, arguments
);
1976 if ( this.isDisabled() && this.isElementAttached() ) {
1977 this.setActive( false );
1982 * Handle document mouse up and key up events.
1985 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1987 OO
.ui
.PopupToolGroup
.prototype.onPopupDocumentMouseKeyUp = function ( e
) {
1988 var $target
= $( e
.target
);
1989 // Only deactivate when clicking outside the dropdown element
1990 if ( $target
.closest( '.oo-ui-popupToolGroup' )[ 0 ] === this.$element
[ 0 ] ) {
1993 if ( $target
.closest( '.oo-ui-popupToolGroup-tools' )[ 0 ] === this.$group
[ 0 ] ) {
1996 this.setActive( false );
1999 // Deprecated alias since 0.28.3
2000 OO
.ui
.PopupToolGroup
.prototype.onBlur = function () {
2001 OO
.ui
.warnDeprecation( 'onBlur is deprecated, use onPopupDocumentMouseKeyUp instead' );
2002 this.onPopupDocumentMouseKeyUp
.apply( this, arguments
);
2008 OO
.ui
.PopupToolGroup
.prototype.onMouseKeyUp = function ( e
) {
2009 // Only close toolgroup when a tool was actually selected
2011 !this.isDisabled() && this.pressed
&& this.pressed
=== this.findTargetTool( e
) &&
2012 ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
)
2014 this.setActive( false );
2016 return OO
.ui
.PopupToolGroup
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2022 OO
.ui
.PopupToolGroup
.prototype.onMouseKeyDown = function ( e
) {
2023 var $focused
, $firstFocusable
, $lastFocusable
;
2024 // Shift-Tab on the first tool in the group jumps to the handle.
2025 // Tab on the last tool in the group jumps to the next group.
2026 if ( !this.isDisabled() && e
.which
=== OO
.ui
.Keys
.TAB
) {
2027 // (We can't use this.items because ListToolGroup inserts the extra fake expand/collapse tool.)
2028 $focused
= $( document
.activeElement
);
2029 $firstFocusable
= OO
.ui
.findFocusable( this.$group
);
2030 if ( $focused
[ 0 ] === $firstFocusable
[ 0 ] && e
.shiftKey
) {
2031 this.$handle
.focus();
2034 $lastFocusable
= OO
.ui
.findFocusable( this.$group
, true );
2035 if ( $focused
[ 0 ] === $lastFocusable
[ 0 ] && !e
.shiftKey
) {
2036 // Focus this group's handle and let the browser's tab handling happen (no 'return false').
2037 // This way we don't have to fiddle with other ToolGroups' business, or worry what to do
2038 // if the next group is not a PopupToolGroup or doesn't exist at all.
2039 this.$handle
.focus();
2040 // Close the popup so that we don't move back inside it (if this is the last group).
2041 this.setActive( false );
2044 return OO
.ui
.PopupToolGroup
.parent
.prototype.onMouseKeyDown
.call( this, e
);
2048 * Handle mouse up and key up events.
2051 * @param {jQuery.Event} e Mouse up or key up event
2053 OO
.ui
.PopupToolGroup
.prototype.onHandleMouseKeyUp = function ( e
) {
2055 !this.isDisabled() &&
2056 ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
)
2063 * Handle mouse down and key down events.
2066 * @param {jQuery.Event} e Mouse down or key down event
2068 OO
.ui
.PopupToolGroup
.prototype.onHandleMouseKeyDown = function ( e
) {
2070 if ( !this.isDisabled() ) {
2071 // Tab on the handle jumps to the first tool in the group (if the popup is open).
2072 if ( e
.which
=== OO
.ui
.Keys
.TAB
&& !e
.shiftKey
) {
2073 $focusable
= OO
.ui
.findFocusable( this.$group
);
2074 if ( $focusable
.length
) {
2079 if ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
) {
2080 this.setActive( !this.active
);
2087 * Check if the tool group is active.
2089 * @return {boolean} Tool group is active
2091 OO
.ui
.PopupToolGroup
.prototype.isActive = function () {
2096 * Switch into 'active' mode.
2098 * When active, the popup is visible. A mouseup event anywhere in the document will trigger
2101 * @param {boolean} value The active state to set
2104 OO
.ui
.PopupToolGroup
.prototype.setActive = function ( value
) {
2105 var containerWidth
, containerLeft
;
2107 if ( this.active
!== value
) {
2108 this.active
= value
;
2110 this.getElementDocument().addEventListener( 'mouseup', this.onPopupDocumentMouseKeyUpHandler
, true );
2111 this.getElementDocument().addEventListener( 'keyup', this.onPopupDocumentMouseKeyUpHandler
, true );
2113 this.$clippable
.css( 'left', '' );
2114 this.$element
.addClass( 'oo-ui-popupToolGroup-active' );
2115 this.$group
.addClass( 'oo-ui-popupToolGroup-active-tools' );
2116 this.togglePositioning( true );
2117 this.toggleClipping( true );
2119 // Try anchoring the popup to the left first
2120 this.setHorizontalPosition( 'start' );
2122 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2123 // Anchoring to the left caused the popup to clip, so anchor it to the right instead
2124 this.setHorizontalPosition( 'end' );
2126 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2127 // Anchoring to the right also caused the popup to clip, so just make it fill the container
2128 containerWidth
= this.$clippableScrollableContainer
.width();
2129 containerLeft
= this.$clippableScrollableContainer
[ 0 ] === document
.documentElement
?
2131 this.$clippableScrollableContainer
.offset().left
;
2133 this.toggleClipping( false );
2134 this.setHorizontalPosition( 'start' );
2136 this.$clippable
.css( {
2137 'margin-left': -( this.$element
.offset().left
- containerLeft
),
2138 width
: containerWidth
2142 this.getElementDocument().removeEventListener( 'mouseup', this.onPopupDocumentMouseKeyUpHandler
, true );
2143 this.getElementDocument().removeEventListener( 'keyup', this.onPopupDocumentMouseKeyUpHandler
, true );
2144 this.$element
.removeClass( 'oo-ui-popupToolGroup-active' );
2145 this.$group
.removeClass( 'oo-ui-popupToolGroup-active-tools' );
2146 this.togglePositioning( false );
2147 this.toggleClipping( false );
2149 this.emit( 'active', this.active
);
2150 this.updateThemeClasses();
2155 * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2156 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
2157 * and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed
2158 * by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured
2159 * with a label, icon, indicator, header, and title.
2161 * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that
2162 * users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits
2163 * users to collapse the list again.
2165 * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory
2166 * requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more
2167 * information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
2170 * // Example of a ListToolGroup
2171 * var toolFactory = new OO.ui.ToolFactory();
2172 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2173 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2175 * // Configure and register two tools
2176 * function SettingsTool() {
2177 * SettingsTool.parent.apply( this, arguments );
2179 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2180 * SettingsTool.static.name = 'settings';
2181 * SettingsTool.static.icon = 'settings';
2182 * SettingsTool.static.title = 'Change settings';
2183 * SettingsTool.prototype.onSelect = function () {
2184 * this.setActive( false );
2186 * SettingsTool.prototype.onUpdateState = function () {};
2187 * toolFactory.register( SettingsTool );
2188 * // Register two more tools, nothing interesting here
2189 * function StuffTool() {
2190 * StuffTool.parent.apply( this, arguments );
2192 * OO.inheritClass( StuffTool, OO.ui.Tool );
2193 * StuffTool.static.name = 'stuff';
2194 * StuffTool.static.icon = 'search';
2195 * StuffTool.static.title = 'Change the world';
2196 * StuffTool.prototype.onSelect = function () {
2197 * this.setActive( false );
2199 * StuffTool.prototype.onUpdateState = function () {};
2200 * toolFactory.register( StuffTool );
2203 * // Configurations for list toolgroup.
2205 * label: 'ListToolGroup',
2207 * title: 'This is the title, displayed when user moves the mouse over the list toolgroup',
2208 * header: 'This is the header',
2209 * include: [ 'settings', 'stuff' ],
2210 * allowCollapse: ['stuff']
2214 * // Create some UI around the toolbar and place it in the document
2215 * var frame = new OO.ui.PanelLayout( {
2219 * frame.$element.append(
2222 * $( 'body' ).append( frame.$element );
2223 * // Build the toolbar. This must be done after the toolbar has been appended to the document.
2224 * toolbar.initialize();
2226 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
2228 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2231 * @extends OO.ui.PopupToolGroup
2234 * @param {OO.ui.Toolbar} toolbar
2235 * @param {Object} [config] Configuration options
2236 * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools
2237 * will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If
2238 * the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that
2239 * are included in the toolgroup, but are not designated as collapsible, will always be displayed.
2240 * To open a collapsible list in its expanded state, set #expanded to 'true'.
2241 * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible.
2242 * Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened.
2243 * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have
2244 * been designated as collapsible. When expanded is set to true, all tools in the group will be displayed
2245 * when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom.
2247 OO
.ui
.ListToolGroup
= function OoUiListToolGroup( toolbar
, config
) {
2248 // Allow passing positional parameters inside the config object
2249 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
2251 toolbar
= config
.toolbar
;
2254 // Configuration initialization
2255 config
= config
|| {};
2257 // Properties (must be set before parent constructor, which calls #populate)
2258 this.allowCollapse
= config
.allowCollapse
;
2259 this.forceExpand
= config
.forceExpand
;
2260 this.expanded
= config
.expanded
!== undefined ? config
.expanded
: false;
2261 this.collapsibleTools
= [];
2263 // Parent constructor
2264 OO
.ui
.ListToolGroup
.parent
.call( this, toolbar
, config
);
2267 this.$element
.addClass( 'oo-ui-listToolGroup' );
2268 this.$group
.addClass( 'oo-ui-listToolGroup-tools' );
2273 OO
.inheritClass( OO
.ui
.ListToolGroup
, OO
.ui
.PopupToolGroup
);
2275 /* Static Properties */
2281 OO
.ui
.ListToolGroup
.static.name
= 'list';
2288 OO
.ui
.ListToolGroup
.prototype.populate = function () {
2289 var i
, len
, allowCollapse
= [];
2291 OO
.ui
.ListToolGroup
.parent
.prototype.populate
.call( this );
2293 // Update the list of collapsible tools
2294 if ( this.allowCollapse
!== undefined ) {
2295 allowCollapse
= this.allowCollapse
;
2296 } else if ( this.forceExpand
!== undefined ) {
2297 allowCollapse
= OO
.simpleArrayDifference( Object
.keys( this.tools
), this.forceExpand
);
2300 this.collapsibleTools
= [];
2301 for ( i
= 0, len
= allowCollapse
.length
; i
< len
; i
++ ) {
2302 if ( this.tools
[ allowCollapse
[ i
] ] !== undefined ) {
2303 this.collapsibleTools
.push( this.tools
[ allowCollapse
[ i
] ] );
2307 // Keep at the end, even when tools are added
2308 this.$group
.append( this.getExpandCollapseTool().$element
);
2310 this.getExpandCollapseTool().toggle( this.collapsibleTools
.length
!== 0 );
2311 this.updateCollapsibleState();
2315 * Get the expand/collapse tool for this group
2317 * @return {OO.ui.Tool} Expand collapse tool
2319 OO
.ui
.ListToolGroup
.prototype.getExpandCollapseTool = function () {
2320 var ExpandCollapseTool
;
2321 if ( this.expandCollapseTool
=== undefined ) {
2322 ExpandCollapseTool = function () {
2323 ExpandCollapseTool
.parent
.apply( this, arguments
);
2326 OO
.inheritClass( ExpandCollapseTool
, OO
.ui
.Tool
);
2328 ExpandCollapseTool
.prototype.onSelect = function () {
2329 this.toolGroup
.expanded
= !this.toolGroup
.expanded
;
2330 this.toolGroup
.updateCollapsibleState();
2331 this.setActive( false );
2333 ExpandCollapseTool
.prototype.onUpdateState = function () {
2334 // Do nothing. Tool interface requires an implementation of this function.
2337 ExpandCollapseTool
.static.name
= 'more-fewer';
2339 this.expandCollapseTool
= new ExpandCollapseTool( this );
2341 return this.expandCollapseTool
;
2347 OO
.ui
.ListToolGroup
.prototype.onMouseKeyUp = function ( e
) {
2348 // Do not close the popup when the user wants to show more/fewer tools
2350 $( e
.target
).closest( '.oo-ui-tool-name-more-fewer' ).length
&&
2351 ( e
.which
=== OO
.ui
.MouseButtons
.LEFT
|| e
.which
=== OO
.ui
.Keys
.SPACE
|| e
.which
=== OO
.ui
.Keys
.ENTER
)
2353 // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which
2354 // hides the popup list when a tool is selected) and call ToolGroup's implementation directly.
2355 return OO
.ui
.ListToolGroup
.parent
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2357 return OO
.ui
.ListToolGroup
.parent
.prototype.onMouseKeyUp
.call( this, e
);
2361 OO
.ui
.ListToolGroup
.prototype.updateCollapsibleState = function () {
2364 if ( this.toolbar
.position
!== 'bottom' ) {
2365 icon
= this.expanded
? 'collapse' : 'expand';
2367 icon
= this.expanded
? 'expand' : 'collapse';
2370 this.getExpandCollapseTool()
2372 .setTitle( OO
.ui
.msg( this.expanded
? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
2374 for ( i
= 0, len
= this.collapsibleTools
.length
; i
< len
; i
++ ) {
2375 this.collapsibleTools
[ i
].toggle( this.expanded
);
2378 // Re-evaluate clipping, because our height has changed
2383 * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2384 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.BarToolGroup BarToolGroup}
2385 * and {@link OO.ui.ListToolGroup ListToolGroup}). MenuToolGroups contain selectable {@link OO.ui.Tool tools},
2386 * which are displayed by label in a dropdown menu. The tool's title is used as the label text, and the
2387 * menu label is updated to reflect which tool or tools are currently selected. If no tools are selected,
2388 * the menu label is empty. The menu can be configured with an indicator, icon, title, and/or header.
2390 * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
2394 * // Example of a MenuToolGroup
2395 * var toolFactory = new OO.ui.ToolFactory();
2396 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2397 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2399 * // We will be placing status text in this element when tools are used
2400 * var $area = $( '<p>' ).text( 'An example of a MenuToolGroup. Select a tool from the dropdown menu.' );
2402 * // Define the tools that we're going to place in our toolbar
2404 * function SettingsTool() {
2405 * SettingsTool.parent.apply( this, arguments );
2406 * this.reallyActive = false;
2408 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2409 * SettingsTool.static.name = 'settings';
2410 * SettingsTool.static.icon = 'settings';
2411 * SettingsTool.static.title = 'Change settings';
2412 * SettingsTool.prototype.onSelect = function () {
2413 * $area.text( 'Settings tool clicked!' );
2414 * // Toggle the active state on each click
2415 * this.reallyActive = !this.reallyActive;
2416 * this.setActive( this.reallyActive );
2417 * // To update the menu label
2418 * this.toolbar.emit( 'updateState' );
2420 * SettingsTool.prototype.onUpdateState = function () {};
2421 * toolFactory.register( SettingsTool );
2423 * function StuffTool() {
2424 * StuffTool.parent.apply( this, arguments );
2425 * this.reallyActive = false;
2427 * OO.inheritClass( StuffTool, OO.ui.Tool );
2428 * StuffTool.static.name = 'stuff';
2429 * StuffTool.static.icon = 'ellipsis';
2430 * StuffTool.static.title = 'More stuff';
2431 * StuffTool.prototype.onSelect = function () {
2432 * $area.text( 'More stuff tool clicked!' );
2433 * // Toggle the active state on each click
2434 * this.reallyActive = !this.reallyActive;
2435 * this.setActive( this.reallyActive );
2436 * // To update the menu label
2437 * this.toolbar.emit( 'updateState' );
2439 * StuffTool.prototype.onUpdateState = function () {};
2440 * toolFactory.register( StuffTool );
2442 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
2443 * // used once (but not all defined tools must be used).
2447 * header: 'This is the (optional) header',
2448 * title: 'This is the (optional) title',
2449 * include: [ 'settings', 'stuff' ]
2453 * // Create some UI around the toolbar and place it in the document
2454 * var frame = new OO.ui.PanelLayout( {
2458 * var contentFrame = new OO.ui.PanelLayout( {
2462 * frame.$element.append(
2464 * contentFrame.$element.append( $area )
2466 * $( 'body' ).append( frame.$element );
2468 * // Here is where the toolbar is actually built. This must be done after inserting it into the
2470 * toolbar.initialize();
2471 * toolbar.emit( 'updateState' );
2473 * For more information about how to add tools to a MenuToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
2474 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki] [1].
2476 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2479 * @extends OO.ui.PopupToolGroup
2482 * @param {OO.ui.Toolbar} toolbar
2483 * @param {Object} [config] Configuration options
2485 OO
.ui
.MenuToolGroup
= function OoUiMenuToolGroup( toolbar
, config
) {
2486 // Allow passing positional parameters inside the config object
2487 if ( OO
.isPlainObject( toolbar
) && config
=== undefined ) {
2489 toolbar
= config
.toolbar
;
2492 // Configuration initialization
2493 config
= config
|| {};
2495 // Parent constructor
2496 OO
.ui
.MenuToolGroup
.parent
.call( this, toolbar
, config
);
2499 this.toolbar
.connect( this, { updateState
: 'onUpdateState' } );
2502 this.$element
.addClass( 'oo-ui-menuToolGroup' );
2503 this.$group
.addClass( 'oo-ui-menuToolGroup-tools' );
2508 OO
.inheritClass( OO
.ui
.MenuToolGroup
, OO
.ui
.PopupToolGroup
);
2510 /* Static Properties */
2516 OO
.ui
.MenuToolGroup
.static.name
= 'menu';
2521 * Handle the toolbar state being updated.
2523 * When the state changes, the title of each active item in the menu will be joined together and
2524 * used as a label for the group. The label will be empty if none of the items are active.
2528 OO
.ui
.MenuToolGroup
.prototype.onUpdateState = function () {
2532 for ( name
in this.tools
) {
2533 if ( this.tools
[ name
].isActive() ) {
2534 labelTexts
.push( this.tools
[ name
].getTitle() );
2538 this.setLabel( labelTexts
.join( ', ' ) || ' ' );
2543 //# sourceMappingURL=oojs-ui-toolbars.js.map.json