1

I've got a confusing ajax issue that I'm not getting past for some reason. My submit callback loads some data and constructs a radio button with some questions. All cool. When the result set has a certain value ($lastquestion = true), then I want to change the submit callback, basically updating a different field in the form.

Here's the module (I apologize for the extra "fluff"):

function my_assisted_guide_menu(){
  $items = array();

  $items['assisted-guide']= array(
    'title' => 'Assisted Guide',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('my_assisted_guide_form'),
    'access callback' => TRUE,
    'description' => 'My Assisted Guide',
    'access arguments' => array('administer news feeds'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'my_assisted_guide.inc',
  );

  return $items;
}


/* Implementation of hook_form()
// * This is the main form function
 */
function my_assisted_guide_form($form, &$form_state){

  if(isset($form_state['input']['answer'] )){
    $next_qid = $form_state['input']['answer'];
  }else{
    $next_qid = 0;
  }

  $data = _nextquestion($next_qid);
  $questiontype = $data[0];
  $question = $data[1];
  $options = $data[2];

  $form['answer'] = array(
    '#type' => 'radios',
    '#title' => t('Answer'),
    '#options' => $options,
    '#size'=>2,
    '#title' => t($question),
    '#description' => 'Select one.',
    '#required' => TRUE,
    '#prefix' => '<div id="questions" class="qna">',
  );  

  //Store question type value for later processing in submit function
  $form['question_type'] = array(
    '#type'=> 'hidden',
    '#value' => $questiontype,
    '#suffix' =>'</div>',  
  );

  $form_state['redirect'] = false;

  $service_providers = "";

  if($questiontype < 3){
    $submit_text = "Next Question";
    $callback = 'get_next_question_callback';
    $wrapper = 'questions';
  }else{
    $submit_text = "Next";
    $callback = 'get_recommendations_callback';
    $wrapper = 'recommendations_list';
  }

  $form['submit'] = array(
      '#type' => 'submit', 
      '#value' => t($submit_text),
      '#ajax' => array(
          'callback' => $callback,
          'wrapper' => $wrapper,
          'method' => 'replace',
      ),
      '#submit' => array('my_assisted_guide_submit'),
      '#prefix' =>'<div id="q_button">',
      '#suffix' =>'</div>',
      );

  if((isset($service_providers)) && (strlen($service_providers)>0) ){
    $form['recommendations_list'] = array(
                                        '#markup' => '<h1>Recommendedations</h1>',
                                        '#prefix' => '<div id="recommendedations">'
                                      );
    $form['service_providers'] = array(
                                       '#markup' => '<div><h3>Service Providers: </h3>'.$service_providers.'</div>',
                                       '#suffix' => '</div>'
                                    );
  }

  return $form;
}


//
function get_next_question_callback($form, $form_state)
{    

  return array(
    '#type' => 'ajax',
    '#commands' => array(
      ajax_command_replace("#questions", render($form['answer'])),
      ajax_command_replace("#q_button", render($form['submit']))
    )
  );
}

//
function get_recommendations_callback($form, $form_state)
{

  return $form['recommendations_list'];

}

function my_assisted_guide_submit($form, &$form_state){
  $next_key = $form_state['values']['answer'];
  $data = _nextquestion($next_key );
  $questiontype = $data[0];
  $question = $data[1];
  $options = $data[2];

  $form['answer']['#options'] = $options;
  $form['answer']['#title'] = $question;
  $form['question_type']['#value'] = $questiontype;

  if($questiontype == 3){
    $form['submit']['#ajax'] = array(
          'callback' => 'get_recommendations_callback',+
          'wrapper' => 'recommendations',
          'method' => 'replace',
      );
    $form['#submit'] = array('my_assisted_guide_submit') ;
  }

  $form_state['rebuild'] = TRUE;

}

function _first_question_nid(){
    $query = new EntityFieldQuery();
    $query->entityCondition('entity_type', 'node')
      ->entityCondition('bundle', 'dt_questions')
      ->propertyCondition('status', NODE_PUBLISHED)
      ->propertyOrderBy('created', 'DESC')
      ->fieldCondition('field_dt_questiontype', 'value', 1)
      ;
      $result = $query->execute();

    $nodeinfo = array_keys($result['node']);
    $nid = $nodeinfo[0];

  return $nid;
}

//Custom function to get the next question based on $next_qid = the question record's nid
//$answer_id is the fci of the selected answer, used to get the next question field value andload that node
function _nextquestion( $answer_id = 0, $question_type = 0 ){
  $next_qid = 0;

  if($question_type == 3){
    $data[0] = "0";
    $data[1] = "End of questions";
    $data[2] = array("0" =>"0");
    return $data;
  }

  if($answer_id == 0){
    //get first question id 
    $next_qid = _first_question_nid();
    $questiontype = 1;
  }else{
    //get nid of answer where answer_id = $next_qid
    $next_q = db_query("select at.field_dt_answer_value as answer_text, at.entity_id as answer_key, 
      a.entity_id as par_nid, q.field_a_dt_question_target_id nqid, qt.field_dt_questiontype_value as qtype
        from field_data_field_dt_answer at
        LEFT JOIN field_data_field_answer a on at.entity_id = a.field_answer_value
        LEFT JOIN field_data_field_a_dt_question q on at.entity_id = q.entity_id 
        LEFT JOIN field_data_field_dt_questiontype qt on q.field_a_dt_question_target_id = qt.entity_id 
      WHERE at.entity_id = :answerid", array(":answerid" => $answer_id ))->fetchObject(); //fetchAllAssoc('at.entity_id');
    $questiontype = $next_q->qtype;
    $next_qid = $next_q->nqid;
  }

  //Load the node
  $node = node_load($next_qid);

  $node_wrapper = entity_metadata_wrapper('node', $node);

  //$field contains an array of objects the answer and next question 
  $field = $node_wrapper->field_answer->value();

  $answer_options_count = count($field);//['field_answer']);

  for($x=0; $x<$answer_options_count; $x++){
    //Check for final question
    $option_index =  $node->field_answer['und'][$x]['value'];
    $options[$option_index] = $field[$x]->field_dt_answer['und']['0']['value'];
  }

  $question = $node->field_dt_question['und']['0']['value'];  

  $data[0] = $questiontype;
  $data[1] = $question;
  $data[2] = $options;
  return $data;
}
  • 'callback' => 'get_recommendations_callback',+ any reason for add '+' sign at end??? – monymirza Sep 27 '13 at 6:59
  • 1
    @monymirza I think it's just a code editing typo, but on the other hand I tried it in one of my forms now and adding a + after parameter didn't change a think. Fun fact, NetBeans didn't even see it as a warning-level mistake. I'm tempted to ask on Stack Overflow, but I'm afraid I will look like an idiot :D – Mołot Sep 27 '13 at 7:25
  • 2
    @Mołot Mathematical operations on strings are fine in PHP, it's equivalent to +'wrapper' => 'recommendations',, which in turn is equivalent to 0 => 'recommendations' as the string is cast to an int for the operation. Definitely a typo in this context, but Netbeans is right not to complain :) – Clive Sep 27 '13 at 9:53
  • @monymirza, that '+' was definitely a typo. Unfortunately, it wasn't the fix, but thanks for catching it. – Jav Sep 27 '13 at 15:32
1

AJAX callback is not meant for making persistent changes. All it can and will do is:

  • Make one-time changes to form array, that are not supposed to last
  • Return proper part of the form for update

You cannot change your form in any permanent way, and changing callback there would last until next request. At next request it will get reseted, and old callback will be used. You need to modify things like that in form building function itself.

  • thanks for your feedback. I'm not sure I follow your suggestion. Would I need to completely rebuild the entire form by putting a wrapper around the first and last element? Would I be better off experimenting with the multi-step form? – Jav Sep 27 '13 at 14:18
  • @jav no, that's not what I was trying to say. First, you should move all logic and loading data from database to form building function, and use $form_state to remember first action has taken place (to re-seed items on consecutive form building calls). Then it might fail again, true, but we'll see where exactly. And get rid of that + – Mołot Sep 27 '13 at 14:25
  • please forgive me, but my I don't completely follow. First, I suppose that I can load all questions/answers to an array and pull the next question from that (if that's what you mean), but this can grow to an undetermined number of questions, so I don't know if that's such a good idea. I think I understand you're suggesting using $form_state as I'm doing, to get the selected answer and rebuild the form, but that's what I'm doing (I think) and it loads questions/answers as desired. It just fails when trying to change the callback. Am I misunderstanding? – Jav Sep 27 '13 at 15:26
  • I just realized I do have the database function external to the form builder, but it calls it only on the first load, then I am re-calling it from the submit. BTW, I actually had this working well as a non-ajax form (with the exception of obvious errors caused by changing a form element without a form rebuild). – Jav Sep 27 '13 at 16:32

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.