Actions

J2.5

Difference between revisions of "How to add custom filters to components"

From Joomla! Documentation

(Modify Template (views / zzz / tmpl / default.php): Added code to highlight found search terms in fields)
 
(25 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 +
{{version/tutor|2.5}}{{RightTOC}}
 
== Overview ==
 
== Overview ==
This how-to is intended to give a decent description on how to add dropdown form elements to the backend admin page for a component. I worked out the steps primarily on my own by dissecting/replicating the Banners component.  In particular, the 'banners' section of com_banners - not! to be confused with 'banner').
+
This is a how-to on how to add '''ordering''' and '''filtering''' (search, custom dropdown, state) to a component backend page.<br/>
 
+
This approach is almost exactly the same for the frontend.<br/>
{{JVer|1.7}} {{JVer|2.5}}
+
In general, the process of adding a '''custom field filter''' breaks down into the following steps:
 
+
* Creating the class that will generate the form dropdown options
In general, the process of adding these fields breaks down into the following steps:
+
* Adapting the view to display the form dropdown input
* Creating the class that will generate the form element
+
* Adapting the model to retrieve data from DB & to set submission values in the Joomla 'state'. For a better understanding of the code samples, the db table is called 'my_company' and the fields are: id, name, state, company, somefield, someotherfieldtosearchin.
* Adapting the view to display the form element
+
* Adapting the model to retrieve data from DB & to set submission values in the Joomla 'state'
+
  
 
== Extend JFormFieldList (models / fields / fieldname.php) ==
 
== Extend JFormFieldList (models / fields / fieldname.php) ==
Line 88: Line 87:
 
In your model you have to include these two functions:
 
In your model you have to include these two functions:
 
<source lang="php">
 
<source lang="php">
protected function getListQuery(){
+
//Add this handy array with database fields to search in
 +
protected $searchInFields = array('text','a.name','someotherfieldtosearchin');
 +
 
 +
//Override construct to allow filtering and ordering on our fields
 +
public function __construct($config = array()) {
 +
$config['filter_fields']=array_merge($this->searchInFields,array('a.company'));
 +
parent::__construct($config);
 +
}
 +
 
 +
protected function getListQuery(){
 
$db = JFactory::getDBO();
 
$db = JFactory::getDBO();
 
$query = $db->getQuery(true);
 
$query = $db->getQuery(true);
  
 
                 //CHANGE THIS QUERY AS YOU NEED...
 
                 //CHANGE THIS QUERY AS YOU NEED...
                 $query->select('id As value, name As text');
+
                 $query->select('id As value, name As text, somefield')
$query->from('#__my_companies AS a');
+
->from('#__my_companies AS a')
$query->order('a.name');
+
->order( $db->escape($this->getState('list.ordering', 'pa.id')) . ' ' .
$query->where('state = 1');
+
$db->escape($this->getState('list.direction', 'desc')));
  
 
 
// Filter
+
$search = $this->getState('filter.search');
+
// Filter search // Extra: Search more than one fields and for multiple words
if (!empty($search)) {
+
$regex = str_replace(' ', '|', $this->getState('filter.search'));
$query->where('(a.name LIKE '.$search.')');
+
if (!empty($regex)) {
 +
$regex=' REGEXP '.$db->quote($regex);
 +
$query->where('('.implode($regex.' OR ',$this->searchInFields).$regex.')');
 +
}
 +
 
 +
// Filter company
 +
$company= $db->escape($this->getState('filter.company'));
 +
if (!empty($company)) {
 +
$query->where('(a.company='.$company.')');
 +
}
 +
 +
// Filter by state (published, trashed, etc.)
 +
$state = $db->escape($this->getState('filter.state'));
 +
if (is_numeric($state)) {
 +
$query->where('a.state = ' . (int) $state);
 +
}
 +
elseif ($state === '') {
 +
$query->where('(a.state = 0 OR a.state = 1)');
 
}
 
}
 
 
 +
//echo $db->replacePrefix( (string) $query );//debug
 
return $query;
 
return $query;
 
}
 
}
Line 122: Line 148:
 
// Load the filter state.
 
// Load the filter state.
 
$search = $this->getUserStateFromRequest($this->context.'.filter.search', 'filter_search');
 
$search = $this->getUserStateFromRequest($this->context.'.filter.search', 'filter_search');
$this->setState('filter.search', $search);
+
//Omit double (white-)spaces and set state
+
$this->setState('filter.search', preg_replace('/\s+/',' ', $search));
 +
 +
//Filter (dropdown) state
 
$state = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
 
$state = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
 
$this->setState('filter.state', $state);
 
$this->setState('filter.state', $state);
 +
 +
//Filter (dropdown) company
 +
$state = $this->getUserStateFromRequest($this->context.'.filter.company', 'filter_company', '', 'string');
 +
$this->setState('filter.company', $state);
 
 
// List state information.
+
//Takes care of states: list. limit / start / ordering / direction
 
parent::populateState('a.name', 'asc');
 
parent::populateState('a.name', 'asc');
 
}
 
}
 
</source>
 
</source>
=== Extra fields and inside words ===
+
 
Here is a small modification to search in multiple fields  in your database ánd to also search inside words.
+
'''For the frontend:''' your ''$app'' variable looks like this:
 +
 
 
<source lang="php">
 
<source lang="php">
// Filter
+
 
$search = $this->getState('filter.search');
+
  protected function populateState($ordering = null, $direction = null)
if (!empty($search)) {
+
        {
$query->where('(a.name LIKE "%'.$search.'%" OR a.city LIKE "%'.$search.'%"));
+
                // Initialise variables.
}
+
                $app = JFactory::getApplication();
 +
 
 
</source>
 
</source>
  
Line 144: Line 178:
 
In you view class, you have to set the state, like this:
 
In you view class, you have to set the state, like this:
 
<source lang="php>
 
<source lang="php>
                $this->items = $this->get('Items');
+
$this->items = $this->get('Items');
 
$this->pagination = $this->get('Pagination');
 
$this->pagination = $this->get('Pagination');
                //Add the state...
 
 
$this->state = $this->get('State');
 
$this->state = $this->get('State');
 +
 +
//Following variables used more than once
 +
$this->sortColumn = $this->state->get('list.ordering');
 +
$this->sortDirection = $this->state->get('list.direction');
 +
$this->searchterms = $this->state->get('filter.search');
 
</source>
 
</source>
  
 
== Modify Template (views / zzz / tmpl / default.php) ==
 
== Modify Template (views / zzz / tmpl / default.php) ==
In your template file, remember to include the html form to filter.
+
=== Basic ===
<source lang="php">
+
Top of template somewhere:<source lang="php">
<div class="filter-search fltlft">
+
//Get companie options
<label class="filter-search-lbl" for="filter_search"><?php echo JText::_('JSEARCH_FILTER_LABEL'); ?></label>
+
JFormHelper::addFieldPath(JPATH_COMPONENT . '/models/fields');
<input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" title="<?php echo JText::_('COM_MYCOMPANY_SEARCH_IN_TITLE'); ?>" />
+
$companies = JFormHelper::loadFieldType('MyCompany', false);
<button type="submit"><?php echo JText::_('JSEARCH_FILTER_SUBMIT'); ?></button>
+
$companyOptions=$companies->getOptions(); // works only if you set your field getOptions on public!!
<button type="button" onclick="document.id('filter_search').value='';this.form.submit();"><?php echo JText::_('JSEARCH_FILTER_CLEAR'); ?></button>
+
</div>
+
 
</source>
 
</source>
 +
In your template file, this fieldset reflects filter options:<source lang="php">
 +
<fieldset id="filter-bar">
 +
<div class="filter-search fltlft">
 +
<input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->searchterms); ?>" title="<?php echo JText::_('Search in company, etc.'); ?>" />
 +
<button type="submit">
 +
<?php echo JText::_('JSEARCH_FILTER_SUBMIT'); ?>
 +
</button>
 +
<button type="button" onclick="document.id('filter_search').value='';this.form.submit();">
 +
<?php echo JText::_('JSEARCH_FILTER_CLEAR'); ?>
 +
</button>
 +
</div>
 +
<div class="filter-select fltrt">
 +
<select name="filter_state" class="inputbox" onchange="this.form.submit()">
 +
<option value="">
 +
<?php echo JText::_('JOPTION_SELECT_PUBLISHED');?>
 +
</option>
 +
<?php echo JHtml::_('select.options', JHtml::_('jgrid.publishedOptions', array('archived'=>false)), 'value', 'text', $this->state->get('filter.state'), true);?>
 +
</select>
 +
 +
<select name="filter_type" class="inputbox" onchange="this.form.submit()">
 +
<option value=""> - Select Company - </option>
 +
<?php echo JHtml::_('select.options', $companyOptions, 'value', 'text', $this->state->get('filter.company'));?>
 +
</select>
  
=== Extra: highlighting===
+
</div>
Here is some extra to make your fields highlight the found search terms.<br/>
+
</fieldset>
<u>Add this somewhere at top of your template:</u><source lang="php">
+
</source>
 +
 
 +
'''Remark''': the name of the ''select'' tag must be the same as defined in the ''populateState'' function. In the example:
 +
 
 +
<source php>
 +
 
 +
    //populateState function
 +
    $state = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
 +
 
 +
    //template
 +
    <select name="filter_state" class="inputbox" onchange="this.form.submit()">
 +
 
 +
 
 +
</source>
 +
 
 +
=== Extra: highlighting search terms ===
 +
Here is some extra to visually mark the found search terms in the results page.<br/>
 +
==== Template ====
 +
<u>Add this somewhere at top somehwere:</u> <source lang="php">
 
$searchterms = $this->state->get('filter.search');
 
$searchterms = $this->state->get('filter.search');
  
//Highlight search terms with js
+
//Highlight search terms with js (if we did a search => more performant and otherwise crash)
JHtml::_('behavior.highlighter', explode(' ',$searchterms));
+
if (strlen($searchterms)>1) JHtml::_('behavior.highlighter', explode(' ',$searchterms));
 
</source>
 
</source>
And further in your template, enclose the specific fields with the default highlighter finder. (Be careful that your DOM is not breaken by the <br/> tags (like it would wrongly placed between table tags for example).<br/>
+
And further in your template, enclose the specific fields with the default highlighter finder.<br/>
 
<u>Example:</u><source lang="php">
 
<u>Example:</u><source lang="php">
<br id="highlighter-start" />
+
<span id="highlighter-start"></span>
<?php echo $item->name; ?>
+
<table class="adminlist">
<br id="highlighter-end" />
+
...
 +
</table>
 +
<span id="highlighter-end"></span>
 +
</source>
 +
==== CSS ====
 +
If your default admin template doesn't have required css, you should add the class in your component css.
 +
<u>Example in controller:</u><source lang="php">
 +
JHtml::stylesheet('com_peopleactions/admin.css', array(), true);
 +
</source>
 +
<u>In admin.css:</u><source lang="css">
 +
.highlight {  background: none repeat scroll 0 0 #FFFF00; }
 
</source>
 
</source>
  
Line 180: Line 267:
  
 
[[User:Doub1ejack|Doub1ejack]]
 
[[User:Doub1ejack|Doub1ejack]]
 +
 +
[[User:E-builds|E-builds]]
  
 
[[Category:Development]]
 
[[Category:Development]]
 
[[Category:Component Development]]
 
[[Category:Component Development]]
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]
[[Category:Joomla! 1.6]]
 
 
[[Category:Joomla! 1.7]]
 
[[Category:Joomla! 1.7]]
 
[[Category:Joomla! 2.5]]
 
[[Category:Joomla! 2.5]]

Latest revision as of 21:42, 7 June 2013

Contents

Overview

This is a how-to on how to add ordering and filtering (search, custom dropdown, state) to a component backend page.
This approach is almost exactly the same for the frontend.
In general, the process of adding a custom field filter breaks down into the following steps:

  • Creating the class that will generate the form dropdown options
  • Adapting the view to display the form dropdown input
  • Adapting the model to retrieve data from DB & to set submission values in the Joomla 'state'. For a better understanding of the code samples, the db table is called 'my_company' and the fields are: id, name, state, company, somefield, someotherfieldtosearchin.

Extend JFormFieldList (models / fields / fieldname.php)

Frequently your filter fields will be very basic; probably simply a dropdown list of one of the columns being displayed on the current page. Regardless, you will need to create your own field element. This really isn't a big deal - this file will probably be less than 70 lines, including comments. The code below will generate the dropdown element to filter by companies. For more information and complex examples on creating this class see Creating_a_custom_form_field_type.

My 'companies' dropdown element:

<?php
 
defined('JPATH_BASE') or die;
 
jimport('joomla.html.html');
jimport('joomla.form.formfield');
jimport('joomla.form.helper');
JFormHelper::loadFieldClass('list');
 
/**
 * Custom Field class for the Joomla Framework.
 *
 * @package             Joomla.Administrator
 * @subpackage          com_my
 * @since               1.6
 */
class JFormFieldMyCompany extends JFormFieldList
{
        /**
         * The form field type.
         *
         * @var         string
         * @since       1.6
         */
        protected $type = 'MyCompany';
 
        /**
         * Method to get the field options.
         *
         * @return      array   The field option objects.
         * @since       1.6
         */
        public function getOptions()
        {
                // Initialize variables.
                $options = array();
 
                $db     = JFactory::getDbo();
                $query  = $db->getQuery(true);
 
                $query->select('id As value, name As text');
                $query->from('#__my_companies AS a');
                $query->order('a.name');
                $query->where('state = 1');
 
                // Get the options.
                $db->setQuery($query);
 
                $options = $db->loadObjectList();
 
                // Check for a database error.
                if ($db->getErrorNum()) {
                        JError::raiseWarning(500, $db->getErrorMsg());
                }
 
                return $options;
        }
}

Obviously, there is only one section here that needs to be customized:

                $query->select('id As value, name As text');
                $query->from('#__my_companies AS a');
                $query->order('a.name');
                $query->where('state = 1');

Change the database name and adjust the sql as necessary. This will produce the text and values for the select element options. I highly recommend not changing the value and text names. Although I did not test this theory, it seems likely that ancestor classes are expecting these names as keys in the resulting array.

Modify Model (models / zzz.php)

In your model you have to include these two functions:

//Add this handy array with database fields to search in
        protected $searchInFields = array('text','a.name','someotherfieldtosearchin');
 
//Override construct to allow filtering and ordering on our fields
        public function __construct($config = array()) {
                $config['filter_fields']=array_merge($this->searchInFields,array('a.company'));
                parent::__construct($config);
        }
 
        protected function getListQuery(){
                $db = JFactory::getDBO();
                $query = $db->getQuery(true);
 
                //CHANGE THIS QUERY AS YOU NEED...
                $query->select('id As value, name As text, somefield')
                        ->from('#__my_companies AS a')
                        ->order(        $db->escape($this->getState('list.ordering', 'pa.id')) . ' ' .
                                        $db->escape($this->getState('list.direction', 'desc')));
 
 
 
                // Filter search // Extra: Search more than one fields and for multiple words
                $regex = str_replace(' ', '|', $this->getState('filter.search'));
                if (!empty($regex)) {
                        $regex=' REGEXP '.$db->quote($regex);
                        $query->where('('.implode($regex.' OR ',$this->searchInFields).$regex.')');
                }
 
                // Filter company
                $company= $db->escape($this->getState('filter.company'));
                if (!empty($company)) {
                        $query->where('(a.company='.$company.')');
                }
 
                // Filter by state (published, trashed, etc.)
                $state = $db->escape($this->getState('filter.state'));
                if (is_numeric($state)) {
                        $query->where('a.state = ' . (int) $state);
                }
                elseif ($state === '') {
                        $query->where('(a.state = 0 OR a.state = 1)');
                }
 
                //echo $db->replacePrefix( (string) $query );//debug
                return $query;
        }
 
        /**
         * Method to auto-populate the model state.
         *
         * Note. Calling getState in this method will result in recursion.
         *
         * @since       1.6
         */
        protected function populateState($ordering = null, $direction = null)
        {
                // Initialise variables.
                $app = JFactory::getApplication('administrator');
 
                // Load the filter state.
                $search = $this->getUserStateFromRequest($this->context.'.filter.search', 'filter_search');
                //Omit double (white-)spaces and set state
                $this->setState('filter.search', preg_replace('/\s+/',' ', $search));
 
                //Filter (dropdown) state
                $state = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
                $this->setState('filter.state', $state);
 
                //Filter (dropdown) company
                $state = $this->getUserStateFromRequest($this->context.'.filter.company', 'filter_company', '', 'string');
                $this->setState('filter.company', $state);
 
                //Takes care of states: list. limit / start / ordering / direction
                parent::populateState('a.name', 'asc');
        }

For the frontend: your $app variable looks like this:

  protected function populateState($ordering = null, $direction = null)
        {
                // Initialise variables.
                $app = JFactory::getApplication();

Modify View (views / zzz / view.html.php)

In you view class, you have to set the state, like this:

                $this->items            = $this->get('Items');
                $this->pagination       = $this->get('Pagination');
                $this->state            = $this->get('State');
 
                //Following variables used more than once
                $this->sortColumn       = $this->state->get('list.ordering');
                $this->sortDirection    = $this->state->get('list.direction');
                $this->searchterms      = $this->state->get('filter.search');

Modify Template (views / zzz / tmpl / default.php)

Basic

Top of template somewhere:
//Get companie options
JFormHelper::addFieldPath(JPATH_COMPONENT . '/models/fields');
$companies = JFormHelper::loadFieldType('MyCompany', false);
$companyOptions=$companies->getOptions(); // works only if you set your field getOptions on public!!
In your template file, this fieldset reflects filter options:
        <fieldset id="filter-bar">
                <div class="filter-search fltlft">
                        <input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->searchterms); ?>" title="<?php echo JText::_('Search in company, etc.'); ?>" />
                        <button type="submit">
                                <?php echo JText::_('JSEARCH_FILTER_SUBMIT'); ?>
                        </button>
                        <button type="button" onclick="document.id('filter_search').value='';this.form.submit();">
                                <?php echo JText::_('JSEARCH_FILTER_CLEAR'); ?>
                        </button>
                </div>
                <div class="filter-select fltrt">
                        <select name="filter_state" class="inputbox" onchange="this.form.submit()">
                                <option value="">
                                        <?php echo JText::_('JOPTION_SELECT_PUBLISHED');?>
                                </option>
                                <?php echo JHtml::_('select.options', JHtml::_('jgrid.publishedOptions', array('archived'=>false)), 'value', 'text', $this->state->get('filter.state'), true);?>
                        </select>
 
                        <select name="filter_type" class="inputbox" onchange="this.form.submit()">
                                <option value=""> - Select Company - </option>
                                <?php echo JHtml::_('select.options', $companyOptions, 'value', 'text', $this->state->get('filter.company'));?>
                        </select>
 
                </div>
        </fieldset>

Remark: the name of the select tag must be the same as defined in the populateState function. In the example:

    //populateState function
    $state = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
 
    //template
    <select name="filter_state" class="inputbox" onchange="this.form.submit()">

Extra: highlighting search terms

Here is some extra to visually mark the found search terms in the results page.

Template

Add this somewhere at top somehwere:
$searchterms = $this->state->get('filter.search');
 
//Highlight search terms with js (if we did a search => more performant and otherwise crash)
if (strlen($searchterms)>1) JHtml::_('behavior.highlighter', explode(' ',$searchterms));

And further in your template, enclose the specific fields with the default highlighter finder.

Example:
<span id="highlighter-start"></span>
        <table class="adminlist">
        ...
        </table>
<span id="highlighter-end"></span>

CSS

If your default admin template doesn't have required css, you should add the class in your component css.

Example in controller:
JHtml::stylesheet('com_peopleactions/admin.css', array(), true);
In admin.css:
.highlight {   background: none repeat scroll 0 0 #FFFF00; }

Contributors

Javier Constanzo

Doub1ejack

E-builds