5

I want to create a menu that will be shown on the node if it is part of a book. For example like this:

enter image description here

That menu should be shown for all books, which means I don't want to have a separate block for each book in the system.

I am not interested in how to implement accordion functionality, I just need a way to get menu items for all book elements of current node.

Book module provides a block with books navigation, but I don't want all books to be visible in this menu.

  • Indeed, interesting question. In D6, the flat book module (drupal.org/project/flat_book) makes the trick. Unfortunately, this module is not port to d7 and even the d6 version seems not maintain anymore. but maybe it's still a good starting point. – xaa Feb 21 '13 at 16:33
4
+50

I was surprised that I couldn't find a contrib module that does this out of the box so I created a simple one.

Here it is on Github too: https://github.com/balsama/bookoblock (bookoblock = "Book Outline Block")

Info file (bookoblock.info):

name = Book Outline Block
description = "Provides a block containing an outline of the current book."
package = Other 
core = 7.x
dependencies[] = book

Module File (bookoblock.module):

<?php
/**
 * Define the Book Outline Block/
 * Implementation of hook_block_info()
 */
function bookoblock_block_info() {
  $block['book_outline'] = array (
    'info' => 'Book Outline',
    'cache' => DRUPAL_CACHE_PER_PAGE,
  );  
  return $block;
}

/**
 * Define the contents and title of the block/
 * Implementation of hook_block_view()
 *
 * Returns a renderable block array containing the current book's outline if
 * the node is part of a book.
 *
 * Returns NULL if the current menu item is not part of a book heirarchy.
 */
function bookoblock_block_view() {
  if ($book = bookoblock_is_book_node()) {
    $tree = menu_tree_all_data(book_menu_name($book['bid']));

    // If we pass 1 as the second param, we just get the top level node in the
    // book. Since the key will be the top level mlid, we'll have to use 
    // ['p1'] in the $book array (which contains the top mlid) when we use it 
    // for the block title.
    $book_name = (book_toc($book['bid'], 1));

    // Build the block title and contents
    $block['subject'] = $book_name[$book['p1']];
    $block['content'] = menu_tree_output($tree);

    // Return the block
    return $block;
  }
  // If the current node isn't part of a book, just return nothing.
  return NULL;
}

/**
 * Checks to see if current node is part of a book.
 * Returns book array if it is part of a book, FALSE is not.
 */
function bookoblock_is_book_node() {
  if ((arg(0) == 'node') && (is_numeric(arg(1)))) {
    $node = menu_get_object();
    if (isset($node->book)) {
      return $node->book;
    }
  }
  return FALSE;
}

Just drop those two files into a folder named bookoblock inside your modules directory and enable the module. You'll now see a new block in the block admin page named Book Outline. That block will only appear on node pages where the node is part of a book. It contains a heierarchical menu of the entire book and it's title is the root node's title.

  • This looks promising, I will try it and let you know. – Bulat Feb 26 '13 at 13:35
  • Just one question, do you know how to limit the number of levels? I suspect I will have to change this line: $tree = menu_tree_all_data(book_menu_name($book['bid']));. – Bulat Feb 26 '13 at 14:25
  • 1
    Just change that line to this: $tree = menu_tree_all_data(book_menu_name($book['bid']), NULL, 2); where "2" = the number of levels you want to show. It's also be pretty easy to put that setting in a module admin page. – Adam Balsam Feb 26 '13 at 15:04
  • 1
    The latest version of the module on github adds a configuration page where you can set the depth of the menu (and an uninstall file to remove that var if you ever uninstall the module). github.com/balsama/bookoblock – Adam Balsam Feb 26 '13 at 15:22
  • @Bulat, this module is pending full project status here: drupal.org/node/1930370. Please consider reviewing it and setting it to RTBC if you would like support for it to be continued. – Adam Balsam Mar 5 '13 at 18:49
2

Book Block is what I've used on a site. It's currently got a dev release but it's been stable for us so far on Drupal 7.

It provides you with a block per book. You can then use context to set the conditions of display.

Update - In terms of scaling it you will have to automate the block position, something like when a new book is created a new context is added with the correct conditions and reactions. Unfortunately this isn't as simple as a drop in contrib solution.

  • This solution does not sound to be scalable. Number of books will grow and users will create new books and content within them. So I want anything that belongs to a book to have this block by default. Unless I am missing something. – Bulat Feb 21 '13 at 17:56
  • Ah ok, you should update the question with the extra information. I'll update my answer with more info. – digital Feb 22 '13 at 12:27
  • Thanks for your answer and update. What I really need is a way to create just one block, that will have that menu. I am sure it is achievable with one block for all books. I am surprised this was not implemented by somebody yet. – Bulat Feb 22 '13 at 13:35
  • I don't know of any contrib solution for this but I can think of a few custom solutions, you could extend the book block module and use the node object to filter the results. – digital Feb 22 '13 at 14:54
0

In addition to Adam's answer I would like to add some information on how to theme the outline of a book.

There are few theming tricks. It took me quite a while to figure them out, so I decided share:

  • You can massage renderable array that you get with menu_tree_output() to change the output. You can either inject html directly to element of the leaf like this:

    $leaf['#title'] = "<span class='my-class'>Custom  html here.. </span>";
    $leaf['#localized_options']['html'] = TRUE;
    $leaf['#attributes']['class'][] = 'my-class-link';
    
  • Or you can change the theming function that will be applied to the element at the desired level of the tree:

    $leaf['#theme'] = 'menu_link__my_book_outline';

in this case you will have to provide reference to this theme in hook_menu and provide implementation of the theme function declared in the hook:

/**
* Implements hook_theme()
*/
function my_book_outline_theme() {
  return array(
        'menu_link__my_book_outline' => array(
           'render element' => 'element'
        )
    );
}

/**
* Returns HTML for the section level elements of the my book outline
* see theme_menu_link for default implementation
*/
function theme_menu_link__my_book_outline(array $variables) {
  $element = $variables['element'];
  $sub_menu = '';
  $output = '<h3>' . $element['#title'] . '</h3>';
  if ($element['#below']) {
    $sub_menu = drupal_render($element['#below']);
  }
  return $output . $sub_menu . "\n";
}

This way you will override the theme_menu_link call with your custom rendering.

  • If you theming depends on levels don't forget that you can always find out the depth of the item - $element['#original_link']['depth']
0

See https://drupal.org/node/1930370#comment-7676439.

How does this request differ from what is available out-of-the-box from the core Book module?

From the original question:

Book module provides a block with books navigation, but I don't want all books to be visible in this menu.

On the core Book Block configuration page, you have the option to Show block only on book pages, which according to the description:

If Show block only on book pages is selected, the block will contain only the one menu corresponding to the current page's book. In this case, if the current page is not in a book, no block will be displayed. The Page specific visibility settings or other visibility settings can be used in addition to selectively display this block.

Unless I'm missing something (again), I think both of us somehow missed this the first time.

Screenshot
Core Book Block Configuration

  • 1
    Did you manage to make it show collapsed items? I guess that could be the original problem. I needed all items of the book with depth level of two to make accordion. When I do it with book's block it only shows one element as expanded. – Bulat Jul 24 '13 at 6:51
  • No, but your original question clearly states, "I am not interested in how to implement accordion functionality, I just need a way to get menu items for all book elements of current node." And the accepted answer doesn't address that either. I think we both missed this simple solution. – Adam Balsam Jul 24 '13 at 10:38
  • 1
    Accordion needs all items of the tree rendered on the page. Accordion can be done with JavaScript and CSS, that is why it's not the main concern of the question. – Bulat Jul 24 '13 at 19:58
0

Books structures are actually stored in the same way as menus, so if you are comfortable editing your database, you can very simply create a custom menu from to the book menu tree (that will therefore be available as a block).

This youtube video shows you how to do this:

http://www.youtube.com/watch?v=FuQIe5ik9as

Alternatively the menu_block module will offer your books as options in the list of menus when you create a new 'menu block' to suit your needs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.