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

Monks,

In my cgi script, I have this question for the user which asks them to pick a filename which they want their report saved as. Here is the code which asks the question:
use CGI qw/:standard/; use CGI::Carp qw( fatalsToBrowser ); "7.) What do you want to name the report:", textfield('report'),
and I want the script to die if the user does not supply a filename. I thought this little piece of code would work:
# Make sure a report name is defined my $report = param('report'); if (!defined($report)) { die "You must supply a filename.\n"; }
but when I initially bring up the page, it dies with the error "You must supply a filename."

My questions are:
  1. Does the variable get assigned a value even if it is left blank?
  2. If so, what can I do to keep it empty until the user hits the "Submit" button?

Thanks,
Dru

Replies are listed 'Best First'.
Re: Why is my CGI.pm script trying to assign data before pressing submit.
by TGI (Parson) on Dec 07, 2001 at 04:54 UTC

    I'll try to answer your question, but first a few curmudgeonly comments:

    1. From your node title, it sounds like you may be making a classic new CGI programmer's thought error. You are not coding a continuous process. Each request your CGI script gets runs as a separate, unique invokation of your script. The last person I taught CGI scripting had to be reminded of this fact about 1000 times before she got it.
    2. Be very careful taking filenames as input from a cgi. There are many nodes about this. Check out ovid's cgi tutorial for a treatment of this. Also read perlsec.
    3. Are you familiar with the state machine model of CGI programming? (Try a Super Search for "state machine" more info.) The idea is that your CGI script needs a way to remember what part of the interactive process it is presenting.

      Your script would probably have three states, let's call them "edit", "preview", and "commit".

      • edit: The edit state should be the defualt state. The script will present a form for the user to fill out. Pressing submit will request that the script is run as preview.
      • preview: This state can lead to either edit or commit. It should print the user's responses to the form in an easy to read format.
      • commit: Writes the file and displays a message, like "file foo saved" probably has a link back to the edit state.
      "Well and good, but how do organize this?" you might be thinking. The most common method is to use a parameter with a name like "state" or "action". You can then define the behavior for each state by using a big fat case statement. There is no native case statement in perl, but a chain of elsif statements works very nicely.

      # Set state to edit if not defined. param(state)||param(name=>'state', value=>'edit'); if (param('state') eq 'edit') { # display the form print start_form('POST',self_url()); # blah blah blah print submit('state','preview'), end_form(); } elsif (param('state' eq 'preview') { # display the results, prettily. print start_form('POST',self_url()), # A bunch of hidden fields to pass our data along hidden(parameter-n, value), # This button commits the data to a file. submit('state','commit'), # This button returns to the edit form, to make changes. submit('state','edit'), end_form(); } elsif ( param('state') eq 'commit') { # Save the file # Print some nice messages. } else { # General purpose error. You should never wind up here # Trap it just in case. }

      I personally, prefer to use here docs for embedding HTML in my perl. I find CGI.pm's HTML generation cumbersome. But some people really like it, TIMTOWTDI. I always use CGI.pm for parameter management though.

      # A sample here doc print <<"END_OF_HTML"; Blah laalbalalsdfasdf asdf blaha END_OF_HTML


      TGI says moo

      I find CGI.pm's HTML generation cumbersome.

      That's a relief, I thought it was just me.
      andy.

      In cases like this, it can be cleaner to use a hash lookup table:
      my %lookup = (edit => sub { let_the_user_edit_it() }, preview => sub { preview_it() }, commit => sub { commit_to_get_fit() }); $lookup{param('state')}->();


      --
      my one true love

        I thought I'd stick with the basic technique of an if else chain, to keep things simple on the perl end of things. But, if you don't mind working with references (which are something every perl programmer should learn to use), a hash of subroutine refs is a most excellent technique.

        my %lookup = (edit => \&let_the_user_edit_it, preview => \&preview_it, commit => \&commit_to_get_fit, ERROR => \&non_existant_state ); if exists $lookup{param('state')} { $lookup{param('state')}->(); } else { $lookup{ERROR)}->(); }

        I made a couple of changes to your code. Storing the subroutine addresses directly, saves one round of subroutine dispatch, a negligable performance savings, but it removes 1 level of indirection and saves on typing--to my mind, it's tidier. I also check for the existance of the hash key before trying to execute its code reference. This lets you give a better feedback in your error page than a plain 500 error usually does.


        TGI says moo

Re: Why is my CGI.pm script trying to assign data before pressing submit.
by Purdy (Hermit) on Dec 07, 2001 at 03:11 UTC
    It sounds like you have both snippets of code in the same script, in which case, your program logic is a bit off (the user isn't given a chance to supply the filename). What I usually do in this type of situation is use hidden fields. I'm not sure offhand how to do that with CGI.pm, but something like so:

    print '<INPUT TYPE="HIDDEN" NAME="PROCESS" VALUE="PARTTWO"><BR>';

    Then in your script, you check if the script is being processed for someone that is just coming to your program for the first time or if they hit submit and using your program to process their input.

    if ( param('PROCESS') ) { # They're using your form to process their input... # Now you can check if the filename is given if (!defined($report)) { die ...; } } else { # They're using your program for the first time print "7.) What do you want to name ..."; }

    HTH,

    Jason

Re: Why is my CGI.pm script trying to assign data before pressing submit.
by Moonie (Friar) on Dec 07, 2001 at 04:04 UTC
    The problem is that when the page is created - the value is not defined. It would be best to check whether the parameter is defined and the value is null or the length of the parameter is 0 after the user submits the page OR do what Purdy suggests! :)
    - Moon
Re: Why is my CGI.pm script trying to assign data before pressing submit.
by Rex(Wrecks) (Curate) on Dec 07, 2001 at 04:29 UTC
    You could always toss it into a while loop.

    while (!defined $report) { $count=$count+1 if ($count == 1) { "7.) What do you want to name the report:", textfield('report'), } else { "7.) You MUST Supply report Name:", textfield('report'), } }


    Just a thought, obviously the if/else would have to do the other things you are attempting.

    "Nothing is sure but death and taxes" I say combine the two and its death to all taxes!
Re: Why is my CGI.pm script trying to assign data before pressing submit.
by Ardenstone (Acolyte) on Dec 07, 2001 at 06:02 UTC
    The posts above did a fine job of already explaining what (looks to be) wrong. However, another solution for this would be a bit of JavaScript instead of Perl (am I allowed to say that here?). Actually, you need both especially since JavaScript doesn't work on all browsers. Whatever script gets the input from the form needs to check the entry but you can be nice to your users and warn them as soon as they hit "submit." Try the following JavaScript:
    function checkform (form) { if (form.report.value == "") { alert("You need to enter a filename!"); form.report.focus; return false; } return true; }

    Inside your initial form tag (I'm not sure how to add this with CGI.pm), you'll need to add:
    onsubmit="return checkform(this);"
    Hope this helps,
    Ardenstone

      With CGI.pm you would need these:

      use CGI; my $p = new CGI; my $js_func = qq| function checkform (form) { if (form.report.value == '') { alert('You need to enter a filename!'); form.report.focus; return false; } return true; } |;

      Place the script in $p->start_html using -script.

      print $p->start_html( -script => $js_func );

      Then, when you start the form, use:

      print $p->start_form(-onsubmit => 'checkform(this)');


      HTH,
      Charles