I just had a read through the code of the Select FormElement in the Drupal core, and it appears to me that you cannot use the "#default_value" key to, well, assign a default value to the element

The #default_value option is used in two occasions in the code:

1) to check whether or not an "empty option" should be inserted, in the processSelect method

2) to set a default value for a MULTIPLE select.

The default value is never used for a single select, which to me makes no sense; if there is a default value set, that option should be selected by default, and there should be no 'empty option'.

Am I crazy, or am I onto something here?

API reference to check the source code:

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21...

Issue fork drupal-2895887

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

ivi.arocom created an issue. See original summary.

Version: 8.2.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. Branches prior to 8.8.x are not supported, and 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.

pameeela’s picture

Status: Active » Closed (cannot reproduce)
Issue tags: +Bug Smash Initiative

Have discussed with @quietone and we decided to close this because the default value for single selects appears to work fine. There are no specifics on how to reproduce the issue described, it's more of a question about the code, but it appears to work as expected.

If anyone has further info on how to reproduce this issue, please provide complete steps to reproduce the issue (starting from "Install Drupal core") in the issue summary and set the issue status back to "Active".

sagesolutions’s picture

Version: 8.9.x-dev » 10.0.x-dev
Status: Closed (cannot reproduce) » Active

I've also came across this scenario and found what works (and what doesn't). I'm currently on Drupal 10.0.3 and PHP 8.1

I needed to create a block with a form that submits to an external url. Its a simple form and only has a select box and submit button.

In the build() function of my block, I added the following:

$form['location'] = [
      '#title' => $this->t('Airport'),
      '#type' => 'select',
      '#options' => [
        'calgary' => 'Calgary',
        'edmonton' => 'Edmonton',
      ],
      '#default_value' => $location,
      '#attributes' => [
        'name' => 'location',
      ],
    ];

return [
      '#theme' => 'reservation_form',
      '#form' => $form,
    ];

The select box renders properly, but the default value is not set.

So, instead I thought to create a Form and then load the form into the block and see if that works. And it did!

So I created the form class extending FormBase and added the select field to the buildForm() method


class ReservationForm extends FormBase { 

public function buildForm(array $form, FormStateInterface $form_state) {

    $form['location'] = [
      '#title' => $this->t('Airport'),
      '#type' => 'select',
      '#options' => [
        'calgary' => 'Calgary',
        'edmonton' => 'Edmonton',
      ],
      '#default_value' => $location,
      '#attributes' => [
        'name' => 'location',
      ],
    ];

    return $form;
  }
...
}

And then in my block, I embedded the form via

class ReservationFormBlock extends BlockBase  {
  
   public function build(): array {

    $form = \Drupal::formBuilder()
      ->getForm('Drupal\my_module\Form\ReservationForm');

    return [
      '#theme' => 'reservation_form',
      '#form' => $form,
    ];
  }

Calling the form this way set the default value on the select field.

phjou’s picture

Same issue here,

If you create a renderable array of a select and use the render service to render it, you will get the select but default_value is just ignored. It definitely doesn't make sense. It should work in both context, when used in a proper drupal form (works already) but also if you render an isolated select.

johan.gant’s picture

I've been scratching my head about this for a while today. I have a views exposed form element as a select element, with about 10 options and should only take one value. I can use #value to pre-populate the initial value but this is then fixed and can't be changed by the user. If I try to use #default_value to indicate a starting value/pre-selection it's completely ignored on this element type.

I'm pretty sure I've used #default_value as an attribute in Drupal 7 and 8. The Select.php class docs state:

* - #default_value: Must be NULL or not set in case there is no value for the
 *   element yet, in which case a first default option is inserted by default.
 *   Whether this first option is a valid option depends on whether the field
 *   is #required or not.

If I try to adjust #empty_value the form tells me 'The submitted value is not allowed'.

Entirely probable that I'm applying the wrong combination of properties (somehow) but I'm confused how #default_value can't be applied on this element (string for single value, array for multiple values), when other elements accept this property in a consistent manner.

What can be done to clarify the appropriate way to use the Select element via the class docs, or is this a bug?

akhil babu’s picture

I was able to reproduce this issue.

If I create a form with a select field and use \Drupal::formBuilder()->getForm() to render the form and return it from a controller, then the default value of the select element gets applied.

But if I do this in controller

    $form['location'] = [
      '#title' => $this->t('Airport'),
      '#type' => 'select',
      '#options' => [
        'calgary' => 'Calgary',
        'edmonton' => 'Edmonton',
      ],
      '#default_value' => 'edmonton',
    ];
  return $form;

Then the element gets rendered, but default value is ignored.

Options in the select element are processed by form_select_options function.

Every option in the select element will have a 'selected' attribute. This attribute should be set to true for the option that is selected as the default value. This is the part of the code that handles this logic.

     $value_valid = \array_key_exists('#value', $element);
//
//
      if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || $value_is_array && in_array($key, $element['#value']) || $empty_choice)) {
        $option['selected'] = TRUE;
      }

The '#value' attribute of the element determies if an option should be selected by default or not.

When the form is rendered through form API, \Drupal\Core\Form\FormBuilder::handleInputElement() sets the '#value' attribute to the element. The function goes through several checks to get the #value. If #value is still not set, then #default_value is set as #value.

        // Final catch. If we haven't set a value yet, use the explicit default
        // value. Avoid image buttons (which come with garbage value), so we
        // only get value for the button actually clicked.
        if (!isset($element['#value']) && empty($element['#has_garbage_value'])) {
          $element['#value'] = $element['#default_value'] ?? '';
        }
      }

This entire process is skipped when form is rendered directly using render service. As a result, '#value' is not set and eventually default value is not highlightet in frontend.

form_select_options is called from template_preprocess_select. I have added a check to add '#value' attribute if its not set. This fixes the issue and default value get's selected when element is rendered. But not sure if this is the proper way to address this problem.

johan.gant’s picture

For what it's worth, I found that I could work around this with something like:

$form['foo'] = [
  '#type' => 'select',
  '#required' => FALSE,
  '#empty_value' => FALSE,
  '#sort_options' => TRUE,
  '#options' => [... list of key/value options ...],
 ];

combined with something like this in a form_alter hook:

$foo_value = \Drupal::request()->query->get('foo');
if (empty($foo_value) {
  $form['foo']['#value'] = 'key_value_from_one_of_the_options',
}

Which essentially means if there's no query string value for the filter, it sets a default value.

Wouldn't it be a lot handier to just set a #default_value attribute on the render element and internalise any other complications around that to the Select render element class? I appreciate I might not fully understand the existing reasons so would be more than happy if someone who can explain it in more simple terms could do that and enlighten me.

Version: 10.0.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.

realgiucas’s picture

It's a pretty big and annoying bug.
explanations would be appreciated.

mohit_aghera’s picture

StatusFileSize
new370.12 KB

I came across this issue during bugsmash traige.

First, I tried to reproduce issue based on comment #7
My controller's callback has code something like this.

  public function testMultipleForms() {
    $form['location'] = [
      '#title' => $this->t('Airport'),
      '#type' => 'select',
      '#options' => [
        'calgary' => 'Calgary',
        'edmonton' => 'Edmonton',
      ],
      '#default_value' => 'edmonton',
    ];
    return $form;
  }

Essentially it returns the form, however I feel this doesn't seem correct way to render the form.
Above approach will render select list, however it won't be the actual form element in dom.
Refer following screenshot.
Render of form array from controller

So I believe we should use the form builder service to build and render the form.

The form builder service also takes care of multiple things like build_id, token etc. to the form.

Later I tried the approach from comment #4
There also rendering form directly in block

class TestBlock extends BlockBase {
  public function build() {
    $form['test'] = [
      '#title' => $this->t('Test Form'),
      '#type' => 'select',
      '#options' => [
        'calgary' => 'Calgary',
        'edmonton' => 'Edmonton',
      ],
      '#default_value' => 'edmonton',
      '#attributes' => [
        'name' => 'location',
      ],
    ];
    return $form;
  }
}

however, this also won't render the actual <form> in DOM and it will just render the select list.
I tested that if we use the form builder service and render the form it works fine.

Considering this I think we should close the issue since right way to build renderable form is to run through form builder service.

Please reopen the issue again if you feel I'm missing something in reproducing the issue.

mohit_aghera’s picture

Status: Active » Postponed (maintainer needs more info)

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

Since there's been no follow up going to close this one out.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.