Easy Way Out Before Lost inside Views Maze

As I have talked in my previous blog about importing large quantity of node without feeds module. Today, I will show you how to build complicated block views without using views module.

Did you have ever faced the situation that views can not solve the problem? I have a unique request from my client http://www.wview.com/ this week. We need to have a related video block for video node page. There are three levels of sorting. First show the episode videos from the same series. Then show the videos having same tag. At last, show the rest of the videos that are not in the same series and have no tags in common. What are those three requirements really means? We need to sort all video nodes according to current node's taxonomy tags. So, views block is not the solution since we select all the nodes. Even though, views is one of the most used modules among Drupal websites; it is not a medicine for everything. As a rule of thumb, if any problem can not solved by one SQL query, then we should look for a solution in other places. The problem we are solving is related to complicated sorting that views can not do it. It is one of the case.

It took me and another Drupal developer all most two days to make a view on it. We can not find a good way to sort them. In the end, we give up views and decide to make a custom module for it. Within couple hours, a customized block was built. I found that a custom block was easier to build. It is more efficient than the views block. Here I would like to show you how does it work.

There are three parts of the module. First, get a list of the nodes. Second, render the list of the nodes. Third part is using the ajax framework to add the load more button.

Most difficult part of the module is to get the list of the nodes. Since all the request of 3 levels of sort are all done with the list of nodes. It is where views is not able to accomplish it. But it is more related to the business logic. Different project may face different logic. There is one thing I want to mention on this part. We need to be careful when build query. We are adding more and more videos to the website. Try to avoid loading too many videos into memory will be the key point to make sure the system will running smoothly after the site grow big. Check blog "build website with a million nodes" for more detail on it.

Here are the codes for the rest of 2 parts.

Render the list of nodes into a block. We use node teaser displays for each node in the block.

/*
* Render the list of nodes into HTML.
*/
function related_videos_render_json($count = 7, $position = 0, $cur_nid = 0) {
  $nodes = related_videos_get_list($count, $position, $cur_nid);
  $nid = array();
  foreach ($nodes as $key => $node) {
    $nids[] = $node->nid;
  }
  if (!empty($nids)) {
    if ($count > count($nids)) {
      $count = 0;
    }
    $nodes = node_load_multiple($nids);
    $build = node_view_multiple($nodes);
    if ($count != 0) {
      $build['loadmore'] = array(
        '#prefix' => '<ul id="related_videos_link" class="load-more"><li>',
        '#suffix' => '</li></ul>',
        '#type' => 'link',
        '#title' => 'Load More',
        '#href' => 'related_videos/nojs/' . $count . '/' . ($position + $count) . '/' . $cur_nid,
        '#id' => 'videos_ajax_link',
        '#options' => array('attributes' => array('class' => array('use-ajax'))),
        '#ajax' => array(
          'wrapper' => 'related_videos_link',
          'method' => 'json',
        ),
      );
    }
  }
  else {
    $build['no_content'] = array(
      '#prefix' => '<p>',
      '#markup' => t('There is currently no content.'),
      '#suffix' => '</p>',
    );
  }
  return render($build);
}

The function related_videos_get_list($count, $position, $cur_nid) is to get the list of node where we put our node sorting logic there.

Then we use two block hooks to create a block for the list of video.

/**
* Implements hook_block_info().
*/
function related_videos_block_info() {
  $blocks['related_videos_block'] = array(
    'info'    => t('Related videos block'),
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/**
* Implements hook_block_view().
*/
function related_videos_block_view($delta = '') {
  switch ($delta) {
    case 'related_videos_block':
      $cur = related_videos_current_node();
      drupal_add_library('system', 'drupal.ajax');
      $block['subject'] = t("Related Videos");
      $block['content'] = '<section class="col-xs-12 col-sm-8 wv-contnet video-related-wrapper"><h2>Related Videos</h2>';
      $block['content'] .= related_videos_render_json(7, 0, $cur->nid);
      $block['content'] .= '</section>';
      break;
  }
  return $block;
}

So, now we have a block can be assign to anywhere with Drupal block system, panel or context module.

The last part is to use Drupal Ajax framework to add a load more button. We load more videos without reload the page.

First, implements the hook_menu to define the callback link for ajax request.

/**
* Implements hook_menu().
*/
function related_videos_menu() {
  $items = array();
  $items['related_videos/ajax'] = array(
    'page callback' => 'related_videos_ajax',
    'access callback' => 'user_access',
    'delivery callback' => 'ajax_deliver',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['related_videos/nojs'] = array(
    'page callback' => 'related_videos_nojs',
    'access callback' => 'user_access',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

The second menu item items['related_videos/nojs'] is for the fall back of browser have javascript disabled. For more detail, check Ajax framework document.

Then we build the callback function related_videos_ajax. We use the callback function in the implementation of hook_menu.

/**
* Ajax callback for related videos content.
*/
function related_videos_ajax($count, $position, $cur) {
  $data = related_videos_render_json($count, $position, $cur);
  $commands = array();

  $commands[] = ajax_command_replace('#related_videos_ajax_link', $data);
  $page = array('#type' => 'ajax', '#commands' => $commands);
  return $page;
}

The function related_videos_render_json is the function we used to render the HTML. We use the same function for the initial block content. Here, we got the HTML of the list of the node and delivered to the browser in JSON format for the load more button. The parameters $count, $position, $cur was passed in from the URL. The URL of the load more link is defined by the related_videos_render_json function.

Those three parts formed a custom block display a uniquely ordered video nodes. Use the powerful Drupal theme function and Ajax framework, it is easy to build a block like this.

Add new comment

Target Image