Select form element does not use #default_value for single-option select

Created on 19 July 2017, over 7 years ago
Updated 7 May 2024, 8 months ago

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...

🐛 Bug report
Status

Active

Version

11.0 🔥

Component
Render 

Last updated 5 days ago

Created by

🇩🇪Germany yoruvo Stuttgart

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇨🇦Canada sagesolutions

    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.

  • 🇨🇦Canada phjou Vancouver 🇨🇦 🇪🇺

    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 ignore. 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.

  • 🇬🇧United Kingdom johan.gant Belfast, UK

    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?

  • 🇮🇳India Akhil Babu Chengannur

    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.

  • 🇬🇧United Kingdom johan.gant Belfast, UK

    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.

  • 🇮🇹Italy realgiucas

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

Production build 0.71.5 2024