Lorenzo Alberton
« Articles
PEAR::Pager Tutorials - Article Pagination and Navigation
Abstract: How to navigate through the paragraphs of a long article with PEAR::Pager.
Article pagination, or how to navigate through the paragraphs of an article with Pager
You've probably seen a lot of websites featuring long, detailed articles, which are split into paragraphs, each of them in a separate page. Users often prefer reading short chunks of text instead of scrolling a very long page (unless they want to print the page, then the opposite applies). In this tutorial, we're going to see how we can build an article pagination system, with a little help from PEAR::Pager.
The database structure
We'll need two tables to store our articles: one with the basic article info (author, title, abstract, submission date) and one containing the paragraphs (one per record, with title and contents). Technically, we could use just one table, using some special delimiters embedded within the text (such as "====END OF PAGE====
") to split the paragraphs, but I believe in the long run this is a much better solution.
CREATE TABLE articles ( id INTEGER NOT NULL, title VARCHAR(250) NOT NULL, abstract TEXT, submission_date TIMESTAMP NOT NULL, author_id INTEGER NOT NULL, CONSTRAINT articles_pkey PRIMARY KEY(id) ); CREATE TABLE paragraphs ( article_id INTEGER NOT NULL, paragraph_id INTEGER NOT NULL, title VARCHAR(250), content TEXT, CONSTRAINT paragraphs_pkey PRIMARY KEY(article_id, paragraph_id), CONSTRAINT paragraphs_fk FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE );
Sample data
Here's some sample data if you want to play with it while reading this tutorial:
-- First Article INSERT INTO articles (id, title, abstract, submission_date, author_id) VALUES ( 1, 'How to navigate through the paragraphs of an article with Pager', 'You\'ve probably seen a lot of websites featuring long, detailed articles, which are split into paragraphs, each of them in a separate page. Users often prefer reading short chunks of text instead of scrolling a very long page (unless they want to print the page, then the opposite applies). In this tutorial, we\'re going to see how we can build an article pagination system, with a little help from PEAR::Pager.', CURRENT_TIMESTAMP, 1 ); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 1, 'The database structure', 'First paragraph here'); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 2, 'Sample data', 'Second paragraph here'); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 3, 'Showtime', 'Third paragraph here'); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 4, 'Alternate navigation', 'Fourth paragraph here'); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 5, 'Article summary', 'Fifth paragraph here'); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (1, 6, 'Printer friendly version', 'Sixth paragraph here'); -- Second Article INSERT INTO articles (id, title, abstract, submission_date, author_id) VALUES ( 2, 'PEAR::Pager tutorials', 'New series of tutorials about PEAR::Pager', CURRENT_TIMESTAMP, 1 ); INSERT INTO paragraphs (article_id, paragraph_id, title, content) VALUES (2, 1, 'Articles index', '1. How to efficiently paginate database results. 2. Create pretty links with Pager and mod_rewrite. 3. Navigation with Pager and AJAX (or simple Javascript). 4. Article pagination.');
Showtime!
Now that we're done with the database structure, we can display the articles in our site, one paragraph per page. For this task, we're going to use the PEAR::MDB2 DBAL and the handy Pager_Wrapper we've already seen in a previous tutorial:
<?php //copy the Pager_Wrapper file where you can include it require_once 'Pager_Wrapper.php'; require_once 'MDB2.php'; $article_id = 1; //if you fetch this parameter via GET/POST, remember to validate it! //skipped the db connection code... //let's just suppose we have a valid db connection in $db. $pager_options = array( 'mode' => 'Sliding', 'perPage' => 1, //we want only ONE paragraph per page 'delta' => 3, ); $query = 'SELECT articles.title AS article_title, articles.submission_date, articles.abstract, paragraphs.title AS paragraph_title, paragraphs.content FROM paragraphs LEFT JOIN articles ON articles.id = paragraphs.article_id WHERE articles.id = '. (int)$article_id .' ORDER BY paragraphs.paragraph_id; $paged_data = Pager_Wrapper_MDB2($db, $query, $pager_options); //show the results echo '<h1>'.$paged_data['data'][0]['article_title'].'</h1>'; echo '<p><i>'.$paged_data['data'][0]['submission_date'].'</i></p>'; if ($paged_data['page_numbers']['current'] == 1) { // also show the abstract on the first page echo '<p>'.$paged_data['data'][0]['abstract'].'</p>'; } echo '<h2>'.$paged_data['data'][0]['paragraph_title'].'</h2>'; echo '<p>'.$paged_data['data'][0]['content'].'</p>'; //show the links echo $paged_data['links']; ?>
Since we told Pager to split the items (the paragraphs, in our case) into groups of one, it will return only one paragraph, and we can navigate through the other paragraphs with the links built by Pager.
This is the output of the script:
Alternate navigation
If you don't like normal links for the navigation, and prefer a <select>
menu instead, you have to add a call to getPageSelectBox()
in the Pager_Wrapper_MDB2()
function, before returning the paged data array, since it's not included in the default version:
function Pager_Wrapper_MDB2(&$db, $query, $pager_options = array(), $disabled = false, $fetchMode = MDB2_FETCHMODE_ASSOC) { // Pager_Wrapper_MDB2() body omitted. Add the following lines before the return call: // ===== START ADDED CODE ====== $selectbox_options = array( 'optionText' => 'page %d', 'autoSubmit' => true, ); $page['select_menu'] = $pager->getPageSelectBox($selectbox_options); // ===== END ADDED CODE ===== return $page; }
Now you can display the navigation menu:
<?php // [snip: same code as in the previous paragraph] //show the menu echo 'Select page: '. $paged_data['select_menu']; ?>
And here's how it will look like:
Article summary
Sometimes, you may want to display an article summary, with a list of the paragraph titles, each one pointing to the complete paragraph. No problem,you can do that.
<?php $query = 'SELECT title FROM paragraphs WHERE article_id = '. (int)$article_id .' ORDER BY paragraph_id'; $paragraph_titles = $db->queryCol($query); //error checking omitted echo '<h2>Summary</h2>'; echo '<ul>'; foreach ($paragraph_titles as $k => $title) { if ($k == ($paged_data['page_numbers']['current'] -1)) { // current page: don't show the link echo '<li>' . $title . '</li>'; } else { echo '<li><a href="article.php?id='. (int)$article_id .'&pageID='. ($k+1) .'">'. $title . '</a></li>'; } } echo '</ul>'; ?>
The result is a summary of the paragraph titles, with links:
Printer friendly version
If you want to offer a printer-friendly version of the complete article, you can fetch all the paragraphs and simply print them one by one:
<?php $query = 'SELECT title, submission_date, abstract FROM articles WHERE id = '. (int)$article_id; $article = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); $query = 'SELECT title, content FROM paragraphs WHERE article_id = '. (int)$article_id .' ORDER BY paragraph_id'; $paragraphs = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); //show main article data: echo '<h1>'.$article['title'].'</h1>'; echo '<p><i>'.$article['submission_date'].'</i></p>'; echo '<p>'.$article['abstract'].'</p>'; foreach ($paragraphs as $paragraph) { echo '<h2>'.$paragraph['title'].'</h2>'; echo '<p>'.$paragraph['content'].'</p>'; }
I hope this tutorial was useful. If you need some clarifications, or have some suggestions about other topics, drop me a mail.
« Go back to PEAR::Pager tutorials index.
Related articles
- PEAR::Pager Tutorials
- PEAR::Pager Tutorials - Paginate database results
- PEAR::Pager Tutorials - Create pretty links with Pager and mod_rewrite
- PEAR::Pager Tutorials - Navigation with Pager and AJAX (or simple Javascript)
- PEAR::Pager Tutorials - Use Pager with Smarty. Use Pager_Wrapper with AJAX
Latest articles
- On batching vs. latency, and jobqueue models
- Updated Kafka PHP client library
- Musings on some technical papers I read this weekend: Google Dremel, NoSQL comparison, Gossip Protocols
- Historical Twitter access - A journey into optimising Hadoop jobs
- Kafka proposed as Apache incubator project
- NoSQL Databases: What, When and Why (PHPUK2011)
- PHPNW10 slides and new job!
Filter articles by topic
AJAX, Apache, Book Review, Charset, Cheat Sheet, Data structures, Database, Firebird SQL, Hadoop, Imagick, INFORMATION_SCHEMA, JavaScript, Kafka, Linux, Message Queues, mod_rewrite, Monitoring, MySQL, NoSQL, Oracle, PDO, PEAR, Performance, PHP, PostgreSQL, Profiling, Scalability, Security, SPL, SQL Server, SQLite, Testing, Tutorial, TYPO3, Windows, Zend FrameworkFollow @lorenzoalberton