Making conditional fields with Drupal, CCK, and jQuery.
by MatteusX
I'll be posting this as a handbook page at Drupal.org.
The Conditional Fields (http://drupal.org/project/conditional_fields) module will work in many simple cases (using the basic CCK types) but if you are using more advanced types or want more control, keep reading.
In this example I have a CCK number field (displayed as a checkbox) which when checked will make a CCK text field visible.
The text field will not be required though, and to make it conditionally required is something I will hopefully cover at a later date.
I am working with the Page content type which Drupal comes with.
First I add my CCK fields to the Page content type:
Label: Membership (This won't appear in the form, but will appear in the node view)
Field name: member (giving us a full field name of field_member)
Type: integer
Form element: Single on/off checkbox
Save, then change these additional values:
Allowed values:
0|I am not a club member
1|I am a club member
Now we want a conditional text field to ask for member number:
Label: Member number
Field name: member_number (Giving us a full field name of field_member_number)
Type: Text (I'm treating the member number as text)
Form element: text field
Save, then save additional settings with no changes.
Go to add a new content type to see if the fields look correct. Right now you'll see both fields, but we're about to change that.
We need to add some JavaScript to the page which will initially hide the text field and then show it when the checkbox is clicked. We will use jQuery because it comes with Drupal and makes tasks like these easier to write and maintain.
There are a number of ways to add JavaScript in Drupal. We can add JavaScript site-wide with an entry in our theme's themename.info file. We could also add JavaScript to specific forms with a custom module that implements hook_form_alter and calls drupal_add_js. This was traditionally my approach until I learned that JavaScript added via hook_form_alter will not be added back during form validation. So, the JavaScript would run initially, but if there were validation errors then it would fail to be included. For more information, this is described here http://www.appnovation.com/drupaladdjs-and-drupaladdcss-hookformalter-and-hookform-drupal-6.
So, like that link suggests we're going to handle this in our theme's template.php file like so:
MYTHEME is the name of your theme so replace it accordingly.
Implement hook_theme to register a theme function for "page_node_form" which is the form ID of the "Create Page" form. If you're working with a different content type, like "product" then it would be "product_node_form" for you. You can find the form ID by viewing the source in your browser, searching for "form_id" and finding the hidden field with that name. It's value is the form ID. For example, mine is:
<input type="hidden" name="form_id" id="edit-page-node-form" value="page_node_form" />
So, my hook_theme is:
function MYTHEME_theme($existing, $type, $theme, $path) {
return array(
'page_node_form' => array(
'arguments' => array('form' => NULL),
),
);
}
Also, be aware that if your theme already has a hook_theme implementation you will want to add this entry to the existing array.
Now we need to create that theme function we just defined. Again, replace MYTHEME with the name of your theme:
function MYTHEME_page_node_form($form) {
drupal_add_js(path_to_theme() . '/test.js');
return drupal_render($form);
}
And here at last we call drupal_add_js to look for a file called test.js in the current theme's directory.
Save your template.php and go make this test.js file in the same directory.
In test.js put the following for now:
Drupal.behaviors.page_node_form = function() {
alert('test');
};
I chose the name page_node_form here - it could be anything and definitely has no connection to the form ID. Using Drupal.behaviors is always a good idea, you can read more about it here: ??
Save test.js. Now we have to clear the template cache (go to admin/settings/performance and click clear cached data button). Or clear the template cache by using the admin_menu and devel modules which is what I prefer to do.
Finally, you can visit the create content page for your content type and you should get an alert message that says test. Click submit with everything blank to cause a validation failure, and you should still get an alert message.
Now we have our JavaScript in place and can start writing that.
jQuery uses CSS identifiers heavily, so we'll need to know the ID of the actual checkbox as well as the ID of the div which wraps our text field. Firefox's Firebug is great for this, and in my case my checkbox looks like:
<input type="checkbox" class="form-checkbox" value="1" id="edit-field-member-value" name="field_member[value]">
So, the ID is edit-field-member-value.
When checked, I don't just want to hide the text input field but the label and the div which wraps it. I want to hide all of this:
<div id="edit-field-member-number-0-value-wrapper" class="form-item">
<label for="edit-field-member-number-0-value">Member number: </label>
<input type="text" class="form-text text" value="" size="60" id="edit-field-member-number-0-value" name="field_member_number[0][value]">
</div>
and the ID which is assigned to that outer div is edit-field-member-number-0-value-wrapper.
Now we can write the JS. Open up test.js and replace it with:
Drupal.behaviors.page_node_form = function() {
if (!$('#edit-field-member-value').is(':checked')) {
$('#edit-field-member-number-0-value-wrapper').hide();
}
$('#edit-field-member-value').click(function(e) {
if ($(this).is(':checked')) {
$('#edit-field-member-number-0-value-wrapper').show();
} else {
$('#edit-field-member-number-0-value-wrapper').hide();
}
});
};
Here's a breakdown of what is happening:
if (!$('#edit-field-member-value').is(':checked')) {
$('#edit-field-member-number-0-value-wrapper').hide();
}
This hides the text field once the page loads but only if the checkbox is not checked. If the checkbox defaults to unchecked then how could this happen? It could happen during validation if the user has already checked the box. In that case we want to show this conditional field. Also, I'm avoiding hiding the field initially with CSS or at the theme level because this will degrade if JavaScript is disabled and still let the entire form work.
$('#edit-field-member-value').click(function(e) {
This binds a click event to the checkbox itself. The following 5 lines of code run when it's clicked.
if ($(this).is(':checked')) {
We know the checkbox was clicked, but not what state that click has left it in. This tests if it is now checked.
$('#edit-field-member-number-0-value-wrapper').show();
This causes the text field (and its label and container) to become visible
$('#edit-field-member-number-0-value-wrapper').hide();
If the checkbox is now unchecked then this hides the text field.
Finally, I like to add a little additional code so that when the checkbox gets unchecked the text field gets cleared out. My final version of the test.js file is:
Drupal.behaviors.page_node_form = function() {
if (!$('#edit-field-member-value').is(':checked')) {
$('#edit-field-member-number-0-value-wrapper').hide();
}
$('#edit-field-member-value').click(function(e) {
if ($(this).is(':checked')) {
$('#edit-field-member-number-0-value-wrapper').show();
} else {
$('#edit-field-member-number-0-value-wrapper').hide();
$('#edit-field-member-number-0-value').val('');
}
});
};
You'll find that this works in the Edit form as well. Happy Drupaling!