Theming forms: system_themes

Last modified: February 19, 2008 - 03:55

This function demonstrates how to make use of multiple checkboxes, theming of form elements in a table, and a seperate theming function which allows for rendering of the table and inline HTML elements.

Before

<?php
function system_themes() {
 
system_listing_save();
 
$form = system_theme_listing();
 
$form .= form_submit(t('Save configuration'));
  print
theme('page', form($form));
}

function
system_theme_listing() {
 
$themes = system_theme_data();
 
ksort($themes);

  foreach (
$themes as $info) {
   
$info->screenshot = dirname($info->filename) . '/screenshot.png';
   
$row = array();

   
// Screenshot column.
   
$row[] = file_exists($info->screenshot) ? theme('image', $info->screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', 'class="screenshot"', false) : t('no screenshot');

   
// Information field.
   
$row[] = "<strong>$info->name</strong><br /><em>" . dirname($info->filename) . '</em>';

   
// enabled, default, and operations columns
   
$row[] = array('data' => form_checkbox('', 'status]['. $info->name, 1, $info->status), 'align' => 'center');
   
$row[] = array('data' => form_radio('', 'theme_default', $info->name, (variable_get('theme_default', 'bluemarine') == $info->name) ? 1 : 0), 'align' => 'center');
    if (
function_exists($info->prefix . '_settings') || function_exists($info->prefix . '_features')) {
     
$row[] = array('data' => l(t('configure'), 'admin/themes/settings/' . $info->name), 'align' => 'center');
    }
    else {
     
$row[] = '';
    }
   
$rows[] = $row;
  }

 
$header = array(t('Screenshot'), t('Name'), t('Enabled'), t('Default'), t('Operations'));
 
$output = form_hidden('type', 'theme');
 
$output .= theme('table', $header, $rows);
  return
$output;
}
?>

After

<?php
function system_themes() {
 
$themes = system_theme_data();
 
ksort($themes);

  foreach (
$themes as $info) {
   
$info->screenshot = dirname($info->filename) . '/screenshot.png';
   
$screenshot = file_exists($info->screenshot) ? theme('image', $info->screenshot, t('Screenshot for %theme theme', array('%theme' => $info->name)), '', array('class' => 'screenshot'), false) : t('no screenshot');

   
### Note both the use of '#markup' to drop the screenshot info into the form, and
    ### the grouping of the screenshot/description under the theme name in the form array
   
$form[$info->name]['screenshot'] = array('#type' => 'markup', '#value' => $screenshot);

   
### Use of a form item.  Notice how '#value' is used, because the value is not editable
    ### by a user
   
$form[$info->name]['description'] = array(
     
'#type' => 'item',
     
'#title' => $info->name,
     
'#value' => dirname($info->filename),
    );

   
### Here the options array for all checkboxes is built.  Notice that the theme name is used
    ### for the key--this will be important later when the form is themed
   
$options[$info->name] = '';

   
### Here the status array is built conditionally--only checkboxes that are checked are
    ### added to this array.  Notice in this array the theme name is put in the element's value,
    ### unlike the $options array
   
if ($info->status) {
     
$status[] = $info->name;
    }
    if (
$info->status && (function_exists($info->prefix . '_settings') || function_exists($info->prefix . '_features'))) {

     
### Note that links can also be included in a markup element.  Markup can hold any
      ### kind of markup that needs to get into the form
     
$form[$info->name]['operations'] = array(
       
'#type' => 'markup',
       
'#value' => l(t('configure'),
       
'admin/themes/settings/' . $info->name),
      );
    }
    else {
     
// Dummy element for form_render. Cleaner than adding a check in the theme function.
     
$form[$info->name]['operations'] = array();
    }
  }

 
### Now that all checkbox options have been built, and all checked boxes are know, the
  ### checkboxes element can be declared.  Notice that the $status array is dropped directly
  ### into '#default_value'
 
$form['status'] = array(
   
'#type' => 'checkboxes',
   
'#options' => $options,
   
'#default_value' => $status,
  );

 
### Radio button groups are built the same basic way, using the same $options array in
  ### this case
 
$form['theme_default'] = array(
   
'#type' => 'radios',
   
'#options' => $options,
   
'#default_value' => variable_get('theme_default', 'bluemarine')
  );

 
### Notice that the two submit buttons are grouped under a 'buttons' group in the form array
  ### This will be important when we examine the execute function
 
$form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
 
$form['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset to defaults') );

 
### Drop the form array into the master form function
 
return drupal_get_form('system_themes', $form);
}
?>

In this case, the theming of the form is fairly complex, so a custom theming function is used. Custom theme functions are declared by prepending theme_ to the form_id of the form you wish to theme. The single arg is the constructed form array.

<?php
function theme_system_themes($form) {

 
### The constructed form array has a number of internal record-keeping elements, so directly
  ### looping through the array would result in errors.  The element_children function
  ### extracts only those form elements that have a value.
 
foreach (element_children($form) as $key) {

   
### Here the table rows are constructed using the previous convention of a rows array.
    ### Notice that there is a check to make sure that the particular form array element
    ### is has valid theme info in it, otherwise an empty row is added
   
$row = array();
    if (
is_array($form[$key]['description'])) {

     
### In order to manually render a portion of the form, form_render is called.  It's
      ### single argument is the section of the form array that is to be rendered.  Note
      ### that form_render is recursive--it will render all form array elements in the portion
      ### of the array that you declare.
     
$row[] = form_render($form[$key]['screenshot']);
     
$row[] = form_render($form[$key]['description']);

     
### $form['status'] is the checkboxes element.  Notice that by using $form['status'][$key],
      ### only the checkbox for the current theme $key gets rendered.  If the rendered checkbox
      ### has a matching value in the above created $status array (which was passed to the
      ### checkboxes element), then it will be rendered as a checked box
     
$row[] = array('data' => form_render($form['status'][$key]), 'align' => 'center');
      if (
$form['theme_default']) {

       
### The radio buttons are rendered using the same logic as the checkboxes
       
$row[] = array('data' => form_render($form['theme_default'][$key]), 'align' => 'center');
       
$row[] = array('data' => form_render($form[$key]['operations']), 'align' => 'center');
      }
    }
   
$rows[] = $row;
  }

 
### Now the table is created using the usual theme_table approach
 
$header = array(t('Screenshot'), t('Name'), t('Enabled'), t('Default'), t('Operations'));
 
$output = theme('table', $header, $rows);

 
### The rendering code remembers which form elements have already been rendered--therefore,
  ### to render any remaining elements (in this case the submit buttons), simply call form_render
  ### using the entire form array, and only unrendered elements will be rendered.  It's good
  ### practice to always end with this, in case other modules may have used form_alter to
  ### include additional form elements
 
$output .= form_render($form);

 
### Finally, the constructed output is returned in the standard fashion.
 
return $output;
}
?>

A custom submit function also exists for this form. Note that the form is executed before it is themed/displayed.

<?php
function system_themes_submit($form_id, $values) {

 
db_query("UPDATE {system} SET status = 0 WHERE type = 'theme'");

 
### $_POST['op'] can be examined just as before to determine which button was pressed
 
if ($_POST['op'] == t('Save configuration')) {
    if (
is_array($values['status'])) {

     
### Only those checkboxes that were checked are returned in the processed form array
     
foreach ($values['status'] as $key => $choice) {
        if (
$choice) {
         
// If theme status is being set to 1 from 0, initialize block data for this theme if necessary.
         
if (db_num_rows(db_query("SELECT status FROM {system} WHERE type = 'theme' AND name = '%s' AND status = 0", $key))) {
           
system_initialize_theme_blocks($key);
          }
         
db_query("UPDATE {system} SET status = 1 WHERE type = 'theme' and name = '%s'", $key);
        }
      }
    }

   
### Likewise, only the selected radio button's value is available in the processed form,
    ### so it can be used to set the default
   
variable_set('theme_default', $values['theme_default']);
  }
  else {
   
variable_del('theme_default');
  }

 
drupal_set_message(t('The configuration options have been saved.'));

 
### Redirecting back to the page, which will reload the form with the updated data
 
drupal_goto('admin/themes');
}
?>

 
 

Drupal is a registered trademark of Dries Buytaert.