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