2

I'm building a form following the ajax examples module, specifically the dependent dropdown form.

I've tried the same approach as the example, calling a static function to retrieve the options for the second one, building the options and returning an array in the callback and finally building the html options. This works only if I have 2 dropdowns, if a third one exists, dependent from the second one, it doesn't work.

This is the code fragment for the dropdowns:

Edit:

As for the suggestion in the last comment here is the function where the dropdowns are been built:

public function buildForm(array $form, FormStateInterface $form_state) {
    ...
    $selected_concept = '';

    if (!empty($form_state->getValue('subject'))) {
        $selected_concept = $form_state->getValue('subject');
    }
    
    $form['concept'] = [
        '#type' => 'container',
    ];

    $form['concept']['subject'] = array(
        '#type' => 'select',
        '#title' => t('Subject'),
        '#options' => $this->service->getOptions('concept_points', 0),
        '#empty_option' => t('- Choose an option -'),
        '#attributes' => ['class' => ['clear-after-submit']],
        '#default_value' => $form_state->hasValue('subject') ? $form_state->getValue('subject') : '',
        '#ajax' => [
          'callback' => '::conceptCallback',
          'wrapper' => 'child-container',
        ],
        '#validated' => TRUE
    );

    $selected_child = '';

    if (!empty($form_state->getValue('child'))) {
        $selected_child = $form_state->getValue('child');
    }
    
    $form['child_concept'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'child-container'],
    ];

    $form['child_concept']['child'] = array(
        '#type' => 'select',
        '#title' => t('child'),
        '#empty_option' => t('- Choose an option -'),
        '#options' => static::getOptions($selected_concept),
        '#attributes' => ['id' => 'child'],
        '#default_value' => $form_state->hasValue('child') ? $form_state->getValue('child') : '',
        '#ajax' => [
            'callback' => '::childCallback',
            'wrapper' => 'grandchild-container',
        ],
        '#states' => [
            '!visible' => [
                'select[name="subject"]' => ['value' => ''],
            ],
        ],
        '#validated' => TRUE
    );

    $form['grandchild_concept'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'grandchild-container'],
    ];

    $form['grandchild_concept']['grandchild'] = array(
        '#type' => 'select',
        '#title' => t('Grandchild'),
        '#empty_option' => t('- Choose an option -'),
        '#options' => static::getOptions($selected_child),
        '#attributes' => ['id' => 'grandchild'],
        '#default_value' => $form_state->hasValue('grandchild') ? $form_state->getValue('grandchild') : '',
        '#states' => [
            '!visible' => [
                'select[name="child"]' => ['value' => ''],
            ],
        ],
        '#validated' => TRUE
    );
...
}

When I select the item on the subject dropdown the second one "child" gets updated with the subject child items. It works as expected.

As soon as I select an item from the child dropdown the third dropdown must be updated also with the child items from the second dropdown. This doesn't work with this approach, I've tested it and the change event on the second dropdown never gets fired, I guess I can't affect a form input with ajax more than once.

If I manage to do this a fourth field has to be added but this is a entity_autocomplete field dependent from the third dropdown.

This are the functions I'm using to modify the dropdowns.

public function conceptCallback(array $form, FormStateInterface $form_state) {
    return $form['child_concept'];
}

public function childCallback(array $form, FormStateInterface $form_state) {
    return $form['grandchild_concept'];
}

public static function getOptions($selected = '') {
    return \Drupal::service('test.functions')->getOptions('concept_points', $selected);
}

The function "getOptions" finds the children terms within a given taxonomy term.

I've searched a lot in forums and in the drupal api docs but I couldn't find any documentation.

Any help would be appreciated, thanks in advance.

UPDATE

I've tried another approaches but since the event never gets fired, I'll try a plain ajax call on a custom route to update the third dropdown.

I'll add the code as soon as I finish it.

UPDATE 2

Looking for documentation for another case I've found a piece of information for D7 I think will be useful for this case. I'll try today if it applies. Just in case here it is: https://www.drupal.org/forum/support/module-development-and-code-questions/2020-02-23/ajax-callback-not-working-on-second#comment-13477234

UPDATE 3

I think I've made it, now the second dropdown actually changes the third dropdown. I'm pasting the code below so it helps anyone struggling with this. The link in the last update did helped a lot, the problem seemed to be the fields, if I understood they are being rebuilt within the ajax call so the second ajax call was never there.

public function buildForm(array $form, FormStateInterface $form_state) {
    ...
    $form['schedule-container'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'schedule-container'],
    ];

    $selected_subject = '';

    if (!empty($form_state->getValue('subject'))) {
        $selected_subject = $form_state->getValue('subject');
    }

    $form['subject_container'] = [
        '#type' => 'container',
    ];

    $form['schedule-container']['subject'] = array(
        '#type' => 'select',
        '#title' => t('Subject'),
        '#options' => $this->service->getOptions('class_schedule', 0),
        '#empty_option' => t('- Choose an option -'),
        '#attributes' => ['class' => ['clear-after-submit']],
        '#default_value' => $form_state->hasValue('subject') ? 
    $form_state->getValue('subject') : '',
        '#ajax' => [
            'callback' => '::scheduleCallback',
            'wrapper' => 'schedule-container',
        ],
        '#validated' => TRUE
    );


    if($form_state->getTriggeringElement()) {
        $selected_topic = '';

        if (!empty($form_state->getValue('topic'))) {
            $selected_topic = $form_state->getValue('topic');
        }

        $form['schedule-container']['topic'] = array(
            '#type' => 'select',
            '#title' => t('Topic'),
            '#empty_option' => t('- Choose an option -'),
            '#options' => static::getOptions($selected_subject),
            '#attributes' => ['id' => 'topic', 'class' => ['clear-after-submit']],
        '#default_value' => $form_state->hasValue('topic') ? 
        $form_state->getValue('topic') : '',
            '#ajax' => [
                'callback' => '::scheduleCallback',
                'wrapper' => 'schedule-container',
            ],
            '#states' => [
                '!visible' => [
                    'select[name="subject"]' => ['value' => ''],
                ],
            ],
            '#validated' => TRUE
        );

        $form['schedule-container']['timeslot'] = array(
            '#type' => 'select',
            '#title' => t('Time slot'),
            '#empty_option' => t('- Choose an option -'),
            '#options' => static::getOptions($selected_topic),
            '#attributes' => ['id' => 'timeslot', 'class' => ['clear-after-submit']],
            '#default_value' => $form_state->hasValue('timeslot') ? 
        $form_state->getValue('timeslot') : '',
            '#states' => [
                '!visible' => [
                   'select[name="topic"]' => ['value' => ''],
                ],
            ],
            '#validated' => TRUE
        );
    }
    ...
}
4
  • Do you need #states? You can determine the visibility when rebuilding the form for Ajax.
    – user89751
    May 11 '21 at 13:25
  • Hi @user89751 thanks for the answer. Not really, it's not mandatory to hide de dropdowns, they can be shown empty when the form is loaded. I wil try what you suggest thanks again.
    – Dante
    May 11 '21 at 13:58
  • You should always show the function, or at least give hte function name, of a block of code in Drupal form questions. Is that code in your ajax callback? The form build? an after build? These all have different nuances.
    – Jaypan
    Jun 2 '21 at 5:46
  • Hi there @Jaypan, first of all thanks for answering. Which function are you refering to?? I thought it will be too much code to share. However if your advice is to put all the form and the function code I'd be more than happy to share it. I think I'll do that in the next update when I test the new approach I've found in the forum, if that's ok for you and the community. Thanks again for the advice.
    – Dante
    Jun 3 '21 at 22:49

Your Answer

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

Browse other questions tagged or ask your own question.