background image

Content tagged with: form_alter

Eric's picture

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;
  }
   
}
?>

Eric's picture

Here's a code snippet that ensures the submitted title is unique across all nodes.

<?php
function MYMODULE_form_alter($form_id, $form) {
  if (
substr($form_id, -10)=='_node_form') {
   
// add custom form validation function
   
$form['#validate'] = array_merge(array('_MYMODULE_helper_validate' => array()), $form['#validate']);
  }
}

function
_MYMODULE_helper_validate($form_id, $form_values, $form) {
 
// ensure title is unique
 
if (strlen($form_values['title'])) {
   
// check for unique title
   
$sql = "select distinct title from {node} where 1=1 ";
   
// don't want to include current title
   
if ($form_values['nid']) $sql .= "and nid != '" . db_escape_string($form_values['nid']) . "'";
       
   
$resource = db_query($sql);
   
$titles = array();
    while(
$result = db_fetch_array($resource)) $titles[] = $result['title'];
   
    if (
in_array($form_values['title'],$titles))
     
form_set_error('title', $form['title']['#title'] . ' has already been submitted.');
  }
}
?>

Eric's picture

Here is a quick code snippet that enables you to add your own custom validation to a CCK node type form.

<?php
function MYMODULE_form_alter($form_id, &$form) {
 
// create a list of node types that require custom validation
 
$nodeTypes = array(
   
'MYNODETYPE1',
   
'MYNODETYPE2',
  );
   
 
// check if the form id is a node form, and if it exists in the above array
 
if (substr($form_id,-10)=='_node_form' && in_array(substr($form_id, 0, strlen($form_id)-10), $nodeTypes)) {
   
$form['#validate'] = array_merge(array('_MYMODULE_node_form_validate' => array()), $form['#validate']);
  }
}

function
_MYMODULE_node_form_validate($form_id, $form_values, $form)
{
 
// DEBUG: use print_r($form_values) to see what has been submitted
  // TODO: check for errors here, and use form_set_error() as necessary

  // for example:
 
if ($form_values['MYFIELD'][0]['value'] == 'SOMETHING NAUGHTY') {
   
form_set_error('MYFIELD', 'MY CUSTOM ERROR MESSAGE');
  }

}
?>

Eric's picture

Here's a quick way to modify the Drupal search form using a few lines of jQuery. Normally, I would implement a form_alter hook, but let's start simple in this example:

<?php
$(document).ready(function(){
 
// set the text of the search button
 
$('div#search input#edit-submit').attr('value','GO');

 
// add some default verbiage to the text input and have it disappear when the user focuses on it
 
var searchString = 'Search...';
  $(
'div#search input#edit-search-theme-form-keys').val(searchString);
  $(
'div#search input#edit-search-theme-form-keys').focus(function(){
    if ($(
this).val()==searchString) $(this).val('');
  });
});
?>


Update: Here's an updated/alternative version of this code snippet which also has a blur() event:

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

 
// set the text of the search button
 
$('form#search-block-form input#edit-submit').attr('value','GO');

 
// add some default verbiage to the text input and have it disappear when the user focuses on it
 
var searchString = 'Search...';
   
 
// set input value (text)
 
$('form#search-block-form input#edit-search-block-form-keys').val(searchString);
   
 
// focus
 
$('form#search-block-form input#edit-search-block-form-keys').focus(function(){
    if ($(
this).val()==searchString) $(this).val('');
  });
   
 
// blur
 
$('form#search-block-form input#edit-search-block-form-keys').blur(function(){
    if ($(
this).val()=='') $(this).val(searchString);
  });
   
});
?>

Eric's picture

Here is a code snippet to redirect the user back and forth from HTTP to HTTPS. This example shows how to transfer node/add forms in SSL.

Adding code to the menu hook to ensure it's executed before anything else:

<?php
function MYMODULE_menu() {
 
// set a variable to keep track if something in the page should be sent in ssl
 
variable_set('MYMODULE_ssl', FALSE);
 
// ...code...
 
$items = array();
 
// ...code...
 
return $items;
}
?>

Adding code to the form_alter hook, to check if certain forms should be sent in SSL

<?php
function MYMODULE_form_alter($form_id, &$form) {
 
// ...code...
  // check if form should be sent in ssl
 
if (substr($form_id, -10) == '_node_form') {
   
variable_set('MYMODULE_ssl', TRUE);
  }
 
// ...code...
}
?>

The guts of the code are added to a function that's called from the theme:

<?php
function _MYMODULE_check_ssl($enableSSL = FALSE, $messages = NULL) {

 
// check for ssl enabled
 
$sslOn = ($_SERVER['HTTPS'] == 'on' ? TRUE : FALSE);
   
 
// check if the page needs to be redirected
 
if ($sslOn != $enableSSL) {
       
   
// if there are drupal messages, set them to the session.
    // when the page is redirected, the messages will be returned to the theme
   
if (strlen($messages)) variable_set('MYMODULE_ssl_messages', $messages);
       
   
// create a new URL
   
$newURL = 'http' . ($sslOn ? '' : 's') . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
       
   
// redirect user
    // drupal_goto?
   
header("Location: $newURL");
    die;
  }
   
 
// check if there is a message stored in the session
 
$messages = variable_get('MYMODULE_ssl_messages', NULL);
  if (
strlen($messages)) {
       
   
// unset message in session
   
variable_set('MYMODULE_ssl_messages', NULL);
       
   
// return message to theme
   
return $messages;
  }

}
?>

Lastly, I added a line to the top of the page.tpl.php file in my theme:

<?php
$messages
.= _MYMODULE_check_ssl( variable_get('MYMODULE_ssl', FALSE), $messages );
?>

Also, check out this page to see how to change links from http to https using jQuery.