1

I have 2 content types: Books and Chapters.

Books have an entity reference field that can accept unlimited values. Chapters have an entity reference field (single value) that corresponds to a book.

These entity reference fields are "linked" with the Corresponding Entity References module.

My goal is to create a block that will be displayed at the top of the Chapter nodes that will act like a pager of some sort and will display the previous and next chapters from the one that is being viewed.

For example, when someone views Chapter 1 of a book, the block will show:

  • Chapter 1 (no link)
  • Chapter 2 (link to Chapter 2)
  • Chapter 3 (link to Chapter 3)
  • Chapter 4 (link to Chapter 4)
  • Chapter 5 (link to Chapter 5)

When someone is viewing Chapter 10, the block will show:

  • Chapter 8 (link to Chapter 8)
  • Chapter 9 (link to Chapter 9)
  • Chapter 10 (no link)
  • Chapter 11 (link to Chapter 11)
  • Chapter 12 (link to Chapter 12)

and when someone is viewing the last chapter (say chapter 25):

  • Chapter 21 (link to Chapter 21)
  • Chapter 22 (link to Chapter 22)
  • Chapter 23 (link to Chapter 23)
  • Chapter 24 (link to Chapter 24)
  • Chapter 25 (no link)

My immediate thought was to create a view, but after I have created the view with the proper contextual filter and relationship, there was no option to sort the results based on the weight of the Chapter in the Book's entity reference listing.

I then tried to use Views PHP and sort the results via PHP with code like that:

$node = node_load($row1->nid);
$weights = $node->field_book_contained_chapter['und'];
array_walk($weights, function (&$v, $k) { $v = array_shift($v); }); 
return array_search($row1->nid_1, $weights) - array_search($row2->nid_1, $weights);

which works fine only if you select to display all items (doesn't work with pagers) but is also very inefficient: it runs through the whole array of referenced entities for every comparison between two rows (and some of my books have more than 1300 chapters). I haven't tested how it performs but my gut tells me that it's not a very elegant solution (I will also have to do additional filtering after I sort them).

Now I'm thinking about creating a custom block with php code that will traverse the $node->filed_book_contained_chapter['und'] array and find the neighboring articles of the current article. This seems like a more efficient solution than the views' one, but still feels kind of an awkward approach.

Is there an obvious way that I'm missing to achieve the functionality that I'm after?

  • Have you already tried the core 'book' module? – Darvanen Sep 11 '14 at 4:00
  • The problem is not about the books and the chapters. It's about presenting nodes that reference to the same node and are "neighbors" to the node currently being viewed. – user17103 Sep 11 '14 at 14:18
2

A solution that comes to mind could include creating a block in a custom module and manually generate your pager-like unordered list. I wrote some quick code to show you how it can be done. I also added some comments in there to help you understand what I'm doing.

Notice, that this code is assembled in a hurry so you may find additional optimizations that you can perform in there.

/**
 * Implements hook_block_info().
 *
 * Declares your custom pager block.
 *
 */
function YOUR_MODULE_block_info() {
  $blocks = array();
  $blocks['parent_book'] = array(
    'info' => t('Parent Law'),
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 *
 * Renders the block. Calls the main rendering function.
 *
 */
function YOUR_MODULE_block_view($delta = '') {
  $block = array();
  switch ($delta) {
    case 'parent_book':
      $block['subject'] = '';
      $block['content'] = _YOUR_MODULE_parent_book_content_generator();
      break;
  }

  return $block;
}

/**
 * Helper function: renders the block's content.
 */
function _YOUR_MODULE_parent_book_content_generator() {
  // We'll need this to create the links to the articles.
  global $base_url;
  // Initialize the $output variable
  $output = '';
  // Checks if this is a node page.
  // No content type checking, you'll either have to add extra code
  // to narrow it down or use something like panels.
  if (arg(0) == 'node') {
    $book_chapter_nid = arg(1);
    if ($book_chapter_nid) {
      // Load the current chapter node object
      $book_chapter = node_load($book_chapter_nid);
      // Get the container book's nid and load its object.
      // If you have a multilingual site you'll have to pass the node's language in there
      $book_nid = $book_chapter->field_container_book[LANGUAGE_NONE][0]['target_id'];
      $book = node_load($book_nid);
      // Create a references array and walk through it to remove the intermediate arrays
      $references = $book->field_book_contained_chapter[LANGUAGE_NONE];
      array_walk($references, function(&$value, $key) {
        $value = array_shift($value);
      });
      // Setup the items that you want to display here
      $items_to_display = 5;
      // Find the current chapter's position in the array
      $current_chapter_position = array_search($book_chapter_nid, $references);
      // Crude method to get a maximum number of loops that will be needed
      $no_of_loops = (2*($items_to_display - 1)) + 1;
      // Initialize our final array
      $chapters_to_show = array();
      // Push the current chapter in the array
      array_push($chapters_to_show, $book_chapter_nid);
      // Index to stop adding items when we've reached our goal of items to display
      // Initialized to 1 since we've already added the current chapter
      $items_added = 1;
      // Loop a specific amount of times
      // Notice that we'll always break out of the loop before it ends
      // i.e. when the number of items added becomes equal to the number of items that we wanted to display
      for ($index = 1; $index <= $no_of_loops; $index++) {
        // The condition to add an item before our current chapter is the not-negative index
        if (($current_chapter_position - $index) >= 0) {
          // Previous chapters are added at the top of the array
          array_unshift($chapters_to_show, $references[$current_chapter_position - $index]);
          // Update index
          $items_added++;
        }
        // Check if we need to exit the loop right now.
        // This is needed for even amount of items to display
        if ($items_added >= $items_to_display) {
          break;
        }
        // The condition to add an item after our current chapter is if the array key exists in the $references array
        if (array_key_exists(($current_chapter_position + $index), $references)) {
          // Next chapters are added at the bottom of the array
          array_push($chapters_to_show, $references[$current_chapter_position + $index]);
          // Update index
          $items_added++;
        }
        // Check if need to exit the loop again.
        if ($items_added >= $items_to_display) {
          break;
        }
      }
      // Time to print our unordered list
      $output .= '<ul class="book-chapter-pager">';
      // Loop through the array that we created and store the nid in $current_chapter variable
      foreach ($chapters_to_show as $current_chapter) {
        // Load the node since we need it to display the title
        // This: check_plain(db_result(db_query('SELECT title FROM {node} WHERE nid = %d',$nid)));
        // is also possible and more efficient than loading the entire node but revisions need to be taken
        // into account if it is used.
        $node = node_load($current_chapter);
        // Wrapper for the current list item
        $output .= '<li class="book-chapter-pager-item">';
        // If we are printing the current chapter, we don't want to print the link markup
        if ($current_chapter <> $book_chapter_nid) {
          $output .= '<a href="' . $base_url . '/node/' . $current_chapter . '" class="book-chapter-pager-link">';
        }
        // The title needs to be printed in any case though
        $output .= $node->title;
        // Close the link markup if applicable
        if ($current_chapter <> $book_chapter_nid) {
          $output .= '</a>';
        }
        // Close the list item markup
        $output .= '</li>';
      }
      // Close the unordered list markup
      $output .= '</ul>';
    }
  }
  // Return our final input which will be stored in $block['content']
  return $output;
}
  • works like a charm! – user17103 Sep 11 '14 at 22:26

Your Answer

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