Providing dynamic select options to
Webform module for Drupal

May 27, 2010

The Webform module for Drupal is tremendously useful, and with the 3.x release, it has become much easier to work with.In this tutorial, I am going to explain how to dynamically populate Webform options in Drupal.

The scenario

Why would you want to do this? Let's suppose you are using Webform to allow your site users to register for upcoming events. In the old days it worked like this: you added a select component to your Webform, and then hand-entered the upcoming events. When the date of an event passed, you would have to go back to your component and update the list. It was either that, or you had to implement hook_form_alter, which is no fun.

With Webform 3.x, quicksketch has provided us with hook_webform_select_options_info(), which we can use to provide our dynamic select options to Webform 3.x.

Implementing hook_webform_select_options_info()

First we need to create a module. In our module file we would add the following:

/** * Provide a select options component to Webform. The values are * populated via a call to _demo_get_options, which returns * an array of nodes. * * @return void * @author Kosta Harlan */ function demo_webform_select_options_info() { $items = array();

if (function_exists('_demo_get_story_nodes')) {
    $items['story-nodes'] = array(
    'title' => t('Story nodes'),
    'options callback' => '_demo_get_story_nodes',
  );
}

if (module_exists('views')) {
    $items['views'] = array(
        'title' => t('A view'),
        'options callback' => '_demo_get_view_options',
    );
}

return $items;

}

This function is providing two items to Webform, "Story nodes" and "A view". You can optionally define a file callback if you want to separate keep a clean separation of your core module functions and the callback you are providing for Webform. Check out webform_hooks.php in your webform module folder for more information.

At this point, all we've done is tell Webform that there are two new options for the select options component. Now we need to provide the functions for the callbacks that we specified above:

/** * Returns an array of Story nodes keyed on the node ID. * * @return array * @author Kosta Harlan */ function _demo_get_story_nodes() { $nodes = array();

$select = db_query(db_rewrite_sql("SELECT nid, title FROM 
          {node} WHERE type = 'story' ORDER BY title"));
while ($node = db_fetch_object($select)) {
    $nodes[$node->nid] = $node->title;
}
return $nodes;

}

The function above is a simple example of a database query. It selects all nodes of the story type and orders them by title. Not terribly useful, but you can tweak the query to provide the results you are looking for.

For most folks, an easier and more sustainable option will be to create a View of the nodes that you want to display to your users, then implement the function below:

/** * Options callback for webform_select_options_info(). * The assumption is that your view is displaying node data. * * @return array of items to populate the select list with. * @author Kosta Harlan */ function _demo_get_view_options() { // Change 'my_sample_view' to the machine name of your View. $view = views_get_view('my_sample_view', true); $view->execute(); $items = array();

foreach ($view->result as $item) {
  $full_node = node_load($item->nid);
  $items[$item->nid] = $full_node->title;
}
return $items;

}

This will then return an array of items based on the View that you defined. If you end up using Views, I would strongly recommend exporting your View as a module and adding it as a dependency in your new module's .info file.

Usage

Now in your webform, click on "Form Components" and under type pick "Select options". Click on "Add", and under "Load a pre-built options list", pick the option you defined above.

And that's it! Now any time you add or delete a Story node, or the results returned by 'my_sample_view' change, the options displayed to the user in your Webform will also change.

The tarball of the code is attached.

Questions? Comments? Let me know.


Comments

Well thank god for this post buddy. Really nice simple example of how to do this and something that works as a starting point of the shelf too. Thanks for taking the time, I was going to have a custom content type that anonymous users could submit but that took everything that was great about webform away and would have been a ballache to get going in a simpler way. Good job.

Excellent post. Exactly what I was looking for. I needed to try to implement a node reference in a webform and using a view seems to be the best way.

Is there any way you can briefly explain how I can include my view (in code) in the module?

Hi Colin,

To export your View as a module:

  1. Enable the “Views exporter” module.
  2. Go to admin/build/views/tools/export
  3. Check the name of the module and type the module name, then follow the directions provided.

-Kosta

Hi Kosta,

When I look in the webform module folder there is no webform_hooks.php file. I also tried installing your module and it doesn’t work. The ‘Load a pre-built options list’ never appears. Is there something I am missing here?

Cheers Sarah

Hey Sarah,

Are you sure you’re using Webform 3? The solution outlined here won’t work with Webform 2.

Kosta

Thanks much! I took your demo module and modified. For some reason the views integration wasn’t working for me — I had created a view that simply returned the nid field, is that what I was supposed to do? But I did get a pre-filled field using the manually coded SQL. Very helpful!

Nice work! Thanks!

I just want to add for people why don't pick up at first; replace 'demo' in the code with the name of your module.

Also, line 10 and 11 of the second code block should read;

$select = db_query(db_rewrite_sql("SELECT nid, title FROM
{node} WHERE type = 'story' ORDER BY title"));

And, the link to the tarball doesn't work (anymore).

Thanks Peter, in the transition to our new Drupal site some of the formatting got lost and files were misplaced. The link to the tarball should work again.

I made an addition to the code.

I don't want to mess around in the code if I want to add a list so I made the code pull the lists automatically from the Views database. The complete code now reads (my module is called dynaoptions);

/** * Provide a select options component to Webform. The values are * populated via a call to _dynaoptions_get_options, which returns * an array of nodes. * * @return void * @author Kosta Harlan */ function dynaoptions_webform_select_options_info() { $items = array();

    if (function_exists('_dynaoptions_get_story_nodes')) {
            $items['story-nodes'] = array(
        'title' => t('Story nodes'),
        'options callback' => '_dynaoptions_get_story_nodes',
      );
    }

    if (module_exists('views')) {
            $viewNames = array("Countries","Councils");
            $result = db_query_range("SELECT name FROM views_view");
            while($viewName = db_fetch_array($result)) {
                $viewNames = $viewName['name'];
            }
            foreach($viewNames as $viewName) {
                $items[$viewName] = array(
                        'title' => $viewName,
                        'options callback' => '_dynaoptions_get_view_options',
                        'options arguments' => $viewName,
                );
            }
    }

return $items;

}

/** * Returns an array of Story nodes keyed on the node ID. * * @return array * @author Kosta Harlan */ function _dynaoptions_get_story_nodes() { $nodes = array();

    $select = db_query(db_rewrite_sql("SELECT nid, title FROM 
              {node} WHERE type = 'story' ORDER BY title"));
    while ($node = db_fetch_object($select)) {
            $nodes[$node->nid] = $node->title;
    }
    return $nodes;

}

/** * Options callback for webform_select_options_info(). * The assumption is that your view is displaying node data. * * @return array of items to populate the select list with. * @author Kosta Harlan */ function _dynaoptions_get_view_options() { $args = func_get_args(); // Change 'my_sample_view' to the machine name of your View. $view = views_get_view($args[3], true); $view->execute(); $items = array();

foreach ($view->result as $item) {
  $full_node = node_load($item->nid);
  $items[$item->nid] = $full_node->title;
}
return $items;

}

/** * Implementation of hook_views_api(). */ function dynaoptions_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'dynaoptions'), //'path' => drupal_get_path('module', 'dynaoptions') . '/includes', ); }

Sorry, this is the correct code. I had some stuff hardcoded and a faulty (and ugly) database call. I now use the views_get_all_views() function provided by Views itself.

  /
   * Provide a select options component to Webform. The values are 
   * populated via a call to _dynaoptions_get_options, which returns 
   * an array of nodes.
   *
   * @return void
   * @author Kosta Harlan
   */
  function dynaoptions_webform_select_options_info() {
        $items = array();
 
        if (function_exists('_dynaoptions_get_story_nodes')) {
                $items['story-nodes'] = array(
            'title' => t('Story nodes'),
            'options callback' => '_dynaoptions_get_story_nodes',
          );
        }
 
        if (module_exists('views')) {
               $viewNames = views_get_all_views();
                foreach($viewNames as $viewName) {
                 if($viewName->disabled==0) {
                    $viewName = $viewName->name;
                    $items[$viewName] = array(
                         'title' => $viewName,
                         'options callback' => '_dynaoptions_get_view_options',
                          'options arguments' => $viewName,
                 );
                 }
              }
        }
 
    return $items;
  }

/
 * Returns an array of Story nodes keyed on the node ID.
 *
 * @return array
 * @author Kosta Harlan
 */
function _dynaoptions_get_story_nodes() {
        $nodes = array();
        
        $select = db_query(db_rewrite_sql("SELECT nid, title FROM 
                  {node} WHERE type = 'story' ORDER BY title"));
        while ($node = db_fetch_object($select)) {
                $nodes[$node->nid] = $node->title;
        }
        return $nodes;
}

/
 * Options callback for webform_select_options_info(). 
 * The assumption is that your view is displaying node data.
 *
 * @return array of items to populate the select list with.
 * @author Kosta Harlan
 */
function _dynaoptions_get_view_options() {
 $args = func_get_args();
   // Change 'my_sample_view' to the machine name of your View.
 $view = views_get_view($args[3], true); 
   $view->execute();
   $items = array();
  
   foreach ($view->result as $item) {
    $full_node = node_load($item->nid);
     $items[$item->nid] = $full_node->title;
    }
  return $items;
}


/
 * Implementation of hook_views_api().
 */
function dynaoptions_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'dynaoptions'),
    //'path' => drupal_get_path('module', 'dynaoptions') . '/includes',
  );
}

This is great Peter, thanks!
I have created a module called Webform options based on this code, but I was wondering: would it be possible to display the views in alphabetical order instead of ordered by id? Although it works great, this would be just a little bit more user friendly as a general solution.

In /sites/all/modules create a folder called webform_options with these two files:

webform_options.info
;$Id$
name = Webform options
description = Enables all views as webform options
package= Webform
core = 7.x

files[] = webform_options.module
version = "7.x-1.0"
core = "7.x"
project = "Webform options"

; Module dependencies
dependencies[] = webform

webform_options.module
<?php

function webform_options_webform_select_options_info() {
$items = array();

if (module_exists('views')) {
$viewNames = views_get_all_views();
foreach($viewNames as $viewName) {
if($viewName->disabled==0) {
$viewName = $viewName->name;
$items[$viewName] = array(
'title' => $viewName,
'options callback' => '_webform_options_get_view_options',
'options arguments' => $viewName,
);
}
}
}

return $items;
}

function _webform_options_get_view_options() {
$args = func_get_args();
$view = views_get_view($args[3], true);
$view->execute();
$items = array();

foreach ($view->result as $item) {
$full_node = node_load($item->nid);
$items[$item->nid] = $full_node->title;
}
return $items;
}

function webform_options_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'webform_options'),
//'path' => drupal_get_path('module', 'webform_options') . '/includes',
);
}

Thanks for posting that! I've been needing to use a View to populate webform -- you should create this as a full project on Drupal.org!

Great tutorial! Just found it while trying to populate a drop-down list in a webform on a site I'm working on.

For some reason, my drop-down is only displaying 10 items, The view is set to Unlimited for "Items to Display" both on Default and the Page view I created for it. Any idea why in the webform it's only showing 10 items or how to change that?

Thanks!

  • Matt

Hi Matt,

In your View, does the "Preview" button on the Default or Page display only show ten results, or does it show more?

Kosta

Hi Kosta,

The Preview shows all node titles, both in Default and Page. But the webform is only showing 10 items in the drop-down list.

Here's the code I used (pastebin): http://drupal.pastebin.us/2640

The site is on an internal development server only right now, so I can't send the link to show you how it looks :(

Thanks,
Matt

The drop-down list for A-Z Programs or Academic Programs? I would remove the $items['view'] array from your select_options_info function and see if you can get the SQL query working. It sounds like an issue with the View as there's nothing in the code to limit the results to 10. If you like, message me on IRC in the #drupal channel, @kostajh.

I think I have something confused, since I just need 1 list pulling from a single view. I just noticed that both options are available in Webform, but I only want one. The "Academic Programs" option is displaying everything, but "A-Z Programs is not". The reason I put "A-Z Programs" into that spot instead of "A view" was I thought it was needed to pull the name of the view. Should I change some option, then, so it's only pulling 1 thing?

Thanks,
Matt

I've implemented this with a rather insane looking select statement so I can filter out submissions.

Have a "story" node that allows 10 people to select it from the list then it disappears.

Problem is when I go to download the results since I have that story hidden now it doesn't show in the results. I've just started looking but maybe you've already solved this problem?

If anybody is having trouble with the view method and their results being limited to the first 10, I fixed it by doing

$view->init_display();
$view->pre_execute();

before

$view->execute();

Hope that helps.

Thank you!
I was stuck on that issue for a few days now.

NOTE!

function db_rewrite_sql() requires properly written query string. So, I had a problem with example in this post, list for anonymous was empty. Then I little changed sql string to "SELECT n.nid, n.title FROM {node} n WHERE n.type = 'product' AND n.status = '1' ORDER BY n.title" after that works perfect :)

Thanks Kosta!

Hi, i am using webform 6.x-3.11 (the last version of this great drupal module).
And i am working in projects and it´s necessary that a grid can be created dinamically from the options of other grid.

I explain I have a grid with 10 files for example with car brands (renault, opel, fiat, peugeot...) and in the columns i have two values (yes, no).

I want to create a new grid dinamically with the options that have been choosen in the first grid like "yes". I mean if i choose "renault" and "peugeot" like "yes" in the first grid, in the second i would have two files (renault, peugeot) with 5 columns (with vlues from 1 to 5)

It's not a solution to have hidden grids and show them, because the posible grids number would be 10! and for that is necessary to do it dinamically.

¿is it possible to do it? I am very confused and lost.
I dont know if its possible to do it in the same page, or that the first grid is in the first page, and the second grid in other page.

I am trying to create a new module, and i am trying to recover from database the values from the first grid, but i dont know why it doesnt work, i would need the files in which i have to do changes, how, and which hooks i need to alter to do it.

Thank you very much!!!

Hi,
This is just what I'm looking for, but.... it doesn't seem to work in Drupal 7.
Do you have anu alternatives for drupal 7?

would be great!!

Hi Kosta,

Thanks a lot for the tutorial! I've been trying to make this feature work for a while and your tutorial has got me almost there. I have a site where users can add up to 5 custom equipment configurations and give them each a custom name. The name is input through a CCK field in their profile. When they make a tech support request through Webform, I used your code to generate a custom query showing each of their five configurations as select list options.

Everything works fine until I view the webform the user has submitted and the result is my uid's configuration (with the appropriate key value, no less). I think there's a problem with the handling of the global user variable in this case, but I'm very new to PHP and SQL, so if you could help me out, I'd really appreciate it! Here's my code:

function webform_custom_webform_select_options_info() {
$items = array();

if (function_exists('_webform_custom_get_custom_options')) {
$items['story-nodes'] = array(
'title' => t('Custom options'),
'options callback' => '_webform_custom_get_custom_options',
);
}
return $items;
}

function _webform_custom_get_custom_options() {
global $user;
$sql ="SELECT `field_configname2_value` , `field_configname3_value` , `field_configname4_value` , `field_configname5_value` , `field_configname_value`
FROM `content_type_config_system` , `node` , `users`
WHERE Node.uid = Users.uid
AND ".$user->uid." = Users.uid
AND Node.nid = Content_type_config_system.nid";

$res = db_query($sql);
while($row = db_fetch_array($res)){
$rows = array($row['field_configname_value'], $row['field_configname2_value'], $row['field_configname3_value'], $row['field_configname4_value'], $row['field_configname5_value']);
}
return $rows;
}

?>

Thanks again!

Thanks very much for the tutorial. It was exactly what I needed to create a list of users for given roles on a form.

hii...

You done fabulous work to giving this tutoial but my question is similar to your tutorial i want to do using webform automatically create two dates as per user selection from list box without using jaascript because its will used for my mobile sites project and the issue is jscript not have support to some of mobiles .So i wnt this like e.g in option list box users select Last working day of the month the it should display in 1st date field the date of last working day of current month and in next field the last Work.Day of next month.Same for all days in week except saturday. How i can do that i newbie to drupal i cant even no how to write code..

If any help or idea related to i m veruythankfull to that...

Great tutorial, and it does exactly what I was looking for. Thank you!

A note for anyone else who thinks like me: The second time I tried this, it threw a ton of errors. I'd gotten overzealous in stripping down my view and had removed the link from the title of the node. I think this prevented it from loading the node and figuring out the nid. Putting the link back solved it.

Thanks, great tutorial..it really very helpful to me..

How can I modify story nodes..I need 2 or more story nodes in my different dropdown Webforms..For example I need winter story node for winter option, I need summer story node for summer option.???

Even for a module-newbie like me, this was easy-peasy!
Great and helpful tutorial.

Drupal7-users: simply change line 4 of demo.info to
core = 7.x
and the example .tar file is ready to use in D7 too.

Thank you, Kosta.

Thank you for this nice tutorial! I have some questions, because I dont know anything about php:

1. How can I add more views? I have different views for different webforms and want to add for webform 1 - view 1, webform 2 - view 2 and so on ...

2. in my view I add some different node fields Content: Level | Content: Type | Content: Days | Content: Times
In my view I get "B1 - Intensiv - Monday - 18:30 to 21:00" but in the list it only prints the node titel. How can I change it?

Thanks
Daniel

I've been using your technique here and have discovered one thing. It pertains to exports/downloads of captured form results. You can expect that the assigned 'options callback' function to be called EVERY TIME form result is gathered together to be assembled into a .csv/.xls file. The continuous calls to views_get_view() can get very expensive and RAM consuming. But it's trivial to work around.

function _demo_get_view_options() {
$items = &drupal_static(__FUNCTION__);
if (!isset($items)) {
$view = views_get_view('my_sample_view', true);
$view->execute();
$items = array();

foreach ($view->result as $item) {
$full_node = node_load($item->nid);
$items[$item->nid] = $full_node->title;
}

return $items;
}

With the use of drupal_static(), views_get_view() will get called once for a single page view/result export loop because it's results are cached in memory (this isn't a MySQL/memcache thing in this case).

Let me know what you think.

Hi Kosta,

I know this is a very old post but i have to try it. I've done a similar custom module but I need to set a default value and it seems impossible.

I tried with "defalt_value" "value" "default". It would be like this:

$items['story-nodes'] = array(
'title' => t('Story nodes'),
'options callback' => '_demo_get_story_nodes',
'default value' => '0',
);
but it is not working at all. Do you know hoy to do it?

Thanks!!

Thanks for the article. I'll try to use it for a form on a website... I suppose it is possible to pass answers from previous questions to the view, so it gives a list of options based on it, right?

Cheers