Thursday, September 17, 2009

Code snippet: How to set the disabled attribute of a CCK field

Code snippet: How to set the disabled attribute of a CCK field
Drupal 6.x · No known problems
Last modified: August 4, 2009 - 01:00

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:

<?php
/**
* @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:

<?php
/**
* 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.

<?php
/**
* 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;
}
?>
‹ Code snippet: How to force uppercase in CCK fields up Code snippet: node reference multiple select without CTRL ›
» Login or register to post comments
Thanks!
ximo - January 20, 2009 - 14:35

Thanks a million for this! I couldn't believe CCK won't allow one to disable fields, but this technique did the trick.
--
Joakim Stai
NodeOne
Login or register to post comments
Works great thanks
pcambra - February 5, 2009 - 09:44

Works great thanks Markus

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

Thanks
Login or register to post comments
or how to change any other
dazmcg - March 19, 2009 - 21:00

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

thanks!
Login or register to post comments
From form_alter itself?
markus_petrux - March 19, 2009 - 22:43

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

<?php
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.

<?php
/**
* Something like this should be placed in your mymodule.install
*/
function mymodule_install() {
  db_query("UPDATE {system} SET weight = 10 WHERE name = 'mymodule'");
}
?>
Login or register to post comments
the important bit works
dazmcg - March 20, 2009 - 15:42

the important bit works :)

Setting the weight manually didn't seem to work but I haven't spent much time on that as I used the util module to set the weight and will solve that minor puzzle later.

Once the weight is set, it does indeed build the cck fields into the form, enabling me to do the things i wanted to do. thanks again markus!

Is there a better way to do this which doesn't rely on module weights? I guess one thing I could do is in the hook_install (when i've gotten it to work) is to check CCK's weight and just +1 it?

THANKS!
Login or register to post comments
Thanks for this snippet--very
ppmax - April 6, 2009 - 13:47

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
Login or register to post comments
'#required' is FormAPI attribute so you can't client-side
markus_petrux - April 6, 2009 - 13:58

'#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.
Login or register to post comments
Thanks for the reply-- After
ppmax - April 6, 2009 - 19:16

Thanks for the reply--

After posting I had a hand-slap to forehead moment when I realized my solution wouldnt work for server-side form props--duh. I'll take this to the forums--sorry for posting to the handbook page ;)

pp
Login or register to post comments
I have a slightly different
gausarts - April 10, 2009 - 23:35

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
gausarts
Login or register to post comments
Trying to set #default_value
doublejosh - May 28, 2009 - 19:47

Not getting much success setting the default value...

<?php
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)
Login or register to post comments
Try to set #attributes = array('onClick' => 'javascript:fun(););
swatee_karpe - July 25, 2009 - 13:59

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.

Swatee Amit Karpe

No comments: