Secure Dynamic Forms and Subforms

This article is a follow up to my first tutorial on the subject: A Dynamically Expanding AJAX/ AHAH Drupal Form Element . The basic flow of that article is still correct, but two month ago I could not figure out how to do dynamic forms without grabbing what I needed out of $_POST, which removes all the great FAPI security. Now I have figured it out and present the dynamic_subform module in Ahah Forms v1.5 , which provides functions for securely creating and updating dynamic forms and subforms.

As I have said a couple of times now, the Ahah side of the Ahah Forms is pretty simple, but creating the dynamic forms is tricky, especially since we want then to work well even with JavaScript disabled. At the Drupal Summit, I was fortunate to have diner with chx, Jeff Eaton, and Chad Fowler, discussing dynamic forms and security. chx was peeved with me for pulling values directly out of the $_POST in my examples and tempting innocents over to the dark side. They all came up with a great solution to this problem that will go into Drupal 6, but that isn’t much help for those of us living in a Drupal 5 world.

At first, it seemed like the solution to the problems might be the #multistep flag. For those that haven’t grokked the FormAPI security model, it is actually pretty cool. When the client posts the results of a form, the params contains the form’s form_id. Drupal then uses that form_id to rebuild the original form, and any values in the post that don’t fit into this form are rejected. This is a lot more convent than most of the Java frameworks, which make you explicitly specify the valid parameters. But it is also not very friendly to dynamic forms. #multistep is an attempt to get around this by building the form twice, once with the original parameters (which get cached in the session) and again with the new, sanitized parameters. Unfortunately #multistep is kind of broken in D5.

And, in addition to wanting a working #multistep for the whole form, I want subform handling, because I want fast Ahah swaps. It’s much faster to just validate and rebuild the part of the form you are actually interested in, rather than the entire form. My solution was to rewrite drupal_get_form, using the same basic approach as #multistep, but extending it to work with subforms and to, well, work.

Here is an example of the new functions in action, pulled from my modified version of poll:

<?php
function poll_form ( $node ) {
...
 
// establish the choices wrapper
 
$form [ 'choices' ] = array (
   
'#type' => 'item' ,
   
'#theme' => 'poll_choice_subform' ,
   
'#ahah_bindings' => array(
      array (
   
'selector' => 'input.poll_choice_remove' ,
   
'event' => 'click' ,
   
'wrapper' => 'poll_choices_wrapper' ,
   
'path' => 'poll/poll_update_js' ,
      ),
    ),           
  );
 
// actually build the widget and put it in the wrapper
   
$form [ 'choices' ] += dynamic_subform_get_embedded ( 'poll_node_form' , 'poll_choices_subform' , $node );
...
}

// called by ahah_forms.js
function _poll_update_js () {
 
$choices = dynamic_subform_get_prepped ( 'poll_node_form' , 'poll_choices_subform' , null );   
  print
theme ( 'status_messages' ) . drupal_render ( $choices );
}
?>

The exciting bit it the two new functions:
dynamic_subform_get_embedded($form_id, $subform_id, ...) is for use inside of the master form declaration and returns a simple form array.
dynamic_subform_get_prepped($form_id, $subform_id, ...) is for direct access and returns a prepped and validated form array, ready to be rendered into html by drupal_render.

Both functions emulate the #multistep trick, using the arguments cached in the session to sanitize $_POST values. The subform function: poll_choices_subform($node, $form_values=null, $pass=null), can rely on the $form_values array having been sanitized before it is handed in to it.

Ahah Forms v1.5 also includes a new example – todos. This is a stripped down version of a dynamic list, which should be easier to understand than poll module.

You can try out creating todo lists, polls and views at the demo site – I am particularly proud of how well they work when javascript is turned off.

demo site is down

it's throwing a 403 and a 500 at the same time...

Thanks for the reminder.

Thanks for the reminder. Yes, the demo site is no longer available. I should hunt down the links to it in old articles.

afterLoad binding?

Hi.
First off all I`d like to Thank You for great module:)

In my application I use forms based on your AHAH Forms module and it works great.

But there is a little thing, which can by very helpful if you will add it to the module. I think about something like "fnAfterLoad" binding. I write very simple patch:

diff -auBbr ahah_forms/ahah_forms.js /sites/ehobbysta/sites/all/modules/ahah_forms/ahah_forms.js
--- ahah_forms/ahah_forms.js 2008-11-17 17:15:39.000000000 +0100
+++ /sites/ehobbysta/sites/all/modules/ahah_forms/ahah_forms.js 2008-11-17 18:11:09.000000000 +0100
@@ -49,6 +49,9 @@
$(wrapper_id).css( 'opacity', '1' );
$('body').css("cursor", "auto");
$(element_id).attr( 'disabled', '' );
+ if(element.fnAfterLoad != "" && element.afterLoad != null){
+ eval( element.afterLoad+"()" );
+ }
} );
};

It helps if you need to add some JS to your forms.
For example I use "datepicker", which should work after every form refresh.

Best regards.
Paul Tomasiewicz

ps.
Sorry for my poor english..

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.