31

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?

  • There is a typo in your question: "disable" – FR6 Mar 16 '11 at 21:01

13 Answers 13

36

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.

  • 3
    With the typo corrected, this code absolutely works for me in D7. Thank you! – beth May 14 '13 at 20:15
  • 1
    Great catch! Glad it worked for you. – timofey.com May 14 '13 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 '13 at 9:56
  • This works nicely. – David Meister Jul 15 '14 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 '14 at 23:17
22

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;
    }
  }
}
  • 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 '11 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. – Chris Cohen Mar 17 '11 at 11:39
  • Good catch, Chris, I changed the code snippet. Thanks! – Eaton Mar 17 '11 at 19:30
  • 2
    The accepted answer seems a bit overkill when compared to @timofey's response. – Citricguy Jul 6 '13 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 (D6).

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';
}

If anyone knows a neater way, I'd love to hear it!

  • Fatal error: Call to undefined function expand_checkboxes() – leymannx Feb 13 '14 at 16:53
  • 1
    @koivo This was a D6 answer, the function no longer exists in D7 – Andy Feb 14 '14 at 15:31
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;
        }
    }
}
  • D7: Same here, using #after_build is working as desired. Also confirming setting disabled using #attribute. – SGhosh Sep 28 '13 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;
}
  • 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 '13 at 10:38
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 enabled by default, but disabled).

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.

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.