Hi all.

I have a simple form with a textfield with autocomplete that update with his value a field container to display a checkboxes field. The container will be updated but do not display the checkboxes or radios. Select type field works fine.

/**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['company_name'] = [
      '#type' => 'textfield',
      '#title' => t('1: Find a Company by Name'),
      '#autocomplete_route_name' => 'ect_company.find_company_by_name_controller_findByName',
      '#autocomplete_route_parameters' => [
        'type' => 'broker',
      ],
      '#ajax' => [
        'callback' => array($this, 'findUsers'),
        'event' => 'autocompleteselect',
        'wrapper' => 'users-wrapper',
        'progress' => array(
          'type' => 'throbber',
          'message' => t('Searching Users...'),
        ),
      ],
    ];

    $form['users_wrapper'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'users-wrapper'],
    ];

    $form['#attached']['library'][] = 'ect_event/drupal.ect_event_add_user.form';
    $form_state->setCached(FALSE);

    return $form;
  }


  /**
   * Ajax callback to find company users.
   */
  public function findUsers(array &$form, FormStateInterface $form_state) {

    $companyName = $form_state->getValue('company_name');
    if (preg_match('/\((\d+)\)/', $companyName, $matches)) {
      $companyId = $matches[1];

      $schedulerData = [
        [
          "value" => "11",
          "label" => "Tivo Preserva",
        ],
        [
          "value" => "90",
          "label" => "Ente Fet",
        ]
      ];

      foreach ($schedulerData as $item) {
        $options[$item['value']] = $item['label'];
      }

      $form['users_wrapper']['users'] = [
        '#type' => 'checkboxes',
        '#title' => t('2: Select users to add from the Company Members'),
        '#options' => $options,
      ];
    }
    else {
      // Create an Ajaxcommand response to display the message
      $message = $this->t('Error on retrieve company id');
    }

    return $form['users_wrapper'];
  }

Comments

vincenzodb created an issue. See original summary.

vincenzodb’s picture

Issue summary: View changes
vincenzodb’s picture

I guess that is a rendering related problem because `processCheckboxes` method it's not called

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

donaldp’s picture

I've independently found this problem with a similar problem replacing one set of radio buttons with another. Similarly with checkboxes. It would appear that the '#options' field is being ignored.

There are comments about this in a number of places including here: https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!Element!Ch...

In my case I've got something similar to the following in my ajax callback.

  public function ajaxNewOptions(array &$form, FormStateInterface $form_state) {
    $form['test_selection'] = [
      '#type' => 'radios',
      '#title' => $this->t('Replaced options'),
      '#options' => array('Option 10' => $this->t('Option 10'), 'Option 20' => $this->t('Option 20'), 'Option 30' => $this->t('Option 30')),
      '#prefix' => '<div id="options-replace">',
      '#suffix' => '</div>',
    ];

    $response = new AjaxResponse();
    $response->addCommand(new ReplaceCommand('#options-replace', $form['test_selection']));
    return $response;
  }

The resulting code on the page, with debug twig messages is:



<!-- THEME DEBUG -->
<!-- THEME HOOK: 'fieldset' -->
<!-- BEGIN OUTPUT from 'core/modules/system/templates/fieldset.html.twig' -->
<fieldset id="--wrapper" class="fieldgroup form-composite js-form-item form-item js-form-wrapper form-wrapper">
      <legend>
        <span class="fieldset-legend">Replaced options</span>
      </legend>
     <div class="fieldset-wrapper">
<!-- THEME DEBUG -->
<!-- THEME HOOK: 'radios' -->
<!-- BEGIN OUTPUT from 'core/modules/system/templates/radios.html.twig' -->
      <div></div>
 <!-- END OUTPUT from 'core/modules/system/templates/radios.html.twig' -->
      </div>
</fieldset>

<!-- END OUTPUT from 'core/modules/system/templates/fieldset.html.twig' -->

The title makes it through but not the options.

I'm going to look a bit deeper into this but I don't have much time to resolve this issue and might have to use a workaround.

donaldp’s picture

I thought I had found a simple fix for this - but this causes other issues:

You can run the array through the appropriate processor to expand them.
Example:

  public function ajaxNewOptions(array &$form, FormStateInterface $form_state) {
    $form['test_selection'] = [
      '#type' => 'radios',
      '#title' => $this->t('Replaced options'),
      '#options' => array('1' => $this->t('Option 1'), '2' => $this->t('Option 2'), '3' => $this->t('Option 3')),
      '#prefix' => '<div id="options-replace">',
      '#suffix' => '</div>',
    ];
//    Checkboxes::processCheckboxes($form['test_selection'],$form_state,$form);
    Radios::processRadios($form['test_selection'],$form_state,$form);

    $response = new AjaxResponse();
    $response->addCommand(new ReplaceCommand('#options-replace',$form['test_selection']));
    return $response;
  }

with of course, the appropriate use clause:

use Drupal\Core\Render\Element\Checkboxes;
or
use Drupal\Core\Render\Element\Radios;

EDIT: I've just noted that although the options appear, this is creating a number of PHP warnings. I will try to determine what the issues is...

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

javier.martin’s picture

I have same issue in my code:

namespace Drupal\drupal_miseries\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Checkboxes;

class CheckboxesAjax extends FormBase {

    protected $values = [
        'select' => [
            '1' => 'Value1',
            '2' => 'Value2',
        ],
        'checkboxes' => [
            '1' => 'checkbox1',
            '2' => 'checkbox2',
        ],
    ];

    public function getFormId() {
        return 'drupal_miseries_checkboxesajax';
    }

    public function ajaxCallback(array &$form, FormStateInterface  $form_state) {
        $form['container']['checkboxes'] = [
            '#type' => 'checkboxes',
            '#title' => $this->t('Checkboxes back from ajax call with select = '.
                $form_state->getValue('select')),
            '#options' => $this->values['checkboxes'],
            '#default_value' => ['1'],
        ];

        // Add processCheckboxes to return checkboxes on ajax call
        // but not process #default_value
        Checkboxes::processCheckboxes($form['container']['checkboxes'],
            $form_state, $form);

        return $form['container'];
    }

    public function buildForm(array $form, FormStateInterface $form_state) {
        $form['select'] = [
            '#type' => 'select',
            '#title' => $this->t('Select form control'),
            '#options' => $this->values['select'],
            '#ajax' => [
                'callback' => [$this, 'ajaxCallback'],
                'wrapper' => 'checkboxes_wrapper',
            ],
        ];

        $form['container'] = [
            '#type' => 'container',
            '#attributes' => ['id' => 'checkboxes_wrapper'],
        ];

        $form['container']['checkboxes'] = [
            '#type' => 'checkboxes',
            '#title' => $this->t('Checkboxes form control'),
            '#options' => $this->values['checkboxes'],
            '#default_value' => array('2'),
        ];

        return $form;
    }

    public function submitForm(array &$form, FormStateInterface $form_state) {

    }
}

If i remove line. ' Checkboxes::processCheckboxes($form['container']['checkboxes'], $form_state, $form);' an empty checkboxes control is returned. Even with this line, #default_value array is not processed an is imposible return a checboxes control with default lines checked.

I think drupal have same issue with checkbox, radio or tableselect form control on ajax callback.

javier.martin’s picture

I have find a fix for this problem. "Checkboxes::processCheckboxes" generate an array like this that finally will be an array of checkbox:

Array
(
    [#type] => checkboxes
    [#title] => stdClass Object
        (
            [__CLASS__] => Drupal\Core\StringTranslation\TranslatableMarkup
            [string] => Checkboxes back from ajax call with select = 2
            [translatedMarkup] => 
            [options] => Array
                (
                )

            [stringTranslation] => stdClass Object
                (
                    [__CLASS__] => Drupal\Core\StringTranslation\TranslationManager
                    [translators] => Array
                        (
                            [30] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [__CLASS__] => Drupal\Core\StringTranslation\Translator\CustomStrings
                                            [settings] => Drupal\Core\Site\Settings
                                            [translations] => Array(0)
                                            [_serviceIds] => Array(0)
                                        )

                                )

                            [0] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [__CLASS__] => Drupal\locale\LocaleTranslation
                                            [storage] => Drupal\locale\StringDatabaseStorage
                                            [configFactory] => Drupal\Core\Config\ConfigFactory
                                            [translations] => Array(0)
                                            [cache] => Drupal\Core\Cache\DatabaseBackend
                                            [lock] => Drupal\Core\ProxyClass\Lock\DatabaseLockBackend
                                            [translateEnglish] => 
                                            [languageManager] => Drupal\language\ConfigurableLanguageManager
                                            [requestStack] => Symfony\Component\HttpFoundation\RequestStack
                                        )

                                )

                        )

                    [sortedTranslators] => 
                    [defaultLangcode] => es
                )

            [arguments] => Array
                (
                )

        )

    [#options] => Array
        (
            [1] => checkbox1
            [2] => checkbox2
        )

    [#default_value] => Array
        (
            [0] => 1
        )

    [#tree] => 1
    [1] => Array
        (
            [#type] => checkbox
            [#title] => checkbox1
            [#return_value] => 1
            [#default_value] => 
            [#attributes] => 
            [#ajax] => 
            [#error_no_message] => 1
            [#weight] => 0.001
        )

    [2] => Array
        (
            [#type] => checkbox
            [#title] => checkbox2
            [#return_value] => 2
            [#default_value] => 
            [#attributes] => 
            [#ajax] => 
            [#error_no_message] => 1
            [#weight] => 0.002
        )

)

At the end of this array indivual checkbox have been generated so we can modify this array to add '#checked' property with TRUE value to all checkbox that must be checked by default.

[1] => Array
        (
            [#type] => checkbox
            [#title] => checkbox1
            [#return_value] => 1
            [#default_value] => 
            [#attributes] => 
            [#ajax] => 
            [#error_no_message] => 1
            [#weight] => 0.001
            [#checked] => 1
        )

    [2] => Array
        (
            [#type] => checkbox
            [#title] => checkbox2
            [#return_value] => 2
            [#default_value] => 
            [#attributes] => 
            [#ajax] => 
            [#error_no_message] => 1
            [#weight] => 0.002
            [#checked] => 1
        )
dawehner’s picture

You are abusing the ajax system in a way it was not designed to be used.
The ajax callback is meant to be used to simply return the substructure of the form array you want to use to replace in the rendered HTML.
In your form builder you need to build up the form you want to render later in the ajax callback.
Given that you have to use the $form_state to build the form dynamically, in your example provide the checkboxes you actually need, and then return them out in your ajax callback.

javier.martin’s picture

You are right!!!!

- I shouldn't call Checkboxes::processCheckboxes, but is the only way to return checkboxes with value on ajax callback.
- I shouldn't add #checked property, but is the only way to return checkboxes checked by default from ajax callback.

The right code for ajax callback should be:


public function ajaxCallback(array &$form, FormStateInterface  $form_state) {
        
       $form['container']['checkboxes'] = [
            '#type' => 'checkboxes',
            '#title' => $this->t('Checkboxes back from ajax call with select = '.
                $form_state->getValue('select')),
            '#options' => $this->values['checkboxes'],
            '#default_value' => ['1', '2'],
        ];

        return $form['container'];
    }

javier.martin’s picture

I think i have solved this issue and dawehner on #11 will be agree with me:

namespace Drupal\drupal_miseries\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Checkboxes;

class CheckboxesAjax extends FormBase {

    protected $values = [
        'select' => [
            '1' => 'Value1',
            '2' => 'Value2',
        ],
        'checkboxes' => [
            '1' => 'checkbox1',
            '2' => 'checkbox2',
        ],
    ];

    public function getFormId() {
        return 'drupal_miseries_checkboxesajax';
    }
    
    public function selectAjaxCallback(array &$form, FormStateInterface $form_state) {
      return $form['container'];
    }
    
    public function checkboxesAjaxCallback(array &$form, FormStateInterface $form_state) {
      $checkbox = $form_state->getTriggeringElement();
      $select = $form_state->getValue('select');
      
      $form['status']['mensaje'] = [
          '#markup' => '<p>Select: '.$select.' '.$checkbox['#title'].' clicked.'.'</p>',
      ];
      
      return $form['status'];
    }
    
    public function buildForm(array $form, FormStateInterface $form_state) {
        $form['select'] = [
            '#type' => 'select',
            '#title' => $this->t('Select form control'),
            '#options' => $this->values['select'],
            '#default_value' => key($this->values['select']),
            '#ajax' => [
                'callback' => [$this, 'selectAjaxCallback'],
                'wrapper' => 'checkboxes_wrapper',
            ],
        ];

        $form['container'] = [
            '#type' => 'container',
            '#attributes' => ['id' => 'checkboxes_wrapper'],
        ];
        
        $form['container']['checkboxes'] = [
            '#type' => 'checkboxes',
            '#title' => $this->t('Checkboxes form control from select @select',
              [ '@select' => empty($form_state->getValues()) ? 
                  $this->values['select'][key($this->values['select'])] : $form_state->getValue('select') ]),
            '#options' => $this->values['checkboxes'],
            '#default_value' => array('2'),
            '#ajax' => [
                'callback' => [$this, 'checkboxesAjaxCallback'],
                'wrapper' => 'status',
            ],
        ];
        
        $form['status'] = [
            '#type' => 'container',
            '#attributes' => ['id' => 'status'],
        ];

        return $form;
    }

    public function submitForm(array &$form, FormStateInterface $form_state) {

    }
}

I have included ajax on checkboxes updating a status message.

Enjoy!!!!!

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Pascal-’s picture

Why does this work properly for a select list but not for checkboxes?

My code works perfecly when I change '#type' => 'checkboxes' to '#type' => 'select' in my ajax callback.
The checkboxes also work fine if I use them in my buildForm()

What I want to do is:
- I have a select list on my form that loads a taxonomy term id
- Based on the id selected, I want to display a field from that taxonomy term as checkboxes on my form
In short: I want to load the '#options' of my checkboxes field in my ajax callback.

I'm getting the following two errors, only when using 'checkboxes' in my ajax callback:
Notice: Undefined index: #title_display in Drupal\Core\Render\Element\Checkboxes::preRenderCompositeFormElement() (line 20 of /app/www/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php)
Notice: Undefined index: #id in Drupal\Core\Render\Element\Checkboxes::preRenderCompositeFormElement() (line 30 of /app/www/core/lib/Drupal/Core/Render/Element/CompositeFormElementTrait.php)

Pascal-’s picture

After some further investigation the two errors above don't seem to have to do anything with the actual problem.
Removing the #title field from the checkboxes also removes the notices, but the checkboxes still do not show.

If anyone has some pointers to where I can continue looking, I'd like to fix this issue if possible.

vincenzodb’s picture

Category: Bug report » Support request

I guess the point is that the call to the `Checkboxes::processCheckboxes` appens only by the `buildForm` method and the related elements will be correctly processed only if they are present in this method.

We can correctly manage form alterations only if form elements are already present during the build form.

I guess that there's not a bug here but a design choice.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

dsmythjr’s picture

I was running into the same issue, and looking within #9 allows the checkboxes options to be populated.

In my ajax callback:

$new_options = array('new array items that I generated');

$form['wrapper']['dynamic_checkboxes']['#options'] = $new_options;

Checkboxes::processCheckboxes($form['wrapper']['dynamic_checkboxes'],
                $form_state, $form);

return $form['wrapper']['dynamic_checkboxes'];

Does this cover the original issue stated?

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

marioki’s picture

#11 is right, you need to build the structure of the form in buildForm.
You can't alter or add new elements in the callback function.
What you need to do is return the wrapper element in the callback and use your business logic in buildForm to alter the Form.

In my form, I needed to change radios' options depending on a different form element (let's call it $form['dep']).
In the callback function I just return $form['wrapper'] and in buildForm I check the value of 'dep' ($form_state->getValue('dep')) and react accordingly in buildForm. If you do this it works with radios too, without processing them in the callback.

Processing in the callback also caused the radio to break for me, it chose the option that was selected before the callback, not the option I chose after the callback.

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.