Simple page turner: Request for comments

A while back on my own website I posted an item describing a simple page turner in Drupal, suitable for providing access to image-based representations of books, postcards, series of photos, and so on. Several people have contacted me to ask how this works so I thought I'd write it up here on drupalib.

The page turner for a given document is a single node that has two main parts: 1) a metadata record created using either the flexinode or Content Creation Kit (otherwise known as CCK) Drupal modules and 2) a PHP snippet (called the "document handler") that manages the navigation between the page images. The metadata is just a simple node type in either flexinode or CCK whose fields function as the metadata elements. My examples use elements taken from unqualified Dublin Core plus some extra ones. One field in the node is reserved for the PHP snippet (I put it in a "Document" or "Display" field), which displays the HTML wrapping the pages and links that allow users to navigate between pages.

Here is the PHP snippet (see this example to see how it looks in action):

<?php
/*
First, set some document-specific variables
*/

// $image_base_path is where all the page images sit, and the common filename up to the image number
$image_base_path = 'http://interoperating.info/mark/files/hawthorne/hawthorne-';
$image_extension = '.jpg';
// The maximum number of pages (i.e., the value of 'Last')
$num_pages = 11;
// This string appears between 'Previous', 'Next', etc. in the navigation links.
$nav_string_separator = ' | ';

/*
Then, create the nav links based on the current page image
*/

// Get any parameters in the current URL
$self = url($_GET['q'], NULL, NULL, TRUE);
// In some environments, the Drupal 'edit' path is caputured in $self -- if it's there, remove it
$self = preg_replace('/edit/','',$self);

if(isset(
$_GET['image'])){
 
// Get the value of the 'image' URL parameter (ex., node/45?image=4)
 
$current_image = $_GET['image'];
} else {
 
// If 'image' is not defined, display the first image
 
$current_image = 1;
}

// Define the 'Next' and 'Previous' images
$next_image = $current_image + 1;
$previous_image = $current_image -1;

// For first page (i.e., ?image=1)
if ($current_image == 1) {
 
$nav_string = l('Next', url($self . '?image=' . $next_image)) . $nav_string_separator .
    
l('Last', url($self . '?image=' . $num_pages));
} else {
 
$nav_string = l('First', url($self . '?image=1')) . $nav_string_separator .
    
l('Prev', url($self . '?image=' . $previous_image)) . $nav_string_separator .
    
l('Next', url($self . '?image=' . $next_image)) . $nav_string_separator .
    
l('Last', url($self. '?image=' .$num_pages));
}

// For last page (i.e., ?image=11 or whatever is the value of $num_pages)
if ($current_image == $num_pages) {
  
$nav_string = l('First', url($self . '?image=1')) . $nav_string_separator .
    
l('Prev', url($self . '?image=' . $previous_image));
}

/*
Finally, display the current page image
*/

if(isset($_GET['image'])){
// Do some simple checks to ensure the parameter is within the valid range
if (($current_image < 1) || ($current_image > $num_pages)) {
    print
"Sorry, there is no image corresponding to the value of 'image' in the URL. Click " .
     
l('here', url($self . '?image=1')) . " to see the first page and previous/next links.";
} else {
   
// If the image value is within between 1 and the value of $num_images,
    //display the corresponding image file
   
print $nav_string;
    print
'<div><img src="' . $image_base_path . $current_image . $image_extension .'" /></div>';
    print
$nav_string;
}

// Default behavior when there is no value for ?image is to display the first image
} else {
 
$nav_string = l('Next', url($self . '?image=' . $next_image)) . $nav_string_separator .
   
l('Last', url($self . '?image=' . $num_pages));
  print
$nav_string;
  print
'<div><img src="' . $image_base_path . $current_image . $image_extension .'" /></div>';
  print
$nav_string;
}
?>

As you can see, the snippet creates the navigational links between pages ("First", "Next", etc.) by determining what the current page number is and then adding or subtracting 1 from the current page number. In order for this to work, the image files must be named sequentially, like "image-1.jpg", "image-2.jpg", and so on.

To use this snippet, set up a CCK content type with your metadata elements and reserve one text field for the PHP snippet. Name you page images sequentially as described in the previous paragraph, adjust the $image_base_path, $image_extension, and $num_pages variables, and save the node. The most common problem will be that there is a mismatch between the location indicated in $image_base_path and the names of your image files.

This approach to combining metadata and a document handler is functional but has a few limitations. First, the $image_base_path, $image_extension, and $num_pages needs to be defined within the snippet for each document. Ideally, these document-specific attributes would be stored in a database table so that one snippet could be generalized to apply to all documents. Second, the display of the metadata and the overall layout of the document's "record" is determined by the default CCK or flexinode template. This could be overcome by following the instructions supplied in the CCK documentation or by using the Contemplate module with CCK, which allows creation of a custom template for the node. Third, the metadata and the associated images together make up a single node. Therefore, all the neat features that Drupal provides for nodes, such as the ability to add comments, to search, and to restrict administration and access to specific user roles, apply to the entire package, not just the metadata or individual page images. In order to allow finer granularity for comments, searching, etc., each record and its attached page images would have to be broken up into distinct nodes. CCK allows linking to other nodes but to date the way these linked nodes are structured in relation to the parent node is not very flexible.

I'm thinking about addressing these shortcomings by writing a Drupal module to package up the document handler and to allow beter integration with CCK and Contemplate. Before I do, I'd like to hear from anyone who may have a use for this type of document presentation. The proposed module should accomodate single-file documents like still images, media files, and single-file document formats such as PDF in addition to the page-image based document illustrated here. For a while I've been interested in exploring Drupal's potential as a collection-oriented digital library content mangement system and the page turner described above may serve as a focal point for that more general discussion.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <h3> <h4> <h5> <h6> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Use [toc list: ol; title: Table of Contents; minlevel: 2; maxlevel: 3; attachments: yes;] to insert a mediawiki style collapsible table of contents. All the arguments are optional.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
                  ___          _   _         
___ __ _ / _ \ ____ | | | | __ _
/ __| / _` | | (_) | |_ / | |_| | / _` |
| (__ | (_| | \__, | / / | _ | | (_| |
\___| \__,_| /_/ /___| |_| |_| \__, |
|___/
Enter the code depicted in ASCII art style.