diff --git a/core/misc/debounce.js b/core/misc/debounce.js new file mode 100644 index 0000000..5896abe --- /dev/null +++ b/core/misc/debounce.js @@ -0,0 +1,25 @@ +/** + * Limits the invocations of a function in a given time frame. + * + * @param {Function} callback + * The function to be invoked. + * + * @param {Number} wait + * The time period within which the callback function should only be + * invoked once. For example if the wait period is 250ms, then the callback + * will only be called at most 4 times per second. + */ +Drupal.debounce = function (callback, wait) { + var timeout, result; + return function () { + var context = this; + var args = arguments; + var later = function () { + timeout = null; + result = callback.apply(context, args); + }; + window.clearTimeout(timeout); + timeout = window.setTimeout(later, wait); + return result; + }; +}; diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js index 3ae33c2..0d4f7cd 100644 --- a/core/misc/tableheader.js +++ b/core/misc/tableheader.js @@ -130,7 +130,6 @@ $.extend(TableHeader, { /** * Sum all [data-offset-top] values and cache it. - * @todo move this out of tableheader.js into a move generic place like drupal.js. */ computeOffsetTop: function () { var $offsets = $('[data-offset-top]'); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index aa5604c..ab148d9 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1290,6 +1290,15 @@ function system_library_info() { ), ); + // A utility function to limit calls to a function with a given time. + $libraries['debounce'] = array( + 'title' => 'Drupal debounce', + 'version' => VERSION, + 'js' => array( + 'core/misc/debounce.js' => array('group' => JS_LIBRARY), + ), + ); + // jQuery. $libraries['jquery'] = array( 'title' => 'jQuery', diff --git a/core/modules/toolbar/js/interactivemenu.js b/core/modules/toolbar/js/interactivemenu.js new file mode 100644 index 0000000..7b5376c --- /dev/null +++ b/core/modules/toolbar/js/interactivemenu.js @@ -0,0 +1,143 @@ +/** + * Decorate a menu with markup and classes for attaching behaviors. + */ + +(function ($) { + + $.fn.interactiveMenu = function () { + + var ui = { + 'handleOpen': Drupal.t('Open'), + 'handleClose': Drupal.t('Close') + }; + + var toggleList = function (event) { + // The toggle. + var $toggle = $(event.target); + var $item = $toggle.closest('li'); + var $list = $item.children('ul'); + var isHidden = $list.hasClass('dormant'); + // Close open siblings. + $item.siblings().filter('.open').find('.handle').trigger('click'); + // Toggle the item open state. + $item + [((isHidden) ? 'add' : 'remove') + 'Class']('open'); + // Toggle the item list visibility. + $list + [((isHidden) ? 'remove' : 'add') + 'Class']('dormant'); + // Twist the toggle. + $toggle + [((isHidden) ? 'add' : 'remove') + 'Class']('open'); + // Adjust the toggle text. + $toggle + .text((isHidden) ? ui.handleClose : ui.handleOpen) + .attr('aria-pressed', isHidden); + }; + /** + * + */ + var initItems = function ($list) { + var boxClass = 'box'; + var handleClass = 'handle'; + // Get lists and items. + var $ul = $list.find('ul').andSelf(); + var $li = $list.find('li'); + // Basic setup + $ul + .each(function (index, element) { + $(this).data('toolbar', { + processed: false, + type: 'list', + level: NaN + }); + }); + // Initialize items and their links. + $li + .each(function (index, element) { + $(this).data('toolbar', { + processed: false, + type: 'item' + }); + }) + // Add a class to item links. + .children('a') + .wrap( + $('
', { + 'class': boxClass + }) + ) + .end() + // Add a handle to each list item if it has a menu. + .each(function (index, element) { + var $item = $(this); + if ($item.children('ul').length > 0) { + $item + .children('.' + boxClass) + .prepend(Drupal.theme('interactionMenuItemToggle', { + 'class': handleClass, + 'text': ui.handleOpen + }) + ); + } + }); + }; + /** + * Adds a level class to each list based on its depth in the menu. + */ + var markListLevels = function ($lists, level) { + level = (!level) ? 1 : level; + $lists + .addClass('level-' + level) + .each(function (index, element) { + $(this).data().toolbar.level = level; + }); + $lists = $lists.children('li').children('ul'); + if ($lists.length > 0) { + markListLevels($lists, (level + 1)); + } + }; + var setLevelVisibility = function ($lists, visibleAfter) { + var level; + $lists + .each(function (index, element) { + var $this = $(this); + level = $(this).data().toolbar.level; + if (level > visibleAfter) { + $this.addClass('dormant'); + } + else { + $this.addClass('visible'); + } + }); + $lists = $lists.children('li').children('ul'); + if ($lists.length > 0) { + setLevelVisibility($lists, visibleAfter); + } + }; + return this.each(function (selector) { + var $menu = $(this).once('decorate-menu'); + if ($menu.length) { + $menu.addClass('root'); + initItems($menu); + markListLevels($menu); + setLevelVisibility($menu, 1); + // Wrap the list in a div to provide a positioning context. + $menu + .wrap('
') + .parent() + // Bind event handlers. + .on('click.interactivemenu', '.handle', toggleList); + } + }); + }; + + /** + * A toggle is an interactive element often bound to a click handler. + * + * @return {String} + * A string representing a DOM fragment. + */ + Drupal.theme.interactionMenuItemToggle = function (options) { + return ''; + }; +}(jQuery)); diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js index 7249f06..dc044a1 100755 --- a/core/modules/toolbar/js/toolbar.js +++ b/core/modules/toolbar/js/toolbar.js @@ -3,12 +3,20 @@ * * Defines the behavior of the Drupal administration toolbar. */ -(function ($, _) { +(function ($) { "use strict"; Drupal.toolbar = Drupal.toolbar || {}; -var transitionEnd = "transitionEnd.toolbar webkitTransitionEnd.toolbar transitionend.toolbar msTransitionEnd.toolbar oTransitionEnd.toolbar"; + +/** + * Store the state of the active tab so it will remain active across page loads. + */ +var activeTab = JSON.parse(localStorage.getItem('Drupal.toolbar.activeTab')); +/** + * Store the state of the tray orientations to maintain them across page loads. + */ +var trayOrientations = JSON.parse(localStorage.getItem('Drupal.toolbar.trayOrientations')) || []; /** * Register tabs with the toolbar. @@ -20,44 +28,48 @@ var transitionEnd = "transitionEnd.toolbar webkitTransitionEnd.toolbar transitio */ Drupal.behaviors.toolbar = { attach: function(context, settings) { - var options = _.extend(this.options, ('toolbar' in settings) ? settings.toolbar : {}); + var options = $.extend(this.options, ('toolbar' in settings) ? settings.toolbar : {}); var $toolbar = $(context).find('#toolbar').once('toolbar'); if ($toolbar.length) { $toolbar - .addClass('toolbar-main') - .on('itemregistered', decorateInteractiveMenu); - var toolbar = new ToolBar($toolbar, options); - // Find and register tabs. Each tab may have an associated tray. - var item, tray, $tray, $trays, tab, $tab, $tabs, name, i; - Drupal.toolbar.tabs = []; - $tabs = $toolbar.find('.bar .tab'); - $trays = $toolbar.find('.tray'); - for (i = 0; i < $tabs.length; i++) { - $tab = $($tabs[i]); - name = $tab.attr('data-toolbar-tray') || ''; - $tray = $trays.filter('[data-toolbar-tray="' + name + '"]'); - tab = new Tab($tab); - item = { - name: name, - tab: tab - }; - if ($tray.length) { - item.tray = new Tray($tray); - } - toolbar.registerTab(item); - } + .addClass('toolbar-main'); + // Set the initial orientation of the trays. + var $tray = $('.tray', $toolbar) + .attr('data-toolbar-orientation', 'vertical') + .addClass('vertical'); + // Add the tray orientation toggles. + $tray + .find('.lining') + .append(Drupal.theme('toolbarOrientationToggle')); + // Ensure the orientation toggle agrees with the current tray orientation. + toggleOrientationToggle($tray, 'vertical'); + // Adjust the body to accomodate trays. + setBodyState(); + // Set up switching between the vertical and horizontal presentation // of the toolbar trays based on a breakpoint. if (options.breakpoints && options.breakpoints['module.toolbar.wide'] !== undefined) { var mql = matchMedia(options.breakpoints['module.toolbar.wide']); - mql.addListener(toolbar.mediaQueryChangeHandler); - toolbar.mediaQueries.push(mql); + mql.addListener(Drupal.toolbar.mediaQueryChangeHandler); if (mql.matches) { - toolbar.mediaQueryChangeHandler(mql); + Drupal.toolbar.mediaQueryChangeHandler(mql); } } - // Assign the toolbar to the Drupal global object. - Drupal.toolbar = toolbar; + // Recalculate the offset top of the toolbar once on initialization. + Drupal.toolbar.setHeight(); + // Call setHeight on screen resize. Wrap it in debounce to prevent + // setHeight from being called too frequently. + var setHeight = Drupal.debounce(Drupal.toolbar.setHeight, 250); + $(window) + .on('resize.toolbar', setHeight); + // Attach behaviors to the toolbar. + $(document) + .on('click.toolbar', '#toolbar .bar .tab', Drupal.toolbar.toggleTray) + .on('click.toolbar', '#toolbar .tray .toggle-orientation button', Drupal.toolbar.orientationChangeHandler); + // Decorate the main menu with an interactive menu. + $('.interactive-menu > .menu', $toolbar).interactiveMenu(); + // Restore the toolbar to its saved state. + restoreState(); } }, options: { @@ -65,291 +77,180 @@ Drupal.behaviors.toolbar = { } }; /** - * A toolbar is an administration action button container. - */ -function ToolBar ($toolbar, options) { - this.$toolbar = $toolbar; - this.$bar = $toolbar.find('.bar'); - this.height = 0; - this.barHeight = 0; - this.items = []; - this.activeItem = null; - this.mediaQueries = []; - this.ui = { - 'activeClass': 'active', - 'trayOpenBodyClass': 'toolbar-tray-open', - }; - // Bind all ToolBar methods to the instance. - _.bindAll(this); - // Recalculate the offset top on screen resize. - // Use throttle to prevent setHeight from being called too frequently. - var setHeight = _.debounce(this.setHeight, 250); - $(window) - .on({ - 'resize.toolbar': setHeight - }); - // Toolbar event handlers. - this.$toolbar - .on('setup.toolbar', this.setHeight) - .on('click.toolbar', '.bar .tab', this.toggleTray) - .on('click.toolbar', '.tray .toggle-orientation button', this.orientationChangeHandler) - .on(transitionEnd, '.tray.active', this.setHeight) - .trigger('setup.toolbar'); -}; -/** - * Extend the prototype of the ToolBar class. + * Toggle a toolbar tab and the associated tray. */ -_.extend(ToolBar.prototype, { - /** - * The height of the toolbar offsets the top of the page content. - * - * Page components can register with the offsettopchange event to know when - * the height of the toolbar changes. - */ - setHeight: function (event) { - var height = 0; - var tray, $tray, $trays, trayH; - this.barHeight = this.$bar.outerHeight(); - var bhpx = this.barHeight + 'px'; - height += this.barHeight; - // Set the top of the all the trays to the height of the bar. - $trays = this.$toolbar.find('.tray'); - for (var i = $trays.length - 1; i >= 0; i--) { - tray = $trays[i]; - if (!tray.style.top.length || (tray.style.top !== bhpx)) { - tray.style.top = bhpx; - } - }; - // Get the height of the active horizontal tray and include it in the total - // height of the toolbar. - height += $trays.filter('.active.horizontal').height('auto').outerHeight() || 0; - // Set the height of the vertical tray to the scrollHeight of the - // documentElement. - $trays.filter('.active.vertical').height(document.documentElement.scrollHeight); - // Indicate the height of the toolbar in the attribute data-offset-top. - if (this.height !== height) { - this.height = height; - this.$toolbar.attr('data-offset-top', height); - // Alter the padding on the top of the body element. - $('body').css('padding-top', height); - $(document).trigger('offsettopchange', height); - $(window).triggerHandler('resize'); - } - }, - /** - * Toggle a toolbar tab and the associated tray. - * - * - */ - toggleTray: function (event) { - var $tab = $(event.target); - var item = this.getItem($tab.data('toolbar').tab.name); - var tab = item.tab; - if (item.tray) { - var tray = item.tray; - event.preventDefault(); - var disableTrays = _.without(this.getTrays(), tray); - for (var i = disableTrays.length - 1; i >= 0; i--) { - disableTrays[i].toggle(false); - this.getItem(disableTrays[i].name).tab.toggle(false); - }; - tab.toggle(); - tray.toggle(tab.active); - this.activeItem = (tab.active) ? item : null; - this.setBodyState(); - this.setHeight(); - this.$toolbar.trigger('itemtoggled', item); - } - }, - /** - * - */ - registerTab: function (item) { - this.items.push(item); - // Save references to the tab and tray instances on the corresponding DOM - // elements. - item.tab.$el.data('toolbar', {tab: item.tab}); - if (item.tray) { - item.tray.$el.data('toolbar', {tray: item.tray}); - } - this.$toolbar.trigger('itemregistered', item); - }, - /** - * - */ - getItem: function (name) { - for (var i = this.items.length - 1; i >= 0; i--) { - if (this.items[i].name === name) { - return this.items[i]; - } - } - return; - }, - /** - * - */ - getTabs: function () { - var tabs = []; - for (var i = this.items.length - 1; i >= 0; i--) { - tabs.push(this.items[i].tab); - } - return tabs; - }, - /** - * - */ - getTrays: function () { - var trays = []; - for (var i = this.items.length - 1; i >= 0; i--) { - if (this.items[i].tray) { - trays.push(this.items[i].tray); - } - } - return trays; - }, - /** - * - */ - orientationChangeHandler: function (event) { - var $button = $(event.target); - var orientation = event.target.value; - var tray = $button.closest('.tray').data('toolbar').tray; - this.changeOrientation(tray, orientation, true); - this.setBodyState(); - this.setHeight(); - this.$toolbar.trigger('toolbarorientationchanged', orientation); - }, - /** - * - */ - mediaQueryChangeHandler: function (mql) { - var orientation = (mql.matches) ? 'horizontal' : 'vertical'; - this.changeOrientation(this.getTrays(), orientation); - this.setBodyState(); - this.setHeight(); - this.$toolbar.trigger('toolbarorientationchanged', orientation); - }, - /** - * - */ - changeOrientation: function (trays, orientation, isOverride) { - trays = (!_.isArray(trays)) ? [trays] : trays; - for (var i = trays.length - 1; i >= 0; i--) { - trays[i].changeOrientation(orientation, isOverride); - }; - }, - /** - * - */ - setBodyState: function () { - var $body = $('body') - .removeClass('toolbar-vertical toolbar-horizontal'); - if (this.activeItem) { - $body - .addClass('toolbar-tray-open') - .addClass('toolbar-' + this.activeItem.tray.getOrientation()); +Drupal.toolbar.toggleTray = function (event) { + var $tab = $(event.target); + var name = $tab.attr('data-toolbar-tray'); + var $toolbar = $tab.closest('#toolbar'); + // Activate the selected tab and associated tray. + var $activateTray = $('[data-toolbar-tray="' + name + '"].tray', $toolbar).toggleClass('active'); + if ($activateTray.length) { + event.preventDefault(); + event.stopPropagation(); + $tab.parent().toggleClass('active'); + // Store the active tab name or remove the setting. + if ($tab.parent().hasClass('active')) { + localStorage.setItem('Drupal.toolbar.activeTab', JSON.stringify(name)); } else { - $body - .removeClass('toolbar-tray-open'); + localStorage.removeItem('Drupal.toolbar.activeTab'); } + // Disable non-selected tabs and trays. + $('.bar .tab', $toolbar).not($tab).parent().removeClass('active'); + $('.tray', $toolbar).not($activateTray).removeClass('active'); + setBodyState(); + Drupal.toolbar.setHeight(); } -}); +}; /** - * Toolbar tray. + * The height of the toolbar offsets the top of the page content. + * + * Page components can register with the offsettopchange event to know when + * the height of the toolbar changes. */ -function Tray ($tray) { - this.$el = $tray; - this.name = this.$el.data()['toolbarTray'] || this.$el.attr('id') ||'no name'; - this.active = false; - this.orientation = 'vertical'; - this.isOrientationLocked = false; - this.setup.apply(this, arguments); -} - +Drupal.toolbar.setHeight = function (event) { + var $toolbar = $('#toolbar'); + var height = 0; + var barHeight = $('.bar', $toolbar).outerHeight(); + var bhpx = barHeight + 'px'; + var tray; + // Add the bar height to the total calculated height of the toolbar. + height += barHeight; + // Set the top of the all the trays to the height of the bar. + var $trays = $('.tray', $toolbar); + for (var tray, i = $trays.length - 1; i >= 0; i--) { + tray = $trays[i]; + if (!tray.style.top.length || (tray.style.top !== bhpx)) { + tray.style.top = bhpx; + } + }; + // Get the height of the active horizontal tray and include it in the total + // height of the toolbar. + height += $trays.filter('.active.horizontal').height('auto').outerHeight() || 0; + // Set the height of the vertical tray to the scrollHeight of the + // documentElement. + $trays.filter('.active.vertical').height(document.documentElement.scrollHeight); + // Indicate the height of the toolbar in the attribute data-offset-top. + var offset = $toolbar.attr('data-offset-top'); + if (offset !== height) { + $toolbar.attr('data-offset-top', height); + // Alter the padding on the top of the body element. + $('body').css('padding-top', height); + $(document).trigger('offsettopchange', height); + $(window).triggerHandler('resize'); + } +}; /** - * Extend the prototype of the Tray. + * */ -_.extend(Tray.prototype, { - /** - * - */ - setup: function () { - this.$el - .addClass(this.orientation) - .find('.lining') - .append(Drupal.theme('toolbarOrientationToggle')); - this.toggleOrientationToggle(); - }, - /** - * - */ - toggle: function (open) { - this.$el.toggleClass('active', open); - }, - /** - * - */ - changeOrientation: function (orientation, isOverride) { - if (isOverride && orientation === 'vertical') { - this.isOrientationLocked = true; +Drupal.toolbar.mediaQueryChangeHandler = function (mql) { + var orientation = (mql.matches) ? 'horizontal' : 'vertical'; + changeOrientation($('.tray', '#toolbar'), orientation); + setBodyState(); + Drupal.toolbar.setHeight(); +}; +/** + * + */ +Drupal.toolbar.orientationChangeHandler = function (event) { + event.preventDefault(); + event.stopPropagation(); + var $button = $(event.target); + var orientation = event.target.value; + var $tray = $button.closest('.tray'); + changeOrientation($tray, orientation, true); + setBodyState(); + Drupal.toolbar.setHeight(); +}; +/** + * + */ +var restoreState = function () { + // Restore the open tab. + if (activeTab) { + $('[data-toolbar-tray="' + activeTab + '"].tab', '#toolbar').trigger('click.toolbar'); + } + // Restore tray orientations. + if (trayOrientations.length) { + var $trays = $('.tray', '#toolbar'); + var $tray; + for (var i = trayOrientations.length - 1; i >= 0; i--) { + $tray = $trays.filter('[data-toolbar-tray="' + trayOrientations[i].tray + '"]'); + changeOrientation($tray, trayOrientations[i].orientation, true); } - if (isOverride && orientation === 'horizontal') { - this.isOrientationLocked = false; + } +}; +/** + * + */ +var changeOrientation = function ($trays, orientation, isOverride) { + for (var i = $trays.length - 1; i >= 0; i--) { + var $tray = $($trays[i]); + if (isOverride) { + $tray.attr('data-toolbar-orientation-locked', (orientation === 'vertical') ? 1 : 0); } - if (!this.isOrientationLocked && orientation === 'horizontal' && this.orientation === 'vertical') { - var self = this; - this.orientation = orientation; - this.$el + var currentOrientation = $tray.attr('data-toolbar-orientation'); + var isOrientationLocked = parseInt($tray.attr('data-toolbar-orientation-locked')); + if (!isOrientationLocked && orientation === 'horizontal' && currentOrientation === 'vertical') { + $tray + .attr('data-toolbar-orientation', orientation) .removeClass('vertical') - .addClass('horizontal'); - this.toggleOrientationToggle(); + .addClass(orientation); + toggleOrientationToggle($tray, orientation); } - if (orientation === 'vertical' && this.orientation === 'horizontal') { - this.orientation = orientation; - this.$el + if (orientation === 'vertical' && currentOrientation === 'horizontal') { + $tray + .attr('data-toolbar-orientation', orientation) .removeClass('horizontal') - .addClass('vertical'); - this.toggleOrientationToggle(); + .addClass(orientation); + toggleOrientationToggle($tray, orientation); } - }, - /** - * - */ - getOrientation: function () { - return (this.isOrientationLocked) ? 'vertical' : this.orientation; - }, - /** - * Change the orientation toggle active state. - */ - toggleOrientationToggle: function () { - this.$el - .find('[value="' + this.orientation + '"]') - .removeClass('active') - .siblings() - .addClass('active'); } -}); - -function Tab ($tab) { - this.$el = $tab; - this.active = false; - this.name = this.$el.data()['toolbarTray'] || this.$el.attr('id') ||'no name'; -} - + // Save the state of overridden trays. + localStorage.setItem('Drupal.toolbar.trayOrientations', + JSON.stringify( + $('[data-toolbar-orientation-locked=1]', '#toolbar') + .map(function () { + var $tray = $(this); + return { + 'tray': $tray.attr('data-toolbar-tray'), + 'orientation': $tray.attr('data-toolbar-orientation') || 'vertical' + }; + }) + .get() + ) + ); +}; /** - * Extend the prototype of the Tray. + * */ -_.extend(Tab.prototype, { - /** - * - */ - toggle: function (open) { - this.active = (open !== undefined) ? open : !this.active; - this.$el.parent('li').toggleClass('active', this.active); +var setBodyState = function () { + var $toolbar = $('#toolbar'); + var $activeTray = $('.tray.active', $toolbar); + var $body = $('body') + .removeClass('toolbar-vertical toolbar-horizontal'); + if ($activeTray.length) { + $body + .addClass('toolbar-tray-open') + .addClass('toolbar-' + $activeTray.attr('data-toolbar-orientation')); + } + else { + $body + .removeClass('toolbar-tray-open'); } -}); +}; +/** + * Change the orientation toggle active state. + */ +var toggleOrientationToggle = function ($tray, orientation) { + $tray + .find('[value="' + orientation + '"]') + .removeClass('active') + .siblings() + .addClass('active'); +} /** * A toggle is an interactive element often bound to a click handler. @@ -360,182 +261,5 @@ _.extend(Tab.prototype, { Drupal.theme.toolbarOrientationToggle = function () { return '
'; }; -/** - * A toggle is an interactive element often bound to a click handler. - * - * @return {String} - * A string representing a DOM fragment. - */ -Drupal.theme.interactionMenuItemToggle = function (options) { - return ''; -}; - -/** - * Interactive menu setup methods. - */ -function decorateInteractiveMenu (event, item) { - if (item.tray && item.tray.name === 'administration') { - item.tray.decorate = interactiveMenuDecorator(); - item.tray.decorate('.interactive-menu > .menu'); - } -} - -/** - * Decorate a menu with markup and classes for attaching behaviors. - */ -var interactiveMenuDecorator = function () { - - var ui = { - 'handleOpen': Drupal.t('Open'), - 'handleClose': Drupal.t('Close') - }; - - var processLists = function (event) { - event.stopPropagation(); - // Mark up the lists and items. - $(event.target) - .trigger('listChange'); - }; - var toggleList = function (event) { - // The toggle. - var $toggle = $(event.target); - var $item = $toggle.closest('li'); - var $list = $item.children('ul'); - var isHidden = $list.hasClass('dormant'); - // Close open siblings. - $item.siblings().filter('.open').find('.handle').trigger('click'); - // Toggle the item open state. - $item - [((isHidden) ? 'add' : 'remove') + 'Class']('open'); - // Toggle the item list visibility. - $list - [((isHidden) ? 'remove' : 'add') + 'Class']('dormant'); - // Twist the toggle. - $toggle - [((isHidden) ? 'add' : 'remove') + 'Class']('open'); - // Adjust the toggle text. - $toggle - .text((isHidden) ? ui.handleClose : ui.handleOpen) - .attr('aria-pressed', isHidden); - // Fire an event to signify that a list has been toggled. - $item.trigger('itemtoggled', [$item.parent().data('toolbar').level, !isHidden]); - }; - var initItems = function (event) { - // The accordion wrapper. - var $wrapper = $(event.target); - var rootClass = 'root'; - var boxClass = 'box'; - var handleClass = 'handle'; - // Get lists and items. - var $root = $wrapper.children('.' + rootClass); - var $ul = $wrapper.find('ul'); - var $li = $wrapper.find('li'); - // Basic setup - $ul - .each(function (index, element) { - $(this).data('toolbar', { - processed: false, - type: 'list', - level: NaN - }); - }); - // Initialize items and their links. - $li - .each(function (index, element) { - $(this).data('toolbar', { - processed: false, - type: 'item' - }); - }) - // Add a class to item links. - .children('a') - .wrap( - $('
', { - 'class': boxClass - }) - ) - .end() - // Add a handle to each list item if it has a menu. - .each(function (index, element) { - var $item = $(this); - if ($item.children('ul').length > 0) { - $item - .children('.' + boxClass) - .prepend(Drupal.theme('interactionMenuItemToggle', { - 'class': handleClass, - 'text': ui.handleOpen - }) - ); - } - }); - }; - /** - * Adds a level class to each list based on its depth in the menu. - */ - var markListLevels = function ($lists, level) { - level = (typeof level === 'object') ? 1 : level; - $lists - .addClass('level-' + level) - .each(function (index, element) { - $(this).data().toolbar.level = level; - }); - $lists = $lists.children('li').children('ul'); - if ($lists.length > 0) { - markListLevels($lists, (level + 1)); - } - }; - var setLevelVisibility = function ($lists, visibleAfter) { - var level; - $lists - .each(function (index, element) { - var $this = $(this); - level = $(this).data().toolbar.level; - if (level > visibleAfter) { - $this.addClass('dormant'); - } - else { - $this.addClass('visible'); - } - }); - $lists = $lists.children('li').children('ul'); - if ($lists.length > 0) { - setLevelVisibility($lists, visibleAfter); - } - }; - return function (selector) { - var context = this; - // Find any menus that have already been decorated. - var $wrapper = this.$el.find(selector); - // Decorate any menus that have not been. - $wrapper - .once('decorate-menu') - .addClass('clearfix') - .each(function (index, element) { - var $root = $(this).addClass('root'); - // Create a set of list-manipulation callbacks. - // Called when items are added or removed. - var listUpdate = $.Callbacks(); - listUpdate.add(_.bind(initItems, context)); - listUpdate.add(_.bind(markListLevels, context, $root)); - listUpdate.add(_.bind(setLevelVisibility, context, $root, 1)); - // Wrap the list in a div to provide a positioning context. - $wrapper = $().add($wrapper).add( - $root - .wrap('
') - .parent() - // Bind event handlers. - .on('setup.toolbar', _.bind(processLists, context)) - .on('listChange.toolbar', listUpdate.fire) - .on('click.toolbar', '.handle', _.bind(toggleList, context)) - /* @todo - .on('clean.toolbar.accordionMode', 'li', cleanItem) - .on('activate.toolbar.accordionMode', 'li', activateItem) - */ - .trigger('setup') - ); - }); - return $wrapper; - }; -}; -}(jQuery, _)); +}(jQuery)); diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index 3651365..5493364 100755 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -172,7 +172,7 @@ function toolbar_toolbar() { function toolbar_view() { $build = array('#theme' => 'toolbar'); - $build['#attached']['library'][] = array('toolbar', 'drupal.toolbar'); + $build['#attached']['library'][] = array('toolbar', 'toolbar'); // Get the configured breakpoint for switch from vertical to horizontal // toolbar presentation. @@ -347,7 +347,7 @@ function toolbar_in_active_trail($path) { * Implements hook_library_info(). */ function toolbar_library_info() { - $libraries['drupal.toolbar'] = array( + $libraries['toolbar'] = array( 'title' => 'Toolbar', 'version' => VERSION, 'js' => array( @@ -359,6 +359,7 @@ function toolbar_library_info() { drupal_get_path('module', 'toolbar') . '/css/toolbar.icons.css', ), 'dependencies' => array( + array('system', 'debounce'), array('system', 'matchMedia'), array('system', 'jquery'), array('system', 'underscore'), @@ -366,6 +367,18 @@ function toolbar_library_info() { array('system', 'drupalSettings'), array('system', 'jquery.once'), array('system', 'jquery.cookie'), + array('toolbar', 'toolbar.interactiveMenu'), + ), + ); + + $libraries['toolbar.interactiveMenu'] = array( + 'title' => 'Toolbar interactive menu decorator', + 'version' => VERSION, + 'js' => array( + drupal_get_path('module', 'toolbar') . '/js/interactivemenu.js' => array(), + ), + 'css' => array( + drupal_get_path('module', 'toolbar') . '/css/interactivemenu.css', ), );