Drupal - Altering forms upside-down

My 100th blog post, and I was thinking on the topic to choose for this blog post. I had multiple in my mind, but then decided to go back to where it all started, Drupal. My first real blog entry on this site related to my Take Control module for Drupal. So, I thought what better way to complete the century with a post related to the same technology (If you are a cricket lover, like most of us here in India, you would comprehend the importance of a century much better Wink).

Okay so, let's get down to business.

Altering forms in Drupal by implementing hook_form_alter is something that comes naturally to any Drupal developer. I had myself been modifying forms created by Drupal or other add-on modules for a long time now, as per the requirements. However, its only in the last week that I have used this hook to alter forms (read turn the forms upside-down and inside-out) in such drastic ways, that I believe I have been enlightened to this Drupal hook only now.

To give you a bit of a background, I was working on a Drupal project where theming was done by a company and I was asked to help them integrate their theme into Drupal as tightly as possible. The theme developers did not had much of a experience working with Drupal before, and hence the theme was lacking its Drupal soul.

They had customized the user registration page completely. They used the Profile module to add custom fields to the registration page, and then tried to customize the rendered form with things like side-by-side fields and field re-ordering. Side-by-side fields could have been done in CSS itself, but what they did was to mix-up fields from Drupal's User and Profile modules. So, there were first-name/last-name fields, then username field (core Drupal field), then other profile fields and the email field (core Drupal field) somewhere in between. They had failed to achieve their desired registration page with Drupal, and hence they created a custom page, outside of Drupal with their layout. My task was to migrate that custom registration page into Drupal.

Well, I began with studying the code for Drupal's core User and Profile modules. I wanted to find out if those modules would be okay if I move their fields around. After some investigation, I found this was possible, so my first attempt at migrating the registration page into Drupal was as follows:

 

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function mymodule_form_user_register_alter(&$form, $form_state, $form_id) { $replacement = array(); $replacement['register_container_start'] = array ('#type'=>'markup', '#value'=>'<div class="outer"><div class="inner">'); $replacement['name'] = $form['account']['name']; $replacement['profile_first_name'] = $form['Personal Information']['profile_first_name']; $replacement['profile_last_name'] = $form['Personal Information']['profile_last_name']; $replacement['mail'] = $form['account']['mail']; $replacement['profile_subscribe'] = $form['Personal Information']['profile_subscribe']; $replacement['profile_mobile'] = $form['Personal Information']['profile_mobile']; $replacement['profile_country'] = $form['Personal Information']['profile_country']; $replacement['profile_city'] = $form['Personal Information']['profile_city']; $replacement['profile_accept'] = $form['Personal Information']['profile_accept']; $replacement['#uid'] = $form['#uid']; $replacement['register_container_end'] = array ('#type'=>'markup', '#value'=>'</div></div>'); $form = array_merge(array_slice($form, 0, 1, TRUE), $replacement, array_slice($form, 4)); }{/syntaxhighlighter}

 

 

You would notice that I picked-up fields from User and Profile modules and mixed them in a custom order, with intermingled User/Profile module fields. In the process, I also eliminated the <fieldset> which Profile module fields are nested into. Profile module fields are nested into a <fieldset> per Category for the fields, and the above codes eliminates those <fieldset>s and renders the fields from User and Profile fields inter-mixed with one another.

A very important reason the above worked was my finding that Profile module does not set '#tree' to TRUE for Profile fields. Had that been the case, it would have required more efforts than the above code to change the order of the fields. Let me explain what more you would have needed to do if you are altering forms in a similar fashion but belonging to modules that either have '#tree' as TRUE for the fields, or custom '#parents' have been defined for those fields.

For the initial rendering, such fields when re-ordered would have rendered on the page fine. But the problem would have arisen after posting-back the values from the form. The location of the posted values in $form_state depends upon the value of '#tree' and '#parents' for any form field in Drupal's Form API (FAPI). Now because you have changed the order for those fields, Drupal and the module(s) to which the altered form originally belonged would be unable to find the value of a field at its designated place in $form_state. This would have leaded to false validation errors as well as incorrect values being persisted to the database in form's validation and submit handlers respectively.

The issue did not apply to me, but in case this applies to you, a solution would be to replace all validation and submit handlers for the form with only your custom handler, that restores the form field values based on '#tree' and '#parents' and then invokes the original validation and submit handlers, something like the following:

 

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function mymodule_form_user_register_alter(&$form, $form_state, $form_id) { .... $form['#submit']=array('my_submit'); $form['#validate']=array('my_validate'); ..... } function my_validate(&$form, $form_state) { //Re-organize $form_state here according to the original #tree and #parents and set values at original positions using form_set_value. ..... //Manually invoke the original validate handlers that you overrode with my_validate } function my_submit(&$form, $form_state) { //Perform any desired pre-processing here. ..... //Manually invoke the original submit handlers that you overrode with my_submit }{/syntaxhighlighter}

I agree that the above almost amounts to hacking into the way Drupal works, but you know business requirements do not really take care about if an approach is unadvisable, if it is required, it needs to be done whatever way available.

A couple of points above would be that you would need to save original validation and submit handlers in form_alter, if you do not know explicitly all the validation and submit handlers. Secondly, you might need to also validate Drupal FAPI's built-in validations (e.g. #required) manually, because FAPI would not find the values for fields in $form_state at an expected place.

Please note that the last 3-4 paras apply only if the form being altered has fields inside <fieldset>s with '#tree' equals TRUE, or with custom '#parents' defined, and you want to re-order those fields overriding <fieldset> boundaries. Otherwise, there's no need to take all this headache of the last 3-4 paras. This is not the case with User/Profile module forms, and hence this was not applicable in my case.

Coming back to my situation, I was able to achieve the exact look-and-feel with the Drupal registration page as was required after some tweaking of CSS and some jquery code. And just when I thought that all that was required was done, the rendered form was rejected out-right to my dismay. And the reason provided was semantic inaccuracy of tags.

The theming team was not happy with the rendered html from Drupal's registration page (which used mainly <div>, <label> and <input> tags for the form markup). The theming team pointed out that their form was built with <dl>, <dt> and <dd> tags instead which expressed the semantic purpose of the form more accurately.

Arrggh!!! not this, Drupal atleast always build form in FAPI using <div> tags and never uses the ones that the theming theme wanted. But as said, what needs to be done has to be done.

So, I got back again, and with some hair-pulling, managed to override Drupal FAPI's generated html using a combination of '#after_render', '#prefix', '#suffix', '#field_prefix', '#field_suffix' and '#theme' attributes for each form element and the form itself.

The form_user_register_alter method ended-up to be a monster in terms of the number of lines of code in the method, and the things being done in it. But the theming theme was happy with the result, and consequently I too was pleased.

Never had I imagined that Drupal forms can be altered in such drastic ways before actually taking-up and executing this assignment.

 

PHP: 

Comments

How do you come up with these things? That is amazing!