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