This is a simple module that enables the site to have drop down/flyout CSS menus for site and admin navigation.

Remember to activate and configure the menu blocks in !link

', array('!link' => l('admin/structure/block', 'admin/structure/block'))); break; } return $output; } /** * Implements hook_form_alter(). */ function nice_menus_form_alter(&$form, $form_state, $form_id) { switch ($form_id) { case 'system_theme_settings': // This is a global setting, so only insert the field // on the global settings page. if (arg(4) && arg(4) != 'global') { return; } // Have to add a custom submit handler since this form doesn't use // the standard system submit handler. $form['#submit'][] = 'nice_menus_system_theme_settings_submit'; // Add global theme setting for a custom CSS file. $form['nice_menus_custom_css'] = array( '#type' => 'textfield', '#title' => t('Path to custom Nice menus CSS file'), '#description' => t('To override the default Nice menus CSS layout, enter the path to your custom CSS file. It should be a relative path from the root of your Drupal install (e.g. sites/all/themes/example/mymenu.css).'), '#default_value' => variable_get('nice_menus_custom_css', ''), // Field appears below submit buttons without this -- yucky. '#weight' => 0, ); break; } } /** * Records the Nice menu custom CSS file per theme. */ function nice_menus_system_theme_settings_submit($form, &$form_state) { variable_set('nice_menus_custom_css', $form_state['values']['nice_menus_custom_css']); } /** * Implements hook_menu(). */ function nice_menus_menu() { $items['admin/config/user-interface/nice_menus'] = array( 'title' => 'Nice menus', 'description' => 'Configure Nice menus.', 'page callback' => 'drupal_get_form', 'page arguments' => array('nice_menus_admin_settings'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); return $items; } /** * Settings form as implemented by hook_menu */ function nice_menus_admin_settings($form, &$form_state) { $form['nice_menus_number'] = array( '#type' => 'textfield', '#description' => t('The total number of independent Nice menus blocks you want. Enter a number between 0 and 99. If you set this to 0, you will have no blocks created but you can still use the Nice menus theme functions directly in your theme.'), '#default_value' => variable_get('nice_menus_number', '2'), '#size' => 2, ); $form['nice_menus_js'] = array( '#type' => 'checkbox', '#title' => t('Use JavaScript'), '#description' => t('This will add Superfish Jquery to Nice menus. This is required for Nice menus to work properly in Internet Explorer.'), '#default_value' => variable_get('nice_menus_js', 1), ); $form['nice_menus_sf_options'] = array( '#type' => 'fieldset', '#title' => t('Advanced: Superfish options'), '#description' => t('You can change the default Superfish options by filling out the desired values here. These only take effect if the Use JavaScript box above is checked.'), '#collapsible' => TRUE, '#collapsed' => TRUE, ); $form['nice_menus_sf_options']['nice_menus_sf_delay'] = array( '#type' => 'textfield', '#title' => t('Mouse delay'), '#description' => t('The delay in milliseconds that the mouse can remain outside a submenu without it closing.'), '#default_value' => variable_get('nice_menus_sf_delay', 800), '#size' => 5, ); $form['nice_menus_sf_options']['nice_menus_sf_speed'] = array( '#type' => 'select', '#title' => t('Animation speed'), '#multiple' => FALSE, '#description' => t('Speed of the menu open/close animation.'), '#options' => array( 'slow' => t('slow'), 'normal' => t('normal'), 'fast' => t('fast'), ), '#default_value' => variable_get('nice_menus_sf_speed', 1), ); // Custom validation to make sure the user is entering numbers. $form['#validate'][] = 'nice_menus_settings_validate'; return system_settings_form($form); } /** * Custom validation for the settings form. */ function nice_menus_settings_validate($form, &$form_state) { $number = $form_state['values']['nice_menus_number']; // Check to make sure it is a number and that is a maximum of 2 digits. if (!is_numeric($number) || strlen($number) > 2) { form_set_error('nice_menus_number', t('You must enter a number from 0 to 99.')); } } /** * Implements hook_init(). * * We are adding the JavaScript and CSS here rather than theme_nice_menu * because when block caching is enabled none of it would get fired * and the menus are unstyled. */ function nice_menus_init() { // Add Superfish JavaScript, if enabled. if (variable_get('nice_menus_js', 1) == 1) { // The script, from http://users.tpg.com.au/j_birch/plugins/superfish. drupal_add_js(drupal_get_path('module', 'nice_menus') . '/superfish/js/superfish.js'); // Add the Superfish options variables. drupal_add_js(array( 'nice_menus_options' => array( 'delay' => variable_get('nice_menus_sf_delay', 800), 'speed' => variable_get('nice_menus_sf_speed', 1), ), ), array('type' => 'setting', 'scope' => JS_DEFAULT)); // Add the bgIframe plugin. drupal_add_js(drupal_get_path('module', 'nice_menus') . '/superfish/js/jquery.bgiframe.min.js'); // Add the HoverIntent plugin. drupal_add_js(drupal_get_path('module', 'nice_menus') . '/superfish/js/jquery.hoverIntent.minified.js'); // The Nice menus implementation. drupal_add_js(drupal_get_path('module', 'nice_menus') . '/nice_menus.js'); } // Add main CSS functionality. drupal_add_css(drupal_get_path('module', 'nice_menus') . '/nice_menus.css', array('group' => CSS_DEFAULT, 'basename' => 'nice_menus.css')); // Add custom CSS layout if specified. if ($custom = variable_get('nice_menus_custom_css', '')) { drupal_add_css($custom, array('group' => CSS_DEFAULT, 'basename' => 'nice_menus_custom.css')); } // Fall back to default layout. else { drupal_add_css(drupal_get_path('module', 'nice_menus') . '/nice_menus_default.css', array('group' => CSS_DEFAULT, 'basename' => 'nice_menus_default.css')); } } /** * Implements hook_block_info(). */ function nice_menus_block_info() { $blocks = array(); for ($i = 1; $i <= variable_get('nice_menus_number', '2'); $i++) { $blocks[$i]['info'] = variable_get('nice_menus_name_' . $i, 'Nice menu ' . $i) . ' (Nice menu)'; // We have too many things changing per user, per page to cache. $blocks[$i]['cache'] = DRUPAL_NO_CACHE; } return $blocks; } /** * Implements hook_block_configure(). */ function nice_menus_block_configure($delta) { $form['nice_menus_name_' . $delta] = array( '#type' => 'textfield', '#title' => t('Menu Name'), '#default_value' => variable_get('nice_menus_name_' . $delta, 'Nice menu ' . $delta), ); $form['nice_menus_menu_' . $delta] = array( '#type' => 'select', '#title' => t('Menu Parent'), '#description' => t('The menu parent from which to show a Nice menu.'), '#default_value' => variable_get('nice_menus_menu_' . $delta, 'navigation:0'), '#options' => menu_get_menus(), ); $form['nice_menus_depth_' . $delta] = array( '#type' => 'select', '#title' => t('Menu Depth'), '#description' => t('The depth of the menu, i.e. the number of child levels starting with the parent selected above. Leave set to -1 to display all children and use 0 to display no children.'), '#default_value' => variable_get('nice_menus_depth_' . $delta, -1), '#options' => drupal_map_assoc(range(-1, 5)), ); $form['nice_menus_type_' . $delta] = array( '#type' => 'select', '#title' => t('Menu Style'), '#description' => t('right: menu items are listed on top of each other and expand to the right') . '
' . t('left: menu items are listed on top of each other and expand to the left') . '
' . t('down: menu items are listed side by side and expand down'), '#default_value' => variable_get('nice_menus_type_' . $delta, 'right'), '#options' => drupal_map_assoc(array('right', 'left', 'down')), ); return $form; } /** * Implements hook_block_save(). */ function nice_menus_block_save($delta, $edit) { variable_set('nice_menus_name_' . $delta, $edit['nice_menus_name_' . $delta]); variable_set('nice_menus_menu_' . $delta, $edit['nice_menus_menu_' . $delta]); variable_set('nice_menus_depth_' . $delta, $edit['nice_menus_depth_' . $delta]); variable_set('nice_menus_type_' . $delta, $edit['nice_menus_type_' . $delta]); } /** * Implements hook_block_view(). */ function nice_menus_block_view($delta) { // Build the Nice menu for the block. list($menu_name) = explode(':', variable_get('nice_menus_menu_' . $delta, 'navigation:0')); $direction = variable_get('nice_menus_type_' . $delta, 'right'); $depth = variable_get('nice_menus_depth_' . $delta, '-1'); if ($output = theme('nice_menus', array('id' => $delta, 'menu_name' => $menu_name, 'direction' => $direction, 'depth' => $depth))) { $block['content'] = $output['content']; if (variable_get('nice_menus_type_' . $delta, 'right') == 'down') { $class = 'nice-menu-hide-title'; } else { $class = 'nice-menu-show-title'; } // If we're building the navigation block // use the same block title logic as menu module. global $user; if ($output['subject'] == t('navigation') && $user->uid) { $subject = $user->name; } else { $subject = $output['subject']; } $block['subject'] = '' . check_plain($subject) . ''; } else { $block['content'] = FALSE; } return $block; } /** * Implements hook_theme(). */ function nice_menus_theme() { return array( 'nice_menus_tree' => array( 'variables' => array('menu_name' => NULL, 'mlid' => NULL, 'depth' => -1, 'menu' => NULL), ), 'nice_menus_build' => array( 'variables' => array('menu' => NULL, 'depth' => -1, 'trail' => NULL), ), 'nice_menus' => array( 'variables' => array('id' => NULL, 'menu_name' => NULL, 'mlid' => NULL, 'direction' => 'right', 'depth' => -1, 'menu' => NULL), ), 'nice_menus_main_menu' => array( 'variables' => array('direction' => 'down', 'depth' => -1), ), 'nice_menus_secondary_menu' => array( 'variables' => array('direction' => 'down', 'depth' => -1), ), ); } /** * Builds the active trail from the page's menu data. * * @param $page_menu * The menu data for a page. * * @return * An array of parent menu item ids. */ function nice_menus_build_page_trail($page_menu) { $trail = array(); foreach ($page_menu as $item) { if ($item['link']['in_active_trail']) { $trail[] = $item['link']['mlid']; } if ($item['below']) { $trail = array_merge($trail, nice_menus_build_page_trail($item['below'])); } } return $trail; } /** * Builds the final Nice menu. * * @param $menu_name * The top-level menu name that contains the menu to use (e.g. navigation * or main-menu) for Drupal menus. For custom $menus this is just the * name for menu display. * @param $mlid * The menu ID from which to start building the items, i.e. the parent * of the displayed menu. * @param $depth * The number of children levels to display. Use -1 to display all children * and use 0 to display no children. * @param $menu * Optional. A custom menu array to use for theming -- it should have * the same structure as that returned by menu_tree_all_data(). * * @return * An HTML string of properly nested Nice menu lists. */ function theme_nice_menus_tree($variables) { $menu_name = $variables['menu_name']; $mlid = $variables['mlid']; $depth = $variables['depth']; $menu = $variables['menu']; // Load the full menu array. $menu = isset($menu) ? $menu : menu_tree_all_data($menu_name); if (isset($menu)) { $page_menu = menu_tree_page_data($menu_name); $trail = nice_menus_build_page_trail($page_menu); unset($page_menu); } // Allow i18n module to translate strings where available. if (module_exists('i18n_menu')) { $menu = i18n_menu_localize_tree($menu); } // Assume depth == 0 by default, overriden if mlid is specified. $parent_depth = 0; // For custom $menus and menus built all the way from the top-level we // don't need to "create" the specific sub-menu and we need to get the title // from the $menu_name since there is no "parent item" array. // Create the specific menu if we have a mlid. if (!empty($mlid)) { // Load the parent menu item. $item = menu_link_load($mlid); $title = check_plain($item['title']); // The depth for our parent item, if it exists. $parent_depth = ($item['depth']) ? $item['depth'] : 0; // Narrow down the full menu to the specific sub-tree we need. for ($p = 1; $p < 10; $p++) { if ($sub_mlid = $item["p$p"]) { $subitem = menu_link_load($sub_mlid); // Menu sets these ghetto-ass keys in _menu_tree_check_access(). $menu = $menu[(50000 + $subitem['weight']) . ' ' . $subitem['title'] . ' ' . $subitem['mlid']]['below']; } } } // Otherwise just set a title and move on. else { // Get the title from the DB since we don't have it in the $menu. $result = db_query("SELECT title FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField(); $title = check_plain($result); } $output['content'] = ''; $output['subject'] = $title; if ($menu) { // Set the total menu depth counting from this parent if we need it. $depth = ($depth > 0) ? ($parent_depth + $depth) : $depth; $output['content'] .= theme('nice_menus_build', array('menu' => $menu, 'depth' => $depth, 'trail' => $trail)); } return $output; } /** * Helper function that builds the nested lists of a Nice menu. * * @param $menu * Menu array from which to build the nested lists. * @param $depth * The number of children levels to display. Use -1 to display all children * and use 0 to display no children. * @param $trail * An array of parent menu items. */ function theme_nice_menus_build($variables) { $menu = $variables['menu']; $depth = $variables['depth']; $trail = $variables['trail']; $output = ''; // Prepare to count the links so we can mark first, last, odd and even. $index = 0; $count = 0; foreach ($menu as $menu_count) { if ($menu_count['link']['hidden'] == 0) { $count++; } } // Get to building the menu. foreach ($menu as $menu_item) { $mlid = $menu_item['link']['mlid']; // Check to see if it is a visible menu item. if (!isset($menu_item['link']['hidden']) || $menu_item['link']['hidden'] == 0) { // Check our count and build first, last, odd/even classes. $index++; $first_class = $index == 1 ? ' first ' : ''; $oddeven_class = $index % 2 == 0 ? ' even ' : ' odd '; $last_class = $index == $count ? ' last ' : ''; // Build class name based on menu path // e.g. to give each menu item individual style. // Strip funny symbols. $clean_path = str_replace(array('http://', 'www', '<', '>', '&', '=', '?', ':', '.'), '', $menu_item['link']['href']); // Convert slashes to dashes. $clean_path = str_replace('/', '-', $clean_path); $class = 'menu-path-' . $clean_path; if ($trail && in_array($mlid, $trail)) { $class .= ' active-trail'; } // If it has children build a nice little tree under it. if ((!empty($menu_item['link']['has_children'])) && (!empty($menu_item['below'])) && $depth != 0) { // Keep passing children into the function 'til we get them all. if ($menu_item['link']['depth'] <= $depth || $depth == -1) { $children = array( '#theme' => 'nice_menus_build', '#prefix' => '', '#menu' => $menu_item['below'], '#depth' => $depth, '#trail' => $trail, ); } else { $children = ''; } // Set the class to parent only of children are displayed. $parent_class = ($children && ($menu_item['link']['depth'] <= $depth || $depth == -1)) ? 'menuparent ' : ''; $element = array( '#below' => $children, '#title' => $menu_item['link']['link_title'], '#href' => $menu_item['link']['href'], '#localized_options' => $menu_item['link']['localized_options'], '#attributes' => array( 'class' => array('menu-' . $mlid, $parent_class, $class, $first_class, $oddeven_class, $last_class), ), ); $variables['element'] = $element; $output .= theme('menu_link', $variables); } else { $element = array( '#below' => '', '#title' => $menu_item['link']['link_title'], '#href' => $menu_item['link']['href'], '#localized_options' => $menu_item['link']['localized_options'], '#attributes' => array( 'class' => array('menu-' . $mlid, $class, $first_class, $oddeven_class, $last_class), ), ); $variables['element'] = $element; $output .= theme('menu_link', $variables); } } } return $output; } /** * Theme function to allow any menu tree to be themed as a Nice menu. * * @param $id * The Nice menu ID. * @param $menu_name * The top parent menu name from which to build the full menu. * @param $mlid * The menu ID from which to build the displayed menu. * @param $direction * Optional. The direction the menu expands. Default is 'right'. * @param $depth * The number of children levels to display. Use -1 to display all children * and use 0 to display no children. * @param $menu * Optional. A custom menu array to use for theming -- * it should have the same structure as that returned * by menu_tree_all_data(). Default is the standard menu tree. * * @return * An HTML string of Nice menu links. */ function theme_nice_menus($variables) { $output = array( 'content' => '', 'subject' => '', ); $id = $variables['id']; $menu_name = $variables['menu_name']; $mlid = $variables['mlid']; $direction = $variables['direction']; $depth = $variables['depth']; $menu = $variables['menu']; if ($menu_tree = theme('nice_menus_tree', array('menu_name' => $menu_name, 'mlid' => $mlid, 'depth' => $depth, 'menu' => $menu))) { if ($menu_tree['content']) { $output['content'] = '' . "\n"; $output['subject'] = $menu_tree['subject']; } } return $output; } /** * Theme the main menu as a Nice menu. * * @param $direction * Optional. The direction the menu expands. Default is 'down'. * @param $depth * The number of children levels to display. Use -1 to display all children * and use 0 to display no children. * * @return * An HTML string of Nice main menu links. */ function theme_nice_menus_main_menu($variables) { $direction = $variables['direction']; $depth = $variables['depth']; $menu_name = variable_get('menu_main_links_source', 'main-menu'); $output = theme('nice_menus', array('id' => 0, 'menu_name' => $menu_name, 'mlid' => 0, 'direction' => $direction, 'depth' => $depth)); return $output['content']; } /** * Theme the secondary menu as a Nice menu. * * @param $direction * Optional. The direction the menu expands. Default is 'down'. * @param $depth * The number of children levels to display. Use -1 to display all children * and use 0 to display no children. * * @return * An HTML string of Nice secondary menu links. */ function theme_nice_menus_secondary_menu($variables) { $direction = $variables['direction']; $depth = $variables['depth']; $menu_name = variable_get('menu_secondary_links_source', 'user-menu'); $output = theme('nice_menus', array('id' => 0, 'menu_name' => $menu_name, 'mlid' => 0, 'direction' => $direction, 'depth' => $depth)); return $output['content']; }