A progress bar widget for FormsAPI

Submitted by Frederic Marand on

Forms API progress barAs a Drupal user, you certainly noticed that update.php displays a nice progress bar to make you wait during its batch operations. And maybe you also noticed theme_progress_bar in the API reference.

The question

Wouldn't it be nice to have that progress bar available as an extended version of markup that would graphically display a value in your forms without stuffing it in a markup element ?

Then you could just use something like:

<?php
/**
 * Sample form using the progress bar widget
 *
 * @return array
 */
function foo_form()
  {
 
$form = array();
 
 
$form[] = array
    (
   
'#type'        => 'fapi_progress_bar',
   
'#title'       => 'FAPI Progress bar',
   
'#description' => 'A FormsAPI progress bar based on theme_progress_bar()',
   
'#value'       => rand(0, 100),
    );

  return
$form;
  }
?>

But is it really complicated to add such a widget to your Drupal forms ? No, it really isn't, since we already have theme_progress_bar to handle the actual formatting, in a version which themes can override. All we need is equip it with some FAPI mechanisms, and all it takes is choosing a name, and using the FAPI themeing mechanism, just like this:

The solution

As a first step, we need to choose a name for this new type. Let's say fapi_progress_bar. And let us suppose we will use the widget #title attribute as the progress bar message, and the normal #value attribute as the progress bar percentage.

We could then go just like this:

<?php
$ret
= theme_progress_bar($element['#value'], $element['#title']);
return
$ret
?>

And this would already work ! But we can do better: Forms API widgets need to output certain predefined classes when some attributes are applied to them, like #required, or when an error condition is declared on them with form_set_error. So let us bring them in with _form_set_class() from form.inc:

<?php
_form_set_class
($element, array('fapi-progress-bar'));
$ret = theme_progress_bar($element['#value'], $element['#title']);
return
$ret
?>

That's better, but what about the #description attribute ? The theme progress bar on its own offers no way to display it, and it has a predefined class, so we can just add it.

<?php
$description
= empty($element['#description'])
  ?
''
 
: '<div class="description">' . $element['#description'] . '</div>' . PHP_EOL;

$ret = "<div>"
 
. theme_progress_bar($element['#value'], $element['#title'])
  .
$description
 
. "</div>\n";
return
$ret;
?>

And there's yet another attribute we need to handle, it's #attributes, and drupal_attributes is the function to format it so we can just add its result to our wrapper div:

<?php
$attributes
= empty($element['#attributes'])
  ?
''
 
: drupal_attributes($element['#attributes']);

$description = empty($element['#description'])
  ?
''
 
: '<div class="description">' . $element['#description'] . '</div>' . PHP_EOL;

$ret = "<div$attributes>"
 
. theme_progress_bar($element['#value'], $element['#title'])
  .
$description
 
. "</div>\n";
return
$ret;
?>

And finally, how do we tell Forms API to use this to interpret our new widget ? It's just a matter of naming this function as a theme function for our new widget:

<?php
/**
 * Dress up theme_progress_bar in FAPI clothing
 *
 * @param array $element
 * @return string
 */
function theme_fapi_progress_bar($element)
  {
 
_form_set_class($element, array('fapi-progress-bar'));

 
$description = empty($element['#description'])
    ?
''
   
: '<div class="description">' . $element['#description'] . '</div>' . PHP_EOL;

 
$attributes = empty($element['#attributes'])
    ?
''
   
: drupal_attributes($element['#attributes']);

 
$ret = "<div$attributes>"
   
. theme_progress_bar($element['#value'], $element['#title'])
    .
$description
   
. "</div>\n";
  return
$ret;
  }
?>

Going further

Of course this is only a sample, to show how you can turn any simple string-generating code into a read-only widget for FormsAPI, à la markup, without allowing it to have children or having a modifiable value. Nothing prevents you from elaborating on the example to create your own widgets, possibly supporting children, unlike this demo.

Also, for better readability, this example does not apply the Drupal coding standards, but your actual code implementing such a widget should typically apply them.

Now, let's consider the next step ?

Anonymous (not verified)

Mon, 2011-01-31 00:34

Hi:

Is this code also good for Drupal 6?

I don't quite understand how I would use/activate the progress bar in my module code...please elaborate...

thanks,
Mona

As presented in this series of three articles, this is just a display widget: it is entirely up to you to refresh it periodically, be it with full page or AJAXified refreshes. It should work mostly the same in D5 and D6, and with few changes on D7.