hacker has asked for the wisdom of the Perl Monks concerning the following question:

I've been doing a lot of reading on how to do this, hidden fields, server-side sessions, and a whole lot of other implementation ideas. Basically hidden fields is the best way I've seen so far to maintain "storage" of the field data between invocations. I don't need to store this data anywhere on the server side, only need to pass it on until the completion of the form, then dump it to the browser window as a configuration file which the user can then use from that point forward.

First, I must preface this with the fact that I did my background research. I've read merlyn's article on Handling Multipage Forms, I've checked SuperSearch and looked for other nodes on hidden fields, I've tried the example in the CGI docs, I have Lincoln's book, checked the c.l.p.m. newsgroup and FAQ, and read many other tutorials and docs on the matter. My issue may be more one of design than actual implementation, so here goes:

I have a long form, about 30 fields, which includes radio_group, popup_menu, and textarea entry fields. The form currently works, monolithically. Not ideal. Each 'section' of the form is divided by task (Identity options, Image options, Gather options, and so on). What I'd prefer to do is separate this into a several-step "wizard" type of interface, where values in the previous step are validated, and passed on to the next step, building on the previous step as I go along. The atypical "Next, Next, Next, Finish" type of interface.

A few design criteria to note: This form is actually part of a much larger website, driven by a single CGI. Clicking on the 'option' which brings up the form is going to be something like index.pl?action=form in this case. I'm not sure if that presents a problem when submitting form elements back to the same CGI, executing the same subroutine (sub form {...}), but that element of design cannot change.

In pseudocode, the current proposed design looks similar to this:

sub form { start_form; # This would be the first page they see when entering # the form for the first time, no param passed into # the subroutine at this point, first time entry if (!param) { Ask user for URL (param('url')); Ask user for Title (param('title')); Next/Reset form buttons; } # This would be the validation of the first form, which # is only shown when the elements in the first page # above are validated and completed if (param('url') && param('title') { validate input (only allow ftp, http, gopher) test that the URL is up (HEAD on url) retrieve Content-Type and Content-Length from url if (($url =~ /.pdf$/i) || ($url =~ /.doc$/i)) { if ($type =~ "pdf") { # turn into pdf:// URI syntax $url =~ s/^\w+/pdf/m; } elsif ($type =~ "doc") { # turn into doc:// URI syntax $url =~ s/^\w+/doc/m; } convert any spaces in 'title' to underscores } if (the first page was filled out and validated) { start_second_page_form Ask user for maxdepth to gather (param('maxdepth')) Ask user for heuristics on URL (param('ag')) Next/Back/Reset form buttons } # ...and so on }

Note that this form must be executed top-down, and all that I've read seems to confirm this. It must also all be within one subroutine, since I can see no other way, unless I change form action to be index.pl?action=form&page=1 and then pass the returns to index.pl?action=form&page=2 and so on. This form has about 7 pages total, based on the logical breakdown and grouping of questions.

My question is.. what is the best way to organize this information, store off the hidden variables from $previous_form and gain access to them progressively as the user walks through the form? If each form "page" had 5 elements, on page_2, I'd have access to 5 elements (from page_1), on page_3, I'd have access to 10 elements from page_1 and page_2, and so on, until all 'n' elements are available as hidden elements in the last page of the form, where the user selects the final 'Submit'.

Each page's form elements have specific things that must be done to them, validate the URL, transform user input, convert/uri_escape the input, etc. This is not simply a text input form with flat, static content in it (as in Randall's article or the CGI docs).

Any ideas/code snippets/modules I can use to help me here? I've got the standalone bits working, validation of the entry (LWP::UserAgent, File::MMagic and LWP::MediaTypes, URI::Escape, and others). I just can't seem to wrap my head around how to design the actual "Next...Next...Next" progression inside one subroutine in a logical way.

Replies are listed 'Best First'.
Re: Multiple-page form wizard questions
by Corion (Patriarch) on Jul 04, 2002 at 11:15 UTC

    The time I wrote such a wizard, I followed the following criteria :

    • All data is collected within the wizard until the last page via hidden fields. This took away the burden of server-side session management and allowed for easy automation, as any program would simply have to feed the last page of the wizard with all the data in one request.
    • All pages had names (instead of numbers), which I then correlated to the functions that displayed the page via a hash. This allowed me to nicely group the interesting stuff of one page together in one place in the code.
    • The navigation had to be via submit buttons, as otherwise the passing of hidden fields wouldn't have worked. This of course broke the navigational features of the browsers, but in a way that didn't create maintenance or diagnosis headaches.

    The resulting wizard framework was nice, as adding another page meant adding another sub, connecting that sub to the name of the page and inserting the name of the page in the sequence of pages. Adding more fields to the wizard also only meant to make the new variables known to the wizard (so that they would survive changing the page) and adding a way to set those values.

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
(wil) Re: Multiple-page form wizard questions
by wil (Priest) on Jul 04, 2002 at 12:16 UTC
    Take a good look around CPAN and you should find CGI::FormMagick. Here's a snippet from the POD documentation, which I think sums up what you're trying to achieve:
    FormMagick is a toolkit for easily building fairly complex form-based web applications. It allows the developer to specify the structure of a multi-page "wizard" style form using XML, then display that form using only a few lines of Perl.

    The version that is there on CPAN now was released on the 24th June 2002, and there is also an example and a tutorial on how to use it. This should overcome your problem of having to "rewrite it anyway to bring it up to 2002 module functionality"

    - wil
Re: Multiple-page form wizard questions
by caedes (Pilgrim) on Jul 04, 2002 at 10:15 UTC
    Here's a bit of pseudo-code that I think will work nicely. Note that the use of CGI.pm might not be exactly right, but you should be about to get an idea of what I'm thinking.
    print form_start; # cycle through all the previous hidden fields plus the # newest form for($cgi->param){ #only pass the fields in %good_fields next unless $good_fields{$_}; #write the good_fields fields into hidden inputs print $cgi->input( -type=>'hidden', -name=>$_, -value=>$cgi->param($_)); } # print out the next form in the sequence based on the # last 'next' hidden field print get_next_form($cgi->param('next')); print end_form;

    -caedes

Re: Multiple-page form wizard questions
by jepri (Parson) on Jul 04, 2002 at 11:42 UTC
    When I did something like this I went the completely opposite - server side storage and cookies. And I had a page for every single script. That sounds horrendeous, but it worked quite well after I made a module to include in each script. It checked cookies, connected to the database, etc. Then each cookie just contained the form validating bits, and was usually no more than 50 lines.

    It also turned out to be a good idea because the client kept changing his mind on bits, and I had the flexibility to accomodate him.

    Also, having done this before I would recommend keeping away from monolithic routines like that unless you really like dealing with if statements nested five or ten levels deep.

    HTH, and I have a funny feeling I've seen a CPAN module to automatically handle the hidden fields thing. ovid was into this a while ago, you might want to browse through his old code posts too.

    ____________________
    Jeremy
    I didn't believe in evil until I dated it.