1

I would use 3 dependent selects using D7 Form Api #ajax. It almost works, that is, when I change the level 1 select, the level 2 is populated, then if I change the second one, the third is well populated.

But when I validate my form (ajax validation), if an error occurs (not related to the selects, just missing field, wrong value etc.), I return the whole form to display error messages. And in this case, the level 2 and level 3 select loose their options et selected value. And as the level 1 select keeps the selected value, I have to change it twice to polulate level 2 select again... not good.

I suppose I loose my selects options because the form is rebuild from cache, and the selects options are not in cache... So I'm doing something in the wrong way. Maybe related to selects or maybe related to form (maybe both !).

    // Called when select level n change to populate select level n+1
    // It does work, but in case of form rebuild, select level n+1 looses its 
    // options
    function mymod_fiche_select_ajax_callback(&$form, &$form_state) {
      // Get select caller
      $sel1 = $form_state['triggering_element']["#name"];
      // Get its value
      $val1 = $form_state["input"][$sel1];

      // Get an array to get the next select level
      $info = mymod_var("select_ajax_$sel1");

      $tab=$info["TAB"];       
      $col=$info["COL"];
      $table=$info["TABLE"];
      $fld=$info["FLD"];
      $linkfld=$info["LINKFLD"];
      $sel2=$info["SEL2"];

      // Get the options form database
      $options =  mymod_get_fen($table, array($linkfld=>$val1));

      // All my form are structured in tab/col
      $form['mc'][$tab]['form_container'][$col][$sel2]['#options']=$options;
      return $form['mc'][$tab]['form_container'][$col][$sel2];

    }

    // This function links two selects 
    function mymod_fiche_select_ajax(&$form, &$form_state,$sel1, $sel2,$table,$tab){

      $linkfld=$sel1;
      $fld=$sel2;

      // Liste les colonnes (left/right)
      $cols=array("left","right");
      foreach($cols as $column){
        if (isset($form['mc'][$tab]['form_container'][$column][$sel1])){
          $formsel1 = &$form['mc'][$tab]['form_container'][$column][$sel1];
        }
        if (isset($form['mc'][$tab]['form_container'][$column][$sel2])){
          $formsel2 = &$form['mc'][$tab]['form_container'][$column][$sel2];
          $col=$column;
        }
        if (isset($formsel1) && isset($formsel2)){
          break;
        }
      }
      $wrapper="wrapper-$sel1-$sel2";
      if (isset($formsel1) && isset($formsel2)){
        $formsel1['#ajax'] = array(
                'callback' => 'mymod_fiche_select_ajax_callback',
                'wrapper' => $wrapper,
        );
        $formsel2['#prefix']=(isset($formsel2['#prefix']) ? $formsel2['#prefix']."<div id='$wrapper'>" : "<div id='$wrapper'>");
        $formsel2['#suffix']=(isset($formsel2['#suffix']) ? "</div>".$formsel2['#suffix'] : "</div>");


          // this will be get in the callback function
    mymod_var("select_ajax_$sel1",array("TABLE"=>$table,"FLD"=>$fld,"TAB"=>$tab,"LINKFLD"=>$linkfld,"SEL2"=>$sel2,"COL"=>$col));

        // sel1 initial value
        if (isset($formsel1["#default_value"]) && $formsel1["#default_value"]!=""){
          $options=mymod_get_fen($table, array($linkfld=>$formsel1["#default_value"]));
            $formsel2["#options"]=$options;
      /* select sample
      $form['mc'][$tab]['form_container'][$column][$field] = array(
          '#type' => 'select',
          '#access' => $access,
          '#title' => t($title),
          '#options' => $options,
          '#default_value' => $value,
          '#required' => $required,
          '#validated' => true,
          '#attributes' => array("class"=>array()), 
      );*/
    }


    // Ajax validation function
function mymod_fiche_ajax(&$form, &$form_state){

        // Save changes in database
        $ret = mymod_query_form_save($form, $form_state);
        if ($ret==0){ // No change detected..
          return $form;
        }
        else if($ret==1) { // Changes saved without error
          $commands = array();
          $commands[] = ajax_command_invoke(NULL, 'fen_close'); 
          return array(
              '#type' => 'ajax',
              '#commands' => $commands,
          );
        }
        else{ // Errors detected, returning form to display messages
          $form_state['rebuild'] = true;
          return $form; // $ret contient la form + messages d'erreur
        }
      }

Thanks in advance.

  • Take a look at that module I see it may help you instead of using custom code drupal.org/project/reference_option_limit – Mohammed Gomma Oct 29 '15 at 9:53
  • how to you set the form's inital values ? and why do you rebuild the form (drupal.stackexchange.com/questions/40263/…) ? – rémy Oct 29 '15 at 13:43
  • @Rémy : The level 2 select is initialized with options = array(""=>"") and #default_value = "". Did I anwer to your question ? – user2244705 Oct 29 '15 at 14:27
  • try to set the options and default value from $form_state – rémy Oct 29 '15 at 14:48
  • @Rémy : About rebuilding from, I'm pretty new to Drupal so maybe its wrong, but if the ajax call set error using form_set_error, I need to set sorm_state[rebuild] to true to froce rebuild form and display error message ? No ? – user2244705 Oct 29 '15 at 14:56
0

Ok, I found my mistake : The ajax callback function must not modify $form.

So my code is now :

        // Called when select level n change to populate select level n+1
        // It does work
        // Not &$form, &$form_state, just $form, $form_state as parameters
        function mymod_fiche_select_ajax_callback($form, $form_state) {
          // Get select caller
          $sel1 = $form_state['triggering_element']["#name"];
          // Get its value
          $val1 = $form_state["input"][$sel1];

          // Get an array to get the next select level
          $info = mymod_var("select_ajax_$sel1");

          $tab=$info["TAB"];       
          $col=$info["COL"];
          $table=$info["TABLE"];
          $fld=$info["FLD"];
          $linkfld=$info["LINKFLD"];
          $sel2=$info["SEL2"];

          // Get the options form database
          $options =  mymod_get_fen($table, array($linkfld=>$val1));

          // All my form are structured in tab/col
/*  NO ! 
Instead of that, I now use a function to populate my select in my main form function.
          $form['mc'][$tab]['form_container'][$col][$sel2]['#options']=$options; 

*/
          // I replace the code above by this one (just save new options to read them in the main form function
          mymod_var("ysSelect_$sel2",$options); 

          // Return the element to tell drupal to rebuild it
          return $form['mc'][$tab]['form_container'][$col][$sel2];

        }

What I understand, is that Drupal doesn't use the element I return ( $form['mc'][$tab]['form_container'][$col][$sel2]) to replace it "as is" using the wrapper. It does rebuild it before that. So the options have to be set in main form function, not in ajax callback.

  • In fact it still does not work as expected. And I can't figure out the logic. Using watchdog, I can see that the first function called is the main form, and only after the mymod_fiche_select_ajax_callback... How is it possible ??? Here are the watchdog entries : DBG 10/30/2015 - 17:18 12 mymod_fiche_select_ajax_callback... admin DBG 10/30/2015 - 17:18 11 mymod_fiche_add_select... admin DBG 10/30/2015 - 17:18 10 mymod_form_distrib... admin. (mymod_form_distrib = main form function). I thought the mymod_fiche_select_ajax_callback triggers mymod_form_distrib. – user2244705 Oct 30 '15 at 16:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.