fieldset | Eric's Drupal Blog

Content tagged with: fieldset

Eric's picture

Creating a multipage form using the Forms API

Recently I've been struggling with creating a multi-step CCK node form in Drupal 5. I was able to implement a solution that used a combination of form_alter and jQuery, but due to the amount of jQuery, it was too slow. I then attempted the same functionality in Drupal 6, but due to the number of changes in the Forms API, I decided to take a step back. Here's my first successful attempt at creating a multi-step form using a Drupal 6 module and the Forms API. The following code adds the ability to jump to any step of the form, save at any point, and dynamically creates steps based on the number of fieldsets. My next step will be transposing this code to work with a CCK node form.

Screenshot:

<?php
function multipage_menu() {
 
$items = array();
   
 
$items['multiStepForm'] = array(
   
'title' => t('Multi Step Form'),
   
'page callback' => 'drupal_get_form',
   
'page arguments' => array('multipage_test_form'),
   
'access arguments' => array('access content'),
  );
   
  return
$items;   
}

function
multipage_test_form(&$form_state) {

 
// enable/disable debugging output
 
$form_state['storage']['#navigation']['debugging'] = false;
   
 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<HR>LOADING FUNCTION: multipage_test_form<HR>";
   
 
// ensure step is set in storage
 
if (empty($form_state['storage']['#navigation']['step'])) {
   
$form_state['storage']['#navigation']['step'] = 1;
  } elseif (
$form_state['storage']['#navigation']['newStep']) {
   
// if the form has been submitted, check for a new step
   
$form_state['storage']['#navigation']['step'] = $form_state['storage']['#navigation']['newStep'];
    unset (
$form_state['storage']['#navigation']['newStep']);
  }
   
 
$form = array();
   
 
// add form fields
 
_multipage_test_form_add_fields($form, $form_state);
   
 
// calculate max number of steps
 
_multipage_test_form_max_steps($form, $form_state);

 
// create tabs
 
_multipage_test_form_create_tabs($form, $form_state);
   
 
// remove form fields per step
 
_multipage_test_form_remove_fields($form, $form_state);

 
// add tabs to form
 
_multipage_test_form_add_tabs($form, $form_state);
   
 
// add buttons to form
 
_multipage_test_form_add_buttons($form, $form_state);
   
  return
$form;
}

function
_multipage_test_form_add_fields(&$form, &$form_state) {
   
 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ADDING FIELDS<hr>";
   
 
// Fieldset A
   
 
$form['fieldset_a'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Fieldset A'),
  );
   
 
$form['fieldset_a']['textfield_a1'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield A1'),
   
'#default_value' => $form_state['storage']['textfield_a1'],
  );

 
$form['fieldset_a']['textfield_a2'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield A2'),
   
'#default_value' => $form_state['storage']['textfield_a2'],
  );

 
$form['fieldset_a']['textfield_a3'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield A3'),
   
'#default_value' => $form_state['storage']['textfield_a3'],
  );
   
 
// Fieldset B
   
 
$form['fieldset_b'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Fieldset B'),
  );
   
 
$form['fieldset_b']['textfield_b1'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield B1'),
   
'#default_value' => $form_state['storage']['textfield_b1'],
  );
   
 
$form['fieldset_b']['textfield_b2'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield B2'),
   
'#default_value' => $form_state['storage']['textfield_b2'],
  );

 
$form['fieldset_b']['textfield_b3'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield B3'),
   
'#default_value' => $form_state['storage']['textfield_b3'],
  );
   
 
// Fieldset C
   
 
$form['fieldset_c'] = array(
   
'#type' => 'fieldset',
   
'#title' => t('Fieldset C'),
  );
   
 
$form['fieldset_c']['textfield_c1'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield C1'),
   
'#default_value' => $form_state['storage']['textfield_c1'],
  );
   
 
$form['fieldset_c']['textfield_c2'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield C2'),
   
'#default_value' => $form_state['storage']['textfield_c2'],
  );

 
$form['fieldset_c']['textfield_c3'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield C3'),
   
'#default_value' => $form_state['storage']['textfield_c3'],
  );

 
// Fields not in a fieldset
   
 
$form['textfield_d1'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield D1'),
   
'#default_value' => $form_state['storage']['textfield_d1'],
  );
   
 
$form['textfield_d2'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield D2'),
   
'#default_value' => $form_state['storage']['textfield_d2'],
  );
   
 
$form['textfield_d3'] = array(
   
'#type' => 'textfield',
   
'#title' => t('Textfield D3'),
   
'#default_value' => $form_state['storage']['textfield_d3'],
  );
  
}

function
_multipage_test_form_max_steps($form, &$form_state) {
   
 
// calculate max stpes if empty
 
if (empty($form_state['storage']['#navigation']['maxSteps'])) {
    
$fieldsetCount = 0;
    
$otherFields = false;
       
   
// loop through form elements
   
foreach ($form as $k => $v) {
     
// check for fieldset
     
if (substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {
       
$fieldsetCount++;
      } else {
       
$otherFields = true;   
      }
    }
       
   
// if there are other fields, increment max steps
   
if ($otherFields) {
     
$fieldsetCount++;
     
$form_state['storage']['#navigation']['otherFields'] = true;
    }
       
   
// add count to storage
   
$form_state['storage']['#navigation']['maxSteps'] = $fieldsetCount;

  }
}

function
_multipage_test_form_create_tabs(&$form, &$form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CREATING TABS<hr>";
   
 
// create list of tabs if not set in storage
 
if (empty($form_state['storage']['#navigation']['tabs'])) {
   
$tabCount = 0;
   
$tabs = array();
   
   
// check for other fields
   
if ($form_state['storage']['#navigation']['otherFields']) {
     
$tabsCount++;
     
$tabs[$tabsCount] = "Start";
    }
       
   
// loop through form items
   
foreach ($form as $k => $v) {
      if (
substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {
       
$tabsCount++;
       
$tabs[$tabsCount] = $v['#title'];
      }
    }
       
   
// add tabs to storage
   
$form_state['storage']['#navigation']['tabs'] = $tabs;   
       
  }
  
}

function
_multipage_test_form_remove_fields(&$form, &$form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<HR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;REMOVING FIELDS<HR>";
   
 
// set fieldset counter based on otherfields
 
if ($form_state['storage']['#navigation']['otherFields']) {
   
$fieldsetCount = 1;
  } else {
   
$fieldsetCount = 0;   
  }
   
 
// loop though form fields
 
foreach ($form as $k => $v) {

   
// ensure this form item is a fieldset
   
if (substr($k,0,9)=='fieldset_' && is_array($v) && $v['#type']=='fieldset') {

     
// increment count of fieldsets
     
$fieldsetCount++;

     
// unset fieldset
     
if ($form_state['storage']['#navigation']['step'] != $fieldsetCount) unset($form[$k]);
       
    } elseif (
is_array($v) && $form_state['storage']['#navigation']['otherFields'] && $form_state['storage']['#navigation']['step']>1) {
     
// unset field
     
unset($form[$k]);
    }
       
  }
   
}

function
_multipage_test_form_add_tabs(&$form, $form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<HR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ADDING TABS<HR>";
   
 
$tabsArray = array();
 
$tabsCount = 0;
   
 
// loop through tabs and create buttons for each one
 
foreach ($form_state['storage']['#navigation']['tabs'] as $k => $v) {
   
$tabsCount++;
   
$tabsArray['tab_'.$k] = array(
     
'#type' => 'submit',
     
'#value' => t($v),
     
'#attributes' => array(
       
'class' => 'tabs' . ($tabsCount == $form_state['storage']['#navigation']['step'] ? ' active' : '')
      ),
    );
  }
   
 
// add tab buttons to beginning of form
 
$form = array_merge($tabsArray, $form);
   
}

function
_multipage_test_form_add_buttons(&$form, &$form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<HR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ADDING BUTTONS<HR>";
   
 
// add previous button
 
if ($form_state['storage']['#navigation']['step']>1) {
   
$form['previous'] = array(
     
'#type' => 'submit',
     
'#value' => t('Previous'),
    );       
  }
   
 
// add next button
 
if ($form_state['storage']['#navigation']['step']<$form_state['storage']['#navigation']['maxSteps']) {
   
$form['next'] = array(
     
'#type' => 'submit',
     
'#value' => t('Next'),
    );       
  }

 
// add save button
 
$form['submit'] = array(
   
'#type' => 'submit',
   
'#value' => t('Save'),
  );
   
}

function
multipage_test_form_validate($form, &$form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>FORM VALIDATED<hr>";

}

function
multipage_test_form_submit($form, &$form_state) {

 
// per debugging
 
if ($form_state['storage']['#navigation']['debugging']) echo "<hr>FORM SUBMITTED<hr>";
   
 
// loop through submitted form values
 
foreach ($form_state['values'] as $k => $v) {
   
// add certain fields to storage
   
if (substr($k, 0, 10)=='textfield_') {
     
$form_state['storage'][$k] = $v;
    }
  }
   
 
// handle button action
 
switch ($form_state['clicked_button']['#value']) {
    case
'Previous':
           
     
// set the state to rebuild
     
$form_state['rebuild'] = TRUE;
           
     
// set new step
     
if ($form_state['storage']['#navigation']['step']>1) {
       
$form_state['storage']['#navigation']['newStep'] = $form_state['storage']['#navigation']['step']-1;
      }
           
      break;
       
    case
'Next':
           
     
// set the state to rebuild
     
$form_state['rebuild'] = TRUE;
           
     
// set new step
     
if ($form_state['storage']['#navigation']['step']<$form_state['storage']['#navigation']['maxSteps']) {
       
$form_state['storage']['#navigation']['newStep'] = $form_state['storage']['#navigation']['step']+1;
      }
           
      break;
       
    case
'Save':
     
// TODO
     
break;
           
   
// tabs
   
default:
           
     
// check if the button value maps to a tab
     
$key = array_search($form_state['clicked_button']['#value'], $form_state['storage']['#navigation']['tabs']);
      if (
$key) {
       
$form_state['storage']['#navigation']['newStep'] = $key;
      }
           
      break;
  }
   
}
?>

gradient spacer
Eric's picture

Creating a CCK multipage form layout using jQuery, part 2

On June 30th, I posted code to split up a a node form into virtual pages of fieldsets. To improve usability even further, I added the functionality to generate tabs for each fieldset and I added a next and previous button (instead of modifying the submit button). Here's the updated code:

<?php
$(document).ready(function(){
   
 
currentPage = 1;
   
 
// get a list of fieldsets
 
fieldsets = mpf_getFieldsets();
   
 
// check if there are any form elements with errors
 
formErrorExists = mpf_checkFormError();
   
  if (
fieldsets.length>1 && !formErrorExists) {

   
// output tabs - start
   
mpf_outputTabs();

   
// hide inactive fieldsets
   
for (i=0; i<fieldsets.length; i++) {
      if (
currentPage != i+1) {
        $(
fieldsets[i]).hide();
      }
    }

   
// hide preview button
   
$('#node-form #edit-preview').hide();

   
// add previous/next buttons
   
mpf_addButtons();

   
// add event for tabs click
   
mpf_tabClick();
       
   
// toggle Li active/inactive class
   
mpf_toggleLiClasses();

  }
   
});

function
mpf_tabClick() {

 
// add event for tabs click
 
$('ul.fieldsetTabs a').click(function(){
       
   
// loop through fieldsets and show/hide
   
for(i=0; i<fieldsets.length; i++) {
           
     
// get classes of fieldset
     
classes = $(fieldsets[i]).attr('class');
           
     
// get first legend html in fieldset
     
legendHtml = $('fieldset.'+classes+' legend:first').html();
           
     
// hide/show fieldsets as necessary
     
if (legendHtml == $(this).html()) {
        $(
fieldsets[i]).show();
       
currentPage = i+1;
      } else {
        $(
fieldsets[i]).hide();
      }
           
     
// toggle button visibility
     
mpf_toggleButtons();
           
    }
       
   
// toggle Li active/inactive class
   
mpf_toggleLiClasses();
       
   
// return false to prevent execution of a tag href
   
return false;
       
  });

}

function
mpf_getFieldsets() {
   
  var
fieldsets = new Array();
   
  $(
'#node-form div.standard fieldset').each(function(i){
       
   
// get a string of classes from fieldset
   
classes = $(this).attr('class');
       
   
// split string of classes on space
   
classesSplit = classes.split(" ");
       
   
// loop through classes
   
for (i=0; i<classesSplit.length; i++) {
     
// if class starts with "group-", add it
     
if (classesSplit[i].substring(0, 6)=='group-') {
       
fieldsets[fieldsets.length] = $(this).get();
      }
    }
  });
   
  return
fieldsets;
   
}

function
mpf_checkFormError() {
  var
formErrorExists = false;
  $(
'#node-form input').each(function(i){
   
classes = $(this).attr('class');
   
classesSplit = classes.split(" ");
    for (
i=0; i<classesSplit.length; i++) {
      if (
classesSplit[i] == 'error') {
       
formErrorExists = true;
      }
     }
   });
   return
formErrorExists;
}

function
mpf_outputTabs() {
 
liHtml = "";
  $(
fieldsets).each(function(index, object){

   
// NOTE: this is not working: $('legend:first', object).html()

   
classes = $(object).attr('class');
   
legendHtml = $('fieldset.'+classes+' legend:first').html();
   
liHtml += "<li><a href='#'>" + legendHtml + "</a></li>";
  });
 
tabsHtml = "<div class='tabs'><ul class='tabs primary fieldsetTabs'>" + liHtml + "</ul></div>";
  $(
'form#node-form').before(tabsHtml);
}

function
mpf_addButtons() {

 
// create html for previous/next buttons
 
buttonPrevious = "<input id='edit-previous' class='form-submit' type='button' value='Previous' />&nbsp;&nbsp;";
 
buttonNext = "<input id='edit-next' class='form-submit' type='button' value='Next' />&nbsp;&nbsp;";
   
 
// add button html to page
 
$('form#node-form div.node-form input#edit-submit').before(buttonPrevious).before(buttonNext);

 
// hide previous button on first page
 
$('#node-form #edit-previous').hide();

 
// add click event to next button
 
$('#node-form #edit-next').click(function(){
       
   
// ensure this is not the last page
   
if (currentPage >= fieldsets.length) return;
       
   
// increment current page
   
currentPage++;
       
   
// hide/show fieldsets
   
for (i=0; i<fieldsets.length; i++) {
      if (
currentPage != i+1) {
        $(
fieldsets[i]).hide();
      } else {
        $(
fieldsets[i]).show();
      }
    }
       
   
// toggle button visibility
   
mpf_toggleButtons();
       
   
// toggle Li active/inactive class
   
mpf_toggleLiClasses();
       
  });

 
// add click event to previous button
 
$('#node-form #edit-previous').click(function(){
       
   
// ensure this is not the first page
   
if (currentPage == 1) return;
       
   
// decrease current page
   
currentPage--;
       
   
// hide/show fieldsets
   
for (i=0; i<fieldsets.length; i++) {
      if (
currentPage != i+1) {
        $(
fieldsets[i]).hide();
      } else {
        $(
fieldsets[i]).show();
      }
    }
       
   
// toggle button visibility
   
mpf_toggleButtons();
       
   
// toggle Li active/inactive class
   
mpf_toggleLiClasses();
       
  });
       
}

function
mpf_toggleButtons() {
 
// show buttons
 
if (currentPage > 1) $('#node-form #edit-previous').show();   
  if (
currentPage < fieldsets.length) $('#node-form #edit-next').show();
   
 
// hide buttons
 
if (currentPage == 1) $('#node-form #edit-previous').hide();
  if (
currentPage == fieldsets.length) $('#node-form #edit-next').hide();
}

function
mpf_toggleLiClasses() {
  $(
'ul.fieldsetTabs li').each(function(i){
    if (
i+1 == currentPage) {
      $(
this).addClass('active');
    } else {
      $(
this).removeClass('active');
    }
  });
}
?>

gradient spacer
Eric's picture

Creating a CCK multipage form layout using jQuery

Adding the following jQuery can simulate a multipage form layout. It works by hiding and showing fieldsets when the page loads and when the user submits the form. This works great for large CCK node types that have many fields and field groups.

<?php
$(document).ready(function(){

  var
fieldsets = new Array();
 
currentPage = 1;
   
 
// get a list of fieldsets
 
$('#node-form div.standard fieldset').each(function(i){
       
   
// get a string of classes from fieldset
   
classes = $(this).attr('class');
       
   
// split string of classes on space
   
classesSplit = classes.split(" ");
       
   
// loop through classes
   
for (i=0; i<classesSplit.length; i++) {
     
// if class starts with "group-", add it
     
if (classesSplit[i].substring(0, 6)=='group-') {
       
fieldsets[fieldsets.length] = $(this).get();
      }
    }
  });
   
 
// check if there are any form elements with errors
 
formErrorExists = false;
  $(
'#node-form input').each(function(i){
   
classes = $(this).attr('class');
   
classesSplit = classes.split(" ");
    for (
i=0; i<classesSplit.length; i++) {
      if (
classesSplit[i] == 'error') {
       
formErrorExists = true;
      }
    }
  });
   
  if (
fieldsets.length>1 && !formErrorExists) {
       
   
// hide preview button
   
$('#node-form #edit-preview').hide();
       
   
// change button?
   
if (currentPage < fieldsets.length) {
      $(
'form#node-form div.node-form input#edit-submit').val('Next');
    }

   
// hide fieldsets
   
for (i=0; i<fieldsets.length; i++) {
      if (
currentPage != i+1) {
        $(
fieldsets[i]).hide();
      }
    }
       
   
// add jquery to submit button
   
$('form#node-form').submit(function(){
           
     
// submit page?
     
if (currentPage >= fieldsets.length) return true;
           
     
// increment current page
     
currentPage++;
           
     
// hide/show fieldsets
     
for (i=0; i<fieldsets.length; i++) {
        if (
currentPage != i+1) {
          $(
fieldsets[i]).hide();
        } else {
          $(
fieldsets[i]).show();
        }
      }
           
     
// change submit button text
     
if (currentPage == fieldsets.length) {
        $(
'form#node-form div.node-form input#edit-submit').val('Submit');
      }
           
      return
false;
           
    });
        
  }
   
});
?>

gradient spacer
Eric's picture

Grouping search results by node type in collapsible fieldsets

This code snippet will group your search results in collapsible fieldsets by node type.

<?php
function MYTHEME_search_page($results, $type) {
 
$html = '<dl class="search-results">';

 
// get a list of node types
 
$nodeTypes = node_get_types();
   
 
// loop through results, group by type
 
$resultTypes = array();
  foreach (
$results as $result) {
   
$resultTypes[$result['node']->type][] = $result;   
  }
   
 
// create fieldsets for each type
 
foreach ($resultTypes as $resultType => $resultTypeResults) {
   
$value = "";
   
// loop through entries
   
foreach ($resultTypeResults as $entry) {
     
$value .= theme('search_item', $entry, $type);
    }
       
   
// add fieldset
   
$html .= theme('fieldset',
      array(
       
'#title' => $nodeTypes[$resultType]->name,
       
'#collapsible' => TRUE,
       
'#collapsed' => FALSE,
       
'#value' => $value,
      )
    );
       
  }
   
 
$html .= '</dl>';
 
$html .= theme('pager', NULL, 10, 0);
   
  return
$html;
}
?>

gradient spacer
Eric's picture

Adding a node preview to the node delete confirmation page

This code snippet will add a collapsible fieldset containing a preview of the node you are attempting to delete.

<?php
function MYMODULE_form_alter($form_id, &$form) {
  if (
$form_id == 'node_delete_confirm') {
   
$form['nodePreviewFieldset'] = array(
     
'#type' => 'fieldset',
     
'#title' => 'Preview',
     
'#weight' => 10,
     
'#collapsed' => TRUE,
     
'#collapsible' => TRUE,
    );
   
   
$form['nodePreviewFieldset']['nodePreview'] = array(
     
'#value' => node_view(node_load(arg(1)), TRUE, FALSE, FALSE)
    );
  }
}
?>

Here's a screenshot:

gradient spacer Syndicate content