<?php
    
/**
    * A datagrid class. The appearance can be customised with CSS
    * See the example file for how.
    *
    * CHANGES
    *
    * 12th April 2008
    * ===============
    *  o Added column ordering support TODO: 1) Allow user to disable sorting
    *    2) Allow configuration of the sort indicator
    *
    * 28th March 2008
    * ===============
    *  o Added example3.php and example4.php
    *
    * 24th March 2008
    * ===============
    *  o Added example2.php which is styled to look like a Windows datagrid
    *
    * xth March 2008
    * ==============
    *  o Changed default colors to be a bit more "jazzy", or less "lame"
    *  o Added header and footer capability
    *  o Table headers - <th> tags now have a col_x class
    *
    * 5th March 2008
    * ==============
    *  o Added a few new methods: GetPageCount(), GetRowCount() and SetPerPage()
    *  o Made sure you can (if you want to) call DisplaY() multiple times on the same page
    *
    * 29th February 2008
    * ==================
    *  o Initial release
    */
    
    
define('DATAGRID_ORDERING_ASC', 1);
    
define('DATAGRID_ORDERING_DESC', 0);
    
    class
Datagrid
    
{
        
/**
        * Holds the order by information
        */
        
private static $orderby;

        
/**
        * Properties;
        */
        
private $properties;

        
/**
        * Getter
        */
        
public function __get($name)
        {
            return @
$this->properties[$name];
        }

        
/**
        * Setter
        */
        
public function __set($name, $value)
        {
            
$this->properties[$name] = $value;
        }
    
        
/**
        * The constructor
        */
        
public function __construct($connection, $resultset)
        {
            
$this->allowSorting   = true;
            
$this->showHeaders    = true;
            
$this->headerHTML     = '';
            
$this->cellpadding    = 0;
            
$this->cellspacing    = 0;
            
$this->connection     = $connection;
            
$this->resultset      = $resultset;
            
$this->numresults     = mysql_num_rows($resultset);
            
$this->startnum       = @(int)$_GET['start'];
            
$this->perPage        = 20;
            
$this->hiddenColumns  = array();
            
$this->colnum         = mysql_num_fields($this->resultset) - count($this->hiddenColumns);
            
$this->noSpecialChars = array();

            
// Don't allow startnum to be lower than zero
            
if ($this->startnum < 0) {
                
$this->startnum = 0;
            }
            
            
// Don't allow startnum to be greater than the number of rows in the result set,
            // well, one less to allow for zero indexing
            
if ($this->startnum >= $this->numresults) {
                
$this->startnum = 0;
            }

            
// Check the MySQL connection is valid
            
if (!$connection OR !is_resource($connection)) {
                die(
'<p /><span style="color: red">Error - the MySQL connection you have passed to the datagrid constructor is not valid</span>');
            }

            
// Check the MySQL result set is valid
            
if (!$resultset OR !is_resource($resultset)) {
                die(
'<p /><span style="color: red">Error - the MySQL result set you have passed to the datagrid constructor is not valid</span>');
            }
        }
        
        
/**
        * Static method which extracts the order by clause from the query string
        *
        * @param string $default The default ORDER BY clause
        */
        
public static function GetOrderBy($column, $direction)
        {
            
/**
            * Get the ordering column
            */
            
if (!empty($_GET['orderBy'])) {
                
$column = $_GET['orderBy'];
                
                
// Store it
                
Datagrid::$orderby['column'] = (string)$column;

            } else {
                
Datagrid::$orderby['column'] = $column;
            }

            
/**
            * Get the ORDER BY direction, defaulting to ASC
            */
            
if (!empty($_GET['orderDir']) AND intval($_GET['orderDir'])) {
                
$direction = intval($_GET['orderDir']);
                
                
// Store it
                
Datagrid::$orderby['direction'] = $direction;

            } else {
                
Datagrid::$orderby['direction'] = 0;
            }

            return
$column . ($direction ? ' ASC' : ' DESC');
        }
        
        
/**
        * Sets the displayed header names for the columns
        *
        * @param array $cols The column names
        */
        
public function SetDisplayNames($cols)
        {
            
$this->colnames = $cols;
        }
        
        
/**
        * Hides a particular column, or multiple columns
        *
        * @param ... strings One or more column names
        */
        
function HideColumn()
        {
            
$args = func_get_args();
            
            foreach (
$args as $column) {
                
$this->hiddenColumns[] = $column;
            }
            
            
$this->hiddenColumns = array_unique($this->hiddenColumns);
        }
        
        
/**
        * Sets the column names (not using the display names) that
        * don't get htmlspecialchars() applied to them
        *
        * @param string ... One or more column names
        */
        
public function NoSpecialChars()
        {
            
$this->noSpecialChars = func_get_args();
        }
        
        
/**
        * Returns the number of pages in the datagrid
        *
        * @return int The number of pages in the datagrid
        */
        
public function GetPageCount()
        {
            return  
ceil(mysql_num_rows($this->resultset) / $this->perPage);
        }
        
        
/**
        * Returns the number of rows in the result set.
        *
        * @return int The number of rows
        */
        
public function GetRowCount()
        {
            return
$this->numresults;
        }
        
        
/**
        * I can't see the need for this, but you may. Simply returns the MySQL result set.
        *
        * @return resource The MySQL result set
        */
        
public function GetResultset()
        {
            return
$this->resultset;
        }
        
        
        
/**
        * Returns the MySQL connection
        *
        * @return resource The MySQL resouce
        */
        
public function GetConnection()
        {
            return
$this->connection;
        }
        
        
/**
        * Sets the header HTML./ This is NOT related to the table
        * column headers. This is here purely for decorative purposes.
        *
        * @param string $html The HTML to set
        */
        
public function SetHeaderHTML($html)
        {
            
$this->headerHTML = $html;
        }

        
/**
        * Sets the MySQL connection
        *
        * @param resource $connection The MySQL connection resouce
        */
        
public function SetConnection($connection)
        {
            
$this->connection = $connection;
        }

        
/**
        * This function sets the amount of rows to display
        * per page
        *
        * @param int $perPage How many rows to show per page
        */
        
public function SetPerPage($perPage)
        {
            
$this->perPage = $perPage;
        }
        
        
/**
        * For whatever reason you can use this to set the MySQL
        * result set
        *
        * @param resource $result The MySQL result set. If you do use this method, it
        *                          should come before the call to Display
        */
        
public function SetResultset($resultset)
        {
            
$this->resultset  = $resultset;
            
$this->numresults = mysql_num_rows($this->resultset);
            
$this->colnum     = mysql_num_fields($this->resultset) - count($this->hiddenColumns);
        }
        
        
/**
        * Adds a rowcallback function which gets called just before each row is going
        * to be displayed
        *
        * @param string &$row The function name that is the callback function.
        */
        
public function AddCallback($callback)
        {
            
$this->rowcallback = $callback;
        }

        
/**
        * Shows the datagrid.
        */
        
function Display()
        {
            
/**
            * Seek to the correct place in the result set
            */
            
mysql_data_seek($this->resultset, $this->startnum);

            
/**
            * Initialise the row number
            */
            
$rownum = 0;

            
/**
            * Get the headers from the first row, then seek back to zero
            */
            
$row = mysql_fetch_array($this->resultset, MYSQL_ASSOC);
            
$this->headers     = array_keys($row);
            
$this->initialcols = count($row);
            
mysql_data_seek($this->resultset, $this->startnum);

            
?>
<script language="javascript" type="text/javascript">
<!--
    /**
    * The row mouseover function
    */
    function MouseOver(rownum)
    {
        var tags = document.getElementsByTagName('td')

        for (var i=0; i<tags.length; i++) {
            if(tags[i].className.indexOf('row_' + rownum + ' ') != -1) {
                tags[i].className = tags[i].className += ' mouseover';
            };
       }
    }
    
    /**
    * the row mouseout function
    */
    function MouseOut(rownum)
    {
        var tags = document.getElementsByTagName('td')

        for (var i=0; i<tags.length; i++) {
            if(tags[i].className.indexOf('row_' + rownum) != -1) {
                tags[i].className = tags[i].className.replace(/ mouseover/, '');
            };
        }
    }
// -->
</script>
<table border="0" cellspacing="<?=$this->cellspacing?>" class="datagrid">
    <thead>
        <?if($this->headerHTML):?>
            <tr>
                <th id="header" colspan="<?=$this->colnum?>">
                    <?=$this->headerHTML?>
                </th>
            </tr>
        <?endif?>

        <?if($this->showHeaders):?>
                <tr>
                    <?foreach($this->headers as $k => $h):?>
                        <?if(in_array($h, $this->hiddenColumns)) continue?>

                        <th class="col_<?=$k?>" title="<?=($printable = htmlspecialchars(!empty($this->colnames[$h]) ? $this->colnames[$h] : $h))?>">
                            <a href="?orderBy=<?=$h?>&orderDir=<?=(!empty($_GET['orderDir']) && $_GET['orderBy'] == $h ? 0 : 1)?>"><?=$printable?></a>
                            
                            <!-- The order indicator -->

                            <?if($h == Datagrid::$orderby['column']):?>
                                <span style="font-family: WebDings">
                                    <?=(!empty(Datagrid::$orderby['direction']) && trim(Datagrid::$orderby['direction']) == 1 ? 5 : 6)?>
                                </span>
                            <?endif?>
                        </th>
                    <?endforeach?>
                </tr>
        <?endif?>
    </thead>

    <tbody>
        <?while($row = mysql_fetch_array($this->resultset, MYSQL_ASSOC)):?>

            <?$colnum = 0; @$rowcount++?>

            <?if($this->rowcallback):?>
                <?call_user_func($this->rowcallback, &$row)?>
            <?endif?>

            <tr onmouseover="MouseOver(<?=intval($rownum)?>)" onmouseout="MouseOut(<?=intval($rownum)?>)">
                <?foreach($row as $k => $v):?>

                    <?if(in_array($k, $this->hiddenColumns)) continue?>

                    <td class="row_<?=intval($rownum)?> col_<?=(!empty($colnum) ? $colnum : 0)?> <?if($rownum % 2 == 1):?>altrow<?endif?>  <?if($colnum % 2 == 1):?>altcol<?endif?>">
                        <?=(in_array($k, $this->noSpecialChars) ? $v : htmlspecialchars($v))?>
                    </td>
                    
                    <?$colnum++?>
                <?endforeach?>
            </tr>

            <?if($rownum++ == ($this->perPage - 1) ) break?>
        <?endwhile?>
    </tbody>
    
    <tfoot>
        <tr>
            <td colspan="<?=$this->colnum?>">
                <?if(@$this->startnum > 0):?>
                    <span style="float: left">
                        <a href="<?=$this->getQueryString(intval($this->startnum) - 15)?>">
                            &laquo; Prev
                        </a>
                    </span>
                <?endif?>


                <?if($this->numresults > (@$this->startnum + 20)):?>
                    <span style="float: right">
                        <a href="<?=$this->getQueryString(intval($this->startnum) + 15)?>">
                            Next &raquo;
                        </a>
                    </span>
                <?endif?>
            </td>
        </tr>
        
        <tr>
            <td align="center" colspan="<?=$this->colnum?>">
                <?=intval($this->startnum) + 1?>-<?=(intval($this->startnum) + $rowcount)?> of <?=intval($this->numresults)?> results
            </td>
        </tr>
    </tfoot>
</table>
            <?php
        
}
        
        
/**
        * A private method used to build the query string
        *
        * @param  int    The starting number
        * @return string The query string
        */
        
private function getQueryString($startnum)
        {
            
$_GET['start'] = $startnum;

            
$qs = '?';
            foreach (
$_GET as $k => $v) {
                
$qs .= urlencode($k) . '=' . urlencode($v)  . '&';
            }

            
// If the query string is just a question mark, lose it
            
if ($qs == '?') {
                
$qs = '';
            }

            return
preg_replace('/&$/', '', $qs);
        }
    }
?>