Actions

Difference between revisions of "Adding sortable columns to a table in a component"

From Joomla! Documentation

(Adapt to Joomla! 2.5 (mainly filter_order -> list.ordering))
(Step 1: The Model: Joomla 3 compabitibility: escape(...) instead of getEscaped(...))
 
(2 intermediate revisions by one user not shown)
Line 1: Line 1:
 +
{{version/tutor|2.5,3.x}}
 
Given that you have a table of data already in your component, how do you make some, or all, of the table columns sortable, like many of them are in the Joomla Administrator?  It's not particularly hard to do, but there are several steps required and details you need to be aware of so everything fits together properly.  There are variations on the procedure given here and once you are confident that you understand how it all works you should feel free to explore other possibilities that may suit your purposes better.
 
Given that you have a table of data already in your component, how do you make some, or all, of the table columns sortable, like many of them are in the Joomla Administrator?  It's not particularly hard to do, but there are several steps required and details you need to be aware of so everything fits together properly.  There are variations on the procedure given here and once you are confident that you understand how it all works you should feel free to explore other possibilities that may suit your purposes better.
  
Line 12: Line 13:
 
}
 
}
 
</source>
 
</source>
 +
 +
The JModelList::populateState only allows predefined values for the ordering. Therefore you'll have to add the names of all columns by which your data can be sorted to the 'filter_fields' config, like this:
 +
<source>
 +
public function __construct($config = array())
 +
 +
$config['filter_fields'] = array(
 +
'column_name_1',
 +
'column_name_2',
 +
// ...
 +
'column_name_3'
 +
);
 +
parent::__construct($config);
 +
}
 +
</source>
 +
If you use column prefixes in your query you can also specify them along with the column name (e.g. 'b.id').
  
 
In the model which generates the data that will form the table, you need to make a change to the method which builds the database query that will be used to populate the HTML table. Most often this is the [[JModelList::getListQuery|getListQuery()]] method, but might not be.
 
In the model which generates the data that will form the table, you need to make a change to the method which builds the database query that will be used to populate the HTML table. Most often this is the [[JModelList::getListQuery|getListQuery()]] method, but might not be.
Line 26: Line 42:
 
// ...
 
// ...
 
 
$query->order($db->getEscaped($this->getState('list.ordering', 'default_sort_column')).' '.
+
$query->order($db->escape($this->getState('list.ordering', 'default_sort_column')).' '.
$db->getEscaped($this->getState('list.direction', 'ASC')));
+
$db->escape($this->getState('list.direction', 'ASC')));
 
 
 
return $query;
 
return $query;
Line 93: Line 109:
 
# Must be 'grid.sort' so that [[JHTML]] will insert the correct behaviour for a sortable column.
 
# Must be 'grid.sort' so that [[JHTML]] will insert the correct behaviour for a sortable column.
 
# This is the name of the column that your visitors will actually see. You need to change this for your particular table columns.
 
# This is the name of the column that your visitors will actually see. You need to change this for your particular table columns.
# This is the name of the corresponding database field (column) that is to be sorted on.  This will be passed to the model, most likely so it can be added to an "ORDER BY" clause in the SQL query statement.
+
# This is the name of the corresponding database field (column) that is to be sorted on.  This will be passed to the model, most likely so it can be added to an "ORDER BY" clause in the SQL query statement (whatever is here needs to match one of the column names specified in the whitelist, including any possible prefixes).
 
# Must be exactly as shown here. It is the current order direction (ascending or descending) and comes from the view (see later).
 
# Must be exactly as shown here. It is the current order direction (ascending or descending) and comes from the view (see later).
 
# Must be exactly as shown here. It is the name of the column that the table is currently sorted on and comes from the view (see later).
 
# Must be exactly as shown here. It is the name of the column that the table is currently sorted on and comes from the view (see later).

Latest revision as of 15:18, 3 June 2013

Given that you have a table of data already in your component, how do you make some, or all, of the table columns sortable, like many of them are in the Joomla Administrator? It's not particularly hard to do, but there are several steps required and details you need to be aware of so everything fits together properly. There are variations on the procedure given here and once you are confident that you understand how it all works you should feel free to explore other possibilities that may suit your purposes better.

This procedure assumes that your component is structured according to the Model-View-Controller (MVC) design pattern. The general idea behind the procedure will still be applicable to non-MVC components if you apply a bit of imagination!

Contents

Step 1: The Model

The first thing you need to do is to populate the order state of your model. Change 'default_column_name' to the name of the column you want to use as the default sort, and change the second parameter to DESC if you wish the default order direction to be descending. Calling the parents populateState method will make sure that the State object is filled and accessible to all of the code that might need it.

protected function populateState($ordering = null, $direction = null) {
        parent::populateState('default_column_name', 'ASC');
}

The JModelList::populateState only allows predefined values for the ordering. Therefore you'll have to add the names of all columns by which your data can be sorted to the 'filter_fields' config, like this:

        public function __construct($config = array())
        {   
                $config['filter_fields'] = array(
                        'column_name_1',
                        'column_name_2',
                        // ...
                        'column_name_3'
                );
                parent::__construct($config);
        }

If you use column prefixes in your query you can also specify them along with the column name (e.g. 'b.id').

In the model which generates the data that will form the table, you need to make a change to the method which builds the database query that will be used to populate the HTML table. Most often this is the getListQuery() method, but might not be.

The model could pull data in from anywhere; it doesn't have to be a database, but in the vast majority of cases the model will be using the Joomla database API to submit SQL queries to a database. Assuming that to be the case, you need to adjust the query so that the sort parameters are taken into account.

This information gets called by whatever function builds the ORDER BY clause, typically a private function like this:

public function getListQuery() {
        $db = JFactory::getDbo();
        $query = $db->getQuery(true);
 
        // ...
 
        $query->order($db->escape($this->getState('list.ordering', 'default_sort_column')).' '.
                $db->escape($this->getState('list.direction', 'ASC')));
 
        return $query;
}

Replace 'default_sort_column' with the column you want to sort by by default. You can also change 'ASC' to 'DESC', depending on which way you want to sort by default.

Step 2: The View

Having generated the sorting variables in the model, you need to assign them to the view so that they show up on the page when it is displayed.

To do this you need to add a few lines of code to your view file, typically view.html.php with code similar to this:

public function display($tpl=null) {
        $items = $this->get('Items');
        $state = $this->get('State');
 
        $this->sortDirection = $state->get('list.direction');
        $this->sortColumn = $state->get('list.ordering');
 
        parent::display($tpl);
}

Step 3: The Template

Now you need to add some elements to the component layout file. The table must be included in a form. This might already be the case as, for example, you might already have implemented pagination or filtering on the table. But if the table is not yet in a form then now is the time to wrap it in <form> and </form> tags. The reason that a form is required is that the sortable columns rely on a bit of JavaScript that will submit the form with sort parameters added. Naturally, this will involve a page load, so if you would prefer an AJAX-based solution, then this procedure is not for you.

The form tags will look something like this:

<form id="adminForm" method="post" name="adminForm">
 
.... table goes here ....
 
</form>

Notice that the form name must be "adminForm". In some special cases you might need to add the action attribute (e.g. action="<?php echo JRoute::_( 'index.php' );?>"), but usually this should not be required, when it's not there the form will just be submitted to the current documents address, i.e. the same page, which is usually the correct behavior.

You also need to add a couple of hidden fields to the form. They can be placed anywhere between the <form> and </form> tags, but generally they are placed just before the closing tag, like this:

<form id="adminForm" method="post" name="adminForm">
 
        .... table goes here ....
 
        <input type="hidden" name="filter_order" value="<?php echo $this->sortColumn; ?>" />
        <input type="hidden" name="filter_order_Dir" value="<?php echo $this->sortDirection; ?>" />
</form>

Now look at the table itself. You might have a table with static headings already, looking vaguely like this:

<tr>
        <th>Name</th>
        <th>Description</th>
</tr>

You need to replace the static column names with calls to the Joomla JHTML static class, so that your code will look something like this:

<tr>
        <th><?php echo JHTML::_( 'grid.sort', 'Name', 'DbNameColumn', $this->sortDirection, $this->sortColumn); ?></th>
        <th><?php echo JHTML::_( 'grid.sort', 'Description', 'DbDescriptionColumn', $this->sortDirection, $this->sortColumn); ?></th>
</tr>

You will definitely need to adapt this code to your specific requirements. The arguments to the JHTML call are as follows:

  1. Must be 'grid.sort' so that JHTML will insert the correct behaviour for a sortable column.
  2. This is the name of the column that your visitors will actually see. You need to change this for your particular table columns.
  3. This is the name of the corresponding database field (column) that is to be sorted on. This will be passed to the model, most likely so it can be added to an "ORDER BY" clause in the SQL query statement (whatever is here needs to match one of the column names specified in the whitelist, including any possible prefixes).
  4. Must be exactly as shown here. It is the current order direction (ascending or descending) and comes from the view (see later).
  5. Must be exactly as shown here. It is the name of the column that the table is currently sorted on and comes from the view (see later).

In short, you need to amend the second and third arguments to each JHTML call appropriately.

Finally, if your sortable table is going to be in the front-end of your site, then you need to add a little snippet of JavaScript to the layout. Alternatively, you can add it to the view code (using JDocument->addScriptDeclaration) if you would rather keep your JavaScript code in the HTML <head> section.

<script language="javascript" type="text/javascript">
function tableOrdering( order, dir, task )
{
        var form = document.adminForm;
 
        form.filter_order.value = order;
        form.filter_order_Dir.value = dir;
        document.adminForm.submit( task );
}
</script>

You don't need to add this code if your sortable table is in the Administrator as this code is loaded for you automatically anyway.

This completes the changes you need to make to the layout. JHTML grid.sort will now add a call to the tableOrdering function so that tableOrdering will be called whenever the user clicks on the column header. tableOrdering puts the name of the column that was clicked, and the sort direction, into the hidden form fields and submits the form; the changed values will get picked up by the populateState method, and then retrieved by your model via the list.ordering/list.dirc.

Step 4: Styling the result

Finally. you might want to apply a bit of CSS styling to make the output a bit more attractive.

Selecting the sortable columns can only be done via their context, so you will probably need to add a CSS class to the <table>, the <tr> or to the <th> tags. This is what the output might look like with a class added to the tag:

<tr class="sortable">
        <th><a href="javascript:tableOrdering('DbName','asc','');" title="Click to sort by this column">Training provider</a></th>
        <th><a href="javascript:tableOrdering('DbDescription','asc','');" title="Click to sort by this column">Location</a></th>
</tr>

To add a bit of space between the column name and the ascending/descending indicator image (a common requirement), you could then apply CSS like this:

tr.sortable th img {
        margin-left: 5px;
}

See also