I've been trying for hours to achieve this using form_alter:

<select id="myId" class="form-select required" name="taxonomy[10]">
    <option value="">- Please select -</option>
    <option value="21" disabled="disabled">Select One</option>
    <option value="21">-Select One</option>
</select>

As you can see, I want one option to be disabled, so it acts like a heading for the option after that. But I just cannot figure out how to do that. I've been able to disable text fields, the whole select box, and pretty much anything else, but not ONE option.

Help is really appreciated!

Comments

Barrett’s picture

Looks like you're not going to be able to do it in 6.x but there's a 7.x patch to do it (http://drupal.org/node/284917).

I'm surprised that didn't get implemented in the 6.x FAPI. The only options I can see are to try a jquery solution or to remove the option entirely.

Jimboy’s picture

Arrgh, already began to think so.

I really need this. So if anybody could point me into the right direction, I'd be really glad. Don't really know where to start with jquery.

Barrett’s picture

Using hook_form_alter to just remove that option isn't an acceptable solution, I take it? Beyond that, all I can do is wish you luck. The jquery isn't an area I have enough knowledge to be helpful.

Jimboy’s picture

Just to quickly point out what I'm trying to do:

Using the module "term permissions" I want to restrict certain terms to certain user roles. The module unsets terms for which you don't have permissions. This destroys the hierachy in the drop down menu. On my site, that's why this is no option: The dropdown doesn't make sense when, for example, important headings are deleted.

Security is not an issue, as only trusted users are allowed to create content.

Maybe someone has an idea for my problem.

Jimboy’s picture

Just a quick bump. Haven't been able to get any further with this and still need it desperately.

drywall’s picture

I'm also encountering this limitation of the Forms API in Drupal 6 — I have a list of checkboxes and one of them I need pre-selected and disabled, but I don't think it can be done.

zkent’s picture

https://www.drupal.org/node/68740#comment-268897

This worked perfectly for a radio group and allows me to disable specific options. It might work for you also.

drywall’s picture

You want to add an attribute of #pre_render to your API call that builds the #options. #pre_render takes an array of functions that should be called before the form is output. You'd then declare a function to manipulate the particular options that need it. Example:

function make_option21_disabled( $form_select ) {
  $form_select[21]['#attributes']['disabled']='disabled';
  return $form_select;
}

See #pre_render in the forms API documentation at http://api.drupal.org/api/drupal/developer--topics--forms_api_reference....

Jimboy’s picture

This sounds great. I'm excited about the solution you found and thankful too.

I just have to say that I don't really know how to implement it just yet.
In term permission's .module file, I found the place where the line is removed.
unset($terms->option[$tid])

These are the lines I want to be disabled instead of removed.

Where would I have to implement the function you described above and #pre_render?
I would be really glad if you could supply me with further hints as this is just a little bit too much for my newbie drupal skills :)

Edit: Just noticed that I don't really need the term permissions module, as the list of terms won't grow too big. So if that's easier, it would also be an option to do this without that module.
Maybe addressing the dropdown made by taxonomy is easier than implementing the function into term permissions?

Jimboy’s picture

After more testing I was able to call the functions defined through #pre_render.
But I still can't seem to set the option to disabled.

$element is the select element.
If I do the following in the function called through #pre_render, it works:

$element['#title'] = 'test';
return $element;

But if I try the following, it doesn't work:

$element['#options'][0]['#attributes']['disabled']='disabled';
return $element;

The error message is:

Fatal error: Cannot use object of type stdClass as array

Any more help on this would be greatly appreciated!

Barrett’s picture

The code where you're setting the option attribute to disabled is not going to work because there is not an #attributes key in the #options[0] item. #options only takes an array of value=>text pairs, not an array of arrays (http://api.drupal.org/api/drupal/developer--topics--forms_api_reference....).

Values: An array in the form of array(t('Display value 1'), t('Display value 2')) or array('return_value1' => t('Display Value 1'), 'return_value2' => t('Display Value 2')) if specific return values are required.

The current FAPI simply doesn't support disabling an individual option. The code example drywall referenced above (in the FAPI #pre_render section) will work to disable a select list, but not individual items with the list.

I'm sorry to be a kill-joy, but that's the way it is. You'll either have to patch the FAPI to support what you're trying to do or find another solution path.

Jimboy’s picture

That's what I was thinking. Thanks for the information!

I would like to use javascript to find a solution for this, but I have no idea were and how to call it.

Could someone give me a further hint on this? Where would I place javascript to be executed, for example, when the "create content" page is loaded? Or the wysiwyg editor, or the form mentioned above, or anything basically :-)

Jimboy’s picture

I was able to figure out a solution using Javascript. This works great in combination with the term permissions module I use, but it would also work in any other situation.

In the term permissions .module file, instead of doing this:
unset($terms->option[$tid]);

You can do this:
drupal_add_js('$("#edit-taxonomy-' . $vid . '>option[value=\''. $tid . '\']").attr("disabled","disabled");', 'inline', 'footer');

Be sure to add the footer-part, otherwise the site hasn't build up for it to work. Hope it helps someone.

tomrenner’s picture

Solution works perfectly for me on standard browsers. Unfortunately not on Safari for iPhone ;-) Might be bit too much jQuery for the little kid... Gonna have to live with it.

NikaDevs’s picture

You can use next code as example:
$form['some_value'] = array(
'#type' => 'select',
'#title' => 'Select with disabled option at the top',
'#options' => array(
t('- Please select -') => array(
'one' => t('First option'),
'two' => t('Second option'),
)
)
);

munroe_richard’s picture

operating on the html just prior to display, e.g.:

...
  	$form['f2']['consumptionProfile'] =
      array(
        '#title' => t('Type of Spending'),
        '#type' => 'select',
        '#options' => $options,
        '#default_value' => $row->consumptionProfile,
  			'#attributes' =>
  				array(
  					'class' => 'monteCarlo',
  				),
  			'#post_render' => array('_ESPlannerBasicPlanningMethod_callback'),
  		) ;

...

function _ESPlannerBasicPlanningMethod_callback(
	$theContent,
	$theElement)
{
	$map =
		array(
			1 => ESPlannerBasic_permission_monteCarlo_aggressive_paid,
			2 => ESPlannerBasic_permission_monteCarlo_cautious_paid,
			3 => ESPlannerBasic_permission_monteCarlo_conservative_paid,
		) ;
	foreach ($map as $k => $p)
	{
		if (!user_access($p))
		{
			$xxx = sprintf('/(value="%d")/', $k) ;
			
			$theContent = preg_replace($xxx, '$1 disabled="disabled"', $theContent) ;
		}
	}
	
	return $theContent ;
}

The code snipped above, checks for user access to some of the features of our product and disables them in the menu if the user doesn't have access.

Best,

Dick Munroe

Barrett’s picture

That's pretty slick, Dick. Thanks for sharing.

syturvy’s picture

Dick's solution works perfect for me. Thanks.

radamiel’s picture

$form['bind_slider_to'] = array(
          '#type' => 'select',
          '#title' => t('Field to have a Slider'),
          '#options' => array(
             'option1' => t('Option 1'),
             'option2' => t('Option 2'),
             t('Disabled option') => array(),
             'option3' => t('Option 3'),
             t('Disabled option 2') => array(),
          ),
);
mohmmadalabed’s picture

after 2 years, now i like your cool solution

arunkumark’s picture

Simple and Easy solution! Works well!

brst t’s picture

20131102 - removed by author

wells’s picture

In case anyone runs up against this, I came up with a solution of using a #post_render, string replacement and a non-standard element in the select array to disable specific options. Here is an example:

$options = array('Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six');
$ids_to_disable = array(2,5,6);

$form['select'] = array(
  '#type' => 'select',
  '#options' => '#options',
  '#post_render' => array('disable_ids'),
  '#ids_to_disable' => $ids_to_disable
);

function disable_ids($rendered, $element) {
  if (empty($element['#ids_to_disable']) === true) {
    return $rendered;
  }
  
  $search = array();
  $replace = array();
  foreach ($element['#ids_to_disable'] as $id) {
    $search[] = '<option value="' . $id. '">';
    $replace[] = '<option value="' . $id. '" disabled="disabled">';
  }
  $rendered = str_replace($search, $replace, $rendered);
  
  return $rendered;
}

Amir Simantov’s picture

Here:

$options = $form['whatever']['#options'];
unset($options['option_to_remove']);
$form['whatever']['#options'] = $options;

Made it in a form that edits the configuration of a node: node/NID/webform/configure

joris_lucius’s picture

Thanks, just what I needed!

ivanzhu’s picture

Thanks a lot.

sant3001’s picture

wells' solution worked for me. Thank you! I just had to do a very small adjustment. When you have a disabled element but it's selected, the rendered HTML is not '' because it contains the selected="selected" piece. So I just removed the > character at the end as follows...

...
foreach ($element['#ids_to_disable'] as $id) {
    $search[] = '<option value="' . $id. '"';
    $replace[] = '<option value="' . $id. '" disabled="disabled"';
}
...

And that worked perfectly for me.

Den Tweed’s picture

Brilliant! Just what I needed, thank you for sharing

Anonymous’s picture

Jimboy's HTML: http://codepen.io/mikeyjk/pen/gPKPVp
My HTML: http://codepen.io/mikeyjk/pen/rxKeNv

Same issue I'm facing, just with a different widget.

In general I have a preference for altering the render array, as opposed to parsing the HTML output. Some tasks definitely lend themselves to editing the HTML, but I'm not convinced this is one of them.

I'm pretty new to the whole form handling workflow, so my understanding will definitely be imperfect. Corrections are appreciated/encouraged.

hook_form_alter: at this point in the 'preparation' phase, the render array has only been populated for the container of the List (text) type - not for any of its children. It's not until somewhere in the 'build' phase that the render array is created for the children of the List.

If I knew where the render array was being formed for the children in the build phase it could lead to a better solution. For instance I'm not ruling out the possibility that I should somehow be doing this at the theme layer. I still need to sit down and follow it through – once I do I'll update this to reflect my findings.

If you attach a method to #after_build in the form_alter form – once that method is invoked by Drupal, the render array for the children will have been populated and modifiable. You can attach the #after_build method on the entire form or just a specific field.

The comment that led me to use #after_build: https://www.drupal.org/node/1043100#comment-5185454

Re; 'preparation' and 'build' phase: https://www.drupal.org/files/fapi_workflow_7.x_v1.1.png

Other solutions discussed:

Create the render array manually, unset the option from the form in form_alter, parse/edit the HTML and JavaScript. These solutions definitely work, but didn't quite fit my use case.

msnassar’s picture

This is how I do it:

$form['happy'] = array(
    '#title' => t('Are you happy?'),
    '#type' => 'checkboxes',
    '#options' => array(
      'yes' => t('Yes'),
      'no' => t('No'),
    ),
);
$form['happy']['yes'] = array(
    '#attributes' => array(
      'class' => array('always-happy'),
    ),
);
$form['happy']['no'] = array(
    '#disabled' => TRUE,
);

I hope this is helpful...

danielhonrade’s picture

This works, thanks a lot!

Daniel Honrade, Jr.

Freelancer
email: danielhonrade@gmail.com

rodrigoaguilera’s picture