33

I have a drop down list that displays various fields based on what is chosen and I know that I can toogle the visibility with states but when I try to use required the * span is display but it isn't actually required. What I mean is that even though it's "required" I can hit submit and not get an error message from drupal. Am I doing something wrong or is this currently broken in Drupal 7.8?

$form['host_info'] = [
  '#type' => 'select',
  '#title' => t("Host Connection"),
  '#options' => [
    'SSH2' => t('SSH2'),
    'Web Service' => t('Web Service'),
  ],
  '#default_value' => t(variable_get('host_info', 'SSH2')),
  '#description' => t("Specify the connection information to the host"),
  '#required' => TRUE,
];

$form['ssh_host'] = [
  '#type' => 'textfield',
  '#title' => t("Host Address"),
  '#description' => t("Host address of the SSH2 server"),
  '#default_value' => t(variable_get('ssh_host')),
  '#states' => [
    'visible' => [
      ':input[name=host_info]' => ['value' => t('SSH2')],
    ],
    'required' => [
      ':input[name=host_info]' => ['value' => t('SSH2')],
    ],
  ],
];

$form['ssh_port'] = [
  '#type' => 'textfield',
  '#title' => t("Port"),
  '#description' => t("Port number of the SSH2 server"),
  '#default_value' => t(variable_get('ssh_port')),
  '#states' => [
    'visible' => [
      ':input[name=host_info]' => ['value' => t('SSH2')],
    ],
    'required' => [
      ':input[name=host_info]' => ['value' => t('Web Service')],
    ],
  ],
];
1
  • You are missing the double quotes for name and you must not use t() for the value. It should be ':input[name="host_info"]' => ['value' => 'SSH2'],
    – leymannx
    Sep 9, 2021 at 8:42

8 Answers 8

21

You will need to validate this yourself in a custom validate function.

Everything configured by #states happens 100% in the browser, everything that it does is not visible for Drupal when the form is submitted (for example, all invisible form field are submitted and validated in the same way if there were no #states).

1
  • 1
    I figured that was the case. When I was researching how to do this I discovered the 'required' attribute with states and thought it would work the way I needed without extra work.
    – Sathariel
    Oct 27, 2011 at 14:56
13

You can use required like this:

'#states'=> [
  'required' => [
    ':input[name="abroad_because[somecheckbox]"]' => ['checked' => TRUE],
  ],
],
3
  • 4
    Yes - this will add the required indicator to the element. But there will be no client or server side validation involved.
    – AKS
    Jun 18, 2013 at 16:17
  • Could be a bug? Required elements buggy with #states
    – colan
    Sep 5, 2014 at 20:11
  • Putting the requires key into the #states array seemed to work for me, although it was the fact that I had an email field validation. So, I wonder if you just use the default drupal #element_validate on the form item it will work? Jul 29, 2016 at 16:53
10
+50

Very similar to Felix Eve's answer only this is a snippet for inline element validation:

You call an element validate function the required element:

$form['element'] = array(
....
  '#element_validate' => array(
     0 => 'my_module_states_require_validate',
   ),
)

Then the validation function finds the required field and checks to see if it's has the correct form value which would reveal the field which needs to be required.

function my_module_states_require_validate($element, $form_state) {
  $required_field_key = key($element['#states']['visible']);
  $required_field = explode('"', $required_field_key);
  if($form_state['values'][$required_field[1]] == $element['#states']['visible'][$required_field_key]['value']) {
    if($form_state['values'][$element['#name']] == '') {
      form_set_error($element['#name'], $element['#title'].' is required.');
    }
  }
}
1
  • 1
    This is the best solution IMHO! Jul 29, 2016 at 16:54
3

Here's a detailed guide on Drupal 7 form #states.

This is the important bit:

/**
 * Form implementation.
 */
function module_form($form, $form_state) {
  $form['checkbox_1'] = [
    '#title' => t('Checkbox 1'),
    '#type' => 'checkbox',
  ];

  // If checkbox is checked then text input
  // is required (with a red star in title).
  $form['text_input_1'] = [
    '#title' => t('Text input 1'),
    '#type' => 'textfield',
    '#states' => [
      'required' => [
        'input[name="checkbox_1"]' => [
          'checked' => TRUE,
        ],
      ],
    ],
  ];

  $form['actions'] = [
    'submit' => [
      '#type' => 'submit',
      '#value' => t('Submit'),
    ],
  ];

  return $form;
}

/**
 * Form validate callback.
 */
function module_form_validate($form, $form_state) {
  // if checkbox is checked and text input is empty then show validation
  // fail message.
  if (!empty($form_state['values']['checkbox_1']) &&
    empty($form_state['values']['text_input_1'])
  ) {
    form_error($form['text_input_1'], t('@name field is required.', [
      '@name' => $form['text_input_1']['#title'],
    ]));
  }
}
0
3

I was able to do it this way in Drupal 8:

'#states' => [
  'required' => [
    [':input[name="host_info"]' => ['value' => 'SSH2']],
  ],
],

Don't put t('SSH2') this will put the translation of it there instead of the value of the option which is an untranslated SSH2.

I suspect that this would work for Drupal 7 too.

1
  • 2
    In drupal 7, as pointed out to answers giving similar solutions, this provides the required field markings, but does not actually perform any validation. Does drupal 8 actually validate fields marked as required usings #states?
    – UltraBob
    Mar 10, 2017 at 13:23
3

There is another way to use AFTER_BUILD function for form and make that field optional. Here is a link for drupal 6.

Add this to your form code

$form['#after_build'][] = 'custom_form_after_build';

Implement after build, test your custom field condition

function custom_form_after_build($form, &$form_state) {
  if (isset($form_state['input']['custom_field'])) {
    $form['another_custom_field']['#required'] = FALSE;
    $form['another_custom_field']['#needs_validation'] = FALSE;
  }
  return $form;
}

In my case, #states was adding multiple * so I have to avoid it and used jquery to hide and show the span that contains *

$('.another-custom-field').find('span').hide();

And

$('.another-custom-field').find('span').show();

Based on my custom_field value.

2

I've just been faced with the same problem so needed to provide custom validation however I wanted this to be controlled via the #states array so I didn't have to specify the same rules twice.

It works by extracting the field name from the jQuery selector (the selector must be in the format :input[name="field_name"] or it won't work).

The code below is only tested in the specific scenario that I was using it in, however I though it may prove useful to someone else.

function hook_form_validate($form, &$form_state) {

    // check for required field specified in the states array

    foreach($form as $key => $field) {

        if(is_array($field) && isset($field['#states']['required'])) {

            $required = false;
            $lang = $field['#language'];

            foreach($field['#states']['required'] as $cond_field_sel => $cond_vals) {

                // look for name= in the jquery selector - if that isn't there then give up (for now)
                preg_match('/name="(.*)"/', $cond_field_sel, $matches);

                if(isset($matches[1])) {

                    // remove language from field name
                    $cond_field_name = str_replace('[und]', '', $matches[1]);

                    // get value identifier (e.g. value, tid, target_id)
                    $value_ident = key($cond_vals);

                    // loop over the values of the conditional field
                    foreach($form_state['values'][$cond_field_name][$lang] as $cond_field_val) {

                        // check for a match
                        if($cond_vals[$value_ident] == $cond_field_val[$value_ident]) {
                            // now we know this field is required
                            $required = true;
                            break 2;
                        }

                    }

                }

            }

            if($required) {
                $field_name = $field[$lang]['#field_name'];
                $filled_in = false;
                foreach($form_state['values'][$field_name][$lang] as $item) {
                    if(array_pop($item)) {
                        $filled_in = true;
                    }
                }
                if(!$filled_in) {
                    form_set_error($field_name, t(':field is a required field', array(':field' => $field[$lang]['#title'])));
                }
            }

        }
    }

}
0

I have nested form fields, and a checkbox , so I needed to work a bit on Dominic Woodman's answer. In case any body else run into the same problem:

function my_module_states_require_validate($element, $form_state) {
  $required_field_key = key($element['#states']['visible']);
  $required_field = explode('"', $required_field_key);
  $keys = explode('[', $required_field[1]);
  $keys = str_replace(']', '', $keys);
  $tmp = $form_state['values'];
  foreach ($keys as $key => $value) {
    $tmp = $tmp[$value];
  }
  if($tmp == $element['#states']['visible'][$required_field_key]['checked']) {
    $keys2 = explode('[', $element['#name']);
    $keys2 = str_replace(']', '', $keys2);
    $tmp2 = $form_state['values'];
    foreach ($keys2 as $key => $value) {
      $tmp2 = $tmp2[$value];
    }
    if($tmp2 == '') {
      form_set_error($element['#name'], $element['#title']. t(' is required.'));
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.