People who have ever tried to use standard hook_form_alter() tricks with CCK fields are easily recognizable: there are huge bald spots where large chunks of their hair used to be, and dark strings of swear words often erupt from the corners where they're working.

The basic reason for this is that CCK fields are "special," since they essentially set up templates for building form fields. Very meta. If you look at a CCK field in hook_form_alter() you'll see a structure kind of like this:

CCK field in hook_form_alter()

A couple of things of note here:

  1. While the form field itself ($form['field_district_number']) contains Form API-like properties such as #theme, #title, #required, etc. the "real" Form API element is actually under the "0" index, since it's the only one with the required #type property.
  2. Even though this field eventually gets rendered as a textfield, at this point $form['field_district_number'][0]['#type'] contains 'number' rather than 'textfield'. Hmmm.... curious!
  3. If you're trying to do anything with radios, checkboxes, or similar, you'll also notice that required properties such as #options do not exist yet, in either place. Those come later.
  4. Pay close attention to that non-descript #columns property: that dictates where the information entered into the form gets saved.

What's up with that? Well, these things all get populated during the CCK form element's "#process" phase, which happens after the basic $form array is built up.

So if you want to dig in here and start monkeying with the "real" properties, you need to come in after the point that the CCK form elements are processed. Luckily, Form API has a phase designed for exactly this purpose: #after_build.

/**
 * Implementation of hook_form_alter().
 */
function example_form_alter(&$form, $form_state, $form_id) {
  // Check for a particular content type's node form.
  if ($form_id == 'example_node_form') {
    // Add an after_build function to process when everything's complete.
    $form['#after_build'][] = 'example_after_build';
  }
}

/**
 * Modify CCK form elements on the example node form.
 */
function example_after_build($form, &$form_state) {
  // TODO: Stuff!

  return $form;
}

Ok, now let's compare a dsm($form) in the #after_build stage with the one from before:

after-build-cck-field.png

Well. There's certainly lots more stuff here now. :P A couple of things of note:

  1. The "number" widget definition we saw before is still there, but now has several additional properties in it, which get added when the #process phase executes.
  2. The key one is this new $form['field_district_number'][0]['value'] property, which finally contains a copy of the Form API element that will actually be rendered: #type => textfield. Pay attention: If you need to make any cosmetic changes to the form (#disabled, #type, etc.), you'll do so here.
  3. There's also this non-descript $form['field_district_number'][0]['#value'] property, which holds the value that will be sent to CCK. If your intent is to make any value changes to the form, you'll do so here.
  4. Note that the name of these properties depends on what's in that #columns property we saw earlier. For node reference fields, for example, this will be 'nid' and not 'value'.
  5. Randomly, I haven't quite figured out why yet, it seems you need to alter $form_state['values']['field_name'][0] in addition to the form element's 'value' property. I haven't quite figured out why that's needed...

Here's an example of this in action.


function example_after_build($form, &$form_state) {
  // Adjust the "cosmetic" #maxlength property of a CCK field.
  // This means affecting the $form['CCK_FIELD_NAME'][0]['value'] version.
  $form['field_qty'][0]['value']['#maxlength'] = 1;

  // Hard-code the value of a numeric textfield.
  // This requires affecting <em>both</em> versions of the field.
  $form['field_voicemail'][0]['value']['#value'] = $voicemail;
  $form['field_voicemail'][0]['#value']['value'] = $voicemail;

  // Hard-code the value of a text field.
  // For whatever reason, this requires affecting the Form API element
  // <em>and</em> the value in $form_state['values']. Hm...
  $form['field_order_email'][0]['value']['#value'] = $user->mail;
  $form_state['values']['field_order_email'][0]['value'] = $user->mail;

  return $form;
}

No, you didn't read that wrong. Sometimes you need to set both ['value']['#value'] and ['#value']['value']. And other times you need to change the field value in $form_state['values']. It seems to be that one controls the value displayed on the form, and the other affects the value sent to the database. You need both to avoid NULL values and "Value is required for field blah blah blah" errors.

If anyone can shed some light on what the heck is going on here, that would be awesome. ;P

Comments

mikeytown2’s picture

http://drupal.org/project/tabs

Or if you need to do it the wrong way; you can do it with JavaScript.

/**
* Implementation of hook_form_alter().
*/
function wrong_form_alter(&$form, $form_state, $form_id) {
  // evil node add/edit page
  if ($form_id == 'evil_node_form') {
    drupal_add_js(drupal_get_path('module', 'wrong') . '/wrong-evil-form-tabs.js', 'module', 'footer');
    drupal_add_js(drupal_get_path('module', 'tabs') . '/ui.core.js');
    drupal_add_js(drupal_get_path('module', 'tabs') . '/ui.tabs.js');
    drupal_add_js(drupal_get_path('module', 'tabs') . '/tabs.js');
    $settings = array(
      'tabs' => array(
        'slide' => FALSE,
        'fade' => FALSE,
        'speed' => 'immediate',
        'auto_height' => FALSE,
        'next_text' => 'next',
        'previous_text' => 'previous',
        'navigation_titles' => 0
      ),
    );
    drupal_add_js($settings, 'setting');
    drupal_add_css(drupal_get_path('module', 'tabs') . '/drupal-tabs.css');
  }
}

wrong-evil-form-tabs.js file inside the "wrong" module

// Build Tab Structure
$('.standard').prepend('<div id="tabs-tabset" class="drupal-tabs js-hide"></div>');
$('#tabs-tabset').append('<div class="description"></div>');
$('#tabs-tabset').append('<ul class="clear-block"></ul>');

...

// Place these elements inside a div with a class of tab1
$('#edit-title-wrapper').wrap('<div class="tab1"></div>');
$('.group-a').wrap('<div class="tab1"></div>');
$('.group-b').wrap('<div class="tab1"></div>');
$('.group-c').wrap('<div class="tab1"></div>');
$('.group-d').wrap('<div class="tab1"></div>');
$('#edit-taxonomy-tags-9-wrapper').parent().wrap('<div class="tab1"></div>');
$('#edit-domain-site-wrapper').parent().wrap('<div class="tab1"></div>');
$('#edit-field-free-form-0-value-wrapper').wrap('<div class="tab1"></div>');
$('#edit-field-free-form-0-format-1-wrapper').parent().wrap('<div class="tab1"></div>');

// Place Content in single div & add to tabs
$('.tab1').wrapAll('<div id="tabset-tab-1" class="tabs-tabset"></div>');
$('#tabset-tab-1').appendTo('#tabs-tabset');
// Create Tab Selector
$('#tabs-tabset ul.clear-block').append('<li class="tabset-tab-1"><a href="#tabset-tab-1"><span class="tab">My Title</span></a></li>');


...
...


// Redo layout
$('#sidebar-right').remove();
$('#sidebar-left').remove();
$('#content').css('margin', '0px').css('width', '960px');
$('#content-inner').css('margin', '0px').css('width', '960px');

Simply repeat the above JS but replace tab1/tab-1 with tab2/tab-2 for another tab. Also make sure your css selectors in jQuery are correct. Using this you can place anything anywhere... be careful. Also the js has to be at the footer, but before the pages onload fires; so it's best to keep the first drupal_add_js pointing to the footer.

If you want the navigation at the bottom of the tab, add this class (tabs-navigation) to $('.standard').prepend('<div id="tabs-tabset" class="drupal-tabs js-hide"></div>');.

aaron1234nz’s picture

CCK does not provide enough information to easily modify fields within fieldsets (without prior knowledge of which fields are in which fieldsets). If you know which fields you wish to alter but do not which fieldsets they are in then walking the $form array is the solution. Below is a set of functions that will alter cck fields, regardless of if they are in a fieldset or not.

/**
 * Implementation of hook_form_alter
 */
function imis_node_manager_form_alter(&$form, $form_state, $form_id) {
    $form['#after_build'][] = 'my_module_cck_alter';
}

/**
 * #after_build function to modify CCK fields
 */
function my_module_cck_alter($form, &$form_state) {
  $cck_fields = array('field_one', 'field_two')
  my_module_cck_walker($form, $cck_fields);
  return $form;
}

/**
 * Recursively walk down the $form array looking for the CCK elements to modify
 * @param $array array
 *     the $form array or piece of the form array when called recursivly (must be passed by reference)
 * @param $cck_fields array
 *    array of cck fields to mark as read only
 * @return
 *    All modification are made by reference.
 */
function my_module_cck_walker(&$array, &$cck_fields) {
  foreach($array as $key => &$value) {
    if (is_array($value)) {
      if ($value['#type'] == 'fieldset') {
       //call this function again recursively
       my_module_cck_walker($value, &$cck_fields);
      }
      elseif (in_array($key, $cck_fields)) {
        $value[0]['value']['#attributes']['readonly'] = 'readonly';
        $value[0]['value']['#description'] = t('New description');
      }
    }
  }
}
illepic’s picture

Aaron, you have no idea exactly how helpful this post was in both solving the fieldset issue as well as understanding the form rendering process. I could not find documentation anywhere that stated that fieldsets are really only assigned/rendered in #after_build. Thank you.

mechler’s picture

I'll echo this sentiment.

Web Developer, Iowa State University College of Veterinary Medicine

TrevorBradley’s picture

I must chime in here as well. I was well on my way to losing my hair as described in the OP dealing with CCK fieldsets. Now I'm all set. Thank you!

orjantorang’s picture

I had to add the attribute "readonly" this way to make it work in my after build function

<?php
$form['field_backupuser'][0]['nid']['nid']['#attributes']['readonly'] = 'readonly';
?>
seanburlington’s picture

I have user generated content - anonymous users may post - but I wanted the email/name for registered users prepopulated to defaults

The following seems to work.

In this case I didn't need the #after_build step

The '0' array seems to be the key bit and #default_value is an array rather than just a value

function example_form_alter(&$form, $form_state, $form_id) {

 // When adding example - for logged in users supply their name/mail as default instead of blank
  if ($form_id == 'example_node_form' || $form_id == 'apps_node_form' ){
    global $user;
    if ($user->uid !=0 ){ 
      $profile =  content_profile_load('profile', $user->uid);
      $mail =  check_plain($user->mail);
      if ($form['field_submitter_name'][0]['#default_value']['value'] == ''){
        $form['field_submitter_name'][0]['#default_value']['value'] = check_plain($profile->field_fname[0]['value'] . ' '. $profile->field_sname[0]['value']);
      }
      if ($form['field_submitter_email'][0]['#default_value']['email'] == ''){
        $form['field_submitter_email'][0]['#default_value']['email'] = check_plain($mail);
      }
    }
  }

}
Max_Headroom’s picture

Thanks Sean
Helped me find a solution for another problem: Preset a value for radio buttons.
I could not get it to work in the #after_build called function.
Set the fields' #default_value in in hook_form_alter did it.

 $form['field_my_radio']['#default_value'][0]['value'] = 1

I suppose it would work for checkboxes and select lists as well. Haven't tested.

Quentin Campbell

FranciscoLuz’s picture

Hay Max,

If I understood you well I was having the same issue and after 2 hours of deep investigation I finally have it worked out.

CCK sets an array of attributes for each radio option. For example:
Say that you have set 'apples' and 'oranges' as options to be chosen by the user and you would like to programmatically set 'apples' as the default value (selected) when the form loads up, then do this in your after build function:

$form['field_MyField']['value']['apples']['#value'] = 'apples';
$form['field_MyField']['value']['oranges']['#value'] = 'apples';

Hope it helps!

Drupal in the Amazon Jungle

wmostrey’s picture

Here is a great article by Lullabot explaining the different between 'value' and '#value'.

ahankinson’s picture

If you have groups defined in your CCK files, you'll have to adjust $form['field_etc...'] to $form['group_name']['field_etc...'] to address the fields.

nealb13’s picture

I am trying to change the Submit Value on a CCK form from 'Save' to 'Go'. I know I would add it to the after_build function, but I don't know the values to change to do this.

I tried looking at all available fields using dsm($form) but nothing outputs when adding it to the after_build section.

Here is my code:

<?php
function check_in_form_alter(&$form, $form_state, $form_id) {
  // Check for a particular content type's node form.

  if ($form_id == 'check_in_node_form') {
    // Add an after_build function to process when everything's complete.
    $form['#after_build'][] = 'check_in_after_build';
  }
  
}

/**
* Modify CCK form elements on the example node form.
*/
function check_in_after_build($form, &$form_state) {

	return dsm($form); 
}

?>
TomSherlock’s picture

You probably have overcome your challenge by now.
But in case you haven't, consider the basics:

  1. Is Devel module enabled?
  2. Are you logged in as User 1 or a user with all relevant admin permissions?
  3. Consider not returning dsm($form); place it on its own line.
dkinzer’s picture

If you add a regular form element inside a cck field via a hook_after_build, you will not see results in the $form_state on form submit. The only way I could get this to work was by adding the my elements directly to $form array in the hook_form_alter function.

This did not work:


/**
 * Implementation of hook_form_FORM_ID_alter().
 */
function leghist_form_paleghist_node_form_alter(&$form, &$form_state) {

  //Add function to manipulate cck node form
  $form['#after_build'][] = 'leghist_cck_after_build';
}


/**
 * Implementation of hook after_build();
 */
function leghist_cck_after_build ($form, &$form_state) {

  //Add display option to titles
  foreach (element_children($form['field_lg_pop_names']) as $key) {

    $form['field_lg_pop_names'][$key]['display'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display'),
      '#process' =>  array('leghist_display_process')
    );

  }

  return $form;
}

/**
 * Implementation of hook element_process
 */
function leghist_display_process($element) {
  dsm('yuk');
}

But this did work:


//Changed from hook_form_FORM_ID_alter to  hook_form_alter
/**
 * Implementation of hook_form_FORM_ID_alter().
 */
function leghist_form_alter(&$form, &$form_state, $form_id) {

  if ($form_id == 'paleghist_node_form') {
    //Add function to manipulate cck node form
    leghist_cck_alter($form);

  }

}


/**
 * Adds Element to $form
 */
function leghist_cck_alter (&$form) {

  //Add display option to titles
  foreach (element_children($form['field_lg_pop_names']) as $key) {

    $form['field_lg_pop_names'][$key]['display'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display'),
      '#process' =>  array('leghist_display_process')
    );

  }

}

/**
 * Implementation of hook element_process
 */
function leghist_display_process ($element) {

  return $element;
}
vishun’s picture

I was not able to get this to work properly as is, but it did inspire a resolution for replacing the process function for certain CCK fields. I copied the process function from the module that needed to be altered and changed its contents in the custom process callback definition, by this example, leghist_display_process.

<?php
//Changed from hook_form_FORM_ID_alter to  hook_form_alter
/**
* Implementation of hook_form_FORM_ID_alter().
*/
function leghist_form_alter(&$form, &$form_state, $form_id) {

  if ($form_id == 'paleghist_node_form') {
    // Add the process callback in place of the normal one
    $form['field_lg_pop_names']['#process'][] = 'leghist_display_process';
  }

}

/**
* Implementation of hook element_process
*/
function leghist_display_process ($element) {
  return $element;
}
?>
M_i_c_h_a_e_l’s picture

I hope you can give me a little advice. (I think I'm posting in the right place).

I'm using a small module (written by a friend), which uses hook_form_alter to display the body of a referenced node (writing assignment) on the edit form of my worksheet content-type. This allows the user to see the referenced writing assignment while editing the worksheet.

This works perfectly.

Now I want to make a modification.

I created a new cck field in the writing assignment content-type. The new field is called content. It's a text field I'm using to replace the existing body field. I have good reason to do that.

So I need to change the module to display the new content field instead of the body.

The reference to body appears 3 times in the current module, but I'm not sure which parts have to change:

<?php
function task_form_alter($form, $form_state, $form_id) {
  if ($form_id == 'worksheet_node_form') {
    $writing_assignment_node_body = node_load($form['field_task_reference'][0]['nid']['#default_value'])->body;
    $task_node_reference_weight = $form['field_task_reference']['#weight']; //get the weight of the node-reference field so we can display the instructions right below it
   
    $form['task_text'] = array(
      '#type'   => 'markup',
      '#value'  => $writing_assignment_node_body,
      '#weight' => $task_node_reference_weight + 1,
    );
  }
}
?>

Also, as I read this and other forum posts, it seems that I need to include an after_build statement, since hook_form_alter seems to handle CCK fields differently.

I know absolutely zero about php and writing modules, but I thought it would be a simple text replacement issue. I would really appreciate any assistance. I just need to know how to modify the code in this module to display the content field instead of the body.

If it's not possible, I'll go back to using the body field, but I would rather not.

Thank you so much in advance.

FranciscoLuz’s picture

@MichaelBrown
I haven't tested it, so double check for eventual typos. Also read the comments in the code.

<?php
function task_form_alter($form, $form_state, $form_id) {
  if ($form_id == 'worksheet_node_form') {
    $form['#after_build'][] = '_worksheet_after_build';
    $form['task_text'] = array(
      '#type'   => 'markup',
      '#value'  => '', //you set the value in the after build function below
    );
  }
}

/**
 * custom after build funcion 
 * you can name it whatever you like but remember to also change the value of $form['#after_build'][]
 */
function _worksheet_after_build($form, &$form_state){
  //double check $form['field_task_reference'][0]['nid']['#default_value'], I think it might need another ['nid'] in it, like this $form['field_task_reference'][0]['nid']['nid']['#default_value']
 
    $writing_assignment_node_body = node_load($form['field_task_reference'][0]['nid']['#default_value'])->body;
    $task_node_reference_weight = $form['field_task_reference']['#weight']; //get the weight of the node-reference field so we can display the instructions right below it

  $form['task_text']['#value'] = $writing_assignment_node_body;
  $form['task_text']['#weight'] = $task_node_reference_weight + 1,
  return $form;
}
?>

Drupal in the Amazon Jungle

eighthourlunch’s picture

Man, I wish my searches had pointed me here three days ago! I'm grateful just the same.

GBain22’s picture

Is there a way to add a class to form elements using this method? I have a select drop down list of cities such as

Liverpool
Manchester

But I'd love to be able to add these as classes to the form, such as:

<option value="liverpool" class="liverpool">Liverpool</option>
<option value="manchester" class="manchester">Manchester</option>

Or even better, add the class in as a different value like:

<option value="liverpool" class="northwest">Liverpool</option>
<option value="manchester" class="northwest">Manchester</option>

Just looking for somewhere to go so I can customise the forms!

podarok’s picture

thanks a lot!
really helpfull
doing backport from D7 to D6 was in confusion

---------------
Andrii Podanenko
CEO, ITCare

bradallenfisher’s picture

You need to create a module and in it place code like this

<?php
/**
 * Implementation of hook_form_alter().
 */
function mymodule_form_alter(&$form, $form_state, $form_id) {
  // Check for a particular content type's node form.
  if ($form_id == 'content_type_name_node_form') {
    // Add an after_build function to process when everything's complete.
    $form['#after_build'][] = 'wysoff_after_build';
  }
}

/**
 * Modify CCK form elements on the example node form.
 */
function mymodule_after_build($form, &$form_state) {
        //Find
	$form['field_eduction'][0]['#prefix'] = '<div class="myclass">';
	$form['field_eduction'][0]['#suffix'] = '</div>';
        return $form;
}
adf1969’s picture

If you want to add some AJAX/AHAH to a CCK/Fields API form in Drupal 7 using hook_form_alter you have to do the following:
1) Add your #ajax array in hook_form_alter

  $form['field_g_parent_type']['#ajax'] = array(
    'event' => 'change',
    'callback' => 'gst_gstgroupschedule_coursechange_js',
    'wrapper' => 'edit-field-g-group-ref',
    'method' => 'replace',
    'effect' => 'fade',    
  );

2) Ensure you set the #process and #processed flags like so:

  $form['field_g_parent_type']['#process'][] = "ajax_process_form";
  $form['field_g_parent_type']['#processed'] = FALSE;

If someone (who actually knows) wants to confirm this is true, please comment.
The above works for me so that is what I'm doing. If there is a better way, please inform :)

(personally, I'm not sure why the addition of the #ajax element doesn't automatically *SET* #process = 'ajax_process_form' but since I didn't write the form_builder code, I don't know if some other voodoo is at work. Then again, I'm sure it is more efficient requiring the resetting of the #processed flag (course, if I set "#processed = FALSE", perhaps it should find the #ajax and *then* add the ajax_process_form but it does not - I tried that first)

So, there it is.
Hope that saves someone else some time.

Andrew.

Luca Monfredo’s picture

Just to try to clarify things, setting #process and #processed flags is NOT needed, I'm working on a module right now and all I did is set the #ajax['wrapper'] and #ajax['callback'] on the first dropdown. The other properties are not marked as "required".
Everything is working as expected here, the only thing that drove me crazy is setting the default values on the second dropdown 'cause CCK fields wrap everything in a container type element. It seems there aren't many examples on the net regarding working with CCK core fields (or maybe I dind't look in the right places).

Anyone experience here is welcome.

Luca

nicodv’s picture

After three days crossing the desert, I found someone (you) that struggled with the ajax dependent dropdowns. I got the ajax example (from the examples module) and tried to modify it to populate its data from my db, but it´s been three long days and still can´t make it work: the second dropdown doesn´t react to the first dropdown. Did you solve it? In case you did, please help me.

Thanks in advance.

Nico

THE VERY LITTLE AGENCY

Luca Monfredo’s picture

Hey Nico,
well, I had my problems too finding examples with cck fields, so here's my little contribution, hoping that it will be helpful.

Suppose you have two CCK fields, let's say for example xfield and yfield, and that yfield is dependent on the choice you make on xfield. Suppose also that the form id you are altering is dependent_example_node_form.
So, all you have to do is properly set the ajax properties on xfield and wrap yfield in a div whose contents will be replaced by the ajax callback.
Essentially, you'll have something like this (we are inside a form_alter function, so if your module is called mymodule, the function is mymodule_form_alter):

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  
  if($form_id == 'dependent_example_node_form') {
    
    $xfield_options = _xfield_get_options();
    $selected = isset($form_state['values']['field_xfield']['und']) ? $form_state['values']['field_xfield']['und'] : key($xfield_options);
    
    $form['field_xfield']['und']['#default_value'] = $selected;
    $form['field_xfield']['und']['#ajax'] = array(
      'callback' => 'xfield_callback',
      'wrapper' => 'yfield_replace'
      );

    $form['field_yfield']['und']['#prefix'] = '<div id="yfield_replace">';
    $form['field_yfield']['und']['#suffix'] = '</div>';
    $form['field_yfield']['und']['#options'] = _yfield_get_options($selected);
    $form['field_yfield']['und']['#default_value'] = isset($form_state['values']['field_yfield']['und']) ? $form_state['values']['field_yfield']['und'] : '';

    return $form;
  	
  }

}

As you can see I'm using some helper functions. Note the callback (xfield_callback) that regenerates yfield everytime a change on xfield is made. I'll include them for clarity:

function xfield_callback($form, $form_state) {
	return $form['field_re_yfield'];
}

function _xfield_get_options() {
  // get options for xfield...
}

function _yfield_get_options($key = '') {
  // get options for yfield...
}

Please, excuse my brevity, I'm a little short on time right now and don't have the time to detail my example. I hope that it will shed some light on you and everyone having headaches managing ajax dependent dropdowns on CCK fields.
Ask anything, I'm looking forward to you to solve your problem. ;)

Have a nice day,
Luca

nicodv’s picture

Thanks, really, it's the first response I get since I started with the dropdowns almost 5 days ago, so thanks.
At first sight, my code and yours are the same, except for the ['und']'s. Sure it's a cause of being kind of new in this, but the ['und'] are "...in case you have more elements after the dropdown element..."? kind of thing, or is that I have to use them to get the code to work?

In case the ['und']'s are no needed and are only for the example, the problem then, should be inside the code of the queries to populate the dropdowns. In that case, do you mind if I post the code so you can take a look any time you have a minute?

Thanks again.

THE VERY LITTLE AGENCY

Luca Monfredo’s picture

The "und" key stands for "undefined". It's a key used for multilanguage systems. I don't know if you are developing in a multilanguage environment, so you should check if that's the case. Anyway, the "und" key should not be needed when working without localization modules activated, but it's only a guess.
The devel module aids you in finding out the structure of the arrays we are dealing with. You should install it and enable the display of the page array. It really helped me out to understand the inner structure of CCK fields and how to alter them with ajax.

Back to your problem. I kindly ask you to provide some detail (and code, of course) of your implementation, and any error that pops out (if any). Let's try to find a solution together.

Regards,
Luca

nicodv’s picture

Here is the whole code. Don't worry about some unconsistency (as my dropdowns are not included in the validation and submit of the form) the code is just for practice.

  
/**
 * A form with a dropdown whose options are dependent on a
 * choice made in a previous dropdown.
 *
 * On changing the first dropdown, the options in the second
 * are updated.
 */
 /**
 * Implements hook_menu().
 */
function dropdowns_menu() {
  $items['dropdowns/example'] = array(
    'title' => 'Dependent dropdown example',
    'description' => 'Just that.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('dropdowns_dependent_dropdown'),
    'access callback' => TRUE,
  );
  $items['dropdowns/dropdown/simple'] = array(
    'title' => 'Simple',
    'description' => 'dependent dropdowns simple form.',
    'page callback' => 'dropdowns_simple_page',
    'access callback' => TRUE,
  );
  return $items;
}
function dropdowns_dependent_dropdown($form, &$form_state) {
  //get the list of options to populate the first dropdown
  $options_first = _dropdowns_get_list1_options();
  // If we have a value for the first dropdown from $form_state['values'] we use
  // this both as the default value for the first dropdown and also as a
  // parameter to pass to the function that retrieves the options for the
  // second dropdown.
 //  if(isset($form_state['values']['activity'])){
//        $selected = isset($form_state['values']['sector']) ? $form_state['values']['sector'] : key($options_first);
//   }
//   else if($chosen_list1_id > 0){
//     $selected = $chosen_list1_id;
//   }
  $selected = isset($form_state['values']['item1']) ? $form_state['values']['item1'] : key($options_first);
  
  $form['item1'] = array(
	'#type' => 'select',
	'#title' => 'item1',
	'#options' => $options_first,
	'#default_value' => $selected,
	 // Bind an ajax callback to the change event (which is the default for the
	// select form type) of the first dropdown. It will replace the second
	// dropdown when rebuilt
	'#ajax' => array(
	  // When 'event' occurs, Drupal will perform an ajax request in the
	  // background. Usually the default value is sufficient (eg. change for
	  // select elements), but valid values include any jQuery event,
	  // most notably 'mousedown', 'blur', and 'submit'.
	  //'event' => 'change',
	  'callback' => 'dropdowns_list2_callback',
	  'wrapper' => 'list2-replace',
	),
  );
  
  $form['item2'] = array(
	'#type' => 'select',
	'#title' => t('item2'), 
	 // The entire enclosing div created here gets replaced when dropdown_first
	// is changed.
	'#prefix' => '<div id="list2-replace">',
	'#suffix' => '</div>',
	// when the form is rebuilt during ajax processing, the $selected variable
	// will now have the new value and so the options will change
	'#options' => _dropdowns_get_list2_options($selected),
	'#default_value' => isset($form_state['values']['list2']) ? $form_state['values']['list2'] : '',
   );
   return $form;
}
	
/**
 * Selects just the second dropdown to be returned for re-rendering
 *
 * Since the controlling logic for populating the form is in the form builder
 * function, all we do here is select the element and return it to be updated.
 *
 * @return renderable array (the second dropdown)
 */
function dropdowns_list2_callback($form, $form_state) {
  return $form['item2'];
}

/**
 * Helper function to populate the first dropdown. This was accomplished with drupal_map_assoc, but
 * i'll try with a db query...
 *
 * @return array of options
 */
function _dropdowns_get_list1_options() {
  $list1 = array();
  $list1_result = db_query("SELECT list1_id, list1_names FROM {list1}");
  foreach ($list1_result as $record) {
	$list1[$record->list1_id] = $record->list1_names;
  }
  return $list1;
}

/**
 * Helper function to populate the second dropdown. This was accomplished with drupal_map_assoc, but
 * i'll try with a db query...
 *
 * @param $key
 *   This will determine which set of options is returned.
 *
 * @return array of options
 */
function _dropdowns_get_list2_options($key = '') {
$options = array();
  $list1_result = db_query("SELECT list1_id FROM {list1}");
  foreach ($list1_result as $record) {
    $list1_id = $record->list1_id;
  
    //$list1_id = $key;
  
	$list2 = array();
    $list2_result = db_query("SELECT list2_names, list2_id FROM {list2} WHERE list1_id = :list1_id",
	array(':list1_id' => $list1_id));
	foreach ($list2_result as $record_2) {
	  $list2[$record_2->list2_id] = $record_2->list2_names;
	}
  }
  $options[$key] = $list2;
  
  if (isset($options[$key])) {
    return $options[$key];
  }
  else {
      $options1 = array('0' => '-- Select Activity --');
    return $options1;
  }
}

/**
 * Menu callback; page demonstrating embedding a form on a page.
 */
function dropdowns_simple_page() {
  $build = array(
    'header_text' => array(
      '#type' => 'markup',
      '#markup' => '<p>' . t('Here is the example of the ajax dependent ddowns.') . '</p>',
    ),
    'example_form' => drupal_get_form('dropdowns_dependent_dropdown'),
  );
  return $build;
}

THE VERY LITTLE AGENCY

Luca Monfredo’s picture

Sorry Nico, but I don't understand if you created the fields programatically or via the admin interface.
Also, you don't tell me what kind of error you see, or if the field simply isn't reacting to the change of the first one.
I suggest you to double check the return values from the db queries and the helper functions first, to be sure that they actually return something and that the returning arrays are properly populated.
Try working on the code without the db queries and see if it works.
I'll try to have a deeper look later.

Regards,
Luca

nicodv’s picture

Sorry for not being more concise. There is no message, the code is ok, but it doesn't populate correctly the second dropdown. I worked on it a little bit more and I made it work partially (at last) but i'm still far from ok.
If you have the time and find the error that keeps it from a correct population, please let me know.

And thanks again for the interest.

nico

THE VERY LITTLE AGENCY

nicodv’s picture

Luca, thaks again. In case someone will go desperate looking for an example of ajax dependent dropdowns that get dynamically populated from a db, here is the code that worked form me:

function dropdowns_dependent_dropdown($form, &$form_state) {
  //get the list of options to populate the first dropdown
  $options_first = _dropdowns_get_list1_options();
  // If we have a value for the first dropdown from $form_state['values'] we use
  // this both as the default value for the first dropdown and also as a
  // parameter to pass to the function that retrieves the options for the
  // second dropdown.

  $selected = isset($form_state['values']['item1']) ? $form_state['values']['item1'] : key($options_first);
  
  $form['item1'] = array(
	'#type' => 'select',
	'#title' => 'item1',
	'#options' => $options_first,
	'#default_value' => $selected,
	 // Bind an ajax callback to the change event (which is the default for the
	// select form type) of the first dropdown. It will replace the second
	// dropdown when rebuilt
	'#ajax' => array(
	  // When 'event' occurs, Drupal will perform an ajax request in the
	  // background. Usually the default value is sufficient (eg. change for
	  // select elements), but valid values include any jQuery event,
	  // most notably 'mousedown', 'blur', and 'submit'.
	  //'event' => 'change',
	  'callback' => 'dropdowns_list2_callback',
	  'wrapper' => 'list2-replace',
	),
  );
  
  $form['item2'] = array(
	'#type' => 'select',
	'#title' => t('item2'), 
	 // The entire enclosing div created here gets replaced when dropdown_first
	// is changed.
	'#prefix' => '<div id="list2-replace">',
	'#suffix' => '</div>',
	// when the form is rebuilt during ajax processing, the $selected variable
	// will now have the new value and so the options will change
	'#options' => _dropdowns_get_list2_options($selected),
	'#default_value' => isset($form_state['values']['list2']) ? $form_state['values']['list2'] : '',
   );
   return $form;
}
	
/**
 * Selects just the second dropdown to be returned for re-rendering
 *
 * Since the controlling logic for populating the form is in the form builder
 * function, all we do here is select the element and return it to be updated.
 *
 * @return renderable array (the second dropdown)
 */
function dropdowns_list2_callback($form, $form_state) {
  return $form['item2'];
}

/**
 * Helper function to populate the first dropdown. This was accomplished with drupal_map_assoc, but
 * i'll try with a db query...
 *
 * @return array of options
 */
function _dropdowns_get_list1_options() {
  $list1 = array();
  $list1_result = db_query("SELECT list1_id, list1_names FROM {list1}");
  foreach ($list1_result as $record) {
	$list1[$record->list1_id] = $record->list1_names;
  }
  return $list1;
}

/**
 * Helper function to populate the second dropdown. This was accomplished with drupal_map_assoc, but
 * i'll try with a db query...
 *
 * @param $key
 *   This will determine which set of options is returned.
 *
 * @return array of options
 */
function _dropdowns_get_list2_options($key = '') {
$options = array();
  $list1_result = db_query("SELECT list1_id FROM {list1}");
  foreach ($list1_result as $record) {
    $list1_id = $record->list1_id;
  
    $list1_id = $key;
  
	$list2 = array();
    $list2_result = db_query("SELECT list2_names, list2_id FROM {list2} WHERE list1_id = :list1_id",
	array(':list1_id' => $list1_id));
	foreach ($list2_result as $record_2) {
	  $list2[$record_2->list2_id] = $record_2->list2_names;
	}
  }
  $options[$list1_id] = $list2;
  
  if (isset($options[$key])) {
    return $options[$key];
  }
  else {
      $options1 = array('0' => '-- Select list1 --');
    return $options1;
  }
}

THE VERY LITTLE AGENCY

Luca Monfredo’s picture

Finally we solved it. Nice work ;)

southweb’s picture

Integrity error fixed.

For me I had to make the following changes to get a basic call back to work as while the callback was executing ok Drupal was raising a form integrity error when the callback was invoked.

1. In hook_form_alter specify the callback and afterbuild functions

Note: I had to specify "method"=> "html" to get the result I wanted.

2. In the afterbuild function set

      $form_state['rebuild'] =false;
      $form_state['executed'] = 1;

3. In the actual callback set

$form_state['cache'] = false;

saru1683’s picture

@Luca Monfredo thnx for your post.

I have 3 dropdowns,
1. Countries
2. States
3. Cities.

Once I select country, fill state dropdown depend on selected country, after that I select state, fill city dropdown depend on selected state this way working perfect,
but when I re change country , state change depend on reselected country while city dropdown as it. need your help to fill city dropdown when re change country.
sorry for my bad explanation.

Thank you.

saru1683’s picture

Solved:

It's working...
I took reference from
http://drupal.stackexchange.com/questions/8529/is-it-possible-to-replace....

Thank you.

earthangelconsulting’s picture

1) has the amount of CCK processing before or after the #after_build function substantially changed between D6 and D7? see comments at bottom of http://www.lullabot.com/articles/fapicck-confusion-value-vs-value ... about how "we won't have to worry about this in D7"?

2) in particular... the #options for the radio button controls... even in the #after_build function, these don't seem to have been rendered into individual radio buttons yet, and if i change #options, it doesn't seem to make a difference to what gets rendered.

i really need to programmatically assign the #options for a pair of radio buttons, and yet have the value still go into a CCK field. this shouldn't be hard, but right now it seems damn near impossible :-(

cheers
Peter Fisera
Earth Angel Consulting

fuzzy76’s picture

That's because CCK for D7 doesn't provide any fields at all. It's basically just a migration / helper package. Fields in core replaced it.

gusantor’s picture

trying to set selected option on a cck select list (node reference) using ajax (7.x)

adding this to after_build doesn't work for me, but at mymodule_form_alter does

$form['field_company']['und']['#value'][0] = $default_node_rerence_nid;
$form['field_company']['und']['#default_value'][0] = (string)$default_node_reference_nid;

corvela’s picture

e.g. $form["#field_info"]["field_eceu"]["allowed_values"] = "one\ntwo\nthree"; in hook_form_alter

Laurent-Jacques Corveleyn
European Commission

kamenrs’s picture

I have a case when I need to remove (not hide) a term reference field from a node edit form for particular language. This is because of a bug in the translation system which causes loosing of the set values when editing a translated node in other than the original language.
I tried to unset() the entire fields in both $form and $form_state arrays, but this causes a number of issues:
1) The field doesn't render, but data is written to the database and again destroying the values set for the original language (when synchronizing) and
2) Error message is displayed from locale.module about missing object property and array keys.

Removing the fields completely from the form and preventing writing of field values in the data base would do the work for me, but I need some advice how to cope with that.

ami7878’s picture

Thanks for saving my hair :)

As I didn't find anywhere about cck input formats, I would like to add to this an example of how to set a default input format with a cck field (using the hook_form_alter() function).

<?php

function MODULENAME_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'MYCONTENT_node_form':
      // Set default cck field input format to Full HTML
      foreach(element_children($form['field_MYCCKFIELD']) as $key) {
        $form['field_MYCCKFIELD'][$key]['#default_value']['format'] = 2;
      }
      break;
  }
}
?>
letsbuild’s picture

brilliant work, this just saved me from resorting to some dirty hacking :)

tannerg’s picture

this is not generating output for me in the after build function. any common reason why that might be?

botanic_spark’s picture

You are a life savior :)))
I had a lot of trouble with this. Thanx again!!! :)

electroger’s picture

Hello,
I try to customize the Biblio module form with a form_alter. It works properly with biblio fields, but additionnals cck fields don't appear...
I don't understand what i have to do with the after_build for displaying cck fields in my custom form...
By the dsm($form['field_my_field']); in after_build, i can see that my field looks correct :

... (Array, 27 elements)

#title (String, 9 characters ) formation
#type (String, 6 characters ) select
#options (Array, 2 elements)
#default_value (Integer) 0
#attributes (Array, 1 element)
#executes_submit_callback (Boolean) TRUE
#limit_validation_errors (Array, 0 elements)
#multiple (Boolean) FALSE
#required (Boolean) TRUE
#weight (Integer) 10
#access (Boolean) FALSE
#input (Boolean) TRUE
#process (Array, 2 elements)
#theme (String, 6 characters ) select
#theme_wrappers (Array, 1 element)
#pre_render (Array, 1 element)
#defaults_loaded (Boolean) TRUE
#tree (Boolean) FALSE
#parents (Array, 1 element)
#array_parents (Array, 1 element)
#processed (Boolean) TRUE
#title_display (String, 6 characters ) before
#id (String, 18 characters ) edit-field-cod-eta
#name (String, 13 characters ) field_cod_eta
#value (Integer) 0
#ajax_processed (Boolean) FALSE
#sorted (Boolean) TRUE

ARUN AK’s picture

Hi webchick,

Thanks for the explanation. Could you please tell more about the third point that you mentioned at first?

3. If you're trying to do anything with radios, checkboxes, or similar, you'll also notice that required properties such as #options do not exist yet, in either place. Those come later.

I need to alter one of the cck checkboxes label. How can I do that?

Thanks and Regards
ARUN AK
www.developersdocs.com

yuvaraju.an’s picture

Hi please tell me the exact syntax to concatenate two fields, the following code i tried but it's giving the error.

Notice: Array to string conversion in client_phno() (line 17 of C:\xampp\htdocs\rent\sites\all\modules\webform_calc\webform_calc.module).
Notice: Array to string conversion in client_phno() (line 17 of C:\xampp\htdocs\rent\sites\all\modules\webform_calc\webform_calc.module).

the code which i tried.

<?php
/**
* Implements hook_form_alter().
*/
function webform_calc_form_alter(&$form, $form_state, $form_id)
{
drupal_set_message($form_id);
if ($form_id == 'client_node_form') {
$form['#validate'][] = 'client_phno';
}
}

function client_phno($form, &$form_state) {

$clientname = $form_state['input']['field_client_name'];
$clientphno = $form_state['input']['field_contact_no'];
$clientname_phno = $clientname."|".$clientphno;
form_set_value(
$form['field_client_reference'],$clientname_phno, $form_state);

return $form;
}

please help me.

Thanks in advance.