Extension:ExtendedPagers

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
ExtendedPagers

Release status: stable

Implementation API
Description extended pagers that support ordering and can jump do a specific page (like e.g. google), (ExtendedIndexPager, ExtendedTablePager)
Author(s) Wolfgang Stöttinger (w.stoettingerTalk)
Last version 1.1
MediaWiki 1.13+
License GPL
Download This page

Check usage (experimental)

This is not a stand-alone extension and therefore only intended to be used by developers but can be useful to display search results. Maybe it will one time find it's way to the trunk, so please help me testing it ;)

I designed this pagers because none of the built-in's supplied the features I needed. Espacially with ordering (sorting) a result and using the paging function which doesn't work properly with all the other pagers when the order columns don't contain unique values.

This pagers implement paging based on row indices and have the additional feature to jump to a specific page in the result. Furthermore they don't 'forget' the column to order when you change the page like the AlphabeticPager does.

Contents


[edit] Configuration

If you want to use one of this pagers in your extension, create a file named ExtendesPagers.php and paste the following line somewhere in the initialization of your extension:

require_once("path/to/your/extension/ExtendedPagers.php");

[edit] Example

Here is a little examle on how you can design the NavigationBar. I used the pager for a custom table so I cant't post that here. If somebody uses this on a default wiki table or even creates a special page as an example, please post it here. (sorry for not using i18n messages)

It is also possible to use multiple indexFields for ordering (sorting), then it should behave like AlphabeticPager (or even better ; )

class ExamplePager extends ExtendedIndexPager {
        function getNavigationBar() {
                global $wgLang;
 
                $status = "<b>Entry ". ($this->mOffset + 1)  . "-" .min($this->mOffset + $this->mLimit, $this->getTotalNumRows()) . " of " . $this->getTotalNumRows() ."</b>" ;
 
                $opts = array( 'parsemag', 'escapenoentities' );
                $linkTexts = array(
                        'prev' => wfMsgExt( 'pager-newer-n', array( 'parsemag' ), $nicenumber ),
                        'next' => wfMsgExt( 'pager-older-n', array( 'parsemag' ), $nicenumber ),
                );
 
                $this->mNavigationBar = $status . "<br/>";
 
                $pagingLinks = $this->getPagingLinks( $linkTexts );
                $limitLinks = $this->getLimitLinks();
                $limits = implode( ' | ', $limitLinks );
 
                $indexBar = $this->getPageIndexBar();
 
                $this->mNavigationBar .= $pagingLinks['prev']. " | ". $pagingLinks['next']. " | $indexBar |<br/>($limits)";
 
                if( !is_array( $this->getIndexField() ) ) {
                        # Early return to avoid undue nesting
                 return $this->mNavigationBar;
                }
 
                $extra = '';
                $first = true;
                $msgs = $this->getOrderTypeMessages();
                foreach( $msgs as $order => $val ) {
                        if( $first ) {
                                $first = false;
                        } else {
                                $extra .= ' | ';
                        }
 
                        if( $order == $this->mOrderType ) {
                                $extra .= wfMsgHTML( $val );
                        } else {
                                $extra .= $this->makeLink(
                                wfMsgHTML( $val ),
                                array( 'order' => $order, 'limit' => $this->mLimit )
                                );
                        }
                }
 
                if( $extra !== '' ) {
                        $this->mNavigationBar .= "($extra)";
                }
 
                return $this->mNavigationBar;
        }
 
        function getQueryInfo() {
                //TODO return the query
                return array();
        }
 
        function getIndexField() {
                //TODO return the index fiels
                return null;
        }
 
        function formatRow( $row ) {
                //TODO format the row
        }
}

[edit] Code

[edit] ExtendedPagers.php

<?php
 
/* 
 * This pager behaves like one would expect a pager to behave when the order columns are not unique values.
 * Uses the offset as a number and supports multiple column sorting.
 */
abstract class ExtendedIndexPager extends IndexPager {
 
        /**
         * Contains the total number of rows, not the limited. 
         */
        public $mTotalNumRows = 0;
        public $mNumberOfPages = 0;
 
        public function __construct() {       
                parent::__construct();
 
                $this->mLimit = intval($this->mLimit);
                $this->mOffset = intval($this->mRequest->getText( 'offset' )); 
        }
 
        /**
         * Do a query with specified parameters, rather than using the object
         * context
         *
         * @param string $offset Index offset, inclusive
         * @param integer $limit Exact query limit
         * @param boolean $descending Query direction, false for ascending, true for descending
         * @return ResultWrapper
         */
        function reallyDoQuery( $offset, $limit, $descending ) {
                $fname = __METHOD__ . ' (' . get_class( $this ) . ')';
                $info = $this->getQueryInfo();
                $tables = $info['tables'];
                $fields = $info['fields'];
                $conds = isset( $info['conds'] ) ? $info['conds'] : array();
                $options = isset( $info['options'] ) ? $info['options'] : array();
                $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array();
                if ( $descending ) {
                        $options['ORDER BY'] = $this->mIndexField;
                        $operator = '>';
                } else {
                        $options['ORDER BY'] = $this->mIndexField . ' DESC';
                        $operator = '<';
                }
 
                $this->mTotalNumRows = $this->mDb->selectField( $tables, 'count(*)', $conds, $fname, $options, $join_conds );
                $this->mNumberOfPages = (int) ceil($this->mTotalNumRows / $this->mLimit); 
 
                $lowerLimit = intval($offset);
                $higherLimit = intval($offset) + intval( $limit );
                $options['LIMIT'] = $higherLimit;
                $options['OFFSET'] = $lowerLimit;
 
                $res = $this->mDb->select( $tables, $fields, $conds, $fname, $options, $join_conds );
 
                return new ResultWrapper( $this->mDb, $res );
        }
 
        /**
         * Get the number of rows in without the limit of the query.
         */
        function getTotalNumRows() {
                if ( !$this->mQueryDone ) {
                        $this->doQuery();
                }
                return $this->mTotalNumRows;
        }
 
        /**
         * Get the number of pages.
         */
        function getNumberOfPages() {
                if ( !$this->mQueryDone ) {
                        $this->doQuery();
                }
                return $this->mNumberOfPages;
        }
 
        /** 
         * returns the current order column or false if ther is only one or it's the default
         */
        private function getOrderUrl() {
                $indexFields = $this->getIndexField(); 
                if(is_array($indexFields) && $this->mOrderType != reset($indexFields)) {
                        return $this->mOrderType;
                }
                return false;
        }
 
        /**
         * Get a URL query array for the prev, next, first and last links.
         */
        function getPagingQueries() {
                if ( !$this->mQueryDone ) {
                        $this->doQuery();
                }
 
                # Don't announce the limit everywhere if it's the default
         $urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit;
 
                # Don't announce the order if it's the default or the only
         $urlOrder = $this->getOrderUrl();
 
                $nextPageOffset = min($this->mOffset + $this->mLimit, $this->mTotalNumRows /* this should never happen */);
                $prevPageOffset = max($this->mOffset - $this->mLimit,0 /* this neither */);
 
                $lastPageOffset = ($this->mTotalNumRows - 1) * $this->mLimit;
 
                if ( $this->mIsFirst ) {
                        $prev = false;
                        $first = false;
                } else {
                        $prev = array( 'offset' => $prevPageOffset, 'limit' => $urlLimit, 'order' => $urlOrder);
                        $first = array( 'limit' => $urlLimit, 'order' => $urlOrder);
                }
                if ( $this->mIsLast ) {
                        $next = false;
                        $last = false;
                } else {
                        $next = array( 'offset' => $nextPageOffset, 'limit' => $urlLimit, 'order' => $urlOrder);
                        $last = array( 'offset' => $lastPageOffset, 'limit' => $urlLimit, 'order' => $urlOrder);
                }
                return array( 'prev' => $prev, 'next' => $next, 'first' => $first, 'last' => $last );
        }
 
        function getLimitLinks() {
                global $wgLang;
 
                # Don't announce the order if it's the default or the only
         $urlOrder = $this->getOrderUrl();
 
                $links = array();
                if ( $this->mIsBackwards ) {
                        $offset = $this->mPastTheEndIndex;
                } else {
                        $offset = $this->mOffset;
                }
                foreach ( $this->mLimitsShown as $limit ) {
                        if ($limit == $this->mLimit)
                                $links[] = $this->makeLink( $wgLang->formatNum( $limit ), null , 'num' );
                        else {
                                $links[] = $this->makeLink( $wgLang->formatNum( $limit ),
                                        array( 'offset' => $offset, 'limit' => $limit, 'order' => $urlOrder ), 'num' );
                        }
                }
                return $links;
        }
        /**
         *      returns an array which contains links to navigate directly to a specific page. 
         */
        function getPageIndexLinks() {
                # Don't announce the order if it's the default or the only
         $urlOrder = $this->getOrderUrl();
 
                $currentPage = (int) ceil($this->mOffset / $this->mLimit );
 
                $pageLinks = array();
                for ($i = 0; $i < $this->mNumberOfPages; $i++) {
                        $offset = $i * $this->mLimit;
                        if ($currentPage == $i)
                                $pageLinks[] = $this->makeLink(($i + 1), null , 'num' );
                        else 
                                $pageLinks[] = $this->makeLink(($i + 1), array('offset' => $offset, 'limit' => $this->mLimit, 'order' => $urlOrder ), 'num');      
                }
                return $pageLinks;
        }
 
        /**
         * returns a formated bar of page index links to navigate directly to a page
         * <br/>Example: getPageIndexBar(2,' | ', '...') with 10 pages, where current = 5 will result in: '1 | ... | 3 | 4 | 5 | 6 | 7 | ... | 10' 
         * @param integer $delta the number of indices shown to the left and the right of the current index
         * @param string $smallSeparator shown between each index within the delta
         * @param string $bigSeparator shown between first and current - delta as well as current + delta and last
         * 
         */
        function getPageIndexBar($delta = 3, $smallSeparator = ' | ', $bigSeparator = '...' ) {
                # Don't announce the order if it's the default or the only
         $urlOrder = $this->getOrderUrl();
                $currentPage = (int) ceil($this->mOffset / $this->mLimit );
 
                $smallSteps = array();
                $first = $last = null;
                for ($i = 0; $i < $this->mNumberOfPages; $i++) {
                        $offset = $i * $this->mLimit;
 
                        if ($currentPage == $i)
                                $pageLink = $this->makeLink(($i + 1), null , 'num' );
                        else 
                                $pageLink = $this->makeLink(($i + 1), array('offset' => $offset, 'limit' => $this->mLimit, 'order' => $urlOrder ), 'num');
 
                        if($i > $currentPage - $delta - 1 && $i < $currentPage + $delta + 1) 
                                $smallSteps[] = $pageLink;                                
                        else if ($i == 0) 
                                $first = $pageLink; 
                        else if ($i == $this->mNumberOfPages -1) 
                                $last = $pageLink; 
                }
 
                $steps = array();
                if ($first)
                        $steps[] = $first;
                $steps[] = implode($smallSeparator, $smallSteps);
                if ($last)
                        $steps[] = $last;
 
                return implode($smallSeparator.$bigSeparator.$smallSeparator, $steps);
        } 
}
 
/**
 * This class is simply a Copy of TablePager but extends ExtendedIndexPager to use it's featurs.
 * Table-based display with a user-selectable sort order
 * @ingroup Pager
 */
abstract class ExtendedTablePager extends ExtendedIndexPager {
        var $mSort;
        var $mCurrentRow;
 
        function __construct() {
                global $wgRequest;
                $this->mSort = $wgRequest->getText( 'sort' );
                if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) {
                        $this->mSort = $this->getDefaultSort();
                }
                if ( $wgRequest->getBool( 'asc' ) ) {
                        $this->mDefaultDirection = false;
                } elseif ( $wgRequest->getBool( 'desc' ) ) {
                        $this->mDefaultDirection = true;
                } /* Else leave it at whatever the class default is */
 
                parent::__construct();
        }
 
        function getStartBody() {
                global $wgStylePath;
                $tableClass = htmlspecialchars( $this->getTableClass() );
                $sortClass = htmlspecialchars( $this->getSortHeaderClass() );
 
                $s = "<table border='1' class=\"$tableClass\"><thead><tr>\n";
                $fields = $this->getFieldNames();
 
                # Make table header
         foreach ( $fields as $field => $name ) {
                        if ( strval( $name ) == '' ) {
                                $s .= "<th>&nbsp;</th>\n";
                        } elseif ( $this->isFieldSortable( $field ) ) {
                                $query = array( 'sort' => $field, 'limit' => $this->mLimit );
                                if ( $field == $this->mSort ) {
                                        # This is the sorted column
                                 # Prepare a link that goes in the other sort order
                                 if ( $this->mDefaultDirection ) {
                                                # Descending
                                         $image = 'Arr_u.png';
                                                $query['asc'] = '1';
                                                $query['desc'] = '';
                                                $alt = htmlspecialchars( wfMsg( 'descending_abbrev' ) );
                                        } else {
                                                # Ascending
                                         $image = 'Arr_d.png';
                                                $query['asc'] = '';
                                                $query['desc'] = '1';
                                                $alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) );
                                        }
                                        $image = htmlspecialchars( "$wgStylePath/common/images/$image" );
                                        $link = $this->makeLink(
                                                "<img width=\"12\" height=\"12\" alt=\"$alt\" src=\"$image\" />" .
                                                htmlspecialchars( $name ), $query );
                                        $s .= "<th class=\"$sortClass\">$link</th>\n";
                                } else {
                                        $s .= '<th>' . $this->makeLink( htmlspecialchars( $name ), $query ) . "</th>\n";
                                }
                        } else {
                                $s .= '<th>' . htmlspecialchars( $name ) . "</th>\n";
                        }
                }
                $s .= "</tr></thead><tbody>\n";
                return $s;
        }
 
        function getEndBody() {
                return "</tbody></table>\n";
        }
 
        function getEmptyBody() {
                $colspan = count( $this->getFieldNames() );
                $msgEmpty = wfMsgHtml( 'table_pager_empty' );
                return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n";
        }
 
        function formatRow( $row ) {
                $s = "<tr>\n";
                $fieldNames = $this->getFieldNames();
                $this->mCurrentRow = $row;  # In case formatValue needs to know
         foreach ( $fieldNames as $field => $name ) {
                        $value = isset( $row->$field ) ? $row->$field : null;
                        $formatted = strval( $this->formatValue( $field, $value ) );
                        if ( $formatted == '' ) {
                                $formatted = '&nbsp;';
                        }
                        $class = 'TablePager_col_' . htmlspecialchars( $field );
                        $s .= "<td class=\"$class\">$formatted</td>\n";
                }
                $s .= "</tr>\n";
                return $s;
        }
 
        function getIndexField() {
                return $this->mSort;
        }
 
        function getTableClass() {
                return 'TablePager';
        }
 
        function getNavClass() {
                return 'TablePager_nav';
        }
 
        function getSortHeaderClass() {
                return 'TablePager_sort';
        }
 
        /**
         * A navigation bar with images
         */
        function getNavigationBar() {
                global $wgStylePath, $wgContLang;
                $path = "$wgStylePath/common/images";
                $labels = array(
                        'first' => 'table_pager_first',
                        'prev' => 'table_pager_prev',
                        'next' => 'table_pager_next',
                        'last' => 'table_pager_last',
                );
                $images = array(
                        'first' => $wgContLang->isRTL() ? 'arrow_last_25.png' : 'arrow_first_25.png',
                        'prev' =>  $wgContLang->isRTL() ? 'arrow_right_25.png' : 'arrow_left_25.png',
                        'next' =>  $wgContLang->isRTL() ? 'arrow_left_25.png' : 'arrow_right_25.png',
                        'last' =>  $wgContLang->isRTL() ? 'arrow_first_25.png' : 'arrow_last_25.png',
                );
                $disabledImages = array(
                        'first' => $wgContLang->isRTL() ? 'arrow_disabled_last_25.png' : 'arrow_disabled_first_25.png',
                        'prev' =>  $wgContLang->isRTL() ? 'arrow_disabled_right_25.png' : 'arrow_disabled_left_25.png',
                        'next' =>  $wgContLang->isRTL() ? 'arrow_disabled_left_25.png' : 'arrow_disabled_right_25.png',
                        'last' =>  $wgContLang->isRTL() ? 'arrow_disabled_first_25.png' : 'arrow_disabled_last_25.png',
                );
 
                $linkTexts = array();
                $disabledTexts = array();
                foreach ( $labels as $type => $label ) {
                        $msgLabel = wfMsgHtml( $label );
                        $linkTexts[$type] = "<img src=\"$path/{$images[$type]}\" alt=\"$msgLabel\"/><br/>$msgLabel";
                        $disabledTexts[$type] = "<img src=\"$path/{$disabledImages[$type]}\" alt=\"$msgLabel\"/><br/>$msgLabel";
                }
                $links = $this->getPagingLinks( $linkTexts, $disabledTexts );
 
                $navClass = htmlspecialchars( $this->getNavClass() );
                $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>\n";
                $cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"';
                foreach ( $labels as $type => $label ) {
                        $s .= "<td $cellAttrs>{$links[$type]}</td>\n";
                }
                $s .= "</tr></table>\n";
                return $s;
        }
 
        /**
         * Get a <select> element which has options for each of the allowed limits
         */
        function getLimitSelect() {
                global $wgLang;
                $s = "<select name=\"limit\">";
                foreach ( $this->mLimitsShown as $limit ) {
                        $selected = $limit == $this->mLimit ? 'selected="selected"' : '';
                        $formattedLimit = $wgLang->formatNum( $limit );
                        $s .= "<option value=\"$limit\" $selected>$formattedLimit</option>\n";
                }
                $s .= "</select>";
                return $s;
        }
 
        /**
         * Get <input type="hidden"> elements for use in a method="get" form.
         * Resubmits all defined elements of the $_GET array, except for a
         * blacklist, passed in the $blacklist parameter.
         */
        function getHiddenFields( $blacklist = array() ) {
                $blacklist = (array)$blacklist;
                $query = $_GET;
                foreach ( $blacklist as $name ) {
                        unset( $query[$name] );
                }
                $s = '';
                foreach ( $query as $name => $value ) {
                        $encName = htmlspecialchars( $name );
                        $encValue = htmlspecialchars( $value );
                        $s .= "<input type=\"hidden\" name=\"$encName\" value=\"$encValue\"/>\n";
                }
                return $s;
        }
 
        /**
         * Get a form containing a limit selection dropdown
         */
        function getLimitForm() {
                # Make the select with some explanatory text
         $url = $this->getTitle()->escapeLocalURL();
                $msgSubmit = wfMsgHtml( 'table_pager_limit_submit' );
                return
                        "<form method=\"get\" action=\"$url\">" .
                        wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) .
                        "\n<input type=\"submit\" value=\"$msgSubmit\"/>\n" .
                        $this->getHiddenFields( array('limit','title') ) .
                        "</form>\n";
        }
 
        /**
         * Return true if the named field should be sortable by the UI, false
         * otherwise
         *
         * @param string $field
         */
        abstract function isFieldSortable( $field );
 
        /**
         * Format a table cell. The return value should be HTML, but use an empty
         * string not &nbsp; for empty cells. Do not include the <td> and </td>.
         *
         * The current result row is available as $this->mCurrentRow, in case you
         * need more context.
         *
         * @param string $name The database field name
         * @param string $value The value retrieved from the database
         */
        abstract function formatValue( $name, $value );
 
        /**
         * The database field name used as a default sort order
         */
        abstract function getDefaultSort();
 
        /**
         * An array mapping database field names to a textual description of the
         * field name, for use in the table header. The description should be plain
         * text, it will be HTML-escaped later.
         */
        abstract function getFieldNames();
}
 
 
?>
Personal tools
Namespaces
Variants
Actions
Site
Support
Download
Development
Communication
Print/export
Toolbox