/** * Collection of tool groups. * * The following is a minimal example using several tools and tool groups. * * @example * // Create the toolbar * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // We will be placing status text in this element when tools are used * var $area = $( '

' ).text( 'Toolbar example' ); * * // Define the tools that we're going to place in our toolbar * * // Create a class inheriting from OO.ui.Tool * function PictureTool() { * PictureTool.super.apply( this, arguments ); * } * OO.inheritClass( PictureTool, OO.ui.Tool ); * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one * // of 'icon' and 'title' (displayed icon and text). * PictureTool.static.name = 'picture'; * PictureTool.static.icon = 'picture'; * PictureTool.static.title = 'Insert picture'; * // Defines the action that will happen when this tool is selected (clicked). * PictureTool.prototype.onSelect = function () { * $area.text( 'Picture tool clicked!' ); * // Never display this tool as "active" (selected). * this.setActive( false ); * }; * // Make this tool available in our toolFactory and thus our toolbar * toolFactory.register( PictureTool ); * * // Register two more tools, nothing interesting here * function SettingsTool() { * SettingsTool.super.apply( this, arguments ); * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'settings'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * $area.text( 'Settings tool clicked!' ); * this.setActive( false ); * }; * toolFactory.register( SettingsTool ); * * // Register two more tools, nothing interesting here * function StuffTool() { * StuffTool.super.apply( this, arguments ); * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'ellipsis'; * StuffTool.static.title = 'More stuff'; * StuffTool.prototype.onSelect = function () { * $area.text( 'More stuff tool clicked!' ); * this.setActive( false ); * }; * toolFactory.register( StuffTool ); * * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a * // little popup window (a PopupWidget). * function HelpTool( toolGroup, config ) { * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { * padded: true, * label: 'Help', * head: true * } }, config ) ); * this.popup.$body.append( '

I am helpful!

' ); * } * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * // 'bar' tool groups display tools' icons only, side-by-side. * type: 'bar', * include: [ 'picture', 'help' ] * }, * { * // 'list' tool groups display both the titles and icons, in a dropdown list. * type: 'list', * indicator: 'down', * label: 'More', * include: [ 'settings', 'stuff' ] * } * // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed * // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here, * // since it's more complicated to use. (See the next example snippet on this page.) * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * * The following example extends the previous one to illustrate 'menu' tool groups and the usage of * 'updateState' event. * * @example * // Create the toolbar * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // We will be placing status text in this element when tools are used * var $area = $( '

' ).text( 'Toolbar example' ); * * // Define the tools that we're going to place in our toolbar * * // Create a class inheriting from OO.ui.Tool * function PictureTool() { * PictureTool.super.apply( this, arguments ); * } * OO.inheritClass( PictureTool, OO.ui.Tool ); * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one * // of 'icon' and 'title' (displayed icon and text). * PictureTool.static.name = 'picture'; * PictureTool.static.icon = 'picture'; * PictureTool.static.title = 'Insert picture'; * // Defines the action that will happen when this tool is selected (clicked). * PictureTool.prototype.onSelect = function () { * $area.text( 'Picture tool clicked!' ); * // Never display this tool as "active" (selected). * this.setActive( false ); * }; * // The toolbar can be synchronized with the state of some external stuff, like a text * // editor's editing area, highlighting the tools (e.g. a 'bold' tool would be shown as active * // when the text cursor was inside bolded text). Here we simply disable this feature. * PictureTool.prototype.onUpdateState = function () { * }; * // Make this tool available in our toolFactory and thus our toolbar * toolFactory.register( PictureTool ); * * // Register two more tools, nothing interesting here * function SettingsTool() { * SettingsTool.super.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'settings'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * $area.text( 'Settings tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * SettingsTool.prototype.onUpdateState = function () { * }; * toolFactory.register( SettingsTool ); * * // Register two more tools, nothing interesting here * function StuffTool() { * StuffTool.super.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'ellipsis'; * StuffTool.static.title = 'More stuff'; * StuffTool.prototype.onSelect = function () { * $area.text( 'More stuff tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * StuffTool.prototype.onUpdateState = function () { * }; * toolFactory.register( StuffTool ); * * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a * // little popup window (a PopupWidget). 'onUpdateState' is also already implemented. * function HelpTool( toolGroup, config ) { * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { * padded: true, * label: 'Help', * head: true * } }, config ) ); * this.popup.$body.append( '

I am helpful!

' ); * } * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * // 'bar' tool groups display tools' icons only, side-by-side. * type: 'bar', * include: [ 'picture', 'help' ] * }, * { * // 'menu' tool groups display both the titles and icons, in a dropdown menu. * // Menu label indicates which items are selected. * type: 'menu', * indicator: 'down', * include: [ 'settings', 'stuff' ] * } * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * toolbar.emit( 'updateState' ); * * @class * @extends OO.ui.Element * @mixins OO.EventEmitter * @mixins OO.ui.GroupElement * * @constructor * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating tool groups * @param {Object} [config] Configuration options * @cfg {boolean} [actions] Add an actions section opposite to the tools * @cfg {boolean} [shadow] Add a shadow below the toolbar */ OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) { // Allow passing positional parameters inside the config object if ( OO.isPlainObject( toolFactory ) && config === undefined ) { config = toolFactory; toolFactory = config.toolFactory; toolGroupFactory = config.toolGroupFactory; } // Configuration initialization config = config || {}; // Parent constructor OO.ui.Toolbar.super.call( this, config ); // Mixin constructors OO.EventEmitter.call( this ); OO.ui.GroupElement.call( this, config ); // Properties this.toolFactory = toolFactory; this.toolGroupFactory = toolGroupFactory; this.groups = []; this.tools = {}; this.$bar = $( '
' ); this.$actions = $( '
' ); this.initialized = false; this.onWindowResizeHandler = this.onWindowResize.bind( this ); // Events this.$element .add( this.$bar ).add( this.$group ).add( this.$actions ) .on( 'mousedown keydown', this.onPointerDown.bind( this ) ); // Initialization this.$group.addClass( 'oo-ui-toolbar-tools' ); if ( config.actions ) { this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) ); } this.$bar .addClass( 'oo-ui-toolbar-bar' ) .append( this.$group, '
' ); if ( config.shadow ) { this.$bar.append( '
' ); } this.$element.addClass( 'oo-ui-toolbar' ).append( this.$bar ); }; /* Setup */ OO.inheritClass( OO.ui.Toolbar, OO.ui.Element ); OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter ); OO.mixinClass( OO.ui.Toolbar, OO.ui.GroupElement ); /* Methods */ /** * Get the tool factory. * * @return {OO.ui.ToolFactory} Tool factory */ OO.ui.Toolbar.prototype.getToolFactory = function () { return this.toolFactory; }; /** * Get the tool group factory. * * @return {OO.Factory} Tool group factory */ OO.ui.Toolbar.prototype.getToolGroupFactory = function () { return this.toolGroupFactory; }; /** * Handles mouse down events. * * @param {jQuery.Event} e Mouse down event */ OO.ui.Toolbar.prototype.onPointerDown = function ( e ) { var $closestWidgetToEvent = $( e.target ).closest( '.oo-ui-widget' ), $closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' ); if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) { return false; } }; /** * Handle window resize event. * * @private * @param {jQuery.Event} e Window resize event */ OO.ui.Toolbar.prototype.onWindowResize = function () { this.$element.toggleClass( 'oo-ui-toolbar-narrow', this.$bar.width() <= this.narrowThreshold ); }; /** * Sets up handles and preloads required information for the toolbar to work. * This must be called after it is attached to a visible document and before doing anything else. */ OO.ui.Toolbar.prototype.initialize = function () { this.initialized = true; this.narrowThreshold = this.$group.width() + this.$actions.width(); $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler ); this.onWindowResize(); }; /** * Setup toolbar. * * Tools can be specified in the following ways: * * - A specific tool: `{ name: 'tool-name' }` or `'tool-name'` * - All tools in a group: `{ group: 'group-name' }` * - All tools: `'*'` - Using this will make the group a list with a "More" label by default * * @param {Object.} groups List of tool group configurations * @param {Array|string} [groups.include] Tools to include * @param {Array|string} [groups.exclude] Tools to exclude * @param {Array|string} [groups.promote] Tools to promote to the beginning * @param {Array|string} [groups.demote] Tools to demote to the end */ OO.ui.Toolbar.prototype.setup = function ( groups ) { var i, len, type, group, items = [], defaultType = 'bar'; // Cleanup previous groups this.reset(); // Build out new groups for ( i = 0, len = groups.length; i < len; i++ ) { group = groups[ i ]; if ( group.include === '*' ) { // Apply defaults to catch-all groups if ( group.type === undefined ) { group.type = 'list'; } if ( group.label === undefined ) { group.label = OO.ui.msg( 'ooui-toolbar-more' ); } } // Check type has been registered type = this.getToolGroupFactory().lookup( group.type ) ? group.type : defaultType; items.push( this.getToolGroupFactory().create( type, this, group ) ); } this.addItems( items ); }; /** * Remove all tools and groups from the toolbar. */ OO.ui.Toolbar.prototype.reset = function () { var i, len; this.groups = []; this.tools = {}; for ( i = 0, len = this.items.length; i < len; i++ ) { this.items[ i ].destroy(); } this.clearItems(); }; /** * Destroys toolbar, removing event handlers and DOM elements. * * Call this whenever you are done using a toolbar. */ OO.ui.Toolbar.prototype.destroy = function () { $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler ); this.reset(); this.$element.remove(); }; /** * Check if tool has not been used yet. * * @param {string} name Symbolic name of tool * @return {boolean} Tool is available */ OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) { return !this.tools[ name ]; }; /** * Prevent tool from being used again. * * @param {OO.ui.Tool} tool Tool to reserve */ OO.ui.Toolbar.prototype.reserveTool = function ( tool ) { this.tools[ tool.getName() ] = tool; }; /** * Allow tool to be used again. * * @param {OO.ui.Tool} tool Tool to release */ OO.ui.Toolbar.prototype.releaseTool = function ( tool ) { delete this.tools[ tool.getName() ]; }; /** * Get accelerator label for tool. * * This is a stub that should be overridden to provide access to accelerator information. * * @param {string} name Symbolic name of tool * @return {string|undefined} Tool accelerator label if available */ OO.ui.Toolbar.prototype.getToolAccelerator = function () { return undefined; };