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?

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;

}

| improve this answer | |
  • 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 Mar 10 '13 at 0:41
  • Ahh right, I see. I've edited my answer. – littledynamo Mar 10 '13 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 Mar 10 '13 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. – littledynamo Mar 10 '13 at 0:55
  • yup without the drupal render i think we have a winner here – Rob Holmes Mar 10 '13 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'];
}
| improve this answer | |
  • 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 Mar 10 '13 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. – littledynamo Mar 10 '13 at 0:20
  • I've added another answer, which shows what I meant in my last comment. – littledynamo Mar 10 '13 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 Mar 10 '13 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'),
);
| improve this answer | |
  • 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 Mar 10 '13 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 Mar 10 '13 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 Mar 10 '13 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 Mar 10 '13 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 Mar 10 '13 at 1:21

Your Answer

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

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