Using TYPO3's default pi browser in your ext

Does your extension need a page browser? Why not use the default TYPO3 page browser? Because it's difficult to find any documentation about using it! I need one for sb_portfolio and after digging through the api and a bit of trial-and-error I finaly got it to work, and here's how.

First add a class variable called pi_alwaysPrev and set it to 1 (can be 0 if you want this option disabled by default), this is used to decide if the previous button is always shown. Admins can then change this value via TypoScript. Also an array called internal is required, and an array to store wraps for the page browser, I called this pbWrapper but you can call this whatever you want:

class yourExtensionName extends tslib_pibase {
    var $prefixId = 'yourExtensionName';
    ...
    var $pi_alwaysPrev = 1;
    var $internal = array();
    var $pbWrapper = array();
    ...
    function main($content, $conf) {

The internal array has several elements used by the page browser to keep track of where it is, which page is displayed, how many results per page and so on. These need setting at some point in your extension:

'currentTable'
This is the table name used in queries

'orderBy'
This is the field to sort results by

'descFlag'
This is the direction of the sort

'results_at_a_time'
How many results to show on each page

'maxPages'
How many pages to show - this is the number of clickable page links in the browser itself, not how many pages are required to browse through all results. This is dictated by the number of records in the table.

'res_count'

This is used to store the total number of results in the table

'dontLinkActivePage'
This disables or enables the link to the current page

'showFirstLast'
Enables or disables First (page) and Last (page) links.

'showRange'
Shows result ranges instead of page numbers. If 'resultsPerPage' is set to 10 then enabling this option would result in page 1 being displayed as 1-10, page 2 being displayed as 11-20 and so on.

'pagefloat'
This is the position of the current page in the displayed page links. It is by default set to 'center' (with american spelling), but can also be an integer. 'center' means that if 'maxPages' = 5, the current page will always be page link 3 so that it is in the centre of the page links. Setting 'pagefloat' to a number makes the current page's link appear there instead.. e.g: 'pagefloat' = 2, 'maxPages' = 5 displays the current page as the second page link.

// sb_portfolio's default setup of the internal array:
$this->internal['currentTable'] = $this->tableName;
$this->internal['orderBy'] = $this->orderField;
$this->internal['descFlag'] = $this->orderDir;
$this->internal['results_at_a_time'] = 10;
$this->internal['maxPages'] = 12;
$this->internal['res_count'];
$this->internal['dontLinkActivePage'] = 1;
$this->internal['showFirstLast'] = 0;
$this->internal['showRange'] = 0;
$this->internal['pagefloat'] = 'center';

You should store the starting point field in an element called pidList, since this is what is used by the page browser. I made the mistake of using the element startingPoint, so I had to either copy the startingPoint element to a new pidList element or rename the element where I stored the starting point. Since the field on the backend is called starting point (and that's what I think of it as being), I decided just to copy the value to a new pidList element:

$this->conf['pidList'] = $this->conf['startingPoint'];

You also need to provide an array to store wraps for the page browser elements. These wraps can then be set in TypoScript by a site admin so that the different parts of the page browser are styled inline with the site's design.

'disabledLinkWrap'
This is the wrap for links that are disabled, like the previous button when you are on the first page.

'activeLinkWrap'
This is the wrap for the current page.

'inactiveLinkWrap'
This is the wrap for all other pages/links.

'browseLinksWrap'
This is a wrap for all the page links/buttons (next/previous etc.).

'showResultsWrap'
This is the wrap for the text saying how many results are shown.

'browseBoxWrap'
This is a wrap for the complete page browser.

// sb_portfolio's default setup of the pbWrapper array:
$this->pbWrapper['disabledLinkWrap']
 = trim($this->conf['pageBrowser.']['wraps.']
 ['disabledLinkWrap']);
$this->pbWrapper['inactiveLinkWrap']
 = trim($this->conf['pageBrowser.']['wraps.']
 ['inactiveLinkWrap']);
$this->pbWrapper['activeLinkWrap']
 = trim($this->conf['pageBrowser.']['wraps.']
 ['activeLinkWrap']);
$this->pbWrapper['browseLinksWrap']
 = trim($this->conf['pageBrowser.']['wraps.']
 ['browseLinksWrap']);
$this->pbWrapper['showResultsWrap']
 = trim($this->conf['pageBrowser.']['wraps.'
 ['showResultsWrap']);
$this->pbWrapper['browseBoxWrap']
 = trim($this->conf['pageBrowser.']['wraps.'] 
 ['browseBoxWrap']);

Obviously you must set some default values for the wraps in your extensions TypoScript template:

// These were my extensions default wraps, you can change the setup of the TS too, this was just how I chose to do it.
plugin.tx_sbportfolio_pi1 {
  pageBrowser {
    wraps {
      disabledLinkWrap =
        <span class="pbLink disabledPbLink">|</span>
      inactiveLinkWrap =
        <span class="pbLink inactivePbLink">|</span>
      activeLinkWrap =
        <span class="pbLink activePbLink">|</span>
      browseLinksWrap = <p class="pbButtons">|</p>
      showResultsWrap = <p class="pbResults">|</p>
      browseBoxWrap =
        <div class="portPageBrowser">|</div>
    }
  }
}

// You could for example have:
plugin.yourExtensionName {
  disabledLinkWrap =
    <span class="pbLink disabledPbLink">|</span>
}

// In which case you would need to alter the assignment
// to the 'disabledLinkWrap' element, thus:
$this->pbWrapper['disabledLinkWrap'] =
  trim($this->conf['disabledLinkWrap']);

Another variable required by the page browser is $this->piVars['pointer'], this keeps track of the current page, set this to zero if it doesn't already exist with the following piece of code (if it does exist, it doesn't need changing!):

if (!isset($this->piVars['pointer'])) {
    $this->piVars['pointer'] = 0;
}

Of course any and all of these properties could be changed by TypoScript variables or even FlexForm values. I used the folowing TS:

plugin.tx_sbportfolio_pi1 {
    # Enable or disable page browser
    pageBrowser = 1

    pageBrowser {
        # $this->internal['results_at_a_time']
        resultsPerPage = 15

        # $this->internal['maxPages']
        numPageLinksDisplayed = 5

        # $this->internal['dontLinkActivePage']
        linkActivePage = 0

        # $this->internal['showFirstLast']
        showFirstAndLastLinks = 0

        # $this->internal['pagefloat']
        curPageLocation = center

        # $this->internal['showRange']
        showItemNumberRanges = 0

        # $this->pi_alwaysPrev
        alwaysShowPrevious = 0
    }
}

You then have to get the number of items and get the right items for the page number

// Get the number of items in total
$query = $this->pi_list_query(
  $this->tableName,
  1,
  $this->cObj->enableFields($this->tableName),
  '',
  '',
  'ORDER BY ' . $this->orderField . ' ' . $this->orderDir
);

$res = $GLOBALS['TYPO3_DB']->sql_query($query);

// Set number of items
list($this->internal['res_count']) =
  $GLOBALS['TYPO3_DB']->sql_fetch_row($res);

// Now get the results for the page
$query = $this->pi_list_query(
  $this->tableName,
  0,
  $this->cObj->enableFields($this->tableName),
  '',
  '',
  'ORDER BY ' . $this->orderField . ' ' .
  $this->orderDir
);

$res = $GLOBALS['TYPO3_DB']->sql_query($query);

// And make a list of them
$this->pi_list_makelist($res);

// Make the page browser and story it in a class
// property for access later in the output function
// The third argument $this->pbWrapper is the array
// containing the wraps. Rename this to whatever array
// name you use.

// The fifth argument
// trim($this->conf['pageBrowser.']['allowJustText'])
// was a TS setting that stops the link text being
// sent through htmlspecialchars()
// so that html tags could be used in Local Language
// strings instead of just text - this allows images
// to be used for buttons

$this->pBrowser = $this->pi_list_browseresults(
  1,
  '',
  $this->pbWrapper,
  'pointer',
  trim($this->conf['pageBrowser.']['allowJustText'])
);

The call to $this->pi_list_makelist() requires that you have two functions in your code pi_list_row and pi_list_header. These handle getting the data for each result and wrapping it adding it to the output, but I already had the logic for sb_portfolio worked out didn't want to have to change that if the page browser was enabled. I was previously using exec_SELECTgetRows to get the results which returns an array or results. All the examples that I could find for using the page browser seemd to do all the formatting and adding of the result to the ouput in pi_list_row, which I didn't want so I just added the result to an array that I used later in the existing output functions. I didn't use pi_list_header so I just added an empty function with a return in it.

// Add the current row to the array.
function pi_list_row($c) {
    $this->queryData[] = $this->internal['currentRow'];
   
    // Procesing of the data for output will be done later in another function, I just need the results so return now.
    return;
}

// Header not needed, so just return when called
function pi_list_header() {
    return;
}

Then all you need to do is have a marker in your extension which can be replaced with the page browser:

// Page browser previously stored in $this->pBrowser needs to replace a marker in your extensions template:
// I called it PAGE_BROWSER, but you can call it whatever you want.
$subpartArray['###PAGE_BROWSER###'] = $this->pBrowser;

Now you have a fully functional page browser.