Hello!
I have just created a new content type called pieza_musical. What I just want to do is update one select element via Ajax depending on another select choice, a dependant select. I'm altering the form_node_add using hook_form_alter to inject the #ajax element and replace in the wrapper the previous select rendered by default in the content type. Everything is working except when it comes to save. I think, due I'm replacing the select via $ajax['method'] = replace, maybe some token is missing and the content type can't be saved. It show these error messages:

Error message

An illegal choice has been detected. Please contact the site administrator.
Género: illegal value.

This is my simple code:

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

  $generos = taxonomy_get_tree(2,0,1);
  $values_generos = array();
  foreach ($generos as $genero) {
    $values_generos[$genero->tid] = $genero->name;
  }

  $form['field_genero']['und']['#options'] = $values_generos;
  // adds ajax 
  $form['field_genero']['und']['#ajax'] =  array(
     'event' => 'change',
     'wrapper' => 'edit-field-subgenero-und',
     'callback' => 'subgenero_ajax_callback',
  );
}

function subgenero_ajax_callback($form, &$form_state) {
  $generoId = $form_state['values']['field_genero']['und'][0]['value'];

  $cat = taxonomy_term_load($generoId);
  $vid = $cat->vid;

  $generos = taxonomy_get_tree($vid,$generoId,1);
  
  $options = array();
  foreach ($generos as $genero) {
    $options[$genero->tid] = $genero->name;
  }
  $form['field_subgenero_ajax'] = array(
    '#type' => 'select',
    '#id' => 'edit-field-subgenero-und',
    '#required' => TRUE,
    '#name' => 'field_subgenero[und]',
    '#options' => $options,
  );
  return $form['field_subgenero_ajax'];
}

So, it is possible to make this is a content type? Should I create a new entity just for this little purpose? Should I create a custom field? Thanks for your help :)

Comments

Jaypan’s picture

You are adding your dependent select element in the ajax callback - this is not allowed. Any form elements have to be added either in the form definition, or a _form_alter() hook. The reason for this is as follows:

1) Form definition generated form
2) Form is passed through _form_alter() hooks
3) Form is cached
4) Complete form is passed to ajax callback
5) Ajax callback determines which elements to return in the ajax response

Since the form is cached in step 3, any elements you add in the ajax callback are not part of the cached form. When you submit your form, the submitted values are compared to the cached form to see if the submitted values were actually possible for the given form (a security measure to prevent hackers from inserting elements into a form to hack the server). Since elements added in the ajax callback are not parts of the cached from, the server throws an error.

To get around this, you can check $form_state['values'] in your form definition, and add your ajax elements there. Ex:

function seven_form_pieza_musical_node_form_alter(&$form, &$form_state, $form_id) {
  if(isset($form_state['values']['field_genero']['und'][0]['value']))
  {
    // Generate your sub-select here
  }
}
sebasospina343’s picture

Thanks for the very good response now I understand your point. For what you say I can conclude that I should'nt add in the content type definition the sub-select element just in the hook_form_alter. So, how can I save this new field it in the database using the content type saving system? Should I add the field in the hook_node_submit or similar hook?

Jaypan’s picture

If by 'save this new field', you mean save the data that the user submits to the database, if it is a node form, you should do it in hook_node_insert(), and hook_node_update().

If you meant 'where should I add the field to the form?', then you should do it in hook_form_alter(), in the same place you are adding #ajax to the existing field.

sebasospina343’s picture

Ok, sorry for being so insistent but I have a final question. Since I created the dropdown dependant in the content type definition, how can I save the newly created field in the ajax response and be saved in the database field in the same location that the content type defined before? I mean, the content type structure is like this:
+Title
+Body
+Genero
+Sub-genero (the dependant)
All this fields are created in drupal's database when I create the content type. So, if sub-genero is gonna be overiden by the ajax field, how can I save this ajax new field in the previous sub-genero field? :)

Jaypan’s picture

I'm afraid I don't understand what you are asking for a few reasons:

Since I created the dropdown dependant in the content type definition

What do you mean by this? What is the content type definition? And are you saying that you created something called a 'dropdown dependent', or that you created a dropdown that is dependent? And finally, how did you create it in the content type definition?

how can I save the newly created field in the ajax response

Are you saying that the newly created field was created in the ajax response? Or are you asking how you can use the ajax response to save the newly created field?

and be saved in the database field in the same location that the content type defined before?

What do you mean by location? The location in the database? Or the location in the code? And if you meant in the code, then do you mean in the same function? Or something else.

if sub-genero is gonna be overiden by the ajax field

Aren't you creating a secondary select, based on the selection in the first select? If not, I've misunderstood your overall goal. If so, then why would it be overridden?

Your explanation leaves me with too many questions to be able to give a response, even one based on supposing what you are asking. Can you try to re-write your question while keeping the questions I asked above in mind, so that I can get a better idea of what you are trying to do overall, and what the problems you are facing are?

sebasospina343’s picture

Ok, sorry for the confussion. I think the best is explain my goal again. I need to create a content type with this fields:

+Title (textfield)
+Body (textarea)
+Genero (select)
+Subgenero (select)

What I need to do is that when I add content to this content type, the field Genero loads taxonomy terms and subgenero loads the childs of genero taxonomy terms. I used the hook_ID_FORM_alter, so I did it in the node_add_form.

This is the structure of the vocabulary:

+Music (vocabulary)
+Rock (this should loads in genero field)
-80's (these 3 childrens should load in subgenero field)
-Grunge
-Indie
+Classical (this should loads in genero field)
-Sonatas (these 3 childrens should load in subgenero field)
-Concertos
-Symphony
+Jazz (this should loads in genero field)
-Big Band (these 3 childrens should load in subgenero field)
-Dixie
-Free Jazz

I added the #ajax in hook_ID_FORM_alter but since this #ajax has a replace method, it will override my subgenero select. So when I save it, as you say before the cache will not match and throw an error.
So the question basically is, how can I did this both selects work in a node_add_form?

Jaypan’s picture

If you add your select form in hook_form_alter(), it will match, and save.

sebasospina343’s picture

As you said it went very good ... just one trouble with this error: An illegal choice has been detected. Please contact the site administrator.
I have tried all but still can't get the error fixed. Any idea?

Jaypan’s picture

What does your code look like now?

sebasospina343’s picture

Still the cache problem. My code:

function seven_form_pieza_node_form_alter(&$form, &$form_state, $form_id) {
  // get taxonomy terms
  $options = get_pieza_terms(2);
  // add options by taxonomy
  $form['field_genre']['und']['#options'] = $options;
  // adds ajax 
  $form['field_genre']['und']['#ajax'] =  array(
     'event' => 'change',
     'wrapper' => 'myWrapper',
     'callback' => 'subgenero_ajax_callback',
  );
  // replaces old field subgenre with the ajax field with the same name
  $form['field_subgenre']['#prefix'] = '<div id=myWrapper>';
  $form['field_subgenre']['#suffix'] = '</div>';
  $form['field_subgenre']['#validate'] = TRUE;
  form_set_cache($form['#build_id'], $form, $form_state);//Update it in backend.
}

function subgenero_ajax_callback($form, $form_state) {
  // gets the id
  $parent = $form_state['values']['field_genre']['und'][0]['value'];
  // get taxonomy terms
  $options = get_pieza_terms(2,$parent);
  // adds new options
  //$form['field_subgenre']['und']['#options'] = $options;

  $form['field_subgenre'] = array(
    '#type' => 'select',
    '#options' => $options,
    '#prefix' => '<div id=myWrapper>',
    '#suffix' => '</div>',
    '#validated' => TRUE,
    '#name' => 'field_subgenre[und]',
  );

  return $form['field_subgenre'];
}

function get_pieza_terms($vid, $parent = 0) {
  $genres = taxonomy_get_tree($vid,$parent,1);
  $values = array();
  foreach ($genres as $genre) {
    $values[$genre->tid] = $genre->name;
  }
  return $values;
}
Jaypan’s picture

You're still adding your form elements in your Ajax callback. Go back and read my very first response.

sebasospina343’s picture

This looks like an impossible to solve error. I surfed the web for one week and still don't get with the solution. There is a lot of person with the same error...could be a bug? I added the field in the form definition, I'm just updating the #options in the ajax callback:

function subgenero_ajax_callback($form, $form_state) {
  // gets the id
  $parent = $form_state['values']['field_genre']['und'][0]['value'];
  // get taxonomy terms
  $options = get_pieza_terms(2,$parent);
  // adds new options
  $form['field_subgenre']['und']['#options'] = $options;
  
   return $form['field_subgenre'];
}

And still is not possible to overpass this error: An illegal choice has been detected. Please contact the site administrator. Everybody is talking about #validated = TRUE to bypass the validation but aparently it has been deprecated in 7 version. Any new ideas? :)

Jaypan’s picture

You can't make any changes to the form in the Ajax callback. Those changes don't exist in the cached form, and therefore you get the error you are seeing. You are selecting a value that isn't part of the cached form - aka an illegal choice.

All changes need to be made in the form before it is cached.

ravibarnwal’s picture

May be this code can help you

function seven_form_pieza_node_form_alter(&$form, &$form_state, $form_id) {
  // get taxonomy terms
  $options = get_pieza_terms(2);
  // add options by taxonomy
  $form['field_genre']['und']['#options'] = $options;
  // adds ajax 
  $form['field_genre']['und']['#ajax'] =  array(
     'event' => 'change',
     'wrapper' => 'myWrapper',
     'callback' => 'subgenero_ajax_callback',
  );
  if(isset($form_state['values']['field_genre']['und'][0]['value'])){
    // gets the id
  $parent = $form_state['values']['field_genre']['und'][0]['value'];
  // get taxonomy terms
  $options = get_pieza_terms(2,$parent);
  // adds new options
  //$form['field_subgenre']['und']['#options'] = $options;
  }
 
  // replaces old field subgenre with the ajax field with the same name
  $form['field_subgenre']['#prefix'] = '<div id=myWrapper>';
  $form['field_subgenre']['#suffix'] = '</div>';
  $form['field_subgenre']['#validate'] = TRUE;
  
  form_set_cache($form['#build_id'], $form, $form_state);//Update it in backend.
}
function subgenero_ajax_callback($form, $form_state) {
  return $form['field_subgenre'];
}


function get_pieza_terms($vid, $parent = 0) {
  $genres = taxonomy_get_tree($vid,$parent,1);
  $values = array();
  foreach ($genres as $genre) {
    $values[$genre->tid] = $genre->name;
  }
  return $values;
}