All Drupal module developers would probably be aware of Drupal Form API’s form_set_value() method, which allows you to “change submitted form values during the form processing cycle.“
You can use this method in the form validate handler to process a different value in the form submit handler, than what was submitted by the browser. However, remember the new value would be used for processing only, and would not be rendered to the browser if you have not specified a redirect url for the form, and the same form renders again after submission.
Now, here was a big problem for me, while working on the next version of my open source Drupal module, Take Control. In the Admin section of this module’s upcoming version, there is an option to generate new Public/Private key pair to be used by module in async Ajax calls.
I needed to generate new key pairs in the submit handler (not validate handler) of this form, and somehow get the new keys generated to be rendered to the browser instead of the original ones. An extensive Google search threw nothing up. However, a small hint in this comment, was enough to validate that what I wanted can be achieved, albeit with some work.
You can set ‘rebuild’=TRUE on your form state to ask Drupal to build your form from scratch.
What this meant was that I can generate new Key Pair in the submit handler, ask Drupal to rebuild the form, and use the new Pair during the rebuild process, as default values for the form fields.
The last piece in the puzzle (rather second last) was how to decide while form rebuilding, which key pair to use, original one or the new one. The solution was simple here, presence of “rebuild” attribute in the form state with a value of true meant that the new pair was to be used.
The absolutely final part of the solution for the problem here was a bit complex. Remember, after Drupal rebuilt your form, all data submitted by the user would be over-written by default values you specify during rebuilding. This is desirable for the actual fields that you want to change, but highly non-user friendly for other fields, where user submitted data should have persisted.
This obviously meant you would need to copy $form_state to your $form fields manually. And this process needed to be recursive due to the probability of presence of fields having #tree=> TRUE which meant that the submitted data in $form_state would be nested.
So, here’s the step-by-step solution to be above problems (the code has been extracted in bits and pieces from the Take Control module as suitable):
1) You need to overwrite data of some fields in the form’s submit handler.
{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function take_control_generate_new_handler($form, &$form_state) {
/// Other code
$keys = take_control_new_keys($algo, $passphrase);
form_set_value($form[‘key’][‘take_control_private_key’], $keys[‘priv’], $form_state);
form_set_value($form[‘key’][‘take_control_public_key’], $keys[‘pub’], $form_state);
$form_state[‘rebuild’] = TRUE;
drupal_set_message(‘New Public /Private Key Pair generated.’, ‘status’);
}{/syntaxhighlighter}
There are 2 important things to note above, i) we still use form_set_value() to overwrite submitted data in $form_state (this data would be used later for rebuilding the form), and ii) we ask Drupal to rebuild the form by setting: $form_state['rebuild']=TRUE.
2) You need to detect manually in your module_form() hook, whether this was en explicit request for rebuild:
{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function take_control_settings_form(&$form_state = array()) {
/// Other code
$rebuild = FALSE;
if (!empty($form_state[‘rebuild’])) {
$rebuild = $form_state[‘rebuild’];
}
if ($rebuild == TRUE) {
// Trigger recursive state restore. Remember, our values overwritten in $form_state would be used for appropriate fields.
take_control_restore_user_input($form, $form_state);
$form_state[‘rebuild’] = FALSE;
}
return system_settings_form($form);
}{/syntaxhighlighter}
3) Restore the form values recursively. You can simply copy-paste the code below for your purposes, just changing the method names:
{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function take_control_restore_user_input(&$form, &$form_state) {
$values = $form_state[‘values’];
foreach ($form as $key => &$parent) {
if (!empty($parent[‘#type’])) {
if (!empty($parent[‘#tree’]) && $parent[‘#tree’] == FALSE) {
$values = $values[$key];
}
take_control_restore_user_input_recursive($key, $parent, $values);
}
}
}
function take_control_restore_user_input_recursive($key, &$parent, &$values) {
$value = $values[$key];
if (!empty($value)) {
$parent[‘#default_value’] = $value;
}
foreach ($parent as $key => &$child) {
if (is_array($child) && !empty($child[‘#type’])) {
if (!empty($child[‘#tree’]) && $child[‘#tree’] == FALSE) {
$values = $values[$key];
}
take_control_restore_user_input_recursive($key, $child, $values);
}
}
}{/syntaxhighlighter}
Hmmm, well, that is lots of code for what should have been a simple method call in my opinion. But as you know, frameworks have their ways of doing things, and its best to stick to their ways for what you are trying to do, for maintenance and consistency.
Hello!
Code looks nice and I’m trying to implement it in a solution of mine. I get an error however on line 4 of your last code snippet saying:
“Fatal error: Cannot use object of type stdClass as array…”
I’ve been trying to remove the empty() methods and fiddling around a bit but with no success.
I’m using hook_form_alter() to make customisations to the form rather then using hook_form(). Could this be a problem you think?
Thank you very much for your detailed answers and support, I will try your advice as soon as I get a chance. Will report back with a successstory I hope :]
Hi ,
I came across your post. Very helpful, I must say…
But one issue which I am unable to resolve is:
I want to add a javascript to my form fields:
example – $form[‘text_status’] = array(
‘#default_value’ => $node->text_status,
‘#title’ => t(‘Status Text’),
‘#type’ => ‘textfield’,
);
These type of text fields, when they are filled in with some values by the user, then a javascript will automatically show a preview on a right hand block. for ex:
http://papermashup.com/demos/widget-builder/
I have the javascript also:
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″ />
<title>Papermashup.com | jQuery Form Preview Tool</title>
<link href=”../style.css” rel=”stylesheet” type=”text/css” />
<script type=”text/javascript” src=”http://ajax.googleapis.com/ajax/libs/jquery/1.3.0/jquery.min.js”></script>
<script type=”text/javascript”>
$(document).ready(function(){
$(‘#preview’).hide();
$(“#photo”).change(update);
$(“#title”).keyup(update);
});
function update(){
$(‘#preview’).slideDown(‘slow’);
var title = $(“#title”).val();
var photo = $(“#photo”).val();
$(‘#Displaytitle’).html(title);
$(‘#image’).html(‘<img src=”‘+photo+'”/>’);
}
</script>
<style>
.left {
width:400px;
float:left;
font-size:13px;
color:#333;
margin-right:20px;
}
.right {
width:320px;
float:left;
margin-right:20px;
}
#preview {
display:none;
min-height:247px;
background-color:#FFC;
padding:10px;
font-size:12px;
color:#999;
border:1px solid #FF9;
}
#title {
margin-top:10px;
padding:5px;
font-size:13px;
color:#000;
border:1px solid #CCC;
font-family:Verdana, Geneva, sans-serif;
}
#photo {
margin-bottom:10px;
}
#image {
margin-top:5px;
}
#Displaytitle {
font-size:14px;
color:#333;
margin-top:5px;
}
</style>
</head>
<body>
<div id=”header”><a href=”http://www.papermashup.com/”><img src=”../images/logo.png” width=”348″ height=”63″ border=”0″ /></a></div>
<div id=”container”>
<h3>jQuery Form Preview Demo</h3>
<div class=”left”>
<form>
Choose a site<br/>
<select name=”pic” class=”click” id=”photo”>
<option value=”images/photo1.png”>Tweet.me.it</option>
<option value=”images/photo2.png”>Dotdashcreate.com</option>
<option value=”images/photo3.png”>Papermashup.com</option>
</select>
<br/>
Add a Tagline
<textarea id=”title” class=”click” name=”title” cols=”40″ rows=”4″>This is your default advert text. </textarea>
</form>
</div>
<div class=”right”>
<noscript>
This is where you could put a link for users who have JavaScript disabled to see a preview 🙂
</noscript>
<div id=”preview”> This is how your advert will look
<div id=”image”></div>
<div id=”Displaytitle”></div>
</div>
</div>
<div class=”clear”></div>
</div>
<div id=”footer”><a href=”http://www.papermashup.com”>papermashup.com</a> | <a href=”http://papermashup.com/create-a-dynamic-form-preview/”>Back to tutorial</a></div>
<script type=”text/javascript”>
var gaJsHost = ((“https:” == document.location.protocol) ? “https://ssl.” : “http://www.”);
document.write(unescape(“%3Cscript src='” + gaJsHost + “google-analytics.com/ga.js’ type=’text/javascript’%3E%3C/script%3E”));
</script>
<script type=”text/javascript”>
try {
var pageTracker = _gat._getTracker(“UA-7025232-1”);
pageTracker._trackPageview();
} catch(err) {}</script>
</body>
</html>
Thanks
Rajesh
Hello Rahul,
I am trying to add validation my new user registration form.
I had made one module for that.
Now here is my code.
I want to check if the user had only entered the numbers in Phone No text field and Mobile No text field.
My module name is form_intro.
Can you give me suggestion, what i had done wrong?
Hello Rahul,
I’ve come acros your site after searching for a whole day for a fix for my problem.
But when I enter for example; A into my inputbox and click on submit the A remains in the inputbox but the error is shown. I’ve tried several methods but somehow drupal only wants to show the submitted content?
Hi Rahul!
Since i am new to drupal, i have a problem in giving validation for the form fields. here are my two form fields, can u please give me a code which validates the below form field.
$form[‘contact_details_designation’] = array(‘#type’ => ‘textfield’,
‘#title’ => t(‘Designation’),
‘#default_value’ => $node->contact_details_designation,
‘#size’ => 25,
‘#attributes’ => array(‘class’ => ‘stateprogram-contact_details_designation’),
‘#required’ => TRUE,
);
$form[‘contact_details_telephone’] = array(‘#type’ => ‘textfield’,
‘#title’ => t(‘Telephone’),
‘#default_value’ => $node->contact_details_telephone,
‘#size’ => 25,
‘#attributes’ => array(‘class’ => ‘stateprogram-contact_details_telephone’),
‘#required’ => FALSE,
);
where i have to check whether
1. Designation field should be in uppercase
2. Telephone should be only numbers
I did not quite understand … seems missing AJAX part …
Amazing Post! Thank you very much for sharing this.
Thank you for sharing. I learned a lot from the post and the comments. As for me, when I learned Drupal I used this book: Learning Drupal 6 Module Development. It is really a great book to learn and practise Drupal.
Hello, I want to say that your blog is providing a lot of usefull informations.
I’m trying to do the samething explained in this post, but I’m using Drupal 7. I’m not able to make it work.
In fact, the values that are changed in the submit handler with the form_set_value function are rendered with the user input value. When a do a print_r() of the form_state[‘values’], I see that my values was changed with your recursive function !
Any idea ? Thanks in advance !
Regards,
Ben
Can you please answer my serious query here: http://drupal.stackexchange.com/questions/19313/form-behavior-when-form-creation-data-changes/19338#19338
$form_state[‘rebuild’] = TRUE;
before:
form_set_error(‘…’);
works well for updating the user submitted values.
Thanks
In Drupal 7, I’m just unset($form_state[‘input’][‘field_name’]);
PS. I’m not sure is it the proper method ^___^
I have managed to enforce the logic and display messages correctly by using another hook. The form was produced by the Ubercart module which has a range of other hooks. These seem to take precedence and once my code was inserted in uc_cart_view_form_alter hook, then things became clearer. When the cart in UC updates, if there are validation errors, the newly inserted values are ignored and the previous ones are used. I suppose this happens because the shopper can checkout at any point (i.e. without the cart form validation) and therefore the old values are more likely to be correct. I hope this makes sense.
Hi,
Thanks to share this.
Hi, I am trying to rebuild a form in Drupal 7. I know its ‘form_build_id’ (e.g. form-_eNZLqaZWy7-6kYuGma5Cxg1Gru7Tmr9W2tsofSJxSg)
I defined this hook_menu and menu callback function:
/**
* Implements hook_menu().
*/
function set_menu_menu() {
$items[‘admin/structure/set_menu/rebuild/%’] = array(
‘title’ => ‘Rebuild’,
‘page callback’ => ‘set_menu_ajax_rebuild’,
‘page arguments’ => array(4),
‘access callback’ => TRUE,
);
return $items;
}
function set_menu_ajax_rebuild($form_build_id) {
$form_state = form_state_defaults();
$form = form_get_cache($form_build_id, $form_state);
$form_state[‘no_redirect’] = TRUE;
// @see drupal_rebuild_form()
// I would like to increase number of elements in my form
$form_state[‘set_menu’][‘count’]++;
$form_state[‘rebuild’] = TRUE;
drupal_process_form($form[‘#form_id’], $form, $form_state);
}
I would like to rebuild a form by hitting url like this http://www.example.com/admin/structure/set_menu/rebuild/form-mQVd8FeimUA… – this url displays no error but it does not rebuild my form.
The code never rebuilds any form. Is it possible to rebuild a form this way?
Thank you.
Can you please help me? How to submit (save) a text box filed/combo box field(that we placed in template by simple html) data to drupal database field.
in our profile edit form template, we have put 2 html combo box with data for country and state,
Also created fields for country and state in drupal user table.
But we didn’t get the way to solve these values to corresponding fields when form submit.
I have a form having two dropdowns. I have written some custom validation also on this form.
I have two department
Dropdown 1( taxonomy)
Dropdown2 ( need to filter user list dependent on dropdown 1)
I have added ajax request to dropdown 1 as below.
$form[“field_dropdown1”][‘und’][‘#ajax’] = array(
‘event’=>’change’,
‘callback’ =>’sandbox_ajax_dropdown_doctor_list’,
‘wrapper’ => ‘edit-field-dropdown2’,
‘method’ => ‘replace’,
‘effect’ => ‘fade’,
);
dropdown1 and dropdown 2 are existing form elements so I can edit the field only. No new field creation.
Now problem is when I changing the dropdown I got ajax error related to sql syntax.
I have written custom sql query in form validation which take dropdown2 value as parameter.
Now I am unable to understand why i is happening.
Below is my total code.
Please suggest me anything which is good.
Code….
<?php
function hms_frm_form_alter(&$form, &$form_state, $form_id) {
dsm($form);
if($form_id == “appointment_node_form”){
global $user;
$_SESSION[‘patientId’] = $form[‘field_patient_id’][‘und’][0][‘nid’][‘#default_value’];
//drupal_set_message(arg(1));
if(arg(0) == “node” && arg(1) == “add”){
//$form[“title”][“#access”] = FALSE;
$title = “Appointment”.arg(3).strtotime(date(“Y-m-d H:i:s”));
drupal_set_title($title);
$form[‘title’][‘#default_value’] = $title;
$form[“field_remark”][“#access”] = FALSE;
$form[“field_appointment_status”][“#access”] = FALSE;
$form[“field_pathological_test”][“#access”] = FALSE;
//$form[“field_doctor”][‘und’][‘#attributes’][‘disabled’] = TRUE;
$form[“field_department”][‘und’][‘#ajax’] = array(
‘event’=>’change’,
‘callback’ =>’sandbox_ajax_dropdown_doctor_list’,
‘wrapper’ => ‘edit-field-doctor’,
‘method’ => ‘replace’,
‘effect’ => ‘fade’,
);
$options = array(‘- Select Doctor -‘);
if (isset($form_state[‘values’][‘field_department’])) {
$form[“field_doctor”][‘und’][‘#attributes’][‘disabled’] = FALSE;
// Pre-populate options for city dropdown list if province id is set
$options = _load_doctor($form_state[‘values’][‘field_department’]);
}
$form[‘field_doctor’][‘und’][0][‘#options’] = $options;
$form[‘field_doctor’][‘und’][‘#default_value’] = isset($form_state[‘values’][‘field_doctor’][‘und’]) ? $form_state[‘values’][‘field_doctor’][‘und’] : ”;
//$form[‘#process’][] = “ajax_process_form”;
//$form[‘#processed’] = FALSE;
$form[‘#executes_submit_callback’]= FALSE;
$form[‘#validate’][] = ‘hms_frm_node_appointment_form_validate’;
$form[‘#submit’][] = ‘hms_frm_node_appointment_form_submit’;
//$form[‘#after_build’][] = ‘hms_frm_after_build’;
}
if(arg(0) == “node” && arg(2) == “edit”){
$form[“title”][“#access”] = FALSE;
// drupal_set_title(“Add Remark”);
if(in_array(‘Doctor’, $user->roles)){
//if($user->roles == “Doctor”){
//hide($form[“field_department”]);
$form[“field_patient_id”][“#access”] = FALSE;
$form[“field_appointment_date”][“#access”] = FALSE;
$form[“field_department”][“#access”] = FALSE;
$form[“field_doctor”][“#access”] = FALSE;
$form[“field_appointment_status”][“#access”] = FALSE;
$form[“field_time_slot”][“#access”] = FALSE;
$form[‘field_appointment_status’][‘und’][‘#default_value’] = “Attended”;
}
//$form[‘actions’][‘submit’][‘#submit’][] = ‘appointment_node_form_submit’;
}
}
if($form_id == “patient_node_form”){
$form[‘actions’][‘submit’][‘#submit’][] = ‘patient_node_form_submit’;
}
return $form;
}
/**
* Function for handling ajax request
*/
function sandbox_ajax_dropdown_doctor_list($form, $form_state) {
// Return the dropdown list including the wrapper
return $form[‘field_doctor’];
}
/**
* Function for populating city
*/
function _load_doctor($department_id) {
$doclist = array(‘- Select Doctor -‘);
$queryDoctor = “SELECT fd.field_department_tid AS DepartmentId ,
users.uid, users.name, ur.rid
FROM users INNER JOIN users_roles AS ur
ON
users.uid = ur.uid
INNER JOIN field_data_field_department AS fd
ON
users.uid = fd.entity_id
WHERE
fd.entity_type = ‘user’ AND
ur.rid = 12 AND
fd.field_department_tid = “. $department_id;
$queryDoctorResult = db_query($queryDoctor);
while($doctor = $queryDoctorResult->fetch()){
// Key-value pair for dropdown options
$doclist[$doctor->uid] = $doctor->name;
}
return $doclist;
}
//function appointment_node_form_submit($form, &$form_state) {
// $form_state[‘redirect’] = ‘admin/patient-history/’.$_SESSION[‘patientId’];
//unset($_SESSION[‘patientId’]);
//}
function patient_node_form_submit($form, &$form_state) {
$form_state[‘redirect’] = ‘admin/patients’;
}
/*
* Validate form submissions, handling extra values
*/
function hms_frm_node_appointment_form_validate($form, &$form_state) {
$slot = $form_state[‘values’][‘field_time_slot’][‘und’][0][‘tid’];
$date = $form_state[‘values’][‘field_appointment_date’][‘und’][0][‘value’];
$doctor = $form_state[‘values’][‘field_doctor’][‘und’][0][‘uid’];
$timestamp = strtotime($date);
$date = date(‘Y-m-d’, $timestamp);
//drupal_set_message($doctor);
//drupal_set_message($slot);
$slot = mysql_escape_string($slot);
$date = mysql_escape_string($date);
$doctor = mysql_escape_string($doctor);
// Total Appointments
if(isset($slot) && isset($doctor) ){
$query = “SELECT COUNT(app.entity_id) AS appointments
FROM
field_data_field_appointment_date AS app
INNER JOIN field_data_field_time_slot AS ts
ON
app.entity_id = ts.entity_id AND
app.entity_type = ts.entity_type
INNER JOIN field_data_field_doctor AS fd
ON
app.entity_id = fd.entity_id AND
app.entity_type = fd.entity_type
INNER JOIN field_data_field_appointment_status AS status
ON
status.entity_id = app.entity_id AND
status.entity_type = app.entity_type
WHERE
app.field_appointment_date_value LIKE ‘%”.$date.”%’ AND
ts.field_time_slot_tid = “. $slot .” AND
fd.field_doctor_uid = “. $doctor .” AND
status.field_appointment_status_value = ‘Open’ “;
drupal_set_message($query);
$queryResult = db_query($query);
$totalAppointment = $queryResult->fetch();
$appointments = $totalAppointment->appointments;
// Max appointment and Available Appointment in the selected Taxonomy
$queryMA = “SELECT max_allo.field_maximum_allocation_value AS MA
FROM
field_data_field_maximum_allocation AS max_allo
WHERE
max_allo.entity_id = “.$slot.” AND
max_allo.entity_type = ‘taxonomy_term’ “;
$result = db_query($queryMA);
$maxAllocation = $result->fetch();
$max = $maxAllocation->MA;
if(($appointments > $max) OR ($appointments == $max) ){
//Form Submit Fail
form_set_error(‘field_time_slot’, t(‘This slot is full. Please select another slot/date.’));
}
}
}
function hms_frm_node_appointment_form_submit($form, &$form_state) {
$slot = $form_state[‘values’][‘field_time_slot’][‘und’][0][‘tid’];
$term = taxonomy_term_load($slot);
$slotName = $term->name;
$date = $form_state[‘values’][‘field_appointment_date’][‘und’][0][‘value’];
$timestamp = strtotime($date);
$date = date(‘Y-m-d’, $timestamp);
}
//End
Thanks in advance.