32

Title basically says it all, I want to disable a single checkbox of a checkboxes type FAPI element.

I don't want to do if with JavaScript, nor is changing it from checkboxes to multiple checkbox elements an option as the element is provided by another module.

Thoughts?

1
  • There is a typo in your question: "disable"
    – FR6
    Mar 16, 2011 at 21:01

13 Answers 13

37

A clean way does exist in Drupal 7! Apparently, per this post, it's not yet documented.

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  $form['checkboxes_element']['#disabled'] = TRUE; //disables all options
  $form['checkboxes_element'][abc]['#disabled'] = TRUE; //disables option, called abc
}

Another example.

You can also set #access function to FALSE, to completely hide the checkbox.

8
  • 3
    With the typo corrected, this code absolutely works for me in D7. Thank you!
    – beth
    May 14, 2013 at 20:15
  • 1
    Great catch! Glad it worked for you. May 14, 2013 at 22:49
  • 2
    If you use #access, then if the checkbox is set in the #default_values, it will not be set in the form state values on submit.
    – Nick
    Jul 11, 2013 at 9:56
  • This works nicely. Jul 15, 2014 at 12:05
  • Will not work with checkboxes (can select more than one element) using Better Exposed Filters (BEF) in views, AFAICT.
    – 1kenthomas
    Aug 16, 2014 at 23:17
23

Unfortunately there's no really clean way to do that in FAPI. Your best bet -- if you're determined -- is to alter an additional #process function onto the checkboxes element.

The default function added to elements of type 'checkboxes' is actually a function (expand_checkboxes()) splits the single element out into multiple elements of type 'checkbox' that are later merged back into one. If you were to piggyback your second process function, it could reach into the expanded group of 'checkbox' elements and disable the one in question.

The following code is utterly untested, so caveat emptor:

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  $form['checkboxes_element']['#process'][] = 'mymodule_disable_element';
}

function mymodule_disable_element($element) {
  foreach (element_children($element) as $key) {
    if ($key == YOUR_CHECK_VALUE) {
      $element[$key]['#disabled'] = TRUE;
      return;
    }
  }
}
4
  • Sounds better than the approach I took, which was to attach a theme function to the element and run a preg_replace() over the output.
    – Decipher
    Mar 16, 2011 at 2:45
  • You could add a "break;" inside the if statement above for a bit more efficiency, unless you expect to see the same element key twice. Mar 17, 2011 at 11:39
  • Good catch, Chris, I changed the code snippet. Thanks!
    – Eaton
    Mar 17, 2011 at 19:30
  • 2
    The accepted answer seems a bit overkill when compared to @timofey's response.
    – Citricguy
    Jul 6, 2013 at 8:41
6

Here is my code for Drupal 7, to change the Roles element in the Edit User page.

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  $form['checkboxes_element']['#pre_render'][] = 'form_process_checkboxes'; // I had to add this one, or it will return the first role only with my callback bellow
  $form['checkboxes_element']['#pre_render'][] = 'mymodule_disable_element';
}

function mymodule_disable_element(($element) {
  foreach (element_children($element) as $key) {
    if ($key == YOUR_CHECK_VALUE) {
      $element[$key]['#attributes']['disabled'] = 'disabled';
    }
  }
  return $element;
}
3

I am using checkboxes as "assign" and "unassign". The client has asked that I disable "unassign", but it is still important to represent "assignment." Keeping in mind that DISABLED checkboxes submit as "false" and if not handled properly will unassign, I split my checkboxes into "process these" and "ignore these disabled ones." Here's how:

// Provide LIVE checkboxes only for un-assigned Partners
$form['partner']['partners'] = array(
  '#type' => 'checkboxes',
  '#options' => array_diff($partners, $assignments),
  '#title' => t($partnername),
);
// Provide DISABLED checkboxes for assigned Partners (but with a different variable, so it doesn't get processed as un-assignment)
$form['partner']['partner_assignments'] = array(
  '#type' => 'checkboxes',
  '#options' => $assignments,
  '#default_value' => array_keys($assignments),
  '#disabled' => TRUE,
  '#title' => t($partnername),
);

Notice that 'partner_assignments' is its own array/variable and will not be processed as "unassign," in my use case. Thanks for posting-- it led me to this solution.

3

D7. Here we had to make sure that when adding a node one certain taxonomy term reference option is always uncheckable and will always be saved to the node. So we went into #after_build and disabled that certain option, but had to make sure that in the end that certain option will be passed on. Cause only disabling it would have stopped that option's travel to future hooks.

// a constant
define('MYTERM', 113);

/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, &$form_state, $form_id) {
    if ($form_id == 'MYCONTENTTYPE_node_form') {
    $form['#after_build'][] = 'MYMODULE_MYCONTENTTYPE_node_form_after_build';
    }
}

/**
 * Implements custom after_build_function()
 */
function MYMODULE_MYCONTENTTYPE_node_form_after_build($form, &$form_state) {
  foreach (element_children($form['field_MYFIELD'][LANGUAGE_NONE]) as $tid) {
    if ($tid == MYTERM) {
      $element = &$form['field_MYFIELD'][LANGUAGE_NONE][$tid];
      $element['#checked'] = TRUE;
      $element['#attributes']['disabled'] = 'disabled';
    }
  }
  // here's ensured the term's travel goes on
  $form['field_MYFIELD'][LANGUAGE_NONE]['#value'] += drupal_map_assoc(array(MYTERM));
  return $form;
}

That's how the disabled option looks like:

enter image description here

2

I couldn't get Eaton's answer to work as written (the #process callback doesn't return anything, and it's called before the checkboxes are expanded) and I also wanted to have the value returned from the disabled checkbox (I wanted it permanently checked). This worked for me for Drupal 6.

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  $form['checkboxes_element']['#process'][] = 'mymodule_disable_element';
}

function mymodule_disable_element($element) {
  $expanded = expand_checkboxes($element);
  $checkbox =& $expanded[YOUR_CHECK_VALUE];
  $checkbox['#disabled'] = TRUE;
  $checkbox['#value_callback'] = 'mymodule_element_value_callback';
  return $expanded;
}

function mymodule_element_value_callback($element, $edit = FALSE) {
  // Return whatever value you'd like here, otherwise the element will return
  // FALSE because it's disabled.
  return 'VALUE';
}
0
1

Here is my code for Drupal 7, to change the Roles element in the Edit User page.

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  $form['checkboxes_element']['#pre_render'][] = 'form_process_checkboxes'; // I had to add this one, or it will return the first role only with my callback bellow
  $form['checkboxes_element']['#pre_render'][] = 'mymodule_disable_element';
}

function mymodule_disable_element(($element) {
  foreach (element_children($element) as $key) {
    if ($key == YOUR_CHECK_VALUE) {
      $element[$key]['#attributes']['disabled'] = 'disabled';
      return $element;
    }
  }
  return $element;
}
1

In Drupal 7, in order to disable options in a select in a fieldable entity, I found that I had to install a #process function. Unfortunately, this disabled the built-in process function, form_process_checkboxes, so that needs to be added back (or called from your process function). Additionally, when disabling checkboxes that are already checked, I discovered that the built-in value callback (form_type_checkboxes_value) ignores defaults when retrieving results from the input.

$field_lang_form = &$your_form[$field][LANGUAGE_NONE];
$field_lang_form['#process'][] = 'form_process_checkboxes';
$field_lang_form['#process'][] = 'YOURMODULE_YOURFUNCTION_process';
$field_lang_form['#value_callback'] = 'YOURMODULE_form_type_checkboxes_value';

Then something like this:

function YOURMODULE_YOURFUNCTION_process($element) {
  // Disallow access YOUR REASON, but show as disabled if option is set.
  foreach (element_children($element) as $field) {
    if (REASON TO DISABLE HERE) {
      if (!empty($element[$field]['#default_value'])) {
        $element[$field]['#disabled'] = TRUE;
      } else {
        $element[$club]['#access'] = FALSE;
      }
    }
  }
  return $element;
}

And finally:

function YOURMODULE_form_type_checkboxes_value($element, $input = FALSE) {
  if ($input !== FALSE) {
    foreach ($element['#default_value'] as $value) {
      if (THIS OPTION WAS SET AND DISABLED - YOUR BUSINESS LOGIC) {
        // This option was disabled and was not returned by the browser. Set it manually.
        $input[$value] = $value;
      }
    }
  }
  return form_type_checkboxes_value($element, $input);
}

I did not find that any of the other answers on this page worked in this case.

1

Here is my example (using #after_build):

$form['legal']['legal_accept']['#type'] = 'checkboxes';
$form['legal']['legal_accept']['#options'] = $options;
$form['legal']['legal_accept']['#after_build'][] = '_process_checkboxes';

Plus the following function callback:

function _process_checkboxes($element) {
  foreach (element_children($element) as $key) {
    if ($key == 0) { // value of your checkbox, 0, 1, etc.
      $element[$key]['#attributes'] = array('disabled' => 'disabled');
      // $element[$key]['#theme'] = 'hidden'; // hide completely
    }
  }
  return $element;
}

Tested on Drupal 6, but it should work for Drupal 7 as well.


Drupal 6

You can use the following function (source):

/*
 * Change options for individual checkbox or radio field in the form
 * You can use this function using form_alter hook.
 * i.e. _set_checkbox_option('field_tier_level', 'associate', array('#disabled' => 'disabled'), $form);
 *
 * @param $field_name (string)
 *    Name of the field in the form
 * @param $checkbox_name (string)
 *    Name of checkbox to change options (if it's null, set to all)
 * @param $options (array)
 *    Custom options to set
 * @param $form (array)
 *    Form to change
 *
 * @author kenorb at gmail.com
 */
function _set_checkbox_option($field_name, $checkbox_name = NULL, $options, &$form) {
    if (isset($form[$field_name]) && is_array($form[$field_name])) {
        foreach ($form[$field_name] as $key => $value) {
            if (isset($form[$field_name][$key]['#type'])) {
                $curr_arr = &$form[$field_name][$key]; // set array as current
                $type = $form[$field_name][$key]['#type'];
                break;
            }
        }
        if (isset($curr_arr) && is_array($curr_arr['#default_value'])) {
            switch ($type) { // changed type from plural to singular
                case 'radios':
                    $type = 'radio';
                    break;
                case 'checkboxes':
                    $type = 'checkbox';
                    break;
            }

            foreach ($curr_arr['#default_value'] as $key => $value) {
                foreach($curr_arr as $old_key => $old_value) { // copy existing options for to current option
                    $new_options[$old_key] = $old_value;
                }
                $new_options['#type'] = $type;  // set type
                $new_options['#title'] = $value;  // set correct title of option
                $curr_arr[$key] = $new_options; // set new options

                if (empty($checkbox_name) || strcasecmp($checkbox_name, $value) == 0) { // check name or set for 
                    foreach($options as $new_key => $new_value) {
                        $curr_arr[$key][$new_key] = $value;
                    }
                }
            }
            unset($curr_arr['#options']); // delete old options settings
        } else {
            return NULL;
        }
    } else {
        return NULL;
    }
}

/*
 * Disable selected field in the form(whatever if it's textfield, checkbox or radio)
 * You can use this function using form_alter hook.
 * i.e. _disable_field('title', $form);
 *
 * @param $field_name (string)
 *    Name of the field in the form
 * @param $form (array)
 *    Form to change
 *
 * @author kenorb at gmail.com
 */
function _disable_field($field_name, &$form) {
    $keyname = '#disabled';

    if (!isset($form[$field_name])) { // case: if field doesn't exists, put keyname in the main array
        $form[$keyname] = TRUE;
    } else if (!isset($form[$field_name]['#type']) && is_array($form[$field_name])) { // case: if type not exist, find type from inside of array
        foreach ($form[$field_name] as $key => $value) {
            if (isset($form[$field_name][$key]['#type'])) {
                $curr_arr = &$form[$field_name][$key]; // set array as current
                break;
            }
        }
    } else {
        $curr_arr = &$form[$field_name]; // set field array as current
    }

    // set the value
    if (isset($curr_arr['#type'])) {
        switch ($curr_arr['#type']) {
            case 'textfield':
            default:
                $curr_arr[$keyname] = TRUE;
        }
    }
}
1
  • D7: Same here, using #after_build is working as desired. Also confirming setting disabled using #attribute.
    – SGhosh
    Sep 28, 2013 at 6:54
0

I am using this following code at drupal 6:-

$form['statuses'] = array(
    '#type' => 'checkboxes',
    '#options' => $statuses,
    '#default_value' => $status_val,
    '#after_build' => array('legal_process_checkboxes')
    );

and the call back function is here:-

/** * Process each checkbox based on 'feture' is already used by a Sub-domain or not. * @param Array $element array of form checkboxes */

function legal_process_checkboxes($element) {

  foreach (element_children($element) as $key) {

    $feature_id = $key;
    $res_total = '';

    $total = feature_used($feature_id) ;

    if ($total) {
      $element[$key]['#attributes'] = array('disabled' => 'disabled');
    }
  }
  return $element;
}
1
  • Welcome to drupal.stackexchange.com! Please fix your code formatting. Comments before legal_process_checkboxes() function have been left outside the "code" format. Thank you.
    – ermannob
    Jan 25, 2013 at 10:38
0

Hook textfield and make a dynamic textbox with info from database

1° get the assoc. array from database eg

$blah = array('test1' => 'Choose for test1', 'test2' => 'Choose for test2', ...)

2° Implements hook_form_alter()

/** * Implements hook_form_alter(). * form id = views-exposed-form */

function test_form_alter(&$form, &$form_state, $form_id)
{
  //only for this particular form
  if ($form['#id'] == "views-exposed-form-advanced-search-page-2")
    {
       $form['phases'] = array(
           '#type' => 'checkboxes',
           '#options' => $blah,
      );
    }
}

3° multiple fields will be checkable !

0

If you're building your own form, instead of running through separate form_alter/#process/#pre_render functions you can just switch from 'checkboxes' to generating individual 'checkbox' elements.

$options = array(
   1 => t('Option one'),
   2 => t('Option two'),
);

// Standard 'checkboxes' method:
$form['my_element'] = array(
  '#type' => 'checkboxes',
  '#title' => t('Some checkboxes'),
  '#options' => $options,
);

// Individual 'checkbox' method:
$form['my_element'] = array(
  '#type' => 'container',
  '#attributes' => array('class' => array('form-checkboxes')),
  '#tree' => TRUE,
  'label' => array('#markup' => '<label>' . t('Some checkboxes') . '</label>',
);
foreach ($options as $key => $label) {
  $form['my_element'][$key] = array(
    '#type' => 'checkbox',
    '#title' => $label,
    '#return_value' => $key,
  );
}
// Set whatever #disabled (etc) properties you need.
$form['my_element'][1]['#disabled'] = TRUE;

'#tree' => TRUE gives you the same tree structure as the checkboxes version when the $form_state['values'] array arrives at validation/submit/rebuild. If you can't or don't want to use #tree for some reason, give each checkbox a '#parents' => array('my_element', $key) property to explicitly set its position in the values structure.

0

One important thing to factor in is that disabled checkboxes do not get submitted, so you might find that you need to force the #value of the checkbox too.. For example:

$element['child1']['#disabled'] = TRUE;
$element['child1']['#value'] = 'child1';

In my case, without this, the $form_state['values'] did not contain my checkbox value (which was checked by default, but disabled).

1
  • It is happening indeed, however this trick doesn't make any change(at least in my case) Sep 26, 2023 at 12:50

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.