13 min read

(Read more interesting articles on Joomla! 1.5here.)

Itemized data

Most components handle and display itemized data. Itemized data is data having many instances; most commonly this reflects rows in a database table. When dealing with itemized data there are three areas of functionality that users generally expect:

  • Pagination
  • Ordering
  • Filtering and searching

In this section we will discuss each of these areas of functionality and how to implement them in the backend of a component.

Pagination

To make large amounts of itemized data easier to understand, we can split the data across multiple pages. Joomla! provides us with the JPagination class to help us handle pagination in our extensions.

There are four important attributes associated with the JPagination class:

  • limitstart: This is the item with which we begin a page, for example the first page will always begin with item 0.
  • limit: This is the maximum number of items to display on a page.
  • total: This is the total number of items across all the pages.
  • _viewall: This is the option to ignore pagination and display all items.

Before we dive into piles of code, let’s take the time to examine the listFooter, the footer that is used at the bottom of pagination lists:

Customize backend Component in Joomla! 1.5

The box to the far left describes the maximum number of items to display per page (limit). The remaining buttons are used to navigate between pages. The final text defines the current page out of the total number of pages.

The great thing about this footer is we don’t have to work very hard to create it! We can use a JPagination object to build it. This not only means that it is easy to implement, but that the pagination footers are consistent throughout Joomla!. JPagination is used extensively by components in the backend when displaying lists of items.

In order to add pagination to our revues list we must make some modifications to our backend revues model. Our current model consists of one private property $_revues and two methods: getRevues() and delete(). We need to add two additional private properties for pagination purposes. Let’s place them immediately following the existing $_revues property:

/** @var array of revue objects */
var $_revues = null;
/** @var int total number of revues */
var $_total = null;
/** @var JPagination object */
var $_pagination = null;

Next we must add a class constructor, as we will need to retrieve and initialize the global pagination variables $limit and $limitstart. JModel objects store a state object in order to record the state of the model. It is common to use the state variables limit and limitstart to record the number of items per page and starting item for the page.

We set the state variables in the constructor:

/**
* Constructor
*/
function __construct()
{
global $mainframe;

parent::__construct();
// Get the pagination request variables
$limit = $mainframe->getUserStateFromRequest(
'global.list.limit',
'limit', $mainframe->getCfg('list_limit'));
$limitstart = $mainframe->getUserStateFromRequest(
$option.'limitstart', 'limitstart', 0);
// Set the state pagination variables
$this->setState('limit', $limit);
$this->setState('limitstart', $limitstart);
}

Remember that $mainframe references the global JApplication object. We use the getUserStateFromRequest() method to get the limit and limitstart variables.

We use the user state variable, global.list.limit, to determine the limit. This variable is used throughout Joomla! to determine the length of lists. For example, if we were to view the Article Manager and select a limit of five items per page, if we move to a different list it will also be limited to five items.

If a value is set in the request value limit (part of the listFooter), we use that value. Alternatively we use the previous value, and if that is not set we use the default value defined in the application configuration.

The limitstart variable is retrieved from the user state value $option, plus .limitstart. The $option value holds the component name, for example com_content. If we build a component that has multiple lists we should add an extra level to this, which is normally named after the entity.

If a value is set in the request value limitstart (part of the listFooter) we use that value. Alternatively we use the previous value, and if that is not set we use the default value 0, which will lead us to the first page.

The reason we retrieve these values in the constructor and not in another method is that in addition to using these values for the JPagination object, we will also need them when getting data from the database.

In our existing component model we have a single method for retrieving data from the database, getRevues(). For reasons that will become apparent shortly we need to create a private method that will build the query string and modify our getRevues() method to use it.

/**
* Builds a query to get data from #__boxoffice_revues
* @return string SQL query
*/
function _buildQuery()
{
$db =& $this->getDBO();
$rtable = $db->nameQuote('#__boxoffice_revues');
$ctable = $db->nameQuote('#__categories');

$query = ' SELECT r.*, cc.title AS cat_title'
. ' FROM ' . $rtable. ' AS r'
. ' LEFT JOIN '.$ctable.' AS cc ON cc.id=r.catid;

return $query;
}

We now must modify our getRevues() method:

/**
* Get a list of revues
*
* @access public
* @return array of objects
*/
function getRevues()
{
// Get the database connection
$db =& $this->_db;
if( empty($this->_revues) )
{
// Build query and get the limits from current state
$query = $this->_buildQuery();
$limitstart = $this->getState('limitstart');
$limit = $this->getState('limit');
$this->_revues = $this->_getList($query,
$limitstart,
$limit);
}
// Return the list of revues
return $this->_revues;
}

We retrieve the object state variables limit and limitstart and pass them to the private JModel method _getList(). The _getList() method is used to get an array of objects from the database based on a query and, optionally, limit and limitstart.

The last two parameters will modify the first parameter, a query, in such a way that we only return the desired results. For example if we requested page 1 and were displaying a maximum of five items per page, the following would be appended to the query: LIMIT 0, 5.

To handle pagination we need to add a method called getPagination() to our model. This method will handle items we are trying to paginate using a JPagination object. Here is our code for the getPagination() method:

/**
* Get a pagination object
*
* @access public
* @return pagination object
*/
function getPagination()
{
if (empty($this->_pagination))
{
// Import the pagination library
jimport('joomla.html.pagination');
// Prepare the pagination values
$total = $this->getTotal();
$limitstart = $this->getState('limitstart');
$limit = $this->getState('limit');
// Create the pagination object
$this->_pagination = new JPagination($total,
$limitstart,
$limit);
}
return $this->_pagination;
}

There are three important aspects to this method. We use the private property $_pagination to cache the object, we use the getTotal() method to determine the total number of items, and we use the getState() method to determine the number of results to display.

The getTotal() method is a method that we must define in order to use. We don’t have to use this name or this mechanism to determine the total number of items. Here is one way of implementing the getTotal() method:

/**
* Get number of items
*
* @access public
* @return integer
*/
function getTotal()
{
if (empty($this->_total))
{
$query = $this->_buildQuery();
$this->_total = $this->_getListCount($query);
}
return $this->_total;
}

This method calls our model’s private method _buildQuery() to build the query, the same query that we use to retrieve our list of revues. We then use the private JModel method _getListCount()to count the number of results that will be returned from the query.

We now have all we need to be able to add pagination to our revues list except for actually adding pagination to our list page. We need to add a few lines of code to our revues/view.html.php file. We will need to access to global user state variables so we must add a reference to the global application object as the first line in our display method:

global $mainframe;

Next we need to create and populate an array that will contain user state information. We will add this code immediately after the code that builds the toolbar:

// Prepare list array
$lists = array();

// Get the user state
$filter_order = $mainframe->getUserStateFromRequest(
$option.'filter_order',
'filter_order', 'published');
$filter_order_Dir = $mainframe->getUserStateFromRequest(
$option.'filter_order_Dir',
'filter_order_Dir', 'ASC');

// Build the list array for use in the layout
$lists['order'] = $filter_order;
$lists['order_Dir'] = $filter_order_Dir;
// Get revues and pagination from the model
$model =& $this->getModel( 'revues' );
$revues =& $model->getRevues();
$page =& $model->getPagination();

// Assign references for the layout to use
$this->assignRef('lists', $lists);
$this->assignRef('revues', $revues);
$this->assignRef('page', $page);

After we create and populate the $lists array, we add a variable $page that receives a reference to a JPagination object by calling our model’s getPagination() method. And finally we assign references to the $lists and $page variables so that our layout can access them.

Within our layout default.php file we must make some minor changes toward the end of the existing code. Between the closing </tbody> tag and the </table> tag we must add the following:

<tfoot>
<tr>
<td colspan="10">
<?php echo $this->page->getListFooter(); ?>
</td>
</tr>
</tfoot>

This creates the pagination footer using the JPagination method getListFooter(). The final change we need to make is to add two hidden fields to the form. Under the existing hidden fields we add the following code:

<input type="hidden" name="filter_order"
value="<?php echo $this->lists['order']; ?>" />
<input type="hidden" name="filter_order_Dir" value="" />

The most important thing to notice is that we leave the value of the filter_order_Dir field empty. This is because the listFooter deals with this for us.

Customize backend Component in Joomla! 1.5

That is it! We now have added pagination to our page.

Ordering

Another enhancement that we can add is the ability to sort or order our data by column, which we can accomplish easily using the JHTML grid.sort type. And, as an added bonus, we have already completed a significant amount of the necessary code when we added pagination.

Most of the changes to revues/view.html.php that we made for pagination are used for implementing column ordering; we don’t have to make a single change. We also added two hidden fields, filter_order and filter_order_Dir, to our layout form, default.php. The first defines the column to order our data and the latter defines the direction, ascending or descending.

Most of the column headings for our existing layout are currently composed of simple text wrapped in table heading tags (<th>Title</th> for example). We need to replace the text with the output of the grid.sort function for those columns that we wish to be orderable. Here is our new code:

<thead>
<tr>
<th width="20" nowrap="nowrap">
<?php echo JHTML::_('grid.sort', JText::_('ID'), 'id',
$this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
<th width="20" nowrap="nowrap">
<input type="checkbox" name="toggle" value=""
onclick="checkAll(
<?php echo count($this->revues); ?>);" />
</th>

<th width="40%">
<?php echo JHTML::_('grid.sort', JText::_('TITLE'),
'title', $this->lists['order_Dir'],
$this->lists['order'] ); ?>

</th>
<th width="20%">
<?php echo JHTML::_('grid.sort', JText::_('REVUER'),
'revuer', $this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
<th width="80" nowrap="nowrap">
<?php echo JHTML::_('grid.sort', JText::_('REVUED'),
'revued', $this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
<th width="80" nowrap="nowrap" align="center">
<?php echo JHTML::_('grid.sort', 'ORDER', 'ordering',
$this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
<th width="10" nowrap="nowrap">
<?php if($ordering) echo JHTML::_('grid.order',
$this->revues); ?>
</th>
<th width="50" nowrap="nowrap">
<?php echo JText::_('HITS'); ?>
</th>
<th width="100" nowrap="nowrap" align="center">
<?php echo JHTML::_('grid.sort', JText::_('CATEGORY'),
'category',
$this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
<th width="60" nowrap="nowrap" align="center">
<?php echo JHTML::_('grid.sort', JText::_('PUBLISHED'),
'published',
$this->lists['order_Dir'],
$this->lists['order'] ); ?>
</th>
</tr>
</thead>

Let’s look at the last column, Published, and dissect the call to grid.sort. Following grid.sort we have the name of the column, filtered through JText::_() passing it a key to our translation file. The next parameter is the sort value, the current order direction, and the current column by which the data is ordered.

In order for us to be able to use these headings to order our data we must make a few additional modifications to our JModel class.

We created the _buildQuery() method earlier when we were adding pagination. We now need to make a change to that method to handle ordering:

/**
* Builds a query to get data from #__boxoffice_revues
* @return string SQL query
*/
function _buildQuery()
{
$db =& $this->getDBO();
$rtable = $db->nameQuote('#__boxoffice_revues');
$ctable = $db->nameQuote('#__categories');
$query = ' SELECT r.*, cc.title AS cat_title'
. ' FROM ' . $rtable. ' AS r'
. ' LEFT JOIN '.$ctable.' AS cc ON cc.id=r.catid'
. $this->_buildQueryOrderBy();

return $query;

}

Our method now calls a method named _buildQueryOrderBy() that builds the ORDER BY clause for the query:

/**
* Build the ORDER part of a query
*
* @return string part of an SQL query
*/
function _buildQueryOrderBy()
{
global $mainframe, $option;
// Array of allowable order fields
$orders = array('title', 'revuer', 'revued', 'category',
'published', 'ordering', 'id');

// Get the order field and direction, default order field
// is 'ordering', default direction is ascending
$filter_order = $mainframe->getUserStateFromRequest(
$option.'filter_order', 'filter_order', 'ordering');
$filter_order_Dir = strtoupper(
$mainframe->getUserStateFromRequest(
$option.'filter_order_Dir', 'filter_order_Dir', 'ASC'));

// Validate the order direction, must be ASC or DESC
if ($filter_order_Dir != 'ASC' && $filter_order_Dir != 'DESC')
{
$filter_order_Dir = 'ASC';
}

// If order column is unknown use the default
if (!in_array($filter_order, $orders))
{
$filter_order = 'ordering';
}
$orderby = ' ORDER BY '.$filter_order.' '.$filter_order_Dir;
if ($filter_order != 'ordering')
{
$orderby .= ' , ordering ';
}
// Return the ORDER BY clause

return $orderby;
}

As with the view, we retrieve the order column name and direction using the application getUserStateFromRequest() method. Since this data is going to be used to interact with the database, we perform some data sanity checks to ensure that the data is safe to use with the database.

Now that we have done this we can use the table headings to order itemized data. This is a screenshot of such a table:

Customize backend Component in Joomla! 1.5

Notice that the current ordering is title descending, as denoted by the small arrow to the right of Title.

LEAVE A REPLY

Please enter your comment!
Please enter your name here