The Quick Tabs module allows you to create blocks of tabbed content. Clicking on the tabs makes the corresponding content display instantly (it uses jQuery). The content for each tabbed section can be either a node, view, block or another quicktab. It is an ideal way to do something like the Most Popular / Most Emailed stories tabs you see on many news websites.

Once created, the quicktab block show up in your block listing, ready to be configured and enabled like other blocks. Multiple quicktabs can be placed on a single page.

Visit the configuration page to see the available styles and select the default style for your quicktabs.

', array('@configuration' => url('admin/settings/quicktabs'))); case 'admin/build/quicktabs/add': return '

'. t('Here you can create a new quicktab block. Once you have created this block you can configure and enable it on the blocks page.', array('@overview' => url('admin/build/block'))) .'

'; } } /** * Implementation of hook_theme(). */ function quicktabs_theme() { return array( 'quicktabs_settings' => array( 'arguments' => array('form' => NULL), 'file' => 'includes/admin.inc', ), 'quicktabs_admin_form_tabs' => array( 'arguments' => array('form' => NULL), 'file' => 'includes/admin.inc', ), 'quicktabs_views_render' => array( 'arguments' => array('view' => NULL, 'tabs' => NULL, 'qtid' => NULL), 'file' => 'includes/quicktabs.views.inc', ), 'quicktabs_tabs' => array( 'arguments' => array('quicktabs', 'active_tab' => 'none'), ), 'quicktabs' => array( 'arguments' => array('quicktabs'), ), 'quicktabs_tab_access_denied' => array( 'arguments' => array('tab'), ), ); } /** * Implementation of hook_menu(). */ function quicktabs_menu() { $items['admin/build/quicktabs'] = array( 'title' => 'Quicktabs', 'description' => 'Create blocks of tabbed content.', 'page callback' => 'quicktabs_list', 'access callback' => 'user_access', 'access arguments' => array('administer quicktabs'), 'type' => MENU_NORMAL_ITEM, 'file' => 'includes/admin.inc', ); $items['admin/build/quicktabs/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['admin/build/quicktabs/add'] = array( 'title' => 'New QT block', 'page callback' => 'drupal_get_form', 'page arguments' => array('quicktabs_form', 'add'), 'access arguments' => array('administer quicktabs'), 'type' => MENU_LOCAL_TASK, 'file' => 'includes/admin.inc', ); $items['admin/build/quicktabs/%quicktabs/edit'] = array( 'title' => 'Edit QT block', 'page callback' => 'drupal_get_form', 'page arguments' => array('quicktabs_form', 'edit', 3), 'access arguments' => array('administer quicktabs'), 'load arguments' => array('edit'), 'type' => MENU_CALLBACK, 'file' => 'includes/admin.inc', ); $items['admin/build/quicktabs/%quicktabs/delete'] = array( 'title' => 'Delete QT block', 'page callback' => 'drupal_get_form', 'page arguments' => array('quicktabs_block_delete', 3), 'access arguments' => array('administer quicktabs'), 'load arguments' => array('delete'), 'type' => MENU_CALLBACK, 'file' => 'includes/admin.inc', ); $items['admin/build/quicktabs/%quicktabs/clone'] = array( 'title' => 'Clone QT block', 'page callback' => 'quicktabs_clone', 'page arguments' => array(3), 'access arguments' => array('administer quicktabs'), 'load arguments' => array('clone'), 'type' => MENU_CALLBACK, 'file' => 'includes/admin.inc', ); $items['admin/settings/quicktabs'] = array( 'title' => 'Quicktabs', 'description' => 'Select the default style for quicktabs.', 'page callback' => 'drupal_get_form', 'page arguments' => array('quicktabs_settings'), 'access callback' => 'user_access', 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, 'file' => 'includes/admin.inc', ); $items['quicktabs/ahah'] = array( 'page callback' => 'quicktabs_ahah', 'access callback' => 'user_access', 'access arguments' => array('administer quicktabs'), 'type' => MENU_CALLBACK, 'file' => 'includes/admin.inc', ); $items['quicktabs/ajax'] = array( 'page callback' => 'quicktabs_ajax', 'access callback' => 'user_access', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_perm(). */ function quicktabs_perm() { return array('administer quicktabs'); } /** * Implementation of hook_block(). */ function quicktabs_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': $blocks = array(); $result = db_query('SELECT qtid, title FROM {quicktabs}'); while ($row = db_fetch_object($result)) { $blocks[$row->qtid]['info'] = $row->title; $blocks[$row->qtid]['cache'] = BLOCK_NO_CACHE; } return $blocks; break; case 'view': $mainblock = array(); if ($quicktabs = quicktabs_load($delta)) { $mainblock['subject'] = $quicktabs['title']; $mainblock['content'] = theme('quicktabs', $quicktabs); } return $mainblock; break; } } /** * Render quicktabs. */ function quicktabs_render($quicktabs) { // convert views arguments to an array, retrieving %-style args from url $quicktabs['tabs'] = _quicktabs_prepare_views_args($quicktabs['tabs']); if ($quicktabs['hide_empty_tabs'] && !$quicktabs['ajax']) { // Remove empty tabpgages. foreach ($quicktabs['tabs'] as $key => $tab) { $contents = quicktabs_render_tabpage($tab, TRUE); if (empty($contents)) { unset($quicktabs['tabs'][$key]); } } $quicktabs['tabs'] = array_merge($quicktabs['tabs']); } $tabs_count = count($quicktabs['tabs']); if ($tabs_count <= 0) { return ''; } if ($quicktabs['style'] == 'default') { $quicktabs['style'] = variable_get('quicktabs_tabstyle', 'nostyle'); } quicktabs_add_css($quicktabs['style']); $javascript = drupal_add_js('misc/progress.js', 'core'); if (!isset($javascript['setting'][1]['quicktabs']) || !array_key_exists('qt_'. $quicktabs['qtid'], $javascript['setting'][1]['quicktabs'])) { // Only the tabs are used in quicktabs.js $settings = array( 'tabs' => $quicktabs['ajax'] ? $quicktabs['tabs'] : quicktabs_array_fill_keys(array_keys($quicktabs['tabs']), 0), ); drupal_add_js(array('quicktabs' => array('qt_'. $quicktabs['qtid'] => $settings)), 'setting'); } drupal_add_js(drupal_get_path('module', 'quicktabs') .'/js/quicktabs.js'); $attributes = drupal_attributes(array( 'id' => 'quicktabs-'. $quicktabs['qtid'], 'class' => 'quicktabs_wrapper quicktabs-style-'. drupal_strtolower($quicktabs['style']), )); $output = ''; $active_tab = _quicktabs_get_active_tab($quicktabs); $output .= theme('quicktabs_tabs', $quicktabs, $active_tab); // The main content area, each quicktab container needs a unique id. $attributes = drupal_attributes(array( 'id' => 'quicktabs_container_'. $quicktabs['qtid'], 'class' => 'quicktabs_main quicktabs-style-'. drupal_strtolower($quicktabs['style']), )); $output .= ''; if ($quicktabs['ajax']) { // Prepare ajax views. _quicktabs_prepare_views($quicktabs['tabs']); // Render only the active tabpage. if (isset($active_tab)) { $attributes = drupal_attributes(array( 'id' => 'quicktabs_tabpage_'. $quicktabs['qtid'] .'_'. $active_tab, 'class' => 'quicktabs_tabpage', )); $output .= ''. quicktabs_render_tabpage($quicktabs['tabs'][$active_tab]) .''; } } else { // Render all tabpgages. foreach ($quicktabs['tabs'] as $key => $tab) { $attributes = drupal_attributes(array( 'id' => 'quicktabs_tabpage_'. $quicktabs['qtid'] .'_'. $key, 'class' => 'quicktabs_tabpage'. ($active_tab == $key ? '' : ' quicktabs-hide'), )); $output .= ''. quicktabs_render_tabpage($tab) .''; } } $output .= ''; return $output; } /** * Ajax callback for tab content. */ function quicktabs_ajax($type) { $args = func_get_args(); $type = array_shift($args); $func = 'quicktabs_ajax_'. $type; $output = call_user_func_array($func, $args); $js_css = quicktabs_ajax_js_css(); $response = array('content' => $output, 'js_css' => $js_css); drupal_json(array('status' => 0, 'data' => $response)); } /** * Ajax callback for node tabpage. */ function quicktabs_ajax_node($nid, $teaser, $hide_title) { $tabpage = array( 'type' => 'node', 'nid' => $nid, 'teaser' => $teaser, 'hide_title' => $hide_title, ); $output = quicktabs_render_tabpage($tabpage); return $output; } /** * Ajax callback for block tabpage. */ function quicktabs_ajax_block($bid, $hide_title) { $tabpage = array( 'type' => 'block', 'bid' => $bid, 'hide_title' => $hide_title, ); $output = quicktabs_render_tabpage($tabpage); return $output; } /** * Ajax callback for qtabs tabpage. */ function quicktabs_ajax_qtabs($qtid) { $tabpage = array( 'type' => 'qtabs', 'qtid' => $qtid, ); $output = quicktabs_render_tabpage($tabpage); return $output; } /** * Theme function to display the access denied tab. * * @ingroup themeable */ function theme_quicktabs_tab_access_denied($tab) { return t('You are not authorized to access this content.'); } /** * Theme function to output quicktabs. * * @ingroup themeable */ function theme_quicktabs($quicktabs) { return quicktabs_render($quicktabs); } /** * Theme function for output of the tabs. Use this to ADD extra classes. * The general structure 'ul.quicktabs_tabs li a' needs to be maintained * for the jQuery to work. * * @ingroup themeable */ function theme_quicktabs_tabs($quicktabs, $active_tab = 'none') { $output = ''; $tabs_count = count($quicktabs['tabs']); if ($tabs_count <= 0) { return $output; } $index = 1; $output .= '
    '; foreach ($quicktabs['tabs'] as $tabkey => $tab) { $class = 'qtab-'. $tabkey; // Add first, last and active classes to the list of tabs to help out themers. $class .= ($tabkey == $active_tab ? ' active' : ''); $class .= ($index == 1 ? ' first' : ''); $class .= ($index == $tabs_count ? ' last': ''); $attributes_li = drupal_attributes(array('class' => $class)); $options = _quicktabs_construct_link_options($quicktabs, $tabkey); // Support for translatable tab titles with i18nstrings.module. if (module_exists('i18nstrings')) { $tab['title'] = i18nstrings('quicktabs:tab:' . $quicktabs['qtid'] . '--' . $tabkey . ':title', $tab['title']); } $output .= ''. l($tab['title'], $_GET['q'], $options) .''; $index++; } $output .= '
'; return $output; } /** * Helper function to construct link options for tab links. */ function _quicktabs_construct_link_options($quicktabs, $tabkey) { $qtid = $quicktabs['qtid']; $ajax = $quicktabs['ajax']; $tab = $quicktabs['tabs'][$tabkey]; $id = 'quicktabs-tab-'. implode('-', array($qtid, $tabkey)); // Need to construct the correct query for the tab links. $query = $_GET; unset($query['quicktabs_'. $qtid]); unset($query['q']); unset($query['page']); $query['quicktabs_'. $qtid] = $tabkey; if ($ajax) { $class = 'qt_ajax_tab'; } else { $class = 'qt_tab'; } $link_options = array( 'attributes' => array( 'id' => $id, 'class' => $class, ), 'query' => $query, 'fragment' => 'quicktabs-'. $qtid, ); return $link_options; } /** * Fetch the necessary CSS files for the tab styles. */ function quicktabs_add_css($style) { // Add quicktabs CSS. drupal_add_css(drupal_get_path('module', 'quicktabs') .'/css/quicktabs.css'); if ($style == 'default') { // Get the default style. $style = variable_get('quicktabs_tabstyle', 'nostyle'); } $style_css = _quicktabs_get_style_css($style); if ($style_css != 'nostyle') { drupal_add_css($style_css, 'module'); } } /** * Helper function to get the css file for given style. */ function _quicktabs_get_style_css($style = 'nostyle') { static $tabstyles; if ($style != 'nostyle') { if (!isset($tabstyles)) { $tabstyles = module_invoke_all('quicktabs_tabstyles'); } foreach ($tabstyles as $css_file => $tabstyle) { if ($style == $tabstyle) { return $css_file; } } } return 'nostyle'; } /** * Implementation of hook_quicktabs_tabstyles(). * * This hook allows other modules to create additional tab styles for * the quicktabs module. * * @return array * An array of key => value pairs suitable for inclusion as the #options in a * select or radios form element. Each key must be the location of a css * file for a quick tabs style. Each value should be the name of the style. */ function quicktabs_quicktabs_tabstyles() { $tabstyles_directory = drupal_get_path('module', 'quicktabs') .'/tabstyles'; $files = file_scan_directory($tabstyles_directory, '\.css$'); $tabstyles = array(); foreach ($files as $file) { // Skip RTL files. if (!strpos($file->name, '-rtl')) { $tabstyles[$file->filename] = drupal_ucfirst($file->name); } } return $tabstyles; } /** * Get a list of all available tab styles, suitable for use as an options array. */ function quicktabs_style_options($include_defaults = TRUE) { $styles = module_invoke_all('quicktabs_tabstyles'); // The keys used for options must be valid html id-s. foreach ($styles as $style) { $style_options[$style] = $style; } ksort($style_options); if ($include_defaults) { $style_options = array('nostyle' => t('No style'), 'default' => t('Default style')) + $style_options; } return $style_options; } /** * Load the quicktabs data. */ function quicktabs_load($qtid, $op = 'view') { $quicktabs = db_fetch_array(db_query('SELECT qtid, title, tabs, ajax, hide_empty_tabs, default_tab, style FROM {quicktabs} WHERE qtid = %d', $qtid)); if (!$quicktabs) { return FALSE; } $tabs = unserialize($quicktabs['tabs']); foreach ($tabs as $key => $tab) { $weight[$key] = $tab['weight']; if ($tab['type'] == 'qtabs' && $tab['qtid'] == $qtid) { unset($tabs[$key]); unset($weight[$key]); } } array_multisort($weight, SORT_ASC, $tabs); $quicktabs['tabs'] = $tabs; drupal_alter('quicktabs', $quicktabs, $op); return $quicktabs; } /** * Render quicktabs tabpage. */ function quicktabs_render_tabpage($tab, $hide_empty = FALSE) { static $cache; $cachekey = md5(serialize($tab)); if (isset($cache[$cachekey])) { return $cache[$cachekey]; } $output = ''; switch ($tab['type']) { case 'qtabs': if (isset($tab['qtid'])) { if ($quicktabs = quicktabs_load($tab['qtid'])) { $output = theme('quicktabs', $quicktabs); } } break; case 'view': if (isset($tab['vid'])) { if (module_exists('views')) { if ($view = views_get_view($tab['vid'])) { if ($view->access($tab['display'])) { $view->set_display($tab['display']); $view->set_arguments($tab['args']); $view_output = $view->preview(); if (!empty($view->result) || $view->display_handler->get_option('empty') || !empty($view->style_plugin->definition['even empty'])) { $output = $view_output; } else { $output = ''; } } elseif (!$hide_empty) { $output = theme('quicktabs_tab_access_denied', $tab); } $view->destroy(); } } elseif (!$hide_empty) { $output = t('Views module not enabled, cannot display tab content.'); } } break; case 'block': if (isset($tab['bid'])) { $pos = strpos($tab['bid'], '_delta_'); $blockmodule = substr($tab['bid'], 0, $pos); $blockdelta = substr($tab['bid'], $pos + 7); $block = (object) module_invoke($blockmodule, 'block', 'view', $blockdelta); if (isset($block->content)) { $block->module = $blockmodule; $block->delta = $blockdelta; $block->region = 'quicktabs_tabpage'; if ($tab['hide_title'] || !isset($block->subject)) { $block->subject = FALSE; } $output = theme('block', $block); } } break; case 'node': if (isset($tab['nid'])) { $node = node_load($tab['nid']); if (!empty($node)) { if (node_access('view', $node)) { $output = node_view($node, $tab['teaser'], $tab['hide_title'], TRUE); } elseif (!$hide_empty) { $output = theme('quicktabs_tab_access_denied', $tab); } } } break; case 'freetext': $output = $tab['text']; break; } $cache[$cachekey] = $output; return $output; } /** * Helper function to determine active tab from the url. */ function _quicktabs_get_active_tab($quicktabs) { $active_tab = isset($quicktabs['default_tab']) ? $quicktabs['default_tab'] : key($quicktabs['tabs']); $active_tab = isset($_GET['quicktabs_'. $quicktabs['qtid']]) ? $_GET['quicktabs_'. $quicktabs['qtid']] : $active_tab; if (isset($active_tab) && isset($quicktabs['tabs'][$active_tab])) { return $active_tab; } return NULL; } /** * Helper function to add views settings to ajax tabs. */ function _quicktabs_prepare_views($tabs) { if (module_exists('views')) { views_add_js('ajax_view'); views_add_css('views'); foreach ($tabs as $key => $tab) { if ($tab['type'] == 'view') { // We need to pass view details to js in case there is ajax paging. $settings = array( 'views' => array( 'ajax_path' => url('views/ajax'), 'ajaxViews' => array( array( 'view_name' => $tab['vid'], 'view_display_id' => $tab['display'], 'view_args' => implode('/', $tab['args']), 'view_path' => $_GET['q'], ), ), ), ); drupal_add_js($settings, 'setting'); } } } } /** * Helper function to use view arguments from the URL. */ function _quicktabs_prepare_views_args($tabs) { foreach ($tabs as $key => $tab) { if ($tab['type'] == 'view') { $url_args = arg(); $args = $tab['args']; foreach ($url_args as $id => $arg) { $args = str_replace("%$id", $arg, $args); } $args = preg_replace(',/?(%\d),', '', $args); $args = $args ? explode('/', $args) : array(); $tab['args'] = $args; $tabs[$key] = $tab; } } return $tabs; } /** * Implementation of hook_locale(). */ function quicktabs_locale($op = 'groups', $group = NULL) { switch ($op) { case 'groups': return array('quicktabs' => t('Quick Tabs')); case 'info': $info['quicktabs']['refresh callback'] = 'quicktabs_locale_refresh'; $info['quicktabs']['format'] = FALSE; return $info; } } /** * Refresh locale strings. */ function quicktabs_locale_refresh() { $result = db_query("SELECT qtid FROM {quicktabs}"); while ($row = db_fetch_object($result)) { $quicktabs = quicktabs_load($row->qtid, 'locale refresh'); foreach ($quicktabs['tabs'] as $tabkey => $tab) { i18nstrings_update('quicktabs:tab:' . $quicktabs['qtid'] . '--' . $tabkey . ':title', $tab['title']); } } return TRUE; } /** * This function is based on the ctools_ajax_render() function in the CTools * AJAX framework. It allows us to pull js/css files and js settings for content * that will be loaded via ajax. */ function quicktabs_ajax_js_css() { $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1); $scripts = drupal_add_js(NULL, NULL); foreach ($scripts as $type => $data) { switch ($type) { case 'setting': $settings = $data; break; case 'inline': // currently we ignore inline javascript. break; default: // If JS preprocessing is off, we still need to output the scripts. // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. foreach ($data as $path => $info) { $js_files[] = base_path() . $path . ($info['cache'] ? $query_string : '?' . time()); } } } $css = drupal_add_css(); foreach ($css as $media => $types) { // If CSS preprocessing is off, we still need to output the styles. // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones. foreach ($types as $type => $files) { if ($type == 'module') { // Setup theme overrides for module styles. $theme_styles = array(); foreach (array_keys($css[$media]['theme']) as $theme_style) { $theme_styles[] = basename($theme_style); } } foreach ($types[$type] as $file => $preprocess) { // If the theme supplies its own style using the name of the module style, skip its inclusion. // This includes any RTL styles associated with its main LTR counterpart. if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) { // Unset the file to prevent its inclusion when CSS aggregation is enabled. unset($types[$type][$file]); continue; } // Only include the stylesheet if it exists. if (file_exists($file)) { $css_files[] = array( 'file' => base_path() . $file . $query_string, 'media' => $media, ); } } } } $js_css = array(); if (!empty($settings)) { $js_css['js_settings'] = call_user_func_array('array_merge_recursive', $settings); } if (!empty($js_files)) { $js_css['js_files'] = $js_files; } if (!empty($css_files)) { $js_css['css_files'] = $css_files; } return $js_css; } /** * Implementation of hook_views_api(). */ function quicktabs_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'quicktabs') .'/includes', ); } /** * PHP4 version of array_fill_keys(). */ function quicktabs_array_fill_keys($keys, $value){ $return = array(); foreach ($keys as $key) { $return[$key] = $value; } return $return; }