Here's a code snippet that you can use to set the disabled attribute of a CCK field.

First, you need to create a small module containing the following code:

/**
 * @file
 * Custom module to set the disabled attribute of CCK fields.
 */

/**
 * Implementation of hook_form_alter().
 */
function mysnippet_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    // Use this check to match node edit form for a particular content type.
    if ('mytype_node_form' == $form_id) {
      $form['#after_build'][] = '_mysnippet_after_build';
    }
    // Use this check to match node edit form for any content type.
//    if ($form['type']['#value'] .'_node_form' == $form_id) {
//      $form['#after_build'][] = '_mysnippet_after_build';
//    }
  }
}

/**
 * Custom after_build callback handler.
 */
function _mysnippet_after_build($form, &$form_state) {
  // Use this one if the field is placed on top of the form.
  _mysnippet_fix_disabled($form['field_myfield']);
  // Use this one if the field is placed inside a fieldgroup.
//  _mysnippet_fix_disabled($form['group_mygroup']['field_myfield']);
  return $form;
}

/**
 * Recursively set the disabled attribute of a CCK field
 * and all its dependent FAPI elements.
 */
function _mysnippet_fix_disabled(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _mysnippet_fix_disabled($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['disabled'] = 'disabled';
}

Explanation:

Setting the #disabled attribute of the element in form_alter doesn't work because the FAPI process handler of the CCK field won't transfer the #disabled attribute to its children elements. So we need to find a different method...

We can do this using our own #after_build handler, but here we cannot simple set the $element['#disabled'] attribute of the element. The reason is this attribute is transformed into $element['#attributes']['disabled'] by _form_builder_handle_input_element executes(), which is the format FAPI theme functions know about. However, _form_builder_handle_input_element executes() is executed before #after_build handlers are invoked, so our own handler needs to set the value as the theme functions expect. See form_builder().

Finally, we need to use a recursive function so that we can set the disabled attribute of all the FAPI elements that depend on the CCK field we're interested in. Think for example about a checkboxes element, etc.

Caveats:

Disabled attributes are not sent to the server with the form. The $_POST array will not have any value for disabled elements, and this may cause unexpected results. Also, if the element is required, will see the error 'field field is required.'.

Possible workaround:

Ok, now we're going to make a copy of the field that we want to disable, we will disable that one, and set the type of the original field to 'hidden'. I'm not sure if this technique will work for any kind of field, but here's a simple example that works:

Let's disable the node title. Well, to say something useful, let's imagine that our custom code also wants to assign a title to the node programmatically. In this case, we'll use the same code as above, but replacing the after_build function with this one:

/**
 * Custom after_build callback handler.
 */
function _mysnippet_after_build($form, &$form_state) {
  // Let's check the title exist.
  if (isset($form['title'])) {

    // Replace the element with an array that contains 2 copies.
    $form['title'] = array(
        'title' => $form['title'],
        '_disabled_title' => $form['title'],
    );

    // Now, we change the type of the original field to 'hidden'.
    $form['title']['title']['#type'] = 'hidden';

    // Now, we can use our recursive function to set the disabled attribute
    // or the field and all its own child elements. The title doesn't have children
    // but maybe another field that you are interested in does.
    _mysnippet_fix_disabled($form['title']['_disabled_title']);
  }
  return $form;
}

Now, we have a form where the 'visible' title element is disabled. The original field is hidden, which is the one that will be sent to the server with the form, and so we won't see the 'field is required' error. :)

Another possible workaround:

Here we're going to copy the '#default_value' attribute of the element to the '#value' attribute. Depending on the type of field, this method may also work, and it is actually a bit more simple.

/**
 * Custom after_build callback handler.
 */
function _mysnippet_after_build($form, &$form_state) {
  // Let's check the title exist.
  if (isset($form['title'])) {

    // Explicitly set the #value attribute of the element.
    $form['title']['#value'] = $form['title']['#default_value'];

    // Now, disable the element and its own children.
    _mysnippet_fix_disabled($form['title']);
  }
  return $form;
}

Comments

pcambra’s picture

Works great thanks Markus

Does anybody know how to keep the default elements selected (i.e. a multiselect) with the element disabled?

Thanks

dazmcg’s picture

or how to change any other attributes of the element? Like #default_value?

thanks!

markus_petrux’s picture

You should be able to alter #default_value attribute for any element in the form directly from hook_form_alter().

function mymodule_form_alter(&$form, $form_state, $form_id) {
  // Is this the node edit form?
  if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
    if ( /* whatever else condition to find the form you're interested in */ ) {
      // The field is at the top level of the form?
      if (isset($form['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['field_myfield']['#default_value'] = 'myvalue';
      }
      // The field is inside a fieldgroup?
      if (isset($form['group_mygroup']['field_myfield'])) {
        // Assign here whatever suits your needs.
        $form['group_mygroup']['field_myfield']['#default_value'] = 'myvalue';
      }
    }
  }
}

However, you should be sure that your module is loaded after the one that generates the form you intend to alter. You can do this by altering the weight of the module in the {system} table from the hook_install() of your module. See the fieldgroup module in CCK to see an example of this.

/**
 * Something like this should be placed in your mymodule.install
 */
function mymodule_install() {
  db_query("UPDATE {system} SET weight = 10 WHERE name = 'mymodule'");
}

Doubt is the beginning, not the end of wisdom.

ppmax’s picture

Thanks for this snippet--very useful.

I have a form with a cck field dependency:
field1: required
field2: required only if a value selected in field1 evaluates to TRUE

I set up both cck fields as required, but then based upon user input for field1 I hide and disable field2 using jquery.

Unfortunately, setting the attribute of field2 to "disabled" doesnt work, and the form fails validation because field2 is "still" required.

My question:
How would one use this technique in the context of javascript? How do I dynamically (client-side) set '#required' to FALSE?

thx
pp

markus_petrux’s picture

'#required' is FormAPI attribute so that's what you need to alter, server side. And this would require a different technique, I'm afraid. I would post in the forums so that the book page doesn't mix too much different stuff which end up hard to find later.

Doubt is the beginning, not the end of wisdom.

gausarts’s picture

I have a slightly different need. I want to remove the title field without autonodetitle. Can we do the unset then?

I tried placing unset($form['title']['_disabled_title']['#title']); and unset($form['title']['title']['#title']); after $form['#after_build'][] = '_mymodule_after_build'; on mymodule_form_alter together with the above code, but each didn't do anything. The title is just disabled, but not disappear. Any hint?

Thanks

Update:
Placing either unset($form['title']); or $form['title'] = array(); on mymodule_form_alter did the trick. But I wonder if it's the right thing to do?

love, light n laughter

doublejosh’s picture

Not getting much success setting the default value...

function _my_module_after_build($form, &$form_state) {
  _my_module_default_clear($form['field_url'][0]);
  return $form;
}

function _my_module_default_clear(&$elements) {
  $elements['#default_value']= 'http://';
}

function my_module_form_alter(&$form, $form_state, $form_id) {
  if($form_id=='post_node_form') {
    $form['field_url'][0]['#after_build'][] = '_my_module_after_build';
  }
}

(doing so because cck links don't allow a default of 'http://' as it's not a valid link)

swatee_karpe’s picture

Please help me to set #attributes = array('onClick' => 'javascript:fun();');

I use drupal 5x and create cck field say payment with two radio buttons by cc and by check.
so i want to give a javascript function call on click of by cc button.

so i m not getting how to set this already created button using UI.

omerida’s picture

Another option, if you want the value submitted via POST but not editable is to use the readonly attribute instead of disabled.

Use the following in your after_build function

/**
* Custom after_build callback handler.
*/
function _mysnippet_after_build($form, &$form_state) {
  // Use this one if the field is placed on top of the form.
  _mysnippet_fix_readonly($form['field_myfield']);
  // Use this one if the field is placed inside a fieldgroup.
//  _mysnippet_fix_readonly($form['group_mygroup']['field_myfield']);
  return $form;
}

/**
* Recursively set the readonly attribute of a CCK field
* and all its dependent FAPI elements.
*/
function _mysnippet_fix_readonly(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _mysnippet_fix_readonly($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['readonly'] = 'readonly';
}

http://musketeers.me/ - Creating next-generation websites with precision and care.
http://www.phparch.com

geshan’s picture

yes readonly is the best work around. thanks.

joblo’s picture

Additionally you can style the readonly inputs for example with colored background in your themes stylesheet:


input[readonly="readonly"] {
    background-color:red;
}

zeezhao’s picture

Thanks for the code. I got it to work for text fields. However some fields like: date, user list, select lists did not...

Please do you know the best way to make other fields readonly?

texas-bronius’s picture

THANKS for sharing this snippet. I would like to expound a bit that if your D6 cck field is within a field group, you'll need to reference the field to pass into this snippet by that field group path as in:

     _mysnippet_set_field_attribute($form['group_quote_attr']['field_quote_leaseterm']);

for the group name "group_quote_attr".

Also, to allow more flexibility and reuse, I added two more params, Attribute Name and Attribute Value to the recursive function and all places that call it:

   ...
     _mysnippet_set_field_attribute($form['group_quote_attr']['field_quote_leaseterm'], 'readonly', 'readonly');
   ...
...
function _mysnippet_set_field_attribute(&$elements, $attribute, $value) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _mysnippet_set_field_attribute($elements[$key], $attribute, $value);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes'][$attribute] = $value;
}

--
http://drupaltees.com
80s themed Drupal T-Shirts

TomSherlock’s picture

Thanks for taking the time and effort to cobble this code together.

I have a field set to accept up to 20 values. I would like to disable the input fields that already have a value while leaving the remaining empty input fields writable.

First i attempted to use your sample code. But I can't seem to get it to work. Perhaps you can assist?

I have taken your sample code and simply changed the 'mysnippet' to my own module and 'field_myfield' to my own field.



/**
* @file
* Custom module to set the disabled attribute of CCK fields.
*/

/**
* Implementation of hook_form_alter().
*/
function myhallowsoc_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    // Use this check to match node edit form for a particular content type.
    if ('hallsocgroup_1beta_node_form' == $form_id) {
        
        $output .= dsm($form_state);
        $output .= dsm($form_id);
      $form['#after_build'][] = '_myhallowsoc_after_build';
    }
    // Use this check to match node edit form for any content type.
//    if ($form['type']['#value'] .'_node_form' == $form_id) {
//      $form['#after_build'][] = '_mysnippet_after_build';
//    }
  }
}

/**
* Custom after_build callback handler.
*/
function _myhallowsoc_after_build($form, &$form_state) {
  // Use this one if the field is placed on top of the form.
  _myhallowsoc_fix_disabled($form['field_hs_volunteers_b1']);
  // Use this one if the field is placed inside a fieldgroup.
//  _mysnippet_fix_disabled($form['group_mygroup']['field_myfield']);
  return $form;
}

/**
* Recursively set the disabled attribute of a CCK field
* and all its dependent FAPI elements.
*/
function _myhallowsoc_fix_disabled(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _myhallowsoc_fix_disabled($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['disabled'] = 'disabled';
}


When i look at the resultant source through Firefox's 'page source', i can see the attribute 'disabled' set to the value of 'disabled'. However the field in question is not actually disabled. The field is populate by selecting a username through autocomplete.

Here is what i see in 'page source':


<tr class="draggable odd"><td class="content-multiple-drag"></td><td><div class="form-item" id="edit-field-hs-volunteers-b1-0-uid-uid-wrapper">
 <input type="text" name="field_hs_volunteers_b1[0][uid][uid]" id="edit-field-hs-volunteers-b1-0-uid-uid" size="60" value="Boooon Coooooof" class="form-text form-autocomplete text" />
</div>
<input class="autocomplete" type="hidden" id="edit-field-hs-volunteers-b1-0-uid-uid-autocomplete" value="http://lincolnschoolpta.dev/userreference/autocomplete/field_hs_volunteers_b1" disabled="disabled" /></td><td class="delta-order"><div class="form-item" id="edit-field-hs-volunteers-b1-0--weight-wrapper">
 <select name="field_hs_volunteers_b1[0][_weight]" class="form-select field_hs_volunteers_b1-delta-order" id="edit-field-hs-volunteers-b1-0--weight" ><option value="-19">-19</option><option value="-18">-18</option><option value="-17">-17</option><option value="-16">-16</option><option value="-15">-15</option><option value="-14">-14</option><option value="-13">-13</option><option value="-12">-12</option><option value="-11">-11</option><option value="-10">-10</option><option value="-9">-9</option><option value="-8">-8</option><option value="-7">-7</option><option value="-6">-6</option><option value="-5">-5</option><option value="-4">-4</option><option value="-3">-3</option><option value="-2">-2</option><option value="-1">-1</option><option value="0" selected="selected">0</option><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="19">19</option></select>

</div>
</td> </tr>
 <tr class="draggable even"><td class="content-multiple-drag"></td><td><div class="form-item" id="edit-field-hs-volunteers-b1-1-uid-uid-wrapper">
 <input type="text" name="field_hs_volunteers_b1[1][uid][uid]" id="edit-field-hs-volunteers-b1-1-uid-uid" size="60" value="Ciiid Heees" class="form-text form-autocomplete text" />
</div>
<input class="autocomplete" type="hidden" id="edit-field-hs-volunteers-b1-1-uid-uid-autocomplete" value="http://lincolnschoolpta.dev/userreference/autocomplete/field_hs_volunteers_b1" disabled="disabled" /></td>

</div>
</td> </tr>



TomSherlock’s picture

I tried the 'readonly' work around, but the attribute stills shows as "disabled".

The following is through Safari's View Source:


<input class="autocomplete autocomplete-processed" type="hidden" id="edit-field-hs-volunteers-b1-0-uid-uid-autocomplete" value="http://lincolnschoolpta.dev/userreference/autocomplete/field_hs_volunteers_b1" disabled="disabled">

cchill319’s picture

I understand why you did it, but try taking the 'field_' prefixes off your field names. If you look at your $field variable you'll see that you're directly altering the object without any extra Drupaly syntax behind the scenes. (Do the same for any fieldset 'group_' prefixes).

In my case the object members look like this:

[path] => Array
        (
           [#type] => fieldset
           ...
           [path] => Array
                    (
                        [#type] => textfield
                        ...
                    )
       )

My version of the code (for content type pr) looks like this:

function pr_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && isset($form['#node'])) {
    if (!isset($form['type']['#value']) || empty($form['type']['#value'])) {
      $form['type']['#value'] = 'pr';
    }
     if (isset($form['#node']) && isset($form['type']['#value']) && $form_id==$form['type']['#value'] .'_node_form') {
      $form['#after_build'][] = '_pr_after_build';      
    }    
  }
}
function _pr_after_build($form, &$form_state) {
  // if field is placed at top level of the form
  // _pr_set_readonly($form['{fieldname}']);
  // if the field is placed inside a fieldgroup.
  _pr_set_readonly($form['path']['path']);
  return $form;
}
function _pr_set_readonly(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      // recurse through all children elements.
      _pr_set_readonly($elements[$key]);
    }
  }
  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
  $elements['#attributes']['readonly'] = 'readonly';
}

Hope this helps.

asmdec’s picture

My question is: Why the FAPI process handler of the CCK field won't transfer the #disabled attribute to its children elements? It's not possible?

I suggested a fix in nodereference_select module (in optionwidgets too) to pass down the #disabled attribute to the children elements.

In nodereference_select_process function we have the following code and comment:

...
    // The following values were set by the content module and need
    // to be passed down to the nested element.
    '#title' => $element['#title'],
    '#required' => $element['#required'],
    '#description' => $element['#description'],
    '#field_name' => $element['#field_name'],
    '#type_name' => $element['#type_name'],
    '#delta' => $element['#delta'],
    '#columns' => $element['#columns'],
...

Why couldn't we set the '#disabled' attribute here too??

Grayside’s picture

Because that is where the form is built for all nodereference widgets. There are two things wrong with changing it there.

  1. It would affect every nodereference input widget, not just the field you want to target.
  2. Hacking the module directly is just that- a dirty hack. Even an awkward workaround that is entirely contained within a custom module is better than needing to keep tracking of hacked code you need to modify with every module release.
johnhanley’s picture

I ran into this issue today. That is, needing to disable a couple of CCK fields when editing a node.

I would ordinarily do something like the following for the given $form_id in hook_form_alter():

      if ($form['nid']['#value']) {
        $form['field_school']['#disabled'] = TRUE;
        $form['field_schedule']['#disabled'] = TRUE;
      }

After double and triple checking my syntax I became suspicious and thankfully found this thread.

Thanks so much for posting this workaround. It's not at all obvious and saved me a bunch of time.

Grayside’s picture

Replace _mysnippet_fix_disabled() with the following:

function _mysnippet_set_denied(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      // Recurse through all children elements.
      _mysnippet_set_denied($elements[$key]);
    }
  }
  $elements['#access'] = FALSE;
}
TomSherlock’s picture

Is it possible to disable a single option from optionwidgets_buttons? The Allowed Values are a string instead of an array.

TomSherlock’s picture

Hi, I'm back here again.

I want to be able to either disable or remove an option. I've been able to write code to remove an option, but encounter the error 'An illegal choice has been detected'.

Only so many users are able to select an option. Once that max is reached the option is either to be disabled or removed. The above error is thrown because _form_validate is looking for the removed option.

johnhanley’s picture

The following should suppress the "An illegal choice..." message and allow the submission to continue.

$form['myfield']['#validated'] = TRUE;

However removing an option is potentially dicey because you risk losing a user's previous selection. I would reconsider your approach.

zeezhao’s picture

Hi Grayside. Thanks for your code.

When I use it to remove a date field within a multigroup, I get an "invalid date" error on the field when the user tries to save changes.

I have a mutligroup with fields like: date, user list, select lists, etc, so I am trying to hide the group when user logged in is different for what the user list is showing. It works well with all the other fields, except the date field.

Please do you have any suggestions on how to get around this?

I also tried the read-only alternative code published in the thread. That works only on text fields, and does not work on : date, user list, select lists...

Thanks

Clément’s picture

I tried this solution but it does not work for my case:

I would like on an episode of a node, display a field but we can not change it. This solution does that very well by cons when recording and publishing, the field is empty.

Example:
When creating the node I put in the field: Test

When editing the field is gray: Test

When recording the fields of publishing is empty!!! I would like my fields have the values: Test

Thank you for your help!

ljrweb’s picture

Seems like something that would have an easy solution for by now..

- I have a "select" dropdown form field.

- I would like to add a javascript function that fires "onChange" or "onSelect"
- ex: alert('good choice!');

It really takes 3 functions to do that?!?

ionmedia’s picture

try Ajax trigger module

bentonboomslang’s picture

Solved my problem well. This was not an obvious solution.
B

WildKitten’s picture

subscribed

ionmedia’s picture

is there any ready to use module for setting up attributes of cck text fields? i want to set disabled for some fields, filled by default or via php/ajax callback

operations’s picture

Hi all,
I found a solution for my problem here: http://stackoverflow.com/questions/1191113/disable-select-form-field-but...

I had two required cck fields, they were not submitted when i use the attribute "disabled" , so I used this "readonly" instead as referred in the above link..

$elements['#attributes']['disabled'] = 'disabled';
=>
$elements['#attributes']['readonly'] = 'readonly';

supadits’s picture

If you want to disable the date cck field, we will have to use the workaround#1 as mention about because the browser has to send the value back so the field validation will not display any error.

Note. the date cck field support multi-value, (the From date and To date). However, the code below only take care the From date


function _mysnippet_after_build($form, &$form_state) {
  // copy all value of the original date field to another field.
  $form['group_name']['field_date_name'][0]['value']['date1'] = $form['group_name']['field_date_name'][0]['value']['date'];

  // hide the original date field
  $form['group_name']['field_date_name'][0]['value']['date']['#type']='hidden';

  // then we pass the new date field, 'date1', to the disable function. this will show the disabled form to user
  _mysnippet_fix_disabled($form['group_name']['field_date_name'][0]['value']['date1']);

  return $form;
}

bledari’s picture

Im I the only one that thinks it would be better to have cck refactored instead of solving things hacking? This should be an issue to cck module. Drupal, has a long way to go.

Grayside’s picture

@bledari: Drupal has an extremely sophisticated Forms building & modifying system that is used for all modules. The reason modifying CCK this way is so complicated is because CCK itself is very complicated.

bledari’s picture

I agree cck code is quite complicated, and I think it should for a core module BUT the module mantainers/creator could easily fix this I suppose. We can submit this as a feature request too. Has anyone done this? We cannot have a core module that cannot disable form elements without writing 20 lines of not obvious code, don't you think?

kutter’s picture

This is an excellent example, and I fought like hell with this myself for a few days.

I came across another alternative that I figured I'd mention here in case others ran into similar - use hook_field_access.

It removes the field entirely, so default values aren't saved, so you still need to work around that, but to me it's a great deal easier than mucking about with the form, and there are no validation errors as a result of the field being missing.

function mymodule_field_access($op, $field, $account, $node){
  switch($op){
    case 'view':
      return TRUE;
      break;
    case 'edit':
      if ($node->type == 'member' && in_array('Member', $account->roles)){
        switch($field['field_name']){
          case 'field_sponsor_level':
          case 'field_member_since':
          case 'field_inactive':
            return FALSE;
            break;
        }
      }
      break;
  }
  return TRUE;
}
arski’s picture

For all those trying to set onclick, onfocus, onblur etc attributes to CCK fields, what you need to do is add your own _after_build function as described above, and then use the following syntax:

$form['field_myfield'][0]['value']['#attributes']['onclick'] = 'return true;';

Note the ['value'] in the middle, something you wouldn't normally expect..

Enjoy.

brunorios1’s picture

and the select fields (combobox)?

they are disabled, but if i remove "disabled = disabled" using firebug, i can change them and submit the new values...

i tried:

  _mysnippet_fix_disabled($form['field_place_city']);
    $form['field_place_city'][0]['#value']['value'] = $form['field_place_city']['#default_value'][0]['value'];

but doesn't works...

thanks!

mauari’s picture

<span style="text-transform: uppercase" type='text'>
TEXT TO DISPLAY IN UPERCASE HERE
</span>

AnY oNe CaN tYpE aNy MoDo In ThE InPuT fIeLd ThIs LiNe MaKe DiSpLaY aLl In UpErCaSe
<span style="text-transform: uppercase" type='text'> ANY ONE CAN TYPE ANY MODE IN THE INPUT FIELD THIS LINE MAKE DISPLAY ALL IN UPERCASE

faster and furios....

ashdrupalnew’s picture

I have implemented the above code but when I submit form on node edit some of my cck fields lost its data. Here is my code

function quadrupal_form_alter(&$form,$form_state,$form_id)
{

  //$type = $form['type']['#value'];
  if ($form_id == 'dealers_node_form')
  {
    $form['#after_build'][] = '_quadrupal_after_build';
   }
}


/**
* Custom after_build callback handler.
*/
function _quadrupal_after_build($form, &$form_state) {
  // Use this one if the field is placed on top of the form.
  _quadrupal_fix_disabled($form['field_dealerid']);
  _quadrupal_fix_disabled($form['field_dealername']);
  _quadrupal_fix_disabled($form['field_dealeraddress']);
  _quadrupal_fix_disabled($form['field_dealeraddress1']);
  _quadrupal_fix_disabled($form['field_dealercity']);
  _quadrupal_fix_disabled($form['field_dealerzip']);
  _quadrupal_fix_disabled($form['field_dealerstate']);
  // Use this one if the field is placed inside a fieldgroup.
//  _mysnippet_fix_disabled($form['group_mygroup']['field_myfield']);
  return $form;
}


/**
* Recursively set the disabled attribute of a CCK field
* and all its dependent FAPI elements.
*/
function _quadrupal_fix_disabled(&$elements) {
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {

      // Recurse through all children elements.
      _quadrupal_fix_disabled($elements[$key]);
    }
  }

  if (!isset($elements['#attributes'])) {
    $elements['#attributes'] = array();
  }
 // $elements['#attributes']['disabled'] = 'disabled';
  $elements['#attributes']['readonly'] = 'readonly';
}

Is their any step I am missing?

Dru-Pal’s picture

From all the ideas on this page (and some others as well, mostly cited here) I could put some code together that avoids the implications above. Like the loss of data that was inadvertently removed from the form structure.
(First of all be warned that disabling form inputs does not hamper forgery of the submitted data in the wild. Sensitive values, e.g. an order total, must always be stored or recalculated on the server side!)

In all the cases where you want form inputs to display their values purely informative however, the following code will hopefully also work for you.


/**
* A way to disable CCK and other form fields.
*/

/**
* Implementation of hook_form_alter().
*/
function mymodule_form_alter( &$form, $form_state, $form_id ) {

  // somewhere in hook_form_alter you might want to
  
  // disable a plain input
  $form['field_ordersum']['#after_build'][] = '_after_build_readonly';

  // or one inside a cck group
  $form['group_dishes']['field_chowmein']['#after_build'][] = '_after_build_readonly';

  // or the entire cck group in one go
  $form['group_dishes']['#after_build'][] = '_after_build_readonly';

  // or even the entire form with all controls, 
  // including the form buttons.
  $form['#after_build'][] = '_after_build_readonly';

}

/**
* Custom after_build callback handler.
*/
function _after_build_readonly( $form, &$form_state ) {

  _disable_components( $form );

  return $form;
}

/**
* Recursively set the disabled attribute to all
* basic (html) elements below the given FAPI structure
* and only to those.
*/
function _disable_components( &$elements ) {

  foreach ( element_children($elements) as $key ) {
    if ( isset($elements[$key]) && $elements[$key] ) {

      // Recurse all children
      _disable_components( $elements[$key] );
    }
  }

  if ( !isset($elements['#type']) ) return;

  // You may want to extend the list below for your specific purposes.

  // Attribute and value must comply with what the html standard
  // says about the resp. element.
  switch ( $elements['#type'] ) {
    case 'textfield':
    case 'textarea':
    case 'password':
      $attr = 'readonly'; $value = 'readonly';
      break;

    case 'select':
    case 'radio':
    case 'option':
    case 'checkbox':
    case 'submit':
    case 'button':
    case 'file':
      $attr = 'disabled'; $value = 'disabled';
      break;

    default:
      return;
  }

  if ( !isset($elements['#attributes']) ) {
    $elements['#attributes'] = array();
  }

  $elements['#attributes'][$attr] = $value;
}

silviogutierrez’s picture

I found a solution that worked for me, as none of the examples above met all my requirements. That is, I could set things as readonly, but the immutability was not enforced. You could just right click and edit with Firebug and save.

Making CCK fields read-only in Drupal 6

Junro’s picture

Hello,

First snippet from Markus works fine, but I would like to use it only on nodes edit form:
When you add a new node, you can fill the field, but you can't modify it after, on nodes edit form.

Any idea?
Thanks

Abhishek Sawant’s picture

Did anyone got this working on node edit form only?

Thanks

Abhishek Sawant
Drupal Developer

itapplication’s picture

Any solution for D7?

Drupal developer

Drupal Theme developer.

Shivcharan

johnhanley’s picture

Setting read-only field attribute for Drupal 7:

function custom_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'custom_node_form':
      // field set to read-only
      $form['field_custom']['#disabled'] = TRUE;
      break;
  }
}

Replace above "custom" with your module, form_id and field definitions respectively.