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