Creating Alphabetical Filter With "Numeric" Category in Drupal Views Using a Custom Filter Handler
One of the things we love about Drupal is that in many areas you can get 80-90% of the way there just by configuring a few things in the admin interface. But when you need to go all the way with the last small changes, it sometimes requires digging deep into the system in areas that aren't well-documented. I recently came across this when I had to take a View which was built to filter nodes by the first letter of their titles, and modify it so that 0 through 9 weren't separate entries. This demonstrates how you can create a new View filter type to accomplish specialty tasks.
The View was easily set up using the "Starts With" filter, hidden so that users could choose a letter through a series of links. The only thing lacking was a way to pass in a value to indicate that it starts with a number, since the "Starts With" filter takes only literal values. After examining the options it seemed that there wasn't a simple way to create a more complex pattern, so I created a new filter called "Alphabetical Sort".
The idea for this filter was that it would take a single letter like the "Starts With" filter did, but if the character # was passed it would look for entries starting with any number using a regex query. Now how do you get talk Views into doing this? Fortunately Views was built with this in mind. You'll need a custom module to implement this - once you have the basics set up you can add the following:
1. In your main module file, add the following function (note that this was for Views 2.x - you can try adjusting the version number for 3.x):
/**
* Convince views to let us override things; this will get it to load a <module>.views.inc file from the module directory
*/
function <module>_views_api()
{
return array(
'api' => 2,
//optional: add 'path' => drupal_get_path('module', '<module>') . '/includes',
//to load <module>.views.inc from another directory
);
}
2. Create <module>.views.inc with these functions:
/**
* Add our custom alphabetical filtering handler for views
*/
function <module>_views_handlers()
{
return array(
'info' => array(),
'handlers' => array(
'<module>_handler_filter_string_alphasort' => array(
'parent' => 'views_handler_filter_string',
'file' => 'includes/<module>_handler_filter_string_alphasort.inc',
),
),
);
}
/**
* Tell views that we want to use our alphabetical filtering handler with node titles
*/
function <module>_views_data_alter(&$data)
{
$data['node']['title']['filter']['handler'] = '<module>_handler_filter_string_alphasort';
}
3. In your module directory, create includes/<module>_handler_filter_string_alphasort.inc with this code; this is modeled after handlers/views_handler_filter_string.inc in the views module but with most of the operations removed:
class <module>_handler_filter_string_alphasort extends views_handler_filter_string {
/**
* Define the new filter so it's available to select in the interface
*/
function operators() {
$operators = parent::operators() +
array(
'alphasort' => array(
'title' => t('Alphabetic Sort'),
'short' => t('alphasort'),
'method' => 'op_alphasort',
'values' => 1,
),
);
return $operators;
}
/**
* Build the sorting query, checking for the number value and converting it to a regex
*/
function op_alphasort($field, $upper) {
if ( $this->value == '#' )
$this->query->add_where($this->options['group'], "$upper($field) REGEXP '^[[:digit:]]'");
else
$this->query->add_where($this->options['group'], "$upper($field) LIKE $upper('%s%%')", $this->value);
}
}
Once you've done this, you can edit your view, add a node title filter if you haven't already done this, click the "Expose" button, set the operator to "Alphabetic Sort", and then save it. Note that in this case, the list of links to individual letters was custom-built so it was an easy matter to change the # link to pass the correct filter value. Try going to <view-url>?title=# and you should now see all the nodes starting with a number!