| MVC workflow with JPagination |
| Tuesday, 16 December 2008 19:02 |
|
The MVC architecture is something that probably can not be explained without solid examples. In this example we will concentrate on showing a list using pagination. We will assume a component called "com_example" which just shows a single page with multiple items. If there are more items then defined by the "List Limit" within the Global Configuration, pagination is added. The entry-point and the controllerJoomla! sees the "option" variable in the URL and therefor knows it should look for the component "com_example". Within the database table "#__components" a check is performed to see if the component is active and if it is, Joomla! will call the entry-point file "example.php". <?php // File "example.php" defined( '_JEXEC' ) or die( 'Restricted access' ); require_once JPATH_COMPONENT.DS.'controller.php'; $controller = new ExampleController( ); $controller->execute(); ?> First of all there is a security check on _JEXEC. After that we can start by including the controller-file ("components/com_example/controller.php") and initialize the controller. The controller-class ExampleController just serves one purpose, there is only one task. Therefor we need not to pass any $task to the execute() method. We even don't need the $controller->redirect() statement - we will never set any redirects! The controller-class is even more simple. We need to create it to tell Joomla! that we have a controller, but it doesn't add anything to the JController-class. <?php
// File "controller.php"
defined( '_JEXEC' ) or die( 'Restricted access' );
jimport('joomla.application.component.controller');
class ExampleController extends JController
{
function display()
{
parent::display();
}
}
?>
The view and the layout fileThe controller points straight to the only view and its display() method. We need a directory called "views" and put an "items" folder inside. That's our view-name so the file "views/items/view.html.php" should contain a class named ExampleViewItems. <?php
// File "views/items/view.html.php"
defined('_JEXEC') or die( 'Restricted access' );
jimport( 'joomla.application.component.view');
class ExampleViewItems extends JView
{
function display($tpl = null)
{
$items = $this->get('Data');
$this->assignRef( 'items', $items );
$pagination = $this->get('Pagination') ;
$this->assignRef( 'pagination', $pagination );
parent::display($tpl);
}
}
?>
The view-class is initialized in the normal way. From our view we then obtain data from the model by using the shortcut-method $this->get('Data'). But instead of only importing $items from the model, we also import $pagination. Before we have a look at the model, we first show our layout-file. <?php
// File "views/items/tmpl/default.php"
defined('_JEXEC') or die( 'Restricted access' );
?>
<?php echo $this->pagination->getPagesCounter(); ?>
<?php if( count( $this->items )) : ?>
<table>
<?php foreach( $this->items as $item ) : ?>
<tr>
<td><?php echo $item->title; ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<?php echo $this->pagination->getPagesLinks(); ?>
We can see now that the JPagination-object that we fetched from the model, contains two methods: The "getPagesCounter()" method prints a little message saying something like "Page 1 of 2", while the "getPagesLinks()" method prints the navigation box. The model - an overviewSo far we have seen just a few things of the JPagination-class. The most interesting in this example is the actual methods within the model. Somewhere we need to tell the model that we don't want all items, but only those that are within the current page. We will first draw an outline of the class with the method-code missing. <?php
// File "models/items.php"
defined('_JEXEC') or die( 'Restricted access' );
jimport('joomla.application.component.model');
class ExampleModelItems extends JModel
{
var $_data = null;
var $_total = null;
var $_pagination = null;
function __construct() {}
function _loadData() {}
function getData() {}
function getTotal() {}
function getPagination() {}
}
?>
What you can see from this outline is that we need at least internal variables ($_data, $_total and $_pagination) for the JPagination-trick to work. The variable $_data is an array containing all our items that are going to be fetched from the database. It is initialized through the _loadData() function. Because this array contains all the records from the table, we can also generate a $_total variable which holds the total number of records found in the database. Because we needed both the data ($_data) as a JPagination-object from the model (see the previous chapter in this appendix) we define also a $_pagination variable. The $_data variable is a private member but can be obtained from the model using the "getData()" method. The same goes for the $_total variable which has a getTotal() method and the $_pagination variable which has a getPagination() method. Later we will check if these are simple getters or if they contain a bit more logic. The model constructorNow we dive into the methods themselves. The constructor has been extended a little bit by setting the $limitstart and $limit variables. The $limitstart variable defines the page we are currently viewing - pages are counted from 0. This variable is pointless unless we define as well how many items should appear on a single page - the $limit variable. function __construct()
{
parent::__construct();
$application = JFactory::getApplication() ;
$config = JFactory::getConfig() ;
$limitstart = JRequest::getInt( 'limitstart', 0 );
$limit = $application->getUserStateFromRequest( 'global.list.limit',
'limit', $config->getValue('config.list_limit'), 'int' );
$this->setState('limitstart', $limitstart);
$this->setState('limit', $limit);
}
The $limit variable is first of all read from the Global Configurations "list.limit" option. To read this we need to instantiate a JRegistry-object through JFactory::getConfig(). We set this as default, but we could also allow an user to change the limit through some kind of selectbox. Because of this we want check $_POST, $_GET and $_SESSION to see if the user defined its own list-limit. This is done through the JApplication::getUserStateFromRequest() method. Instead of setting the variables $limitstart and $limit just as private internal variables, they are saved through the JModel::setState() method instead. The point of this is just a theoratical one: While $_data and $_pagination can be seen as solid variables that are part of the data-model, the $limit and $limitstart variables are seen as user-variables - which means the user could change these variables when the right form-elements are available. If you don't believe this, forget about this and just use private variables instead. The model: Loading the dataWe have initialized some user-preferences ($limit and $limitstart) to build a pagination mechanism. But we also need data from the database. That's the purpose of the _loadData() method. It first checks with empty() functions whether the $_data array is already filled - and if not, it performs a simple database query. function _loadData()
{
if (empty($this->_data) && empty($this->_total))
{
$query = 'SELECT * FROM #__example' ;
$this->_db->setQuery($query);
$this->_data = $this->_db->loadObjectList();
$this->_total = count( $this->_data ) ;
}
return $this->_data ;
}
After we have filled $_data with the records from the database, we also calculate the total of records and put it in $_total. The model: Fetching the dataNow that we have a perfect _loadData() method to grab the items from the database, we are ready to hand them over to the outside world. Within the getData() method we first of all call _loadData(). We can do this multiple times without a harm. The method detects by itself whether $_data is already filled or not, and only performs a database query when it has been called for the first time. function getData()
{
$this->_loadData() ;
$limitstart = $this->getState('limitstart');
$limit = $this->getState('limit');
return array_slice( $this->_data, $limitstart, $limit );
}
But we don't want to return the full $_data array. We just want to return that part which is indicated by the pagination: Either the first page or the page indicated by $limitstart. We simply use array_slice() to return only the items needed. The model: PaginationBefore we finish the model by explaining the getPagination() function, here is the getTotal() method. It is just a simple getter: function getTotal()
{
return $this->_total;
}
The getPagination() method starts with a call to _loadData(). It doesn't matter if we call getData() or getPagination(). The first thing we do is get the data from the database and we only do it once. The same applies to $_pagination. If there is no $_pagination available yet, we create it. Otherwise we just return the existing object. function getPagination()
{
$this->_loadData() ;
if (empty($this->_pagination))
{
jimport('joomla.html.pagination');
$limitstart = $this->getState('limitstart');
$limit = $this->getState('limit');
$total = $this->getTotal();
$this->_pagination = new JPagination( $total, $limitstart, $limit );
}
return $this->_pagination;
}
The magic of creating the actual JPagination-object just involves including the library-file through jimport() and creating the object with the right arguments. |