4

Im trying to get a set of checkboxes produced via Drupal's form api to pass the WCAG AA accessibility standards, the problem i am facing is with regard to labels and for attributes.

For example i am returning the following form definition via a hook_block_view(), i cant use drupal_get_form as normal as i dont want a form element wrapping the output which means i lose some of the processing that drupal_get_form does which is why i have to add in form_process_checkboxes and am missing some other functionality that gives the individual checkboxes their ID's(which is then used as the for attribute)

$form['tests_taken'] = array(
  '#type' => 'checkboxes',
  '#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
  '#title' => t('What standardized tests did you take?'),
   '#attributes' => array(
     'name' => 'example-checkboxes[]'
   )
);


$form['tests_taken'] = form_process_checkboxes($form['tests_taken']);

dsm($form);

return $form;

This produces the following markup.

<div class="form-checkboxes"><div class="form-item form-type-checkbox">
    <input name="example-checkboxes[]" type="checkbox" value="SAT" class="form-checkbox"> 
<label class="option">SAT </label>
</div>
<div class="form-item form-type-checkbox">
    <input name="example-checkboxes[]" type="checkbox" value="ACT" class="form-checkbox"> 
<label class="option">ACT </label>
</div>
</div>

I need the markup to either have labels with 'for' attributes pointing to the input elements or wrap the input elements with the label tag.

Heres thebest ive managed to come up with so far which doenst seem very satisfying

$form['tests_taken'] = array(
    '#type' => 'checkboxes',
    '#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
    '#title' => t('What standardized tests did you take?'),
    '#attributes' => array(
      'name' => 'example-checkboxes[]'
    )
  );

  $form['tests_taken'] = form_process_checkboxes($form['tests_taken']);

  foreach (element_children($form['tests_taken']) as $key) {
       $form['tests_taken'][$key]['#id'] = drupal_html_id('edit-' . $form['tests_taken'][$key]['#attributes']['name'] . '_' . $key);

  }

  dsm($form);
  return $form;

Which now produces this markup

<div class="form-checkboxes"><div class="form-item form-type-checkbox">
<input name="example-checkboxes[]" type="checkbox" id="edit-example-checkboxes-sat" value="SAT" class="form-checkbox"> 
<label class="option" for="edit-example-checkboxes-sat">SAT </label>
</div>
<div class="form-item form-type-checkbox">
<input name="example-checkboxes[]" type="checkbox" id="edit-example-checkboxes-act" value="ACT" class="form-checkbox"> 
<label class="option" for="edit-example-checkboxes-act">ACT </label>
</div>
</div>

Anyone know of a cleaner way of doing this?

3 Answers 3

1

Try changing your code to the following, no need for the call to form_process_checkboxes.

function my_module_block_view($delta='') {
  $block = array();
  switch ($delta) {
    case 'myform':
    $block['content'] = drupal_get_form('mymodule_form');
    break;
  }
  return $block;
}

function mymodule_form() {

  $form = array();
  $form['tests_taken'] = array(
    '#type' => 'checkboxes',
    '#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
    '#title' => t('What standardized tests did you take?'),
    '#attributes' => array(
      'name' => 'example-checkboxes[]'
    )
  );

  dsm($form);

  return $form;

}

12
  • See my comment above, this i think would work fine via a menu callback but via a hook_block_view some of the process functions you get with a form are missing hance why form_process_checkboxes was necessary. This is ultimately down to the renderable array being rendered by a drupal_render()
    – Rob Holmes
    Commented Mar 10, 2013 at 0:41
  • Ahh right, I see. I've edited my answer. Commented Mar 10, 2013 at 0:48
  • Hi Thanks, You are right that this works... however now i would have lost the ability to modify the renderable array in hook_block_view_alter() as it has now been pre-rendered and returned as a string. Im going to play with this as there might be a combo here that has the best of both worlds
    – Rob Holmes
    Commented Mar 10, 2013 at 0:53
  • Yes, my mistake, no need for the call to drupal_render. If you look at line 1396 of modules/user/user/module you can see an example of hook_block_view that uses a form. Commented Mar 10, 2013 at 0:55
  • yup without the drupal render i think we have a winner here
    – Rob Holmes
    Commented Mar 10, 2013 at 0:58
0

You could use hook_theme_registry_alter to override the theme function theme_form_element_label:

function mymodule_theme_registry_alter(&$theme_registry) {

    $theme_registry['form_element_label']['theme path'] = drupal_get_path('module', 'mymodule');
    $theme_registry['form_element_label']['function'] = 'mymodule_form_element_label';

}

Then just copy the implementation of theme_form_element_label from /include/form.inc and add a for element to the attributes:

function mymodule_form_element_label($variables) {

    $element = $variables['element'];
    // This is also used in the installer, pre-database setup.
    $t = get_t();

    // If title and required marker are both empty, output no label.
    if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
        return '';
    }

    // If the element is required, a required marker is appended to the label.
    $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : '';

    $title = filter_xss_admin($element['#title']);

    $attributes = array();
    // ADD A FOR ATTRIBUTE TO THE LABEL AND SET IT TO THE ELEMENT ID
    $attributes['for'] = $element['#id'];
    // Style the label as class option to display inline with the element.
    if ($element['#title_display'] == 'after') {
        $attributes['class'] = 'option';
    }
    // Show label only to screen readers to avoid disruption in visual flows.
    elseif ($element['#title_display'] == 'invisible') {
        $attributes['class'] = 'element-invisible';
    }

    if (!empty($element['#id'])) {
        $attributes['for'] = $element['#id'];
    }

    // The leading whitespace helps visually separate fields from inline labels.
    return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";

}

This would add a for attribute to EVERY form element that has a label. If you only wanted to add it to checkboxes then you could add a check to the value of $element['#type'] e.g:

if($element['#type'] == 'checkboxes') {
    $attributes['for'] = $element['#id'];
}
4
  • Hi thanks, thats interesting, although i cant see how this line.. $attributes['for'] = $element['#id']; does anything that this statement later on does not do if (!empty($element['#id'])) { $attributes['for'] = $element['#id']; } and i still have the problem of assigning ID's to the individual checkboxes somehow
    – Rob Holmes
    Commented Mar 10, 2013 at 0:05
  • I didn't even notice that line later on in the function! Good spot. I have tested the above code with your checkboxes element only (none of the other code) and it outputted the 'for' attribute successfully. Drupal assigned an id automatically (e.g. edit-tests-taken-sat). I would try removing the call to form_process_checkboxes and also remove the foreach code that assigns your own id. Commented Mar 10, 2013 at 0:20
  • I've added another answer, which shows what I meant in my last comment. Commented Mar 10, 2013 at 0:31
  • Because i am using the form api in a hook_block_view() im pretty sure that i wont get any checkboxes at all without the form_process_checkboxes due to the fact that in this situation the element is rendered via a drupal_render call instead of via a drupal_get_form as it would be if it was a menu callback. That leaves the question as to what else apart from the #process function is missing when rendering checkboxes outside of a normal form situation.
    – Rob Holmes
    Commented Mar 10, 2013 at 0:38
0

I believe this is documented in Drupal's Form API Reference.

Format a set of checkboxes. #options is an associative array, where the key is the #return_value of the checkbox and the value is displayed.

You should be able to set the labels corresponding to each checkbox in the #options array. The array can be of the format:

array(
  'submitted_value1' => t('Label 1'),
  'submitted_value2' => t('Label 2'),
);
5
  • Sorry i should have specified that this is for drupal 7. The question is about the 'for' attribute for the label and not setting the label itself. The line '#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))), produces an array the same as you have posted, but the situation is left where there are no ID's for the individual checkboxes and as a result no 'for' attributes applied.
    – Rob Holmes
    Commented Mar 10, 2013 at 0:14
  • After re-reading your question, it looks like you are not using drupal_get_form, so you are not following the typical Drupal form API. The for attribute will show up if the given checkbox has an #id which should be taken care of by drupal_get_form.
    – Aiias
    Commented Mar 10, 2013 at 0:38
  • yes this was the reason for the question and why form_process_checkboxes was necessary due to the use outside of the normal drupal_get_form scenario. If you return a renderable array for a block it gets run through a bog standard drupal_render()
    – Rob Holmes
    Commented Mar 10, 2013 at 0:40
  • You should still be able to use drupal_get_form within your hook_block_view(). Does this work for you? $block['content'] = drupal_get_form('my_form_id');
    – Aiias
    Commented Mar 10, 2013 at 0:47
  • Per your comment on littledynamo's answer, if you want to remove the form tag, but keep the form data you can always use something like preg_replace('#<form[^>]*>|</form>#', '', $content);. However, you are going to run into other issues with the form_build_id and form_token.
    – Aiias
    Commented Mar 10, 2013 at 1:21

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.