/** * @file */ (function ($) { Drupal.admin = Drupal.admin || {}; Drupal.admin.behaviors = Drupal.admin.behaviors || {}; Drupal.admin.hashes = Drupal.admin.hashes || {}; /** * Core behavior for Administration menu. * * Test whether there is an administration menu is in the output and execute all * registered behaviors. */ Drupal.behaviors.adminMenu = { attach: function (context, settings) { // Initialize settings. settings.admin_menu = $.extend({ suppress: false, margin_top: false, position_fixed: false, tweak_modules: false, tweak_permissions: false, tweak_tabs: false, destination: '', basePath: settings.basePath, hash: 0, replacements: {} }, settings.admin_menu || {}); // Check whether administration menu should be suppressed. if (settings.admin_menu.suppress) { return; } var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context); // Client-side caching; if administration menu is not in the output, it is // fetched from the server and cached in the browser. if (!$adminMenu.length && settings.admin_menu.hash) { Drupal.admin.getCache(settings.admin_menu.hash, function (response) { if (typeof response == 'string' && response.length > 0) { $('body', context).append(response); } var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context); // Apply our behaviors. Drupal.admin.attachBehaviors(context, settings, $adminMenu); // Allow resize event handlers to recalculate sizes/positions. $(window).triggerHandler('resize'); }); } // If the menu is in the output already, this means there is a new version. else { // Apply our behaviors. Drupal.admin.attachBehaviors(context, settings, $adminMenu); } } }; /** * Collapse fieldsets on Modules page. */ Drupal.behaviors.adminMenuCollapseModules = { attach: function (context, settings) { if (settings.admin_menu.tweak_modules) { $('#system-modules fieldset:not(.collapsed)', context).addClass('collapsed'); } } }; /** * Collapse modules on Permissions page. */ Drupal.behaviors.adminMenuCollapsePermissions = { attach: function (context, settings) { if (settings.admin_menu.tweak_permissions) { // Freeze width of first column to prevent jumping. $('#permissions th:first', context).css({ width: $('#permissions th:first', context).width() }); // Attach click handler. $modules = $('#permissions tr:has(td.module)', context).once('admin-menu-tweak-permissions', function () { var $module = $(this); $module.bind('click.admin-menu', function () { // @todo Replace with .nextUntil() in jQuery 1.4. $module.nextAll().each(function () { var $row = $(this); if ($row.is(':has(td.module)')) { return false; } $row.toggleClass('element-hidden'); }); }); }); // Collapse all but the targeted permission rows set. if (window.location.hash.length) { $modules = $modules.not(':has(' + window.location.hash + ')'); } $modules.trigger('click.admin-menu'); } } }; /** * Apply margin to page. * * Note that directly applying marginTop does not work in IE. To prevent * flickering/jumping page content with client-side caching, this is a regular * Drupal behavior. */ Drupal.behaviors.adminMenuMarginTop = { attach: function (context, settings) { if (!settings.admin_menu.suppress && settings.admin_menu.margin_top) { $('body:not(.admin-menu)', context).addClass('admin-menu'); } } }; /** * Retrieve content from client-side cache. * * @param hash * The md5 hash of the content to retrieve. * @param onSuccess * A callback function invoked when the cache request was successful. */ Drupal.admin.getCache = function (hash, onSuccess) { if (Drupal.admin.hashes.hash !== undefined) { return Drupal.admin.hashes.hash; } $.ajax({ cache: true, type: 'GET', dataType: 'text', // Prevent auto-evaluation of response. global: false, // Do not trigger global AJAX events. url: Drupal.settings.admin_menu.basePath.replace(/admin_menu/, 'js/admin_menu/cache/' + hash), success: onSuccess, complete: function (XMLHttpRequest, status) { Drupal.admin.hashes.hash = status; } }); }; /** * TableHeader callback to determine top viewport offset. * * @see toolbar.js */ Drupal.admin.height = function () { var $adminMenu = $('#admin-menu'); var height = $adminMenu.outerHeight(); // In IE, Shadow filter adds some extra height, so we need to remove it from // the returned height. if ($adminMenu.css('filter') && $adminMenu.css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) { height -= $adminMenu.get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength; } return height; }; /** * @defgroup admin_behaviors Administration behaviors. * @{ */ /** * Attach administrative behaviors. */ Drupal.admin.attachBehaviors = function (context, settings, $adminMenu) { if ($adminMenu.length) { $adminMenu.addClass('admin-menu-processed'); $.each(Drupal.admin.behaviors, function () { this(context, settings, $adminMenu); }); } }; /** * Apply 'position: fixed'. */ Drupal.admin.behaviors.positionFixed = function (context, settings, $adminMenu) { if (settings.admin_menu.position_fixed) { $adminMenu.addClass('admin-menu-position-fixed'); $adminMenu.css('position', 'fixed'); } }; /** * Move page tabs into administration menu. */ Drupal.admin.behaviors.pageTabs = function (context, settings, $adminMenu) { if (settings.admin_menu.tweak_tabs) { var $tabs = $(context).find('ul.tabs.primary'); $adminMenu.find('#admin-menu-wrapper > ul').eq(1) .append($tabs.find('li').addClass('admin-menu-tab')); $(context).find('ul.tabs.secondary') .appendTo('#admin-menu-wrapper > ul > li.admin-menu-tab.active') .removeClass('secondary'); $tabs.remove(); } }; /** * Perform dynamic replacements in cached menu. */ Drupal.admin.behaviors.replacements = function (context, settings, $adminMenu) { for (var item in settings.admin_menu.replacements) { $(item, $adminMenu).html(settings.admin_menu.replacements[item]); } }; /** * Inject destination query strings for current page. */ Drupal.admin.behaviors.destination = function (context, settings, $adminMenu) { if (settings.admin_menu.destination) { $('a.admin-menu-destination', $adminMenu).each(function () { this.search += (!this.search.length ? '?' : '&') + Drupal.settings.admin_menu.destination; }); } }; /** * Apply JavaScript-based hovering behaviors. * * @todo This has to run last. If another script registers additional behaviors * it will not run last. */ Drupal.admin.behaviors.hover = function (context, settings, $adminMenu) { // Delayed mouseout. $('li.expandable', $adminMenu).hover( function () { // Stop the timer. clearTimeout(this.sfTimer); // Display child lists. $('> ul', this) .css({left: 'auto', display: 'block'}) // Immediately hide nephew lists. .parent().siblings('li').children('ul').css({left: '-999em', display: 'none'}); }, function () { // Start the timer. var uls = $('> ul', this); this.sfTimer = setTimeout(function () { uls.css({left: '-999em', display: 'none'}); }, 400); } ); }; /** * Apply the search bar functionality. */ Drupal.admin.behaviors.search = function (context, settings, $adminMenu) { // @todo Add a HTML ID. var $input = $('input.admin-menu-search', $adminMenu); // Initialize the current search needle. var needle = $input.val(); // Cache of all links that can be matched in the menu. var links; // Minimum search needle length. var needleMinLength = 2; // Append the results container. var $results = $('
').insertAfter($input); /** * Executes the search upon user input. */ function keyupHandler() { var matches, $html, value = $(this).val(); // Only proceed if the search needle has changed. if (value !== needle) { needle = value; // Initialize the cache of menu links upon first search. if (!links && needle.length >= needleMinLength) { // @todo Limit to links in dropdown menus; i.e., skip menu additions. links = buildSearchIndex($adminMenu.find('li:not(.admin-menu-action, .admin-menu-action li) > a')); } // Empty results container when deleting search text. if (needle.length < needleMinLength) { $results.empty(); } // Only search if the needle is long enough. if (needle.length >= needleMinLength && links) { matches = findMatches(needle, links); // Build the list in a detached DOM node. $html = buildResultsList(matches); // Display results. $results.empty().append($html); } } } /** * Builds the search index. */ function buildSearchIndex($links) { return $links .map(function () { var text = (this.textContent || this.innerText); // Skip menu entries that do not contain any text (e.g., the icon). if (typeof text === 'undefined') { return; } return { text: text, textMatch: text.toLowerCase(), element: this }; }); } /** * Searches the index for a given needle and returns matching entries. */ function findMatches(needle, links) { var needleMatch = needle.toLowerCase(); // Select matching links from the cache. return $.grep(links, function (link) { return link.textMatch.indexOf(needleMatch) !== -1; }); } /** * Builds the search result list in a detached DOM node. */ function buildResultsList(matches) { var $html = $('