Simplify HTMLTitleTextField::validate
[lhc/web/wiklou.git] / resources / lib / oojs-ui / oojs-ui-toolbars.js
1 /*!
2 * OOUI v0.28.0
3 * https://www.mediawiki.org/wiki/OOUI
4 *
5 * Copyright 2011–2018 OOUI Team and other contributors.
6 * Released under the MIT license
7 * http://oojs.mit-license.org
8 *
9 * Date: 2018-08-14T23:16:18Z
10 */
11 ( function ( OO ) {
12
13 'use strict';
14
15 /**
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.
19 *
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.
23 *
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.
28 *
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}.
35 *
36 * The following is an example of a basic toolbar.
37 *
38 * @example
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 );
44 *
45 * // We will be placing status text in this element when tools are used
46 * var $area = $( '<p>' ).text( 'Toolbar example' );
47 *
48 * // Define the tools that we're going to place in our toolbar
49 *
50 * // Create a class inheriting from OO.ui.Tool
51 * function SearchTool() {
52 * SearchTool.parent.apply( this, arguments );
53 * }
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 );
65 * };
66 * SearchTool.prototype.onUpdateState = function () {};
67 * // Make this tool available in our toolFactory and thus our toolbar
68 * toolFactory.register( SearchTool );
69 *
70 * // Register two more tools, nothing interesting here
71 * function SettingsTool() {
72 * SettingsTool.parent.apply( this, arguments );
73 * }
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 );
81 * };
82 * SettingsTool.prototype.onUpdateState = function () {};
83 * toolFactory.register( SettingsTool );
84 *
85 * // Register two more tools, nothing interesting here
86 * function StuffTool() {
87 * StuffTool.parent.apply( this, arguments );
88 * }
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 );
96 * };
97 * StuffTool.prototype.onUpdateState = function () {};
98 * toolFactory.register( StuffTool );
99 *
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: {
104 * padded: true,
105 * label: 'Help',
106 * head: true
107 * } }, config ) );
108 * this.popup.$body.append( '<p>I am helpful!</p>' );
109 * }
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 );
115 *
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).
118 * toolbar.setup( [
119 * {
120 * // 'bar' tool groups display tools' icons only, side-by-side.
121 * type: 'bar',
122 * include: [ 'search', 'help' ]
123 * },
124 * {
125 * // 'list' tool groups display both the titles and icons, in a dropdown list.
126 * type: 'list',
127 * indicator: 'down',
128 * label: 'More',
129 * include: [ 'settings', 'stuff' ]
130 * }
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.)
134 * ] );
135 *
136 * // Create some UI around the toolbar and place it in the document
137 * var frame = new OO.ui.PanelLayout( {
138 * expanded: false,
139 * framed: true
140 * } );
141 * var contentFrame = new OO.ui.PanelLayout( {
142 * expanded: false,
143 * padded: true
144 * } );
145 * frame.$element.append(
146 * toolbar.$element,
147 * contentFrame.$element.append( $area )
148 * );
149 * $( 'body' ).append( frame.$element );
150 *
151 * // Here is where the toolbar is actually built. This must be done after inserting it into the
152 * // document.
153 * toolbar.initialize();
154 * toolbar.emit( 'updateState' );
155 *
156 * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
157 * {@link #event-updateState 'updateState' event}.
158 *
159 * @example
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 );
164 *
165 * // We will be placing status text in this element when tools are used
166 * var $area = $( '<p>' ).text( 'Toolbar example' );
167 *
168 * // Define the tools that we're going to place in our toolbar
169 *
170 * // Create a class inheriting from OO.ui.Tool
171 * function SearchTool() {
172 * SearchTool.parent.apply( this, arguments );
173 * }
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 );
185 * };
186 * SearchTool.prototype.onUpdateState = function () {};
187 * // Make this tool available in our toolFactory and thus our toolbar
188 * toolFactory.register( SearchTool );
189 *
190 * // Register two more tools, nothing interesting here
191 * function SettingsTool() {
192 * SettingsTool.parent.apply( this, arguments );
193 * this.reallyActive = false;
194 * }
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' );
206 * };
207 * SettingsTool.prototype.onUpdateState = function () {};
208 * toolFactory.register( SettingsTool );
209 *
210 * // Register two more tools, nothing interesting here
211 * function StuffTool() {
212 * StuffTool.parent.apply( this, arguments );
213 * this.reallyActive = false;
214 * }
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' );
226 * };
227 * StuffTool.prototype.onUpdateState = function () {};
228 * toolFactory.register( StuffTool );
229 *
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: {
234 * padded: true,
235 * label: 'Help',
236 * head: true
237 * } }, config ) );
238 * this.popup.$body.append( '<p>I am helpful!</p>' );
239 * }
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 );
245 *
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).
248 * toolbar.setup( [
249 * {
250 * // 'bar' tool groups display tools' icons only, side-by-side.
251 * type: 'bar',
252 * include: [ 'search', 'help' ]
253 * },
254 * {
255 * // 'menu' tool groups display both the titles and icons, in a dropdown menu.
256 * // Menu label indicates which items are selected.
257 * type: 'menu',
258 * indicator: 'down',
259 * include: [ 'settings', 'stuff' ]
260 * }
261 * ] );
262 *
263 * // Create some UI around the toolbar and place it in the document
264 * var frame = new OO.ui.PanelLayout( {
265 * expanded: false,
266 * framed: true
267 * } );
268 * var contentFrame = new OO.ui.PanelLayout( {
269 * expanded: false,
270 * padded: true
271 * } );
272 * frame.$element.append(
273 * toolbar.$element,
274 * contentFrame.$element.append( $area )
275 * );
276 * $( 'body' ).append( frame.$element );
277 *
278 * // Here is where the toolbar is actually built. This must be done after inserting it into the
279 * // document.
280 * toolbar.initialize();
281 * toolbar.emit( 'updateState' );
282 *
283 * @class
284 * @extends OO.ui.Element
285 * @mixins OO.EventEmitter
286 * @mixins OO.ui.mixin.GroupElement
287 *
288 * @constructor
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
294 * the toolbar.
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>.
298 */
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;
305 }
306
307 // Configuration initialization
308 config = config || {};
309
310 // Parent constructor
311 OO.ui.Toolbar.parent.call( this, config );
312
313 // Mixin constructors
314 OO.EventEmitter.call( this );
315 OO.ui.mixin.GroupElement.call( this, config );
316
317 // Properties
318 this.toolFactory = toolFactory;
319 this.toolGroupFactory = toolGroupFactory;
320 this.groupsByName = {};
321 this.activeToolGroups = 0;
322 this.tools = {};
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;
331
332 // Events
333 this.$element
334 .add( this.$bar ).add( this.$group ).add( this.$actions )
335 .on( 'mousedown keydown', this.onPointerDown.bind( this ) );
336
337 // Initialization
338 this.$group.addClass( 'oo-ui-toolbar-tools' );
339 if ( config.actions ) {
340 this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) );
341 }
342 this.$popups.addClass( 'oo-ui-toolbar-popups' );
343 this.$bar
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 );
349 };
350
351 /* Setup */
352
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 );
356
357 /* Events */
358
359 /**
360 * @event updateState
361 *
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
364 * tools is required.
365 *
366 * @param {...Mixed} data Application-defined parameters
367 */
368
369 /**
370 * @event active
371 *
372 * An 'active' event is emitted when the number of active toolgroups increases from 0, or
373 * returns to 0.
374 *
375 * @param {boolean} There are active toolgroups in this toolbar
376 */
377
378 /* Methods */
379
380 /**
381 * Get the tool factory.
382 *
383 * @return {OO.ui.ToolFactory} Tool factory
384 */
385 OO.ui.Toolbar.prototype.getToolFactory = function () {
386 return this.toolFactory;
387 };
388
389 /**
390 * Get the toolgroup factory.
391 *
392 * @return {OO.Factory} Toolgroup factory
393 */
394 OO.ui.Toolbar.prototype.getToolGroupFactory = function () {
395 return this.toolGroupFactory;
396 };
397
398 /**
399 * Handles mouse down events.
400 *
401 * @private
402 * @param {jQuery.Event} e Mouse down event
403 */
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 ] ) {
408 return false;
409 }
410 };
411
412 /**
413 * Handle window resize event.
414 *
415 * @private
416 * @param {jQuery.Event} e Window resize event
417 */
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()
422 );
423 };
424
425 /**
426 * Get the (lazily-computed) width threshold for applying the oo-ui-toolbar-narrow
427 * class.
428 *
429 * @private
430 * @return {number} Width threshold in pixels
431 */
432 OO.ui.Toolbar.prototype.getNarrowThreshold = function () {
433 if ( this.narrowThreshold === null ) {
434 this.narrowThreshold = this.$group[ 0 ].offsetWidth + this.$actions[ 0 ].offsetWidth;
435 }
436 return this.narrowThreshold;
437 };
438
439 /**
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.
442 */
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();
448 }
449 };
450
451 /**
452 * Set up the toolbar.
453 *
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.
458 *
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
466 */
467 OO.ui.Toolbar.prototype.setup = function ( groups ) {
468 var i, len, type, toolGroup, groupConfig,
469 items = [],
470 defaultType = 'bar';
471
472 // Cleanup previous groups
473 this.reset();
474
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';
482 }
483 if ( groupConfig.label === undefined ) {
484 groupConfig.label = OO.ui.msg( 'ooui-toolbar-more' );
485 }
486 }
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;
493 } else {
494 // Groups without name are deprecated
495 OO.ui.warnDeprecation( 'Toolgroups must have a \'name\' property' );
496 }
497 toolGroup.connect( this, { active: 'onToolGroupActive' } );
498 }
499 this.addItems( items );
500 };
501
502 /**
503 * Handle active events from tool groups
504 *
505 * @param {boolean} active Tool group has become active, inactive if false
506 * @fires active
507 */
508 OO.ui.Toolbar.prototype.onToolGroupActive = function ( active ) {
509 if ( active ) {
510 this.activeToolGroups++;
511 if ( this.activeToolGroups === 1 ) {
512 this.emit( 'active', true );
513 }
514 } else {
515 this.activeToolGroups--;
516 if ( this.activeToolGroups === 0 ) {
517 this.emit( 'active', false );
518 }
519 }
520 };
521
522 /**
523 * Get a toolgroup by name
524 *
525 * @param {string} name Group name
526 * @return {OO.ui.ToolGroup|null} Tool group, or null if none found by that name
527 */
528 OO.ui.Toolbar.prototype.getToolGroupByName = function ( name ) {
529 return this.groupsByName[ name ] || null;
530 };
531
532 /**
533 * Remove all tools and toolgroups from the toolbar.
534 */
535 OO.ui.Toolbar.prototype.reset = function () {
536 var i, len;
537
538 this.groupsByName = {};
539 this.tools = {};
540 for ( i = 0, len = this.items.length; i < len; i++ ) {
541 this.items[ i ].destroy();
542 }
543 this.clearItems();
544 };
545
546 /**
547 * Destroy the toolbar.
548 *
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.
551 */
552 OO.ui.Toolbar.prototype.destroy = function () {
553 $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
554 this.reset();
555 this.$element.remove();
556 };
557
558 /**
559 * Check if the tool is available.
560 *
561 * Available tools are ones that have not yet been added to the toolbar.
562 *
563 * @param {string} name Symbolic name of tool
564 * @return {boolean} Tool is available
565 */
566 OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) {
567 return !this.tools[ name ];
568 };
569
570 /**
571 * Prevent tool from being used again.
572 *
573 * @param {OO.ui.Tool} tool Tool to reserve
574 */
575 OO.ui.Toolbar.prototype.reserveTool = function ( tool ) {
576 this.tools[ tool.getName() ] = tool;
577 };
578
579 /**
580 * Allow tool to be used again.
581 *
582 * @param {OO.ui.Tool} tool Tool to release
583 */
584 OO.ui.Toolbar.prototype.releaseTool = function ( tool ) {
585 delete this.tools[ tool.getName() ];
586 };
587
588 /**
589 * Get accelerator label for tool.
590 *
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.
594 *
595 * @param {string} name Symbolic name of tool
596 * @return {string|undefined} Tool accelerator label if available
597 */
598 OO.ui.Toolbar.prototype.getToolAccelerator = function () {
599 return undefined;
600 };
601
602 /**
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.
607 *
608 * Every Tool subclass must implement two methods:
609 *
610 * - {@link #onUpdateState}
611 * - {@link #onSelect}
612 *
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.
616 *
617 * For more information, please see the [OOUI documentation on MediaWiki][1].
618 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
619 *
620 * @abstract
621 * @class
622 * @extends OO.ui.Widget
623 * @mixins OO.ui.mixin.IconElement
624 * @mixins OO.ui.mixin.FlaggedElement
625 * @mixins OO.ui.mixin.TabIndexedElement
626 *
627 * @constructor
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.
632 *
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.
636 *
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.
640 */
641 OO.ui.Tool = function OoUiTool( toolGroup, config ) {
642 // Allow passing positional parameters inside the config object
643 if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
644 config = toolGroup;
645 toolGroup = config.toolGroup;
646 }
647
648 // Configuration initialization
649 config = config || {};
650
651 // Parent constructor
652 OO.ui.Tool.parent.call( this, config );
653
654 // Properties
655 this.toolGroup = toolGroup;
656 this.toolbar = this.toolGroup.getToolbar();
657 this.active = false;
658 this.$title = $( '<span>' );
659 this.$accel = $( '<span>' );
660 this.$link = $( '<a>' );
661 this.title = null;
662 this.checkIcon = new OO.ui.IconWidget( {
663 icon: 'check',
664 classes: [ 'oo-ui-tool-checkIcon' ]
665 } );
666
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 } ) );
671
672 // Events
673 this.toolbar.connect( this, { updateState: 'onUpdateState' } );
674
675 // Initialization
676 this.$title.addClass( 'oo-ui-tool-title' );
677 this.$accel
678 .addClass( 'oo-ui-tool-accel' )
679 .prop( {
680 // This may need to be changed if the key names are ever localized,
681 // but for now they are essentially written in English
682 dir: 'ltr',
683 lang: 'en'
684 } );
685 this.$link
686 .addClass( 'oo-ui-tool-link' )
687 .append( this.checkIcon.$element, this.$icon, this.$title, this.$accel )
688 .attr( 'role', 'button' );
689 this.$element
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 );
696 };
697
698 /* Setup */
699
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 );
704
705 /* Static Properties */
706
707 /**
708 * @static
709 * @inheritdoc
710 */
711 OO.ui.Tool.static.tagName = 'span';
712
713 /**
714 * Symbolic name of tool.
715 *
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.
718 *
719 * @abstract
720 * @static
721 * @inheritable
722 * @property {string}
723 */
724 OO.ui.Tool.static.name = '';
725
726 /**
727 * Symbolic name of the group.
728 *
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}.
731 *
732 * @abstract
733 * @static
734 * @inheritable
735 * @property {string}
736 */
737 OO.ui.Tool.static.group = '';
738
739 /**
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.
741 *
742 * @abstract
743 * @static
744 * @inheritable
745 * @property {string|Function}
746 */
747 OO.ui.Tool.static.title = '';
748
749 /**
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.
752 *
753 * @static
754 * @inheritable
755 * @property {boolean}
756 */
757 OO.ui.Tool.static.displayBothIconAndLabel = false;
758
759 /**
760 * Add tool to catch-all groups automatically.
761 *
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 (*).
764 *
765 * @static
766 * @inheritable
767 * @property {boolean}
768 */
769 OO.ui.Tool.static.autoAddToCatchall = true;
770
771 /**
772 * Add tool to named groups automatically.
773 *
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).
777 *
778 * @static
779 * @property {boolean}
780 * @inheritable
781 */
782 OO.ui.Tool.static.autoAddToGroup = true;
783
784 /**
785 * Check if this tool is compatible with given data.
786 *
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.
790 *
791 * @static
792 * @inheritable
793 * @param {Mixed} data Data to check
794 * @return {boolean} Tool can be used with data
795 */
796 OO.ui.Tool.static.isCompatibleWith = function () {
797 return false;
798 };
799
800 /* Methods */
801
802 /**
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).
808 *
809 * This is an abstract method that must be overridden in a concrete subclass.
810 *
811 * @method
812 * @protected
813 * @abstract
814 */
815 OO.ui.Tool.prototype.onUpdateState = null;
816
817 /**
818 * Handle the tool being selected. This method is called when the user triggers this tool,
819 * usually by clicking on its label/icon.
820 *
821 * This is an abstract method that must be overridden in a concrete subclass.
822 *
823 * @method
824 * @protected
825 * @abstract
826 */
827 OO.ui.Tool.prototype.onSelect = null;
828
829 /**
830 * Check if the tool is active.
831 *
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.
834 *
835 * @return {boolean} Tool is active
836 */
837 OO.ui.Tool.prototype.isActive = function () {
838 return this.active;
839 };
840
841 /**
842 * Make the tool appear active or inactive.
843 *
844 * This method should be called within #onSelect or #onUpdateState event handlers to make the tool
845 * appear pressed or not.
846 *
847 * @param {boolean} state Make tool appear active
848 */
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();
853 };
854
855 /**
856 * Set the tool #title.
857 *
858 * @param {string|Function} title Title text or a function that returns text
859 * @chainable
860 */
861 OO.ui.Tool.prototype.setTitle = function ( title ) {
862 this.title = OO.ui.resolveMsg( title );
863 this.updateTitle();
864 return this;
865 };
866
867 /**
868 * Get the tool #title.
869 *
870 * @return {string} Title text
871 */
872 OO.ui.Tool.prototype.getTitle = function () {
873 return this.title;
874 };
875
876 /**
877 * Get the tool's symbolic name.
878 *
879 * @return {string} Symbolic name of tool
880 */
881 OO.ui.Tool.prototype.getName = function () {
882 return this.constructor.static.name;
883 };
884
885 /**
886 * Update the title.
887 */
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 ),
892 tooltipParts = [];
893
894 this.$title.text( this.title );
895 this.$accel.text( accel );
896
897 if ( titleTooltips && typeof this.title === 'string' && this.title.length ) {
898 tooltipParts.push( this.title );
899 }
900 if ( accelTooltips && typeof accel === 'string' && accel.length ) {
901 tooltipParts.push( accel );
902 }
903 if ( tooltipParts.length ) {
904 this.$link.attr( 'title', tooltipParts.join( ' ' ) );
905 } else {
906 this.$link.removeAttr( 'title' );
907 }
908 };
909
910 /**
911 * Destroy tool.
912 *
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.
915 */
916 OO.ui.Tool.prototype.destroy = function () {
917 this.toolbar.disconnect( this );
918 this.$element.remove();
919 };
920
921 /**
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}.
926 *
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.
930 *
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].
933 *
934 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
935 *
936 * @abstract
937 * @class
938 * @extends OO.ui.Widget
939 * @mixins OO.ui.mixin.GroupElement
940 *
941 * @constructor
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).
950 */
951 OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
952 // Allow passing positional parameters inside the config object
953 if ( OO.isPlainObject( toolbar ) && config === undefined ) {
954 config = toolbar;
955 toolbar = config.toolbar;
956 }
957
958 // Configuration initialization
959 config = config || {};
960
961 // Parent constructor
962 OO.ui.ToolGroup.parent.call( this, config );
963
964 // Mixin constructors
965 OO.ui.mixin.GroupElement.call( this, config );
966
967 // Properties
968 this.toolbar = toolbar;
969 this.tools = {};
970 this.pressed = null;
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.onCapturedMouseKeyUpHandler = this.onCapturedMouseKeyUp.bind( this );
977
978 // Events
979 this.$group.on( {
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 )
988 } );
989 this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } );
990 this.aggregate( { disable: 'itemDisable' } );
991 this.connect( this, {
992 itemDisable: 'updateDisabled',
993 disable: 'onDisable'
994 } );
995
996 // Initialization
997 this.$group.addClass( 'oo-ui-toolGroup-tools' );
998 this.$element
999 .addClass( 'oo-ui-toolGroup' )
1000 .append( this.$group );
1001 this.onDisable( this.isDisabled() );
1002 this.populate();
1003 };
1004
1005 /* Setup */
1006
1007 OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget );
1008 OO.mixinClass( OO.ui.ToolGroup, OO.ui.mixin.GroupElement );
1009
1010 /* Events */
1011
1012 /**
1013 * @event update
1014 */
1015
1016 /**
1017 * @event active
1018 *
1019 * An 'active' event is emitted when any popup is shown/hidden.
1020 *
1021 * @param {boolean} The popup is visible
1022 */
1023
1024 /* Static Properties */
1025
1026 /**
1027 * Show labels in tooltips.
1028 *
1029 * @static
1030 * @inheritable
1031 * @property {boolean}
1032 */
1033 OO.ui.ToolGroup.static.titleTooltips = false;
1034
1035 /**
1036 * Show acceleration labels in tooltips.
1037 *
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').
1042 *
1043 * @static
1044 * @inheritable
1045 * @property {boolean}
1046 */
1047 OO.ui.ToolGroup.static.accelTooltips = false;
1048
1049 /**
1050 * Automatically disable the toolgroup when all tools are disabled
1051 *
1052 * @static
1053 * @inheritable
1054 * @property {boolean}
1055 */
1056 OO.ui.ToolGroup.static.autoDisable = true;
1057
1058 /**
1059 * @abstract
1060 * @static
1061 * @inheritable
1062 * @property {string}
1063 */
1064 OO.ui.ToolGroup.static.name = null;
1065
1066 /* Methods */
1067
1068 /**
1069 * @inheritdoc
1070 */
1071 OO.ui.ToolGroup.prototype.isDisabled = function () {
1072 return this.autoDisabled || OO.ui.ToolGroup.parent.prototype.isDisabled.apply( this, arguments );
1073 };
1074
1075 /**
1076 * @inheritdoc
1077 */
1078 OO.ui.ToolGroup.prototype.updateDisabled = function () {
1079 var i, item, allDisabled = true;
1080
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;
1086 break;
1087 }
1088 }
1089 this.autoDisabled = allDisabled;
1090 }
1091 OO.ui.ToolGroup.parent.prototype.updateDisabled.apply( this, arguments );
1092 };
1093
1094 /**
1095 * Handle disable events.
1096 *
1097 * @protected
1098 * @param {boolean} isDisabled
1099 */
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 );
1103 };
1104
1105 /**
1106 * Handle mouse down and key down events.
1107 *
1108 * @protected
1109 * @param {jQuery.Event} e Mouse down or key down event
1110 */
1111 OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) {
1112 if (
1113 !this.isDisabled() &&
1114 ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
1115 ) {
1116 this.pressed = this.findTargetTool( e );
1117 if ( this.pressed ) {
1118 this.pressed.setActive( true );
1119 this.getElementDocument().addEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
1120 this.getElementDocument().addEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
1121 return false;
1122 }
1123 }
1124 };
1125
1126 /**
1127 * Handle captured mouse up and key up events.
1128 *
1129 * @protected
1130 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1131 */
1132 OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
1133 this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
1134 this.getElementDocument().removeEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, 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 );
1138 };
1139
1140 /**
1141 * Handle mouse up and key up events.
1142 *
1143 * @protected
1144 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1145 */
1146 OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
1147 var tool = this.findTargetTool( e );
1148
1149 if (
1150 !this.isDisabled() && this.pressed && this.pressed === tool &&
1151 ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
1152 ) {
1153 this.pressed.onSelect();
1154 this.pressed = null;
1155 e.preventDefault();
1156 e.stopPropagation();
1157 }
1158
1159 this.pressed = null;
1160 };
1161
1162 /**
1163 * Handle mouse over and focus events.
1164 *
1165 * @protected
1166 * @param {jQuery.Event} e Mouse over or focus event
1167 */
1168 OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) {
1169 var tool = this.findTargetTool( e );
1170
1171 if ( this.pressed && this.pressed === tool ) {
1172 this.pressed.setActive( true );
1173 }
1174 };
1175
1176 /**
1177 * Handle mouse out and blur events.
1178 *
1179 * @protected
1180 * @param {jQuery.Event} e Mouse out or blur event
1181 */
1182 OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) {
1183 var tool = this.findTargetTool( e );
1184
1185 if ( this.pressed && this.pressed === tool ) {
1186 this.pressed.setActive( false );
1187 }
1188 };
1189
1190 /**
1191 * Get the closest tool to a jQuery.Event.
1192 *
1193 * Only tool links are considered, which prevents other elements in the tool such as popups from
1194 * triggering tool group interactions.
1195 *
1196 * @private
1197 * @param {jQuery.Event} e
1198 * @return {OO.ui.Tool|null} Tool, `null` if none was found
1199 */
1200 OO.ui.ToolGroup.prototype.findTargetTool = function ( e ) {
1201 var tool,
1202 $item = $( e.target ).closest( '.oo-ui-tool-link' );
1203
1204 if ( $item.length ) {
1205 tool = $item.parent().data( 'oo-ui-tool' );
1206 }
1207
1208 return tool && !tool.isDisabled() ? tool : null;
1209 };
1210
1211 /**
1212 * Handle tool registry register events.
1213 *
1214 * If a tool is registered after the group is created, we must repopulate the list to account for:
1215 *
1216 * - a tool being added that may be included
1217 * - a tool already included being overridden
1218 *
1219 * @protected
1220 * @param {string} name Symbolic name of tool
1221 */
1222 OO.ui.ToolGroup.prototype.onToolFactoryRegister = function () {
1223 this.populate();
1224 };
1225
1226 /**
1227 * Get the toolbar that contains the toolgroup.
1228 *
1229 * @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
1230 */
1231 OO.ui.ToolGroup.prototype.getToolbar = function () {
1232 return this.toolbar;
1233 };
1234
1235 /**
1236 * Add and remove tools based on configuration.
1237 */
1238 OO.ui.ToolGroup.prototype.populate = function () {
1239 var i, len, name, tool,
1240 toolFactory = this.toolbar.getToolFactory(),
1241 names = {},
1242 add = [],
1243 remove = [],
1244 list = this.toolbar.getToolFactory().getTools(
1245 this.include, this.exclude, this.promote, this.demote
1246 );
1247
1248 // Build a list of needed tools
1249 for ( i = 0, len = list.length; i < len; i++ ) {
1250 name = list[ i ];
1251 if (
1252 // Tool exists
1253 toolFactory.lookup( name ) &&
1254 // Tool is available or is already in this group
1255 ( this.toolbar.isToolAvailable( name ) || this.tools[ name ] )
1256 ) {
1257 // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before
1258 // creating it, but we can't call reserveTool() yet because we haven't created the tool.
1259 this.toolbar.tools[ name ] = true;
1260 tool = this.tools[ name ];
1261 if ( !tool ) {
1262 // Auto-initialize tools on first use
1263 this.tools[ name ] = tool = toolFactory.create( name, this );
1264 tool.updateTitle();
1265 }
1266 this.toolbar.reserveTool( tool );
1267 add.push( tool );
1268 names[ name ] = true;
1269 }
1270 }
1271 // Remove tools that are no longer needed
1272 for ( name in this.tools ) {
1273 if ( !names[ name ] ) {
1274 this.tools[ name ].destroy();
1275 this.toolbar.releaseTool( this.tools[ name ] );
1276 remove.push( this.tools[ name ] );
1277 delete this.tools[ name ];
1278 }
1279 }
1280 if ( remove.length ) {
1281 this.removeItems( remove );
1282 }
1283 // Update emptiness state
1284 if ( add.length ) {
1285 this.$element.removeClass( 'oo-ui-toolGroup-empty' );
1286 } else {
1287 this.$element.addClass( 'oo-ui-toolGroup-empty' );
1288 }
1289 // Re-add tools (moving existing ones to new locations)
1290 this.addItems( add );
1291 // Disabled state may depend on items
1292 this.updateDisabled();
1293 };
1294
1295 /**
1296 * Destroy toolgroup.
1297 */
1298 OO.ui.ToolGroup.prototype.destroy = function () {
1299 var name;
1300
1301 this.clearItems();
1302 this.toolbar.getToolFactory().disconnect( this );
1303 for ( name in this.tools ) {
1304 this.toolbar.releaseTool( this.tools[ name ] );
1305 this.tools[ name ].disconnect( this ).destroy();
1306 delete this.tools[ name ];
1307 }
1308 this.$element.remove();
1309 };
1310
1311 /**
1312 * A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools}, {@link OO.ui.PopupTool PopupTools},
1313 * and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be registered with a tool factory. Tools are
1314 * registered by their symbolic name. See {@link OO.ui.Toolbar toolbars} for an example.
1315 *
1316 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1317 *
1318 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1319 *
1320 * @class
1321 * @extends OO.Factory
1322 * @constructor
1323 */
1324 OO.ui.ToolFactory = function OoUiToolFactory() {
1325 // Parent constructor
1326 OO.ui.ToolFactory.parent.call( this );
1327 };
1328
1329 /* Setup */
1330
1331 OO.inheritClass( OO.ui.ToolFactory, OO.Factory );
1332
1333 /* Methods */
1334
1335 /**
1336 * Get tools from the factory
1337 *
1338 * @param {Array|string} [include] Included tools, see #extract for format
1339 * @param {Array|string} [exclude] Excluded tools, see #extract for format
1340 * @param {Array|string} [promote] Promoted tools, see #extract for format
1341 * @param {Array|string} [demote] Demoted tools, see #extract for format
1342 * @return {string[]} List of tools
1343 */
1344 OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, demote ) {
1345 var i, len, included, promoted, demoted,
1346 auto = [],
1347 used = {};
1348
1349 // Collect included and not excluded tools
1350 included = OO.simpleArrayDifference( this.extract( include ), this.extract( exclude ) );
1351
1352 // Promotion
1353 promoted = this.extract( promote, used );
1354 demoted = this.extract( demote, used );
1355
1356 // Auto
1357 for ( i = 0, len = included.length; i < len; i++ ) {
1358 if ( !used[ included[ i ] ] ) {
1359 auto.push( included[ i ] );
1360 }
1361 }
1362
1363 return promoted.concat( auto ).concat( demoted );
1364 };
1365
1366 /**
1367 * Get a flat list of names from a list of names or groups.
1368 *
1369 * Normally, `collection` is an array of tool specifications. Tools can be specified in the
1370 * following ways:
1371 *
1372 * - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
1373 * - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
1374 * tool to a group, use OO.ui.Tool.static.group.)
1375 *
1376 * Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
1377 * catch-all selector `'*'`.
1378 *
1379 * If `used` is passed, tool names that appear as properties in this object will be considered
1380 * already assigned, and will not be returned even if specified otherwise. The tool names extracted
1381 * by this function call will be added as new properties in the object.
1382 *
1383 * @private
1384 * @param {Array|string} collection List of tools, see above
1385 * @param {Object} [used] Object containing information about used tools, see above
1386 * @return {string[]} List of extracted tool names
1387 */
1388 OO.ui.ToolFactory.prototype.extract = function ( collection, used ) {
1389 var i, len, item, name, tool,
1390 names = [];
1391
1392 collection = !Array.isArray( collection ) ? [ collection ] : collection;
1393
1394 for ( i = 0, len = collection.length; i < len; i++ ) {
1395 item = collection[ i ];
1396 if ( item === '*' ) {
1397 for ( name in this.registry ) {
1398 tool = this.registry[ name ];
1399 if (
1400 // Only add tools by group name when auto-add is enabled
1401 tool.static.autoAddToCatchall &&
1402 // Exclude already used tools
1403 ( !used || !used[ name ] )
1404 ) {
1405 names.push( name );
1406 if ( used ) {
1407 used[ name ] = true;
1408 }
1409 }
1410 }
1411 } else {
1412 // Allow plain strings as shorthand for named tools
1413 if ( typeof item === 'string' ) {
1414 item = { name: item };
1415 }
1416 if ( OO.isPlainObject( item ) ) {
1417 if ( item.group ) {
1418 for ( name in this.registry ) {
1419 tool = this.registry[ name ];
1420 if (
1421 // Include tools with matching group
1422 tool.static.group === item.group &&
1423 // Only add tools by group name when auto-add is enabled
1424 tool.static.autoAddToGroup &&
1425 // Exclude already used tools
1426 ( !used || !used[ name ] )
1427 ) {
1428 names.push( name );
1429 if ( used ) {
1430 used[ name ] = true;
1431 }
1432 }
1433 }
1434 // Include tools with matching name and exclude already used tools
1435 } else if ( item.name && ( !used || !used[ item.name ] ) ) {
1436 names.push( item.name );
1437 if ( used ) {
1438 used[ item.name ] = true;
1439 }
1440 }
1441 }
1442 }
1443 }
1444 return names;
1445 };
1446
1447 /**
1448 * ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes must
1449 * specify a symbolic name and be registered with the factory. The following classes are registered by
1450 * default:
1451 *
1452 * - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
1453 * - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
1454 * - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
1455 *
1456 * See {@link OO.ui.Toolbar toolbars} for an example.
1457 *
1458 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1459 *
1460 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1461 *
1462 * @class
1463 * @extends OO.Factory
1464 * @constructor
1465 */
1466 OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() {
1467 var i, l, defaultClasses;
1468 // Parent constructor
1469 OO.Factory.call( this );
1470
1471 defaultClasses = this.constructor.static.getDefaultClasses();
1472
1473 // Register default toolgroups
1474 for ( i = 0, l = defaultClasses.length; i < l; i++ ) {
1475 this.register( defaultClasses[ i ] );
1476 }
1477 };
1478
1479 /* Setup */
1480
1481 OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );
1482
1483 /* Static Methods */
1484
1485 /**
1486 * Get a default set of classes to be registered on construction.
1487 *
1488 * @return {Function[]} Default classes
1489 */
1490 OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
1491 return [
1492 OO.ui.BarToolGroup,
1493 OO.ui.ListToolGroup,
1494 OO.ui.MenuToolGroup
1495 ];
1496 };
1497
1498 /**
1499 * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}. Each popup tool is configured
1500 * 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
1501 * an #onSelect or #onUpdateState method, as these methods have been implemented already.
1502 *
1503 * // Example of a popup tool. When selected, a popup tool displays
1504 * // a popup window.
1505 * function HelpTool( toolGroup, config ) {
1506 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1507 * padded: true,
1508 * label: 'Help',
1509 * head: true
1510 * } }, config ) );
1511 * this.popup.$body.append( '<p>I am helpful!</p>' );
1512 * };
1513 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1514 * HelpTool.static.name = 'help';
1515 * HelpTool.static.icon = 'help';
1516 * HelpTool.static.title = 'Help';
1517 * toolFactory.register( HelpTool );
1518 *
1519 * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about
1520 * toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1521 *
1522 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1523 *
1524 * @abstract
1525 * @class
1526 * @extends OO.ui.Tool
1527 * @mixins OO.ui.mixin.PopupElement
1528 *
1529 * @constructor
1530 * @param {OO.ui.ToolGroup} toolGroup
1531 * @param {Object} [config] Configuration options
1532 */
1533 OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
1534 // Allow passing positional parameters inside the config object
1535 if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
1536 config = toolGroup;
1537 toolGroup = config.toolGroup;
1538 }
1539
1540 // Parent constructor
1541 OO.ui.PopupTool.parent.call( this, toolGroup, config );
1542
1543 // Mixin constructors
1544 OO.ui.mixin.PopupElement.call( this, config );
1545
1546 // Events
1547 this.popup.connect( this, { toggle: 'onPopupToggle' } );
1548
1549 // Initialization
1550 this.popup.setPosition( toolGroup.getToolbar().position === 'bottom' ? 'above' : 'below' );
1551 this.$element.addClass( 'oo-ui-popupTool' );
1552 this.popup.$element.addClass( 'oo-ui-popupTool-popup' );
1553 this.toolbar.$popups.append( this.popup.$element );
1554 };
1555
1556 /* Setup */
1557
1558 OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
1559 OO.mixinClass( OO.ui.PopupTool, OO.ui.mixin.PopupElement );
1560
1561 /* Methods */
1562
1563 /**
1564 * Handle the tool being selected.
1565 *
1566 * @inheritdoc
1567 */
1568 OO.ui.PopupTool.prototype.onSelect = function () {
1569 if ( !this.isDisabled() ) {
1570 this.popup.toggle();
1571 }
1572 return false;
1573 };
1574
1575 /**
1576 * Handle the toolbar state being updated.
1577 *
1578 * @inheritdoc
1579 */
1580 OO.ui.PopupTool.prototype.onUpdateState = function () {
1581 };
1582
1583 /**
1584 * Handle popup visibility being toggled.
1585 *
1586 * @param {boolean} isVisible
1587 */
1588 OO.ui.PopupTool.prototype.onPopupToggle = function ( isVisible ) {
1589 this.setActive( isVisible );
1590 this.toolGroup.emit( 'active', isVisible );
1591 };
1592
1593 /**
1594 * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools}
1595 * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used
1596 * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from
1597 * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list}
1598 * when the ToolGroupTool is selected.
1599 *
1600 * // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2', defined elsewhere.
1601 *
1602 * function SettingsTool() {
1603 * SettingsTool.parent.apply( this, arguments );
1604 * };
1605 * OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
1606 * SettingsTool.static.name = 'settings';
1607 * SettingsTool.static.title = 'Change settings';
1608 * SettingsTool.static.groupConfig = {
1609 * icon: 'settings',
1610 * label: 'ToolGroupTool',
1611 * include: [ 'setting1', 'setting2' ]
1612 * };
1613 * toolFactory.register( SettingsTool );
1614 *
1615 * For more information, please see the [OOUI documentation on MediaWiki][1].
1616 *
1617 * Please note that this implementation is subject to change per [T74159] [2].
1618 *
1619 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars#ToolGroupTool
1620 * [2]: https://phabricator.wikimedia.org/T74159
1621 *
1622 * @abstract
1623 * @class
1624 * @extends OO.ui.Tool
1625 *
1626 * @constructor
1627 * @param {OO.ui.ToolGroup} toolGroup
1628 * @param {Object} [config] Configuration options
1629 */
1630 OO.ui.ToolGroupTool = function OoUiToolGroupTool( toolGroup, config ) {
1631 // Allow passing positional parameters inside the config object
1632 if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
1633 config = toolGroup;
1634 toolGroup = config.toolGroup;
1635 }
1636
1637 // Parent constructor
1638 OO.ui.ToolGroupTool.parent.call( this, toolGroup, config );
1639
1640 // Properties
1641 this.innerToolGroup = this.createGroup( this.constructor.static.groupConfig );
1642
1643 // Events
1644 this.innerToolGroup.connect( this, {
1645 disable: 'onToolGroupDisable',
1646 // Re-emit active events from the innerToolGroup on the parent toolGroup
1647 active: this.toolGroup.emit.bind( this.toolGroup, 'active' )
1648 } );
1649
1650 // Initialization
1651 this.$link.remove();
1652 this.$element
1653 .addClass( 'oo-ui-toolGroupTool' )
1654 .append( this.innerToolGroup.$element );
1655 };
1656
1657 /* Setup */
1658
1659 OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool );
1660
1661 /* Static Properties */
1662
1663 /**
1664 * Toolgroup configuration.
1665 *
1666 * The toolgroup configuration consists of the tools to include, as well as an icon and label
1667 * to use for the bar item. Tools can be included by symbolic name, group, or with the
1668 * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information.
1669 *
1670 * @property {Object.<string,Array>}
1671 */
1672 OO.ui.ToolGroupTool.static.groupConfig = {};
1673
1674 /* Methods */
1675
1676 /**
1677 * Handle the tool being selected.
1678 *
1679 * @inheritdoc
1680 */
1681 OO.ui.ToolGroupTool.prototype.onSelect = function () {
1682 this.innerToolGroup.setActive( !this.innerToolGroup.active );
1683 return false;
1684 };
1685
1686 /**
1687 * Synchronize disabledness state of the tool with the inner toolgroup.
1688 *
1689 * @private
1690 * @param {boolean} disabled Element is disabled
1691 */
1692 OO.ui.ToolGroupTool.prototype.onToolGroupDisable = function ( disabled ) {
1693 this.setDisabled( disabled );
1694 };
1695
1696 /**
1697 * Handle the toolbar state being updated.
1698 *
1699 * @inheritdoc
1700 */
1701 OO.ui.ToolGroupTool.prototype.onUpdateState = function () {
1702 this.setActive( false );
1703 };
1704
1705 /**
1706 * Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
1707 *
1708 * @param {Object.<string,Array>} group Toolgroup configuration. Please see {@link OO.ui.ToolGroup toolgroup} for
1709 * more information.
1710 * @return {OO.ui.ListToolGroup}
1711 */
1712 OO.ui.ToolGroupTool.prototype.createGroup = function ( group ) {
1713 if ( group.include === '*' ) {
1714 // Apply defaults to catch-all groups
1715 if ( group.label === undefined ) {
1716 group.label = OO.ui.msg( 'ooui-toolbar-more' );
1717 }
1718 }
1719
1720 return this.toolbar.getToolGroupFactory().create( 'list', this.toolbar, group );
1721 };
1722
1723 /**
1724 * BarToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
1725 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
1726 * and {@link OO.ui.ListToolGroup ListToolGroup}). The {@link OO.ui.Tool tools} in a BarToolGroup are
1727 * displayed by icon in a single row. The title of the tool is displayed when users move the mouse over
1728 * the tool.
1729 *
1730 * BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar is
1731 * set up.
1732 *
1733 * @example
1734 * // Example of a BarToolGroup with two tools
1735 * var toolFactory = new OO.ui.ToolFactory();
1736 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
1737 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
1738 *
1739 * // We will be placing status text in this element when tools are used
1740 * var $area = $( '<p>' ).text( 'Example of a BarToolGroup with two tools.' );
1741 *
1742 * // Define the tools that we're going to place in our toolbar
1743 *
1744 * // Create a class inheriting from OO.ui.Tool
1745 * function SearchTool() {
1746 * SearchTool.parent.apply( this, arguments );
1747 * }
1748 * OO.inheritClass( SearchTool, OO.ui.Tool );
1749 * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
1750 * // of 'icon' and 'title' (displayed icon and text).
1751 * SearchTool.static.name = 'search';
1752 * SearchTool.static.icon = 'search';
1753 * SearchTool.static.title = 'Search...';
1754 * // Defines the action that will happen when this tool is selected (clicked).
1755 * SearchTool.prototype.onSelect = function () {
1756 * $area.text( 'Search tool clicked!' );
1757 * // Never display this tool as "active" (selected).
1758 * this.setActive( false );
1759 * };
1760 * SearchTool.prototype.onUpdateState = function () {};
1761 * // Make this tool available in our toolFactory and thus our toolbar
1762 * toolFactory.register( SearchTool );
1763 *
1764 * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
1765 * // little popup window (a PopupWidget).
1766 * function HelpTool( toolGroup, config ) {
1767 * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
1768 * padded: true,
1769 * label: 'Help',
1770 * head: true
1771 * } }, config ) );
1772 * this.popup.$body.append( '<p>I am helpful!</p>' );
1773 * }
1774 * OO.inheritClass( HelpTool, OO.ui.PopupTool );
1775 * HelpTool.static.name = 'help';
1776 * HelpTool.static.icon = 'help';
1777 * HelpTool.static.title = 'Help';
1778 * toolFactory.register( HelpTool );
1779 *
1780 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
1781 * // used once (but not all defined tools must be used).
1782 * toolbar.setup( [
1783 * {
1784 * // 'bar' tool groups display tools by icon only
1785 * type: 'bar',
1786 * include: [ 'search', 'help' ]
1787 * }
1788 * ] );
1789 *
1790 * // Create some UI around the toolbar and place it in the document
1791 * var frame = new OO.ui.PanelLayout( {
1792 * expanded: false,
1793 * framed: true
1794 * } );
1795 * var contentFrame = new OO.ui.PanelLayout( {
1796 * expanded: false,
1797 * padded: true
1798 * } );
1799 * frame.$element.append(
1800 * toolbar.$element,
1801 * contentFrame.$element.append( $area )
1802 * );
1803 * $( 'body' ).append( frame.$element );
1804 *
1805 * // Here is where the toolbar is actually built. This must be done after inserting it into the
1806 * // document.
1807 * toolbar.initialize();
1808 *
1809 * For more information about how to add tools to a bar tool group, please see {@link OO.ui.ToolGroup toolgroup}.
1810 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
1811 *
1812 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
1813 *
1814 * @class
1815 * @extends OO.ui.ToolGroup
1816 *
1817 * @constructor
1818 * @param {OO.ui.Toolbar} toolbar
1819 * @param {Object} [config] Configuration options
1820 */
1821 OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
1822 // Allow passing positional parameters inside the config object
1823 if ( OO.isPlainObject( toolbar ) && config === undefined ) {
1824 config = toolbar;
1825 toolbar = config.toolbar;
1826 }
1827
1828 // Parent constructor
1829 OO.ui.BarToolGroup.parent.call( this, toolbar, config );
1830
1831 // Initialization
1832 this.$element.addClass( 'oo-ui-barToolGroup' );
1833 this.$group.addClass( 'oo-ui-barToolGroup-tools' );
1834 };
1835
1836 /* Setup */
1837
1838 OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );
1839
1840 /* Static Properties */
1841
1842 /**
1843 * @static
1844 * @inheritdoc
1845 */
1846 OO.ui.BarToolGroup.static.titleTooltips = true;
1847
1848 /**
1849 * @static
1850 * @inheritdoc
1851 */
1852 OO.ui.BarToolGroup.static.accelTooltips = true;
1853
1854 /**
1855 * @static
1856 * @inheritdoc
1857 */
1858 OO.ui.BarToolGroup.static.name = 'bar';
1859
1860 /**
1861 * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup}
1862 * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup--an overlaid menu or list of tools with an
1863 * optional icon and label. This class can be used for other base classes that also use this functionality.
1864 *
1865 * @abstract
1866 * @class
1867 * @extends OO.ui.ToolGroup
1868 * @mixins OO.ui.mixin.IconElement
1869 * @mixins OO.ui.mixin.IndicatorElement
1870 * @mixins OO.ui.mixin.LabelElement
1871 * @mixins OO.ui.mixin.TitledElement
1872 * @mixins OO.ui.mixin.FlaggedElement
1873 * @mixins OO.ui.mixin.ClippableElement
1874 * @mixins OO.ui.mixin.FloatableElement
1875 * @mixins OO.ui.mixin.TabIndexedElement
1876 *
1877 * @constructor
1878 * @param {OO.ui.Toolbar} toolbar
1879 * @param {Object} [config] Configuration options
1880 * @cfg {string} [header] Text to display at the top of the popup
1881 */
1882 OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
1883 // Allow passing positional parameters inside the config object
1884 if ( OO.isPlainObject( toolbar ) && config === undefined ) {
1885 config = toolbar;
1886 toolbar = config.toolbar;
1887 }
1888
1889 // Configuration initialization
1890 config = $.extend( {
1891 indicator: config.indicator === undefined ? ( toolbar.position === 'bottom' ? 'up' : 'down' ) : config.indicator
1892 }, config );
1893
1894 // Parent constructor
1895 OO.ui.PopupToolGroup.parent.call( this, toolbar, config );
1896
1897 // Properties
1898 this.active = false;
1899 this.dragging = false;
1900 this.onBlurHandler = this.onBlur.bind( this );
1901 this.$handle = $( '<span>' );
1902
1903 // Mixin constructors
1904 OO.ui.mixin.IconElement.call( this, config );
1905 OO.ui.mixin.IndicatorElement.call( this, config );
1906 OO.ui.mixin.LabelElement.call( this, config );
1907 OO.ui.mixin.TitledElement.call( this, config );
1908 OO.ui.mixin.FlaggedElement.call( this, config );
1909 OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
1910 OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, {
1911 $floatable: this.$group,
1912 $floatableContainer: this.$handle,
1913 hideWhenOutOfView: false,
1914 verticalPosition: this.toolbar.position === 'bottom' ? 'above' : 'below'
1915 } ) );
1916 OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
1917
1918 // Events
1919 this.$handle.on( {
1920 keydown: this.onHandleMouseKeyDown.bind( this ),
1921 keyup: this.onHandleMouseKeyUp.bind( this ),
1922 mousedown: this.onHandleMouseKeyDown.bind( this ),
1923 mouseup: this.onHandleMouseKeyUp.bind( this )
1924 } );
1925
1926 // Initialization
1927 this.$handle
1928 .addClass( 'oo-ui-popupToolGroup-handle' )
1929 .attr( 'role', 'button' )
1930 .append( this.$icon, this.$label, this.$indicator );
1931 // If the pop-up should have a header, add it to the top of the toolGroup.
1932 // Note: If this feature is useful for other widgets, we could abstract it into an
1933 // OO.ui.HeaderedElement mixin constructor.
1934 if ( config.header !== undefined ) {
1935 this.$group
1936 .prepend( $( '<span>' )
1937 .addClass( 'oo-ui-popupToolGroup-header' )
1938 .text( config.header )
1939 );
1940 }
1941 this.$element
1942 .addClass( 'oo-ui-popupToolGroup' )
1943 .prepend( this.$handle );
1944 this.$group.addClass( 'oo-ui-popupToolGroup-tools' );
1945 this.toolbar.$popups.append( this.$group );
1946 };
1947
1948 /* Setup */
1949
1950 OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup );
1951 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IconElement );
1952 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IndicatorElement );
1953 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.LabelElement );
1954 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TitledElement );
1955 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FlaggedElement );
1956 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.ClippableElement );
1957 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FloatableElement );
1958 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TabIndexedElement );
1959
1960 /* Methods */
1961
1962 /**
1963 * @inheritdoc
1964 */
1965 OO.ui.PopupToolGroup.prototype.setDisabled = function () {
1966 // Parent method
1967 OO.ui.PopupToolGroup.parent.prototype.setDisabled.apply( this, arguments );
1968
1969 if ( this.isDisabled() && this.isElementAttached() ) {
1970 this.setActive( false );
1971 }
1972 };
1973
1974 /**
1975 * Handle focus being lost.
1976 *
1977 * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
1978 *
1979 * @protected
1980 * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
1981 */
1982 OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
1983 var $target = $( e.target );
1984 // Only deactivate when clicking outside the dropdown element
1985 if ( $target.closest( '.oo-ui-popupToolGroup' )[ 0 ] === this.$element[ 0 ] ) {
1986 return;
1987 }
1988 if ( $target.closest( '.oo-ui-popupToolGroup-tools' )[ 0 ] === this.$group[ 0 ] ) {
1989 return;
1990 }
1991 this.setActive( false );
1992 };
1993
1994 /**
1995 * @inheritdoc
1996 */
1997 OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) {
1998 // Only close toolgroup when a tool was actually selected
1999 if (
2000 !this.isDisabled() && this.pressed && this.pressed === this.findTargetTool( e ) &&
2001 ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
2002 ) {
2003 this.setActive( false );
2004 }
2005 return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
2006 };
2007
2008 /**
2009 * @inheritdoc
2010 */
2011 OO.ui.PopupToolGroup.prototype.onMouseKeyDown = function ( e ) {
2012 var $focused, $firstFocusable, $lastFocusable;
2013 // Shift-Tab on the first tool in the group jumps to the handle.
2014 // Tab on the last tool in the group jumps to the next group.
2015 if ( !this.isDisabled() && e.which === OO.ui.Keys.TAB ) {
2016 // (We can't use this.items because ListToolGroup inserts the extra fake expand/collapse tool.)
2017 $focused = $( document.activeElement );
2018 $firstFocusable = OO.ui.findFocusable( this.$group );
2019 if ( $focused[ 0 ] === $firstFocusable[ 0 ] && e.shiftKey ) {
2020 this.$handle.focus();
2021 return false;
2022 }
2023 $lastFocusable = OO.ui.findFocusable( this.$group, true );
2024 if ( $focused[ 0 ] === $lastFocusable[ 0 ] && !e.shiftKey ) {
2025 // Focus this group's handle and let the browser's tab handling happen (no 'return false').
2026 // This way we don't have to fiddle with other ToolGroups' business, or worry what to do
2027 // if the next group is not a PopupToolGroup or doesn't exist at all.
2028 this.$handle.focus();
2029 // Close the popup so that we don't move back inside it (if this is the last group).
2030 this.setActive( false );
2031 }
2032 }
2033 return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyDown.call( this, e );
2034 };
2035
2036 /**
2037 * Handle mouse up and key up events.
2038 *
2039 * @protected
2040 * @param {jQuery.Event} e Mouse up or key up event
2041 */
2042 OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) {
2043 if (
2044 !this.isDisabled() &&
2045 ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
2046 ) {
2047 return false;
2048 }
2049 };
2050
2051 /**
2052 * Handle mouse down and key down events.
2053 *
2054 * @protected
2055 * @param {jQuery.Event} e Mouse down or key down event
2056 */
2057 OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) {
2058 var $focusable;
2059 if ( !this.isDisabled() ) {
2060 // Tab on the handle jumps to the first tool in the group (if the popup is open).
2061 if ( e.which === OO.ui.Keys.TAB && !e.shiftKey ) {
2062 $focusable = OO.ui.findFocusable( this.$group );
2063 if ( $focusable.length ) {
2064 $focusable.focus();
2065 return false;
2066 }
2067 }
2068 if ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) {
2069 this.setActive( !this.active );
2070 return false;
2071 }
2072 }
2073 };
2074
2075 /**
2076 * Check if the tool group is active.
2077 *
2078 * @return {boolean} Tool group is active
2079 */
2080 OO.ui.PopupToolGroup.prototype.isActive = function () {
2081 return this.active;
2082 };
2083
2084 /**
2085 * Switch into 'active' mode.
2086 *
2087 * When active, the popup is visible. A mouseup event anywhere in the document will trigger
2088 * deactivation.
2089 *
2090 * @param {boolean} value The active state to set
2091 * @fires active
2092 */
2093 OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
2094 var containerWidth, containerLeft;
2095 value = !!value;
2096 if ( this.active !== value ) {
2097 this.active = value;
2098 if ( value ) {
2099 this.getElementDocument().addEventListener( 'mouseup', this.onBlurHandler, true );
2100 this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true );
2101
2102 this.$clippable.css( 'left', '' );
2103 this.$element.addClass( 'oo-ui-popupToolGroup-active' );
2104 this.$group.addClass( 'oo-ui-popupToolGroup-active-tools' );
2105 this.togglePositioning( true );
2106 this.toggleClipping( true );
2107
2108 // Try anchoring the popup to the left first
2109 this.setHorizontalPosition( 'start' );
2110
2111 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2112 // Anchoring to the left caused the popup to clip, so anchor it to the right instead
2113 this.setHorizontalPosition( 'end' );
2114 }
2115 if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
2116 // Anchoring to the right also caused the popup to clip, so just make it fill the container
2117 containerWidth = this.$clippableScrollableContainer.width();
2118 containerLeft = this.$clippableScrollableContainer[ 0 ] === document.documentElement ?
2119 0 :
2120 this.$clippableScrollableContainer.offset().left;
2121
2122 this.toggleClipping( false );
2123 this.setHorizontalPosition( 'start' );
2124
2125 this.$clippable.css( {
2126 'margin-left': -( this.$element.offset().left - containerLeft ),
2127 width: containerWidth
2128 } );
2129 }
2130 } else {
2131 this.getElementDocument().removeEventListener( 'mouseup', this.onBlurHandler, true );
2132 this.getElementDocument().removeEventListener( 'keyup', this.onBlurHandler, true );
2133 this.$element.removeClass( 'oo-ui-popupToolGroup-active' );
2134 this.$group.removeClass( 'oo-ui-popupToolGroup-active-tools' );
2135 this.togglePositioning( false );
2136 this.toggleClipping( false );
2137 }
2138 this.emit( 'active', this.active );
2139 this.updateThemeClasses();
2140 }
2141 };
2142
2143 /**
2144 * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2145 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup}
2146 * and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed
2147 * by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured
2148 * with a label, icon, indicator, header, and title.
2149 *
2150 * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that
2151 * users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits
2152 * users to collapse the list again.
2153 *
2154 * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory
2155 * requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more
2156 * information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
2157 *
2158 * @example
2159 * // Example of a ListToolGroup
2160 * var toolFactory = new OO.ui.ToolFactory();
2161 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2162 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2163 *
2164 * // Configure and register two tools
2165 * function SettingsTool() {
2166 * SettingsTool.parent.apply( this, arguments );
2167 * }
2168 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2169 * SettingsTool.static.name = 'settings';
2170 * SettingsTool.static.icon = 'settings';
2171 * SettingsTool.static.title = 'Change settings';
2172 * SettingsTool.prototype.onSelect = function () {
2173 * this.setActive( false );
2174 * };
2175 * SettingsTool.prototype.onUpdateState = function () {};
2176 * toolFactory.register( SettingsTool );
2177 * // Register two more tools, nothing interesting here
2178 * function StuffTool() {
2179 * StuffTool.parent.apply( this, arguments );
2180 * }
2181 * OO.inheritClass( StuffTool, OO.ui.Tool );
2182 * StuffTool.static.name = 'stuff';
2183 * StuffTool.static.icon = 'search';
2184 * StuffTool.static.title = 'Change the world';
2185 * StuffTool.prototype.onSelect = function () {
2186 * this.setActive( false );
2187 * };
2188 * StuffTool.prototype.onUpdateState = function () {};
2189 * toolFactory.register( StuffTool );
2190 * toolbar.setup( [
2191 * {
2192 * // Configurations for list toolgroup.
2193 * type: 'list',
2194 * label: 'ListToolGroup',
2195 * icon: 'ellipsis',
2196 * title: 'This is the title, displayed when user moves the mouse over the list toolgroup',
2197 * header: 'This is the header',
2198 * include: [ 'settings', 'stuff' ],
2199 * allowCollapse: ['stuff']
2200 * }
2201 * ] );
2202 *
2203 * // Create some UI around the toolbar and place it in the document
2204 * var frame = new OO.ui.PanelLayout( {
2205 * expanded: false,
2206 * framed: true
2207 * } );
2208 * frame.$element.append(
2209 * toolbar.$element
2210 * );
2211 * $( 'body' ).append( frame.$element );
2212 * // Build the toolbar. This must be done after the toolbar has been appended to the document.
2213 * toolbar.initialize();
2214 *
2215 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1].
2216 *
2217 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2218 *
2219 * @class
2220 * @extends OO.ui.PopupToolGroup
2221 *
2222 * @constructor
2223 * @param {OO.ui.Toolbar} toolbar
2224 * @param {Object} [config] Configuration options
2225 * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools
2226 * will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If
2227 * the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that
2228 * are included in the toolgroup, but are not designated as collapsible, will always be displayed.
2229 * To open a collapsible list in its expanded state, set #expanded to 'true'.
2230 * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible.
2231 * Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened.
2232 * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have
2233 * been designated as collapsible. When expanded is set to true, all tools in the group will be displayed
2234 * when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom.
2235 */
2236 OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
2237 // Allow passing positional parameters inside the config object
2238 if ( OO.isPlainObject( toolbar ) && config === undefined ) {
2239 config = toolbar;
2240 toolbar = config.toolbar;
2241 }
2242
2243 // Configuration initialization
2244 config = config || {};
2245
2246 // Properties (must be set before parent constructor, which calls #populate)
2247 this.allowCollapse = config.allowCollapse;
2248 this.forceExpand = config.forceExpand;
2249 this.expanded = config.expanded !== undefined ? config.expanded : false;
2250 this.collapsibleTools = [];
2251
2252 // Parent constructor
2253 OO.ui.ListToolGroup.parent.call( this, toolbar, config );
2254
2255 // Initialization
2256 this.$element.addClass( 'oo-ui-listToolGroup' );
2257 this.$group.addClass( 'oo-ui-listToolGroup-tools' );
2258 };
2259
2260 /* Setup */
2261
2262 OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup );
2263
2264 /* Static Properties */
2265
2266 /**
2267 * @static
2268 * @inheritdoc
2269 */
2270 OO.ui.ListToolGroup.static.name = 'list';
2271
2272 /* Methods */
2273
2274 /**
2275 * @inheritdoc
2276 */
2277 OO.ui.ListToolGroup.prototype.populate = function () {
2278 var i, len, allowCollapse = [];
2279
2280 OO.ui.ListToolGroup.parent.prototype.populate.call( this );
2281
2282 // Update the list of collapsible tools
2283 if ( this.allowCollapse !== undefined ) {
2284 allowCollapse = this.allowCollapse;
2285 } else if ( this.forceExpand !== undefined ) {
2286 allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand );
2287 }
2288
2289 this.collapsibleTools = [];
2290 for ( i = 0, len = allowCollapse.length; i < len; i++ ) {
2291 if ( this.tools[ allowCollapse[ i ] ] !== undefined ) {
2292 this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] );
2293 }
2294 }
2295
2296 // Keep at the end, even when tools are added
2297 this.$group.append( this.getExpandCollapseTool().$element );
2298
2299 this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 );
2300 this.updateCollapsibleState();
2301 };
2302
2303 /**
2304 * Get the expand/collapse tool for this group
2305 *
2306 * @return {OO.ui.Tool} Expand collapse tool
2307 */
2308 OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () {
2309 var ExpandCollapseTool;
2310 if ( this.expandCollapseTool === undefined ) {
2311 ExpandCollapseTool = function () {
2312 ExpandCollapseTool.parent.apply( this, arguments );
2313 };
2314
2315 OO.inheritClass( ExpandCollapseTool, OO.ui.Tool );
2316
2317 ExpandCollapseTool.prototype.onSelect = function () {
2318 this.toolGroup.expanded = !this.toolGroup.expanded;
2319 this.toolGroup.updateCollapsibleState();
2320 this.setActive( false );
2321 };
2322 ExpandCollapseTool.prototype.onUpdateState = function () {
2323 // Do nothing. Tool interface requires an implementation of this function.
2324 };
2325
2326 ExpandCollapseTool.static.name = 'more-fewer';
2327
2328 this.expandCollapseTool = new ExpandCollapseTool( this );
2329 }
2330 return this.expandCollapseTool;
2331 };
2332
2333 /**
2334 * @inheritdoc
2335 */
2336 OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) {
2337 // Do not close the popup when the user wants to show more/fewer tools
2338 if (
2339 $( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length &&
2340 ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
2341 ) {
2342 // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which
2343 // hides the popup list when a tool is selected) and call ToolGroup's implementation directly.
2344 return OO.ui.ListToolGroup.parent.parent.prototype.onMouseKeyUp.call( this, e );
2345 } else {
2346 return OO.ui.ListToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
2347 }
2348 };
2349
2350 OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () {
2351 var i, icon, len;
2352
2353 if ( this.toolbar.position !== 'bottom' ) {
2354 icon = this.expanded ? 'collapse' : 'expand';
2355 } else {
2356 icon = this.expanded ? 'expand' : 'collapse';
2357 }
2358
2359 this.getExpandCollapseTool()
2360 .setIcon( icon )
2361 .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
2362
2363 for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
2364 this.collapsibleTools[ i ].toggle( this.expanded );
2365 }
2366
2367 // Re-evaluate clipping, because our height has changed
2368 this.clip();
2369 };
2370
2371 /**
2372 * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to
2373 * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.BarToolGroup BarToolGroup}
2374 * and {@link OO.ui.ListToolGroup ListToolGroup}). MenuToolGroups contain selectable {@link OO.ui.Tool tools},
2375 * which are displayed by label in a dropdown menu. The tool's title is used as the label text, and the
2376 * menu label is updated to reflect which tool or tools are currently selected. If no tools are selected,
2377 * the menu label is empty. The menu can be configured with an indicator, icon, title, and/or header.
2378 *
2379 * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
2380 * is set up.
2381 *
2382 * @example
2383 * // Example of a MenuToolGroup
2384 * var toolFactory = new OO.ui.ToolFactory();
2385 * var toolGroupFactory = new OO.ui.ToolGroupFactory();
2386 * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
2387 *
2388 * // We will be placing status text in this element when tools are used
2389 * var $area = $( '<p>' ).text( 'An example of a MenuToolGroup. Select a tool from the dropdown menu.' );
2390 *
2391 * // Define the tools that we're going to place in our toolbar
2392 *
2393 * function SettingsTool() {
2394 * SettingsTool.parent.apply( this, arguments );
2395 * this.reallyActive = false;
2396 * }
2397 * OO.inheritClass( SettingsTool, OO.ui.Tool );
2398 * SettingsTool.static.name = 'settings';
2399 * SettingsTool.static.icon = 'settings';
2400 * SettingsTool.static.title = 'Change settings';
2401 * SettingsTool.prototype.onSelect = function () {
2402 * $area.text( 'Settings tool clicked!' );
2403 * // Toggle the active state on each click
2404 * this.reallyActive = !this.reallyActive;
2405 * this.setActive( this.reallyActive );
2406 * // To update the menu label
2407 * this.toolbar.emit( 'updateState' );
2408 * };
2409 * SettingsTool.prototype.onUpdateState = function () {};
2410 * toolFactory.register( SettingsTool );
2411 *
2412 * function StuffTool() {
2413 * StuffTool.parent.apply( this, arguments );
2414 * this.reallyActive = false;
2415 * }
2416 * OO.inheritClass( StuffTool, OO.ui.Tool );
2417 * StuffTool.static.name = 'stuff';
2418 * StuffTool.static.icon = 'ellipsis';
2419 * StuffTool.static.title = 'More stuff';
2420 * StuffTool.prototype.onSelect = function () {
2421 * $area.text( 'More stuff tool clicked!' );
2422 * // Toggle the active state on each click
2423 * this.reallyActive = !this.reallyActive;
2424 * this.setActive( this.reallyActive );
2425 * // To update the menu label
2426 * this.toolbar.emit( 'updateState' );
2427 * };
2428 * StuffTool.prototype.onUpdateState = function () {};
2429 * toolFactory.register( StuffTool );
2430 *
2431 * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
2432 * // used once (but not all defined tools must be used).
2433 * toolbar.setup( [
2434 * {
2435 * type: 'menu',
2436 * header: 'This is the (optional) header',
2437 * title: 'This is the (optional) title',
2438 * include: [ 'settings', 'stuff' ]
2439 * }
2440 * ] );
2441 *
2442 * // Create some UI around the toolbar and place it in the document
2443 * var frame = new OO.ui.PanelLayout( {
2444 * expanded: false,
2445 * framed: true
2446 * } );
2447 * var contentFrame = new OO.ui.PanelLayout( {
2448 * expanded: false,
2449 * padded: true
2450 * } );
2451 * frame.$element.append(
2452 * toolbar.$element,
2453 * contentFrame.$element.append( $area )
2454 * );
2455 * $( 'body' ).append( frame.$element );
2456 *
2457 * // Here is where the toolbar is actually built. This must be done after inserting it into the
2458 * // document.
2459 * toolbar.initialize();
2460 * toolbar.emit( 'updateState' );
2461 *
2462 * For more information about how to add tools to a MenuToolGroup, please see {@link OO.ui.ToolGroup toolgroup}.
2463 * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki] [1].
2464 *
2465 * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
2466 *
2467 * @class
2468 * @extends OO.ui.PopupToolGroup
2469 *
2470 * @constructor
2471 * @param {OO.ui.Toolbar} toolbar
2472 * @param {Object} [config] Configuration options
2473 */
2474 OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
2475 // Allow passing positional parameters inside the config object
2476 if ( OO.isPlainObject( toolbar ) && config === undefined ) {
2477 config = toolbar;
2478 toolbar = config.toolbar;
2479 }
2480
2481 // Configuration initialization
2482 config = config || {};
2483
2484 // Parent constructor
2485 OO.ui.MenuToolGroup.parent.call( this, toolbar, config );
2486
2487 // Events
2488 this.toolbar.connect( this, { updateState: 'onUpdateState' } );
2489
2490 // Initialization
2491 this.$element.addClass( 'oo-ui-menuToolGroup' );
2492 this.$group.addClass( 'oo-ui-menuToolGroup-tools' );
2493 };
2494
2495 /* Setup */
2496
2497 OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );
2498
2499 /* Static Properties */
2500
2501 /**
2502 * @static
2503 * @inheritdoc
2504 */
2505 OO.ui.MenuToolGroup.static.name = 'menu';
2506
2507 /* Methods */
2508
2509 /**
2510 * Handle the toolbar state being updated.
2511 *
2512 * When the state changes, the title of each active item in the menu will be joined together and
2513 * used as a label for the group. The label will be empty if none of the items are active.
2514 *
2515 * @private
2516 */
2517 OO.ui.MenuToolGroup.prototype.onUpdateState = function () {
2518 var name,
2519 labelTexts = [];
2520
2521 for ( name in this.tools ) {
2522 if ( this.tools[ name ].isActive() ) {
2523 labelTexts.push( this.tools[ name ].getTitle() );
2524 }
2525 }
2526
2527 this.setLabel( labelTexts.join( ', ' ) || ' ' );
2528 };
2529
2530 }( OO ) );
2531
2532 //# sourceMappingURL=oojs-ui-toolbars.js.map.json