define([ 'jquery', 'aloha/core', 'ui/surface', 'ui/tab', 'ui/floating', 'ui/context', 'i18n!ui/nls/i18n', 'jqueryui' ], function ( $, Aloha, Surface, Tab, floating, Context, i18n ) { 'use strict'; /** * The toolbar is configured via `settings.toolbar` and is defined as an * array of tabs with component groups, where the groups are arrays of * controls. * * There are separate components for each context, but only the components * for the active context are shown. * * As a container for tabs, the toolbar serves to group together groups of * control components so that they can be shown and hidden together in their * feature/functional set. For example groups of table controls would be * placed in a table tab, groups of list controls in an image tab, and so * forth. * * Toolbar class and manager * * @class * @extends {Surface} */ var Toolbar = Surface.extend({ _moveTimeout: null, $_container: null, _tabBySlot: null, _tabs: [], /** * Toolbar constructor. * * @param {!Array.<(Object|Array|string)>} tabs * @constructor * @override */ _constructor: function (context, tabs) { var tabSettings, tabInstance, i, key; this._super(context); this.$element = $('
', {'class': 'aloha-ui aloha-ui-toolbar', 'unselectable': 'on'}); this.$_container = Tab.createContainer().appendTo(this.$element); this._tabBySlot = {}; for (i = 0; i < tabs.length; i++) { tabSettings = tabs[i]; tabInstance = new Tab(context, { label: i18n.t(tabSettings.label, tabSettings.label), showOn: tabSettings.showOn, container: this.$_container }, tabSettings.components); for (key in tabInstance._elemBySlot) { if (tabInstance._elemBySlot.hasOwnProperty(key)) { this._tabBySlot[key] = tabInstance; } } this._tabs.push({tab: tabInstance, settings: tabSettings}); } // Pinning behaviour is global in that if one toolbar is pinned, // then all other toolbars will be pinned to that position. floating.makeFloating(this, Toolbar); }, adoptInto: function (slot, component) { var tab = this._tabBySlot[slot]; return tab && tab.adoptInto(slot, component); }, getActiveContainer: function () { return this.$_container.data('aloha-active-container'); }, getContainers: function () { return this.$_container.data('aloha-tabs'); }, /** * Moves the toolbar into the optimal position near the active editable. * * @param {number} duration The length of time the moving animation * should run. */ _move: function (duration) { // We need to order the invocation of the floating animation to // occur after the the height of the toolbar's DOM has been // caluclated. var toolbar = this; if (toolbar._moveTimeout) { clearTimeout(toolbar._moveTimeout); } toolbar._moveTimeout = setTimeout(function () { toolbar._moveTimeout = null; if (Aloha.activeEditable && Toolbar.isFloatingMode) { floating.floatSurface( toolbar, Aloha.activeEditable, duration, Toolbar.setFloatingPosition ); } // 20ms should be small enough to appear instantaneous to the // user but large enough to avoid doing unnecessary work when // the selection changes multiple times within a short time // frame. }, 20); }, addPin: function () { var $pin = $('
'); var $element = this.$element; $element.find('.ui-tabs').append($pin); $element.find('.ui-tabs').hover(function () { $element.addClass('aloha-ui-hover'); }, function () { $element.removeClass('aloha-ui-hover'); }); if (!Toolbar.isFloatingMode) { $pin.addClass('aloha-ui-pin-down'); } var surface = this; $pin.click(function () { Toolbar.isFloatingMode = !Toolbar.isFloatingMode; var position; if (Toolbar.isFloatingMode) { position = { top: Toolbar.pinTop, left: Toolbar.pinLeft }; } else { position = surface.$element.offset(); position.top -= $(window).scrollTop(); } Toolbar.setFloatingPosition(position); floating.togglePinSurface(surface, position, Toolbar.isFloatingMode); }); }, /** * Shows the toolbar. */ show: function () { Toolbar.$surfaceContainer.children().detach(); Toolbar.$surfaceContainer.append(this.$element); Toolbar.$surfaceContainer.stop().fadeTo(200, 1); var position = Toolbar.getFloatingPosition(); this.$element.stop().css({ top: position.top, left: position.left }); this._move(); }, /** * Hides the toolbar. */ hide: function () { Toolbar.$surfaceContainer.stop().fadeOut(200, function () { Toolbar.$surfaceContainer.children().detach(); }); } }); $.extend(Toolbar, { /** * An element on which all toolbar surfaces are to be rendered on the * page. * @type {jQuery.} */ $surfaceContainer: null, /** * Whether or not floating toolbar surfaces should be pinned. * @type {boolean} */ isFloatingMode: true, /** * Left position of pinned toolbars. * @type {number} */ pinLeft: 0, /** * Top position of pinned toolbars. * @type {number} */ pinTop: 0, /** * Initializes the toolbar manager. Adds the surface container * element, and sets up floating behaviour settings. */ init: function () { // TODO should use context.js to get the context element Toolbar.$surfaceContainer = $('
', { 'class': 'aloha aloha-surface aloha-toolbar', 'unselectable': 'on' }).hide(); // In the built aloha.js, init will happend before the body has // finished loading, so we have to defer appending the element. $(function () { Toolbar.$surfaceContainer.appendTo('body'); }); Surface.trackRange(Toolbar.$surfaceContainer); var pinState = floating.getPinState(); Toolbar.pinTop = pinState.top; Toolbar.pinLeft = pinState.left; Toolbar.isFloatingMode = !pinState.isPinned; }, setFloatingPosition: function (position) { Toolbar.pinTop = position.top; Toolbar.pinLeft = position.left; }, getFloatingPosition: function () { return { top: Toolbar.pinTop, left: Toolbar.pinLeft }; } }); Toolbar.init(); return Toolbar; });