Filtering Drupal Views Dynamically (or, "Leverage the big CMS and web application frameworks! Only losers start from scratch!)"

First make a dynamically filtered view on the fly like the big boys do!

Then, stick it in a panel! There are other ways of doing it, but by using this simple method you can easily:

(By the way, we're working with: Drupal 5.x, Drupal Handbooks (Go Documentation Team!), Views 1 (thanks merlinofchaos!), Panels 2 (thanks Earl Miles), my favorite beverage.)

 

Contents of this torrid yet hopefully useful polemic:

It's all in the docs
Where to start: let's exhaust off-the-shelf flexibility first
Using arguments with views
Arguments that act like filters
Programming a very simple view from scratch with on-the-fly filters
Using Panels 2 to handle the dynamically filtered view through its URL parameters
Re-use, re-use and re-use again (never start from scratch)

It's all in the docs

Well, like everything else, it's all documented in drupal.org: the document team do a terrific job documenting, and you can find just about anything there.
Here I just feel like writing it up clear like with a simple example so I don't forget what actually worked for me when I needed it.

Relevant references:

  1. Creating views programmatically: http://drupal.org/node/138828
  2. Views 1.x module developer API: http://drupal.org/handbook/modules/views/api
  3. Panels 2.0 handbook docs: http://drupal.org/node/201914
  4. Views 1 handbook documentation: http://drupal.org/node/109604

Where to start: let's exhaust off-the-shelf flexibility first

To explain what we are looking for here, let's use the car dealer content type I was using in the recent article on how to leverage Drupal content types, by way of example.

If I want to simply list car dealers, I can whip up a view that does just that. Leveraging views even more, I can use exposed filters, and develop for myself a regular little interactive dealer browser in just a few minutes, without any code at all (this is described in detail here: Creating an interactive browser. Stick it on the menu.).

Using arguments with views

If I want to add on-the-fly flexibility, I can go even further without coding. I can make use of the views arguments functionality, so that different results will be listed according to how the view is invoked via the URL.

For example, let's suppose that we want a context sensitive list, without the exposed filters, to show car dealers from a particular state?

To do this, I clone the view (never touch anything that is actually working), and edit it, making some changes to the name and the page url (name: "browse_dealers" => "browse_dealers_by_city", url: "view/browse/dealers-by-city"). I then open the section "Arguments", select the field "Text: City (field_dealer_city), and click on the "Add Argument" button. We click the Save button at the foot of the form in order to make our changes permanent.

(For more detailed help with using Views arguments, see Reference 4 above).

Now, to test our new argument driven View manually, we can simply visit the corresponding url, and we will obtain a list of car dealers from the city. For example, http://example.com/view/browse/dealers-by-city/chicago will list car dealers from Chicago you have entered into the system.

The plot thickens, however, regarding anything not involving exact matches (unless you opt for the use of either wildcards, or the optional PHP argument handling code mentioned below). Let's suppose we add an integer field indicating sales volume (number of vehicles sold per year). And let's suppose that we want to publish a page with links to lists of dealers falling within certain sales volume ranges? For example, dealers who sell one to five hundred vehicles per month, dealers who sell five hundred to one thousand vehicles per month, dealers who sell more than a thousand vehicles per month, for example?

When we add the field as an argument, however, there is no straightforward way to indicate ranges. As we know, with filters, we could put two in, one greater than or equal, and the other less than, to get something like this:

  • Click to see lists of dealers by sales volume:
    • Dealers who sell more than a thousand vehicles per month
    • Dealers who sell between five hundred and a thousand vehicles per month
    • Dealers who sell less than five hundred vehicles per month

I could create a filtered view of this in just a few minutes, with two filters for each case.

What I definitely need are dynamic on-the-fly filters, so I can call a URL with dynamic arguments that have all the operating power of filters.

Of course, something we ccould do is to use the built-in option to add code via the PHP code Argument Handler option found in the arguments section of the View editing form. This is covered in the following excellent article:

Adding Sort via Argument Handler & Visualizing $View Object - http://groups.drupal.org/node/4203

Readers of this article are encouraged to follow up that option. Here, however we are examining another option altogether.

What we really need are...

Arguments that act like filters

That is, filters I can stick on the URL as if they were arguments and have them just work. What we need is to whip up a "few lines of code" to filter views dynamically. But how do we get started with a programmed, dynamically filtered view, with
the operational power of filters and the dynamics of arguments?

We actually have two problems:

1. How to tell Drupal how to handle the following in terms of the menu system:

2. How to actually create the view from scratch on the fly in accordance with the parameters?

Let's deal with the second one first, then install the solution on the basis of answering the first question (hint: Panels 2).

Programming a very simple view from scratch with on-the-fly filters

So, we need to program and test a simple programmatically generated view. As our environment (we need a built-in environment: never start from scratch) we can either use a php-enabled page, or else the php execute block that comes with Moshe Weitzman's great devel module (On a site being developed, I always go to Administer > Site building > Blocks and enable the Execute PHP block in the Footer region, for the admin/programmer role only, giving me a nice testbed workspace).

My eyes are on two sources in order to do this, Reference 1 above, and the views.module code itself, admirably commented. And I have a few car dealers in the system with sales volume specified.

We are going to create a view which never existed before in pure PHP code. The Drupal Handbook page seems to be telling us that we need to follow these simple steps:

  • Create the view object
  • Create its page attribute
  • Create some filters
  • Do some housekeeping
  • Execute the view
  • Show the results

The commented result looks something like this:

<?php
// on the fly id and description for view
$view = views_create_view('dealers_in_luck', 'Dealers still in luck');
// title, table view, pager on and 5 nodes-a-page... check out views.module for other params!
views_view_add_page($view, t('Crisis? What crisis...?'), NULL, 'table', true, 5, '', 1, false);
// we specify fields, necessary because this is a table view; first the node title
views_view_add_field($view, 'node', 'title', false, 0,  'views_handler_field_nodelink');
// show us the monthly sales volume too (view, db table, db field, ... and handler
views_view_add_field($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value', false, 0,  'content_views_field_handler_group');
// filters now; first, just show car dealers
views_view_add_filter($view, 'node', 'type',   '=', 'dealer', '');
// just published ones at that
views_view_add_filter($view, 'node', 'status', '=', 1, '');
// show me all of the ones with more than 500 sales volume per month
views_view_add_filter($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value_default',   '>=',  '500', '');
views_load_cache();
views_sanitize_view($view);
print views_build_view('embed', $view, array(), false);
?>

It works! We are rewarded with the following output:

<div class='view view-dealers-in-luck'><div class='view-content view-content-dealers-in-luck'><table>
 <thead><tr><th sort="views_handler_field_nodelink" class="view-cell-header view-field-node-title"></th><th sort="content_views_field_handler_group" class="view-cell-header view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value"></th> </tr></thead>
<tbody>
 <tr class="odd"><td class="view-field view-field-node-title">Internacional  De Vehiculos</td><td class="view-field view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value">525</td> </tr>
 <tr class="even"><td class="view-field view-field-node-title">Automotores San Jorge S.A</td><td class="view-field view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value">600</td> </tr>
 <tr class="odd"><td class="view-field view-field-node-title">Dist.Los Coches La Sabana S.A.</td><td class="view-field view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value">700</td> </tr>
 <tr class="even"><td class="view-field view-field-node-title">Autoniza Ltda</td><td class="view-field view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value">1250</td> </tr>
 <tr class="odd"><td class="view-field view-field-node-title">Centrodiesel</td><td class="view-field view-field-node-data-field-dealer-monthly-sales-volu-field-dealer-monthly-sales-volu-value">1500</td> </tr>
</tbody></table>
</div></div>

Try changing the parameters, and the views type (list, teaser, full). Particularly, use good old print_r to find out the views_build_view()'s field names we will be needing later:

<?php
// on the fly id and description for view
$view = views_create_view('dealers_in_luck', 'Dealers still in luck');
// title, table view, pager on and 5 nodes-a-page... check out views.module for other params!
views_view_add_page($view, t('Crisis? What crisis...?'), NULL, 'table', true, 5, '', 1, false);
// we specify fields, necessary because this is a table view; first the node title
views_view_add_field($view, 'node', 'title', false, 0,  'views_handler_field_nodelink');
// show us the monthly sales volume too (view, db table, db field, ... and handler
views_view_add_field($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value', false, 0,  'content_views_field_handler_group');
// filters now; first, just show car dealers
views_view_add_filter($view, 'node', 'type',   '=', 'dealer', '');
// just published ones at that
views_view_add_filter($view, 'node', 'status', '=', 1, '');
// show me all of the ones with more than 500 sales volume per month
views_view_add_filter($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value_default',   '>=',  '500', '');
views_load_cache();
views_sanitize_view($view);
print_r(views_build_view('items', $view, array(), false));
?>

Now, how did I know what details to fill in for the filters? By "cheating", of course: I made a view and exported it, then I knew what to do (Drupal as a CMS and framework prototyper: Never start a project from scratch!). So I went into Administer > Site building > Views, made a quick view with two exposed filters which would do interactively what I here wish to do programmatically, and then clicked on the "Export" link corresponding to the newly created view. I copied the result into a text editor. I essentially had a PHP standard object instantiation of the view and its parts; checking out the filters part, I got all the info I needed:

  $view = new stdClass();
  $view->name = 'list_dealers_by_volume';
  $view->description = 'Test filter';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = '';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = '';
  $view->page_empty_format = '1';
  $view->page_type = 'table';
  $view->url = 'view/list/dealers-by-volume';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '10';
  $view->sort = array (
  );
  $view->argument = array (
  );
  $view->field = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => '',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
    array (
      'tablename' => 'node_data_field_dealer_monthly_sales_volu',
      'field' => 'field_dealer_monthly_sales_volu_value',
      'label' => '',
      'handler' => 'content_views_field_handler_group',
      'options' => 'default',
    ),
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'dealer',
),
    ),
    array (
      'tablename' => 'node_data_field_dealer_monthly_sales_volu',
      'field' => 'field_dealer_monthly_sales_volu_value_default',
      'operator' => '>',
      'options' => '',
      'value' => '500',
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node, node_data_field_dealer_monthly_sales_volu);
  $views[$view->name] = $view;

Using Panels 2 to handle the dynamically filtered view through its URL parameters

So what I have in mind is a table or a cloud of prepackaged ranges to click on, ranges which could occur in several other contexts, with different kinds of ranges (maybe the 626 - 1293 range may take on signifcance for some reason!), and which would fit the parameterized URL, for example:

http://example.com/view/browse/dealers-by-volume/626/1293

would show me all the dealers whose monthly sales volume fell into the range of 626 - 1293 units, for example.

Enter stage left Panels 2.

I simply go into Administer > Site building > Panel pages, click on the add tab, choose any layout, choose any panel name, say dealers_by_volumne, and in the Path field put "dealers-by-volume/%".

That means that this panel will always be called when the URL corresponds to dealers-by-volume/{anything} .

And click Next.
Then, in the content part, I stick the following code into a new custom pane.

  • Click on the "+" icon.
  • Choose "New custom content".
  • Appropriate Title
  • Choose PHP Code input format
  • Put the following code into the Body section and click on Submit:

<?php
// on the fly id and description for view
$view = views_create_view('dealers_in_luck', 'Dealers still in luck');
// title, table view, pager on and 5 nodes-a-page... check out views.module for other params!
views_view_add_page($view, t('Crisis? What crisis...?'), NULL, 'table', true, 5, '', 1, false);
// we specify fields, necessary because this is a table view; first the node title
views_view_add_field($view, 'node', 'title', false, 0,  'views_handler_field_nodelink');
// view, db table, db field, ... and handler
views_view_add_field($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value', false, 0,  'content_views_field_handler_group');
// filters now; first, just show car dealers
views_view_add_filter($view, 'node', 'type',   '=', 'dealer', '');
// just published ones at that
views_view_add_filter($view, 'node', 'status', '=', 1, '');
// show me all of the ones falling between arg(1) and arg(2) sales volume per month
views_view_add_filter($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value_default',   '>=',  arg(1), '');
views_view_add_filter($view, 'node_data_field_dealer_monthly_sales_volu', 'field_dealer_monthly_sales_volu_value_default',   '<',  arg(2), '');
views_load_cache();
views_sanitize_view($view);
// Let's get creative! We can because we re-use stuff and have the time to be creative!
// Instead of "embed" and just printing that out, 
// let's use the more interesting "items" view building option 
// (check it out in the views.module docs on this function
$the_dealers = views_build_view('items', $view, array(), false);
print '<table><thead><th>Dealer</th><th>Monthly sales volume in units</th></thead>';
$odd = 1;
foreach ($the_dealers['items'] as $dealer) {
  $class = ($odd == 1)?'odd':'even';
   print '<tr class = "' . $class . '"><td><a href="/node/' . $dealer -> nid . '">' . $dealer -> node_title . '</a>'  .  
'</td><td>' . $dealer -> node_data_field_vehiculo_precio_desde_field_vehiculo_precio_desde_value
 . '</td><td>' . $dealer -> node_data_field_dealer_monthly_sales_volu_field_dealer_monthly_sales_volu_value
 . '</td></tr>';
  $odd = ($odd == 1)?0:1;
}
print '</table>';
?>

(you can see it makes use of the good old arg() function to work, although a more complex example would actually use a Panels 2 context)

That's it. Try it, you'll see:

URL: http://example.com/dealers-by-volume/500/1000

Result:

Dealers by monthly sales volume

Dealer Monthly sales volume in units
Internacional De Vehiculos   525
Automotores San Jorge S.A   600
Dist.Los Coches La Sabana S.A.   700

 

URL: http://example.com/dealers-by-volume/0/500

Result:

Dealers by monthly sales volume

Dealer Monthly sales volume in units
Automotores La Floresta S.A.   50
Continautos. S.A.   100

With re-use, standing on the shoulders of giants, it's easy: only losers start from scratch.

Re-use, re-use and re-use again (never start from scratch)

Cannot avoid adding in my two-cents regarding the "real men don't use CMS" fiasco originated by blogger Scott Miller last week in his Why Wordpress, Drupal, and other CMS’s are bad for Innovation embarrassment. The writer of this piece has shown absolute ignorance in terms of what it takes for any non-trivial software development project, especially web applications, to survive at all. Just as Picasso spent a large part of his childhood copying the masters, internalized in his consciousness forever and impossible to separate from his work of a lifetime, in art and in science, and certainly in any kind of software development, true greatness comes from bricolage: re-use, that is (There is even a CMS named after this term)!

Starting from scratch, especially using the frameworks of fashion, is a recipe for disaster, and this article is also meant to show by way of example how re-use lays at the heart of true creativity.

Not to mention, of course, that writing code implies testing for bugs and testing for security, and the more you reuse, the more testing (and quality) you have built right into your work.

a good article, thinks for

a good article, thinks for it

but it didn't respond an important question : how to connect a form to a view, so a user can make his search with his arguments ?
i can't believe there is no easy way for that
if not, i prefer code it "from scratch" in 5 minutes :)

perhaps you could write something about that ? thinks

Port to Drupal 6.x

Are you planning to port this article to Drupal 6.x, I'm trying to do the same, but with no success...

Follow the process, not the form

Hi Neuquino,

It does not look that easy a task, but in the meantime, a suggestion: notice how I did this in the first place: I made a view, exported it and then salvaged a bunch of stuff.

Since the export is php code to create a view in memory, that format may work. Make a similar view in views 2 and export that, and take a look at that code.

I have been working with Drupal 6, but have not covered this angle yet, will try to do so as soon as I get the time.

If anyone else has a solution, let fly please!

Victor

Having a really tough time

Having a really tough time following this. Some screen shots would really help. Especially, of what you mean by 'dyanmically filtered view'.

Great post

Fantastic post, absolutely fantastic, and I totally agree with the sentiments on re-use. Real men re-use code as neccesary, it's simply common sense.

How to add CCK fields to view programmatically and another probl

The initial post is http://drupal.org/node/281279
But the problem not solved, so i reproduce it here in hope anybody resolve it and make it clear to me and others.

In my module i create view programmatically in such fasion:
$future_matches_view = views_create_view('our_player_future_matches', 'All future matches for the player');
views_view_add_page($future_matches_view, t('Future matches'), NULL, 'table', true, 10, '', 1, false);
views_view_add_filter($future_matches_view, 'node', 'nid', 'views_handler_operator_or', $future_matches, 'id');
views_view_add_field($future_matches_view, 'node', 'title', '', FALSE, 0, 'views_handler_field_nodelink');
views_load_cache();
views_sanitize_view($future_matches_view);
$output .= views_build_view('embed', $future_matches_view, array(), false, false);
As you see i added filter, provided by viewsphpfilter module and passed to it variable $future_matches(i have a snippet to count it) and i also added field node title and everything works just fine. But also i need to add more fields to this table view, such as date-i have a CCK field date and two fields each one for competitor, who will take part in the match- i have CCK fields for competitors(nodereferance, as they are nodes-"bio" module).
So i need to add more views_view_add_field(), but can't figure out parameters which to pass(table, field and handlers).
Any help is appreciated.

Later was added:

I was wrong-
views_view_add_filter($future_matches_view, 'node', 'nid', 'views_handler_operator_or', $future_matches, 'id');
this filter doesn't work too!
Though in another place i created view with something like that:
views_view_add_filter($another_view, 'node', 'type', '=', 'TYPE', '');
and everything worked fine.
Why it doesn't work now and how to workaround this?

Real women, real men, real all of us...

... all benefit from reuse.

This is also why a framework in constant flux is something to be avoided as much as possible.

Thanks for your comment.

Quick Speed Tip

Great article, thank you.

Here's a quick tip for a little better speed. Take conditions out of the loop when possible.

$class = array('even', 'odd');
$i = 0;
 
foreach ($the_dealers['items'] as $dealer):
     echo '<tr class = "', $class[$i = 1 - $i], '">';
     ...
endforeach;

Thanks, Richard!

Cool!

I guess, actually, we should probably be using a theming function anyway, like theme ('table', ....) .

See http://api.drupal.org/api/function/theme_table/5

Would be a good exercise to see what the parameters should be.

Security? Why not use Perl CMS instead.

When it comes to security, and with ALL the respect to Drupal. I suggest you stick to Perl CMS. You can find many of them in http//www.sourceforge.net I found there: WebAPP from: http://www.web-app.net and very happy about it since!

I am sorry for my English, I am from Romania.
Marius

Security resides in the community

Thanks for your comment, Marius!

The value of an Open Source CMS in all respects resides not in a particular programming language or development environment, but rather in the community involved in its use, and hence (in the Open Source model) in its development.

Drupal security? Unparalleled!

See http://drupal.org/security-team

saludos,

Victor

Drupal scurity?

I am afraid that Marius is right, ask PHP experts they will tell you same thing, PHP has not been out long enough to be considered as safe and certainly not for CMS.

Otherwise with all the respect to these long list of names on the link you gave, I am afraid the list of security issues related to drupal modules is by far much longer...

Forget PHP CMS
Asta la vista!
Ricky

Security of PHP

PHP hasn't been out long enough to be secure and so don't use Drupal? tosh. Windows has been around longer and that hasn't helped.

the number one consideration when choosing an open CMS (or any software for that matter) is momentum: size of the community, number and enthusiasm of developers, richness of the body of work, reputation.

Drupal scores high on all of these. Stop being a geek.

Have to agree

IT Skeptic right, no two ways about it. However, one always has to see what project you are going to be using different platforms and frameworks...

Window is based on..something else, so is PHP

PHP is based on Perl, so does most (if not ALL) the products provided by Windows.
Again.

Why not use the original products, eg. Mac (windows did steal alot from them..) and Perl instead of PHP (which is originally Personal Home Page language based on Perl...).

I guess some of you did not do home work when you thought that was a good argumet for using PHP...

The right tool for the right job

That is the only valid criteria. Otherwise following your logic we would all go back to Algol99 or whatever

You guys should stick to CSS and HTML talk

You guys should stick to CSS and HTML talk.
PHP is easy for CSS and HTML kids and nothing else. It is tragic to read how non programmers are actually trying to analyze PHP as a language comparing to the very original language PHP is based on.

Maybe they could explain us while Parrot (Perl) is the future platform for both PHP, Perl, Phyton, TCL and more, while PHP is nothing but a platform for insecure kids made CMS portals...

Perl is a very powerful language, Content Managements Systems is just one tiny drop of what one can do with Perl, creating PHP is also... one more tiny drop of what one can do with Perl....

I seriously suggest to the PHP fans from above to stick to CSS and HTML discussions.

Wow

Not only only is your post practically unintelligible, but any valid message you may have had is lost in the sneering condescension you display towards "CSS and HTML kids".

What does that mean: CSS and HTML kids? Are you trying to somehow denigrate html and css developers? If so, why? It's quite an odd statement...

or, you could just be a very poor troll...

good day.