Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drupal 8 Ajax forgetting form changes

I have an issue with multiple AJAX requests modifying a form within drupal 8.

Let me explain - I have been trying to build a quiz module within drupal, and decided to use a widget to create a variable amount of quiz questions, within this question widget is a list of possible answers. I have created an AJAX button within my question widget which allows answers to be removed and it works the first time it is submitted, but for some reason the second time I run the ajax call the form is reset (like no changes have been made, and no answers have been removed). I have debugged the form array after I have unset an answer, aswell as when the ajax callback is run the second time.

The ajax-enabled button uses the default ajax replace method and I have tried different methods of returning the form (minus an answer) within my AJAX callback including simply unsetting the selected form element and returning the form, aswell as using the AjaxResponse and HtmlCommand classes. I have also tried to rebuild both the form, via formbuilder, and form_state with no joy. Also each button and answer have unique names/id's.

Here is my widget code (including the button definition):

<?php
/**
 * @file
 * Contains \Drupal\pp_quiz\Plugin\Field\FieldWidget\QuestionWidget.
 */
namespace Drupal\pp_quiz\Plugin\Field\FieldWidget;

use Drupal\pp_quiz\Controller\QuizController;
use Drupal\pp_quiz\Ajax\AjaxHandler;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Plugin implementation of the 'question_widget' widget
 *
 * @FieldWidget(
 *   id = "question_widget",
 *   label = @Translation("Quiz question widget"),
 *   field_types = {
 *     "quizquestion_type",
 *   },
 * )
 */
 class QuestionWidget extends WidgetBase
 {
     public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
         $ajaxhandler = new AjaxHandler();

         $input = $form_state->getUserInput();

         //grab answer count from element array (using delta index)
         $question = $items->get($delta)->getValue();
         $answercount = count(unserialize($question['answer']));

         $element['question'] = array(
             '#type'=>'text_format',
             '#format' => 'normal',
             '#title' => gettext('Question'), 
             '#description' => gettext("The Question Text"), 
             '#required' => TRUE,
             '#element_validate' => array(
                 array(
                     '\Drupal\pp_quiz\Controller\QuizController', 
                     'validateQuestion'
                 ),
             ),
         );
         $element['answers_description'] = array('#markup' => 'Create answers below and select which are correct by checking boxes');
         $tableheader = array(
             'answer' => array('data' => t('Answer'), 'field' => 'answer'),
         );

         for ($i=0; $i<$answercount; $i++) {
             $name = "{$delta}answer{$i}";

             $options[$name] = array(
                 'answer' => array(
                     'data' => array(
                         '#type'=>'textfield',
                         //fix for losing answers on addmore button
                         '#value'=>isset($input[$name]) ? $input[$name] : '',
                         '#name' => $name,
                         '#required' => TRUE,
                     ),
                 ),
             );
         }

         $element['answers'] = array(
             '#type'=>'tableselect',
             '#header' => $tableheader,
             '#options' => $options,
         );

         $element['removeanswer'] = array(
             '#type' => 'submit', 
             '#value' => t('Remove Answer'), 
             '#name' => "{$delta}removeanswer",
             '#questionno' => $delta,
             '#attributes' => array(
                 'class' => array('removeanswer')
             ),
             '#ajax' => array(
                 'callback' => array(
                     $ajaxhandler,
                     'removeAnswerAjax',
                 ),
                 'wrapper' => 'edit-field-quiz-questions-wrapper',
             ),

         );

         $element = array(
             '#type' => 'question',
         ) + $element;

         return $element;
     }
 }

As you can see above, my 'removeanswer' submit button element has an ajax callback to a function called 'removeAnswerAjax' on the class 'AjaxHandler'. Below is the code for this callback:

<?php

/**
 * @file
 * Contains \Drupal\pp_quiz\Ajax\AjaxHandler.
 */

namespace Drupal\pp_quiz\Ajax;

use \Drupal\pp_quiz\Controller\QuizController;
use \Drupal\pp_quiz\Entities\QuizResults;
use \Drupal\Core\Form\FormStateInterface;
use \Drupal\Core\Ajax\AjaxResponse;
use \Drupal\Core\Ajax\HtmlCommand;

class AjaxHandler {

    public function removeAnswerAjax(&$form, FormStateInterface $form_state) {
        $questionno = $form_state->getTriggeringElement()['#questionno'];

        $response = new AjaxResponse();

        //find selected answer for question number (questionno)
        foreach($form['field_quiz_questions']['widget'][$questionno]['answers']['#value'] as $answer_key=>$answer) {
            unset($form['field_quiz_questions']['widget'][$questionno]['answers']['#options'][$answer]);
            unset($form['field_quiz_questions']['widget'][$questionno]['answers']['#default_value'][$answer]);
            unset($form['field_quiz_questions']['widget'][$questionno]['answers'][$answer]);
        }

        $response->addCommand(new HtmlCommand('#edit-field-quiz-questions-wrapper', $form['field_quiz_questions']['widget']));

        $form_state->setRebuild();

        return $response;
    }
}

If anyone could shed any light on why the form is being reset before it is passed as an argument to my ajax callback, I would love you forever :-)

Thanks for reading.

like image 547
RobertBourne Avatar asked Jan 06 '16 12:01

RobertBourne


1 Answers

One possible reason that your Ajax doesn't takes effect the second time is rebuilding of the form, in Ajax callback. As you are rebuilding the form using -

$form_state->setRebuild()

When a form is rebuilt, all the field and form ids are suffixed by a random build number. thus the selector is not found and the response is not replaced in DOM.

Try removing the form rebuild.

like image 88
Vivek Srivastava Avatar answered Oct 10 '22 08:10

Vivek Srivastava