by Angie Byron on June 25, 2006 // Short URL

Custom paging for views

Introduction

Angie/webchick here, and this is my very first article on the Lullabot site :)

One of the projects I'm working on at the moment is The World, an audio news magazine co-produced by BBC World Service, PRI and WGBH Boston. They put out a new show every week day, and the content is divided into separate sections (represented by taxonomy) such as Global Hit, Geo Quiz, and so on.

One of the requirements was to show only the content for a given day when clicking on certain sections of the site, and allow next/previous links to take you backward and forward in the list. This is in contrast to the default Drupal pager, which merely goes by X nodes per page. Further, there should not be any "dead" links; the next/previous links should always point to a date with valid content.

So this required a few things:

  1. A list of nodes, filtered by taxonomy term.
  2. A method for filtering this list by a given date.
  3. A way to show the most recent show's content if no date is specified.
  4. Logic to figure out what the next/previous dates are (we can't do a simple "current day +/- 1").
  5. Code to display the pager.
  6. Some way to tie it all together.

Here's the solution I came up with -- source code is attached at the end.

The basic stuff

The operative word in the above narrative is list. Anytime you need "lists of stuff," you should immediately think of Views. Download and enable the module, then click administer >> views to get started.

The preliminary setup was pretty straight-forward:

  • Name: taxonomy_by_date
  • Access: anonymous user and authenticated user
  • Description: View a list of taxonomy filtered by date
  • Check Provide page view (expand the Page fieldset)
  • URL: taxonomy_by_date
  • View Type: Teaser List
  • Uncheck Use pager

Fun with Arguments

The first requirement, setting up a taxonomy-filtered node list, is super easy. Expand the Arguments fieldset, select Taxonomy: Term ID from the list, and tell it to Display all values if a term isn't specified. Now taxonomy_by_date/1 will show you all of the nodes tagged with term 1, taxonomy_by_date/2 will show you all of the nodes tagged with term 2, and just taxonomy_by_date by itself will show everything.

Next, we need a way to restrict those lists further by date. So let's add a second argument, this time Node: Posted Full Date and again Display all values if none is specified. Now, you can go to a URL like taxonomy_by_date/2/20060622 to show only the content tagged with taxonomy term 2 that was created on June 22, 2006.

However, that's not quite what we want; we want to show the most recently published content if a date isn't specified. So how do we attack the problem of needing to "inject" an argument into a view where one is lacking?

The answer lies in the Argument handling code section. This is a really handy Views feature which allows you to make changes to the view on-the-fly, as it's being built! I wrote up a handbook page which explains this functionality in a bit more detail. For now though, let's check out some code:

// Default to term 1 if none was set
if (!$args[0]) {
  $args[0] = 1;
}

// Default to most recent date if none was set
if (!$args[1]) {
  $timezone = _views_get_timezone();
  $latest = db_result(db_query("
    SELECT DATE_FORMAT(FROM_UNIXTIME(n.created+$timezone), '%Y%m%%d')
    FROM {node} n
    INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid = %d
    ORDER BY n.created DESC LIMIT 1", $args[0]));
  $args[1] = $latest;
}

return $args;

$args here is an array of the arguments that are passed into the current view. So in a URL like taxonomy_by_date/2/20060622, $args[0] would be 2, and $args[1] would be 20060622. This code checks to see if both $args[0] and $args[1] are set; if not, it gives them default values (1 for term and the most recent date, respectively).

We're using db_result() here because we're only interested in one value: the date if the newest content in that term formatted as YYYYMMDD.

Note this weird little bit: '%Y%m%%d' -- there are two %%'s here in order to escape %d because that has significance in db_query -- it indicates the value should be replaced with something numeric. Also, note that the code is not between <?php and ?> ... this is intentional; doing so throws an error.

So now, going to taxonomy_by_date/ will automatically show us all the most recent content in term 1. Sweet!

More Fun with the Date Pager

Now, the tricky part... how to get those next/previous date links in there?

I struggled with this for a couple days and eventually came up with placing code for the pager in the Footer text of the Page section, with PHP code as the input format. At this point, the view is built and is accessible via the global variable $GLOBALS['current_view']. Here's the code:

<?php
// Get current view object and its arguments
$view = $GLOBALS['current_view'];
$args = $view->args;
$term = $args[0];

// Retrieve array of unique dates
$timezone = _views_get_timezone();
$result = db_query("
  SELECT DISTINCT DATE_FORMAT(FROM_UNIXTIME(n.created+
$timezone), '%Y%m%%d')
  AS date
  FROM {node} n
  INNER JOIN {term_node} tn ON n.nid = tn.nid
  WHERE tn.tid = %d ORDER BY n.created DESC"
, $term);
while (
$date = db_fetch_object($result)) {
 
$date_list[] = $date->date;
}

// Find current and last positions
$current = array_search($args[1], $date_list);
$last = count($date_list) - 1;

// Find previous date
if ($current == 0) {
 
$prev = NULL;
}
else {
 
$prev = $date_list[$current-1];
}

// Find next date
if ($current == $last) {
 
$next = NULL;
}
else {
 
$next = $date_list[$current+1];
}

print
theme('date_pager', $prev, $next, $term);
?>

Essentially what we're doing is grabbing a list of each unique date that a node was created, and tossing that into an array. We figure out if we're on the first or the last date in the list, and pass the next/previous links into the date pager, respectively.

Finally, here's the theme_date_pager function (I just stuck this in a small custom module):

<?php
/**
* Displays date pager at the bottom of the taxonomy_by_date view
*
* @param $prev
*   A string containing the previous date, in the form of
*   YYYYMMDD, or NULL if no previous date
* @param $next
*   A string containing the next date, in the form of
*   YYYYMMDD, or NULL if no next date
* @param $term
*   The term that's being filtered
* @return
*  A string containing the HTML of the date pager
*
* @ingroup themeable
*/
function theme_date_pager($prev, $next, $term) {
 
$output = '';
 
$links = '';

  if (
$prev) {
   
$links .= l(t('< ') . format_date(strtotime($prev), 'custom', 'F j, Y'),
     
'taxonomy_by_date/'. $term .'/'. $prev,
       array(
'class' => 'pager-previous', 'title' => t('Go to previous date')));
  }
  if (
$next) {
   
$links .= l(format_date(strtotime($next), 'custom', 'F j, Y') . t(' >'),
     
'taxonomy_by_date/'. $term .'/'. $next,
      array(
'class' => 'pager-next', 'title' => t('Go to next date')));
  }

  if (!empty(
$links)) {
  
$output .= '<div id="pager">';
  
$output .= '<span class="pager-list">'. $links .'</span>';
  
$output .= '</div>';
  }

  return
$output;
}
?>

This displays links like:

And there you have it! As promised, you can also download an export of the view.

What next?

After talking with Eaton a bit on IRC, we agreed that it seems like some type of "browser" view type could come in very handy for handling various requirements that come up. I've created a "browser" view type feature request at Drupal.org to discuss that. If anyone has any implementation ideas, feel free to jump in!

Angie Byron

Powered by Drupal!

Comments

Gunnar Langemark

The world running Drupal?

Does that mean that The BBC has a site called The World, which runs on Drupal? I guess it does. That is excellent news!
This is a case which can leverage Drupal in the media world bigtime I think.

Reply

dado@drupal.org

The World: very cool

The World (my favorite radio program) using Drupal (my favorite CMS), and it's being developed by some of the world's most competent Drupallers. Should be a smashing site!

Reply

moshe Weitzman

pager ?

how does this pager differ from a list where only 1 node is shown per page?

Reply

angie

Not always one node...

Certain sections will indeed only have one node per page. For example, there is only one featured "Global Hit" per day -- however, there may be N published nodes in a particular category, say "Latest Editions," and all N should show up on that date's page.

Reply

John H

Interesting example, but...

...it seems like it would be MUCH easier to simply use page in a custom Views theme.

Still, the article is well-written and is a good example of what's possible. Nice job!

Reply

Enes

Custom pager navigation trough taxonomy terms of specific vocab

Hi,
I have a problem, I tried posting on drupal forum but did not get the answer, hope you can help
My site structure is like this

vocabularies: projects, clients, type of work...
projects
project 01
------ item 01
------ item 02
------ item 03
project 02
------ item 01
------ item 02
------ item 03
project 03
------ item 01
------ item 02
------ item 03
...

I need to make custom pager that will appear on items but go trough prev/next project and I caaant do it, Im pretty much new to php and drupal
thanx in advance

Reply

Anonymous

You lost me at: "Finally,

You lost me at:
"Finally, here's the theme_date_pager function (I just stuck this in a small custom module):"

How? This seems to be precisely what myself and many others are looking for. Thank you for your help.

Reply

norm1710

You lost me at: "Finally, too

The rest works well but no date function.

Where should this go?

Thanks for your articles - always well written!

Reply

matslats

version 6

I'm trying to make a month by month pager for views in version 6

This method doesn't work at all because there is no global variable containing the current view, and I don't know what variables are available to the eval code in the footer.

There doesn't seem to be a module which can do this either.

Reply