Diff for /loncom/interface/lonhelper.pm between versions 1.4 and 1.5

version 1.4, 2003/03/28 20:25:19 version 1.5, 2003/04/10 18:02:09
Line 30 Line 30
 # (.helper handler  # (.helper handler
 #  #
   
   # FIXME: Change register calls to register with the helper.
   # Then have the helper reg and unreg the tags.
   # This removes my concerns about breaking other code.
   
 =pod  =pod
   
 =head1 lonhelper - HTML Helper framework for LON-CAPA  =head1 lonhelper - HTML Helper framework for LON-CAPA
Line 46  directory and having the .helper file ex Line 50  directory and having the .helper file ex
   
 All classes are in the Apache::lonhelper namespace.  All classes are in the Apache::lonhelper namespace.
   
 =head2 lonxml  
   
 The helper uses the lonxml XML parsing support. The following capabilities  
 are directly imported from lonxml:  
   
 =over 4  
   
 =item * <startouttext> and <endouttext>: These tags may be used, as in problems,  
         to directly output text to the user.  
   
 =back  
   
 =head2 lonhelper XML file format  =head2 lonhelper XML file format
   
 A helper consists of a top-level <helper> tag which contains a series of states.  A helper consists of a top-level <helper> tag which contains a series of states.
Line 72  of the helper itself, such as "Parameter Line 64  of the helper itself, such as "Parameter
 State tags are required to have an attribute "name", which is the symbolic  State tags are required to have an attribute "name", which is the symbolic
 name of the state and will not be directly seen by the user. The wizard is  name of the state and will not be directly seen by the user. The wizard is
 required to have one state named "START", which is the state the wizard  required to have one state named "START", which is the state the wizard
 will start with. by convention, this state should clearly describe what  will start with. By convention, this state should clearly describe what
 the helper will do for the user, and may also include the first information  the helper will do for the user, and may also include the first information
 entry the user needs to do for the helper.  entry the user needs to do for the helper.
   
Line 239  sub new { Line 231  sub new {
                   
         my $file = Apache::File->new($self->{FILENAME});          my $file = Apache::File->new($self->{FILENAME});
         my $contents = <$file>;          my $contents = <$file>;
         &Apache::loncommon::get_unprocessed_cgi($contents);  
           # Now load in the contents
           for my $value (split (/&/, $contents)) {
               my ($name, $value) = split(/=/, $value);
               $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
               $self->{VARS}->{$name} = $value;
           }
   
         $file->close();          $file->close();
     } else {      } else {
         # Only valid if we're just starting.          # Only valid if we're just starting.
Line 297  sub _varsInFile { Line 296  sub _varsInFile {
     return join ('&', @vars);      return join ('&', @vars);
 }  }
   
   # Use this to declare variables.
   # FIXME: Document this
   sub declareVar {
       my $self = shift;
       my $var = shift;
   
       if (!defined($self->{VARS}->{$var})) {
           $self->{VARS}->{$var} = '';
       }
   
       my $envname = 'form.' . $var . '.forminput';
       if (defined($ENV{$envname})) {
           $self->{VARS}->{$var} = $ENV{$envname};
       }
   }
   
 sub changeState {  sub changeState {
     my $self = shift;      my $self = shift;
     $self->{STATE} = shift;      $self->{STATE} = shift;
Line 393  HEADER Line 408  HEADER
         $result .= "</center>\n";          $result .= "</center>\n";
     }      }
   
       foreach my $key (keys %{$self->{VARS}}) {
           $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
       }
   
     $result .= <<FOOTER;      $result .= <<FOOTER;
               </td>                </td>
             </tr>              </tr>
Line 503  element named "formName" and place the s Line 522  element named "formName" and place the s
 variable named varName. This is for things like checkboxes or   variable named varName. This is for things like checkboxes or 
 multiple-selection listboxes where the user can select more then   multiple-selection listboxes where the user can select more then 
 one entry. The selected entries are delimited by triple pipes in   one entry. The selected entries are delimited by triple pipes in 
 the helper variables, like this:  CHOICE_1|||CHOICE_2|||CHOICE_3  the helper variables, like this:  
   
    CHOICE_1|||CHOICE_2|||CHOICE_3
   
 =back  =back
   
 =cut  =cut
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::element',
                                 ('nextstate'));
   }
   
 # Because we use the param hash, this is often a sufficent  # Because we use the param hash, this is often a sufficent
 # constructor  # constructor
 sub new {  sub new {
Line 527  sub new { Line 553  sub new {
     return $self;      return $self;
 }     }   
   
   sub start_nextstate {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       
       $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
                                                                $parser);
       return '';
   }
   
   sub end_nextstate { return ''; }
   
 sub preprocess {  sub preprocess {
     return 1;      return 1;
 }  }
Line 546  sub process_multiple_choices { Line 586  sub process_multiple_choices {
   
     my $formvalue = $ENV{'form.' . $formname};      my $formvalue = $ENV{'form.' . $formname};
     if ($formvalue) {      if ($formvalue) {
         # Must extract values from $wizard->{DATA} directly, as there          # Must extract values from querystring directly, as there
         # may be more then one.          # may be more then one.
         my @values;          my @values;
         for my $formparam (split (/&/, $wizard->{DATA})) {          for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
             my ($name, $value) = split(/=/, $formparam);              my ($name, $value) = split(/=/, $formparam);
             if ($name ne $formname) {              if ($name ne $formname) {
                 next;                  next;
Line 558  sub process_multiple_choices { Line 598  sub process_multiple_choices {
             $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;              $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
             push @values, $value;              push @values, $value;
         }          }
         $helper->setVar($var, join('|||', @values));          $helper->{VARS}->{$var} = join('|||', @values);
     }      }
           
     return;      return;
Line 573  package Apache::lonhelper::message; Line 613  package Apache::lonhelper::message;
 =head2 Element: message  =head2 Element: message
   
 Message elements display the contents of their <message_text> tags, and  Message elements display the contents of their <message_text> tags, and
 transition directly to the state in the <next_state> tag. Example:  transition directly to the state in the <nextstate> tag. Example:
   
  <message>   <message>
    <next_state>GET_NAME</next_state>     <nextstate>GET_NAME</nextstate>
    <message_text>This is the <b>message</b> the user will see,      <message_text>This is the <b>message</b> the user will see, 
                  <i>HTML allowed</i>.</message_text>                   <i>HTML allowed</i>.</message_text>
    </message>     </message>
   
 This will display the HTML message and transition to the <next_state> if  This will display the HTML message and transition to the <nextstate> if
 given. The HTML will be directly inserted into the wizard, so if you don't  given. The HTML will be directly inserted into the wizard, so if you don't
 want text to run together, you'll need to manually wrap the <message_text>  want text to run together, you'll need to manually wrap the <message_text>
 in <p> tags, or whatever is appropriate for your HTML.  in <p> tags, or whatever is appropriate for your HTML.
   
   Message tags do not add in whitespace, so if you want it, you'll need to add
   it into states. This is done so you can inline some elements, such as 
   the <date> element, right between two messages, giving the appearence that 
   the <date> element appears inline. (Note the elements can not be embedded
   within each other.)
   
 This is also a good template for creating your own new states, as it has  This is also a good template for creating your own new states, as it has
 very little code beyond the state template.  very little code beyond the state template.
   
Line 597  use strict; Line 643  use strict;
   
 BEGIN {  BEGIN {
     &Apache::lonxml::register('Apache::lonhelper::message',      &Apache::lonxml::register('Apache::lonhelper::message',
                               ('message', 'next_state', 'message_text'));                                ('message', 'message_text'));
 }  }
   
 # Don't need to override the "new" from element  sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
 # CONSTRUCTION: Construct the message element from the XML  # CONSTRUCTION: Construct the message element from the XML
 sub start_message {  sub start_message {
Line 617  sub end_message { Line 666  sub end_message {
     return '';      return '';
 }  }
   
 sub start_next_state {  sub start_message_text {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;      my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
   
       $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',
                                                                  $parser);
   }
           
     $paramHash->{NEXT_STATE} = &Apache::lonxml::get_all_text('/next_state',  sub end_message_text { return 1; }
                                                              $parser);  
   sub render {
       my $self = shift;
   
       return $self->{MESSAGE_TEXT};
   }
   # If a NEXTSTATE was given, switch to it
   sub postprocess {
       my $self = shift;
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   }
   1;
   
   package Apache::lonhelper::choices;
   
   =pod
   
   =head2 Element: choices
   
   Choice states provide a single choice to the user as a text selection box.
   A "choice" is two pieces of text, one which will be displayed to the user
   (the "human" value), and one which will be passed back to the program
   (the "computer" value). For instance, a human may choose from a list of
   resources on disk by title, while your program wants the file name.
   
   <choices> takes an attribute "variable" to control which helper variable
   the result is stored in.
   
   <choices> takes an attribute "multichoice" which, if set to a true
   value, will allow the user to select multiple choices.
   
   B<SUB-TAGS>
   
   <choices> can have the following subtags:
   
   =over 4
   
   =item * <nextstate>state_name</nextstate>: If given, this will cause the
         choice element to transition to the given state after executing. If
         this is used, do not pass nextstates to the <choice> tag.
   
   =item * <choice />: If the choices are static,
         this element will allow you to specify them. Each choice
         contains  attribute, "computer", as described above. The
         content of the tag will be used as the human label.
         For example,  
         <choice computer='234-12-7312'>Bobby McDormik</choice>.
   
   <choice> may optionally contain a 'nextstate' attribute, which
   will be the state transisitoned to if the choice is made, if
   the choice is not multichoice.
   
   =back
   
   To create the choices programmatically, either wrap the choices in 
   <condition> tags (prefered), or use an <exec> block inside the <choice>
   tag. Store the choices in $state->{CHOICES}, which is a list of list
   references, where each list has three strings. The first is the human
   name, the second is the computer name. and the third is the option
   next state. For example:
   
    <exec>
       for (my $i = 65; $i < 65 + 26; $i++) {
           push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
       }
    </exec>
   
   This will allow the user to select from the letters A-Z (in ASCII), while
   passing the ASCII value back into the helper variables, and the state
   will in all cases transition to 'next'.
   
   You can mix and match methods of creating choices, as long as you always 
   "push" onto the choice list, rather then wiping it out. (You can even 
   remove choices programmatically, but that would probably be bad form.)
   
   FIXME: Document and implement <exec> and <condition> in the element package.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::choices',
                                 ('choice', 'choices'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_choices {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       # Need to initialize the choices list, so everything can assume it exists
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
       $paramHash->{CHOICES} = [];
     return '';      return '';
 }  }
   
 sub end_next_state { return ''; }  sub end_choices {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
 sub start_message_text {      if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::choices->new();
       return '';
   }
   
   sub start_choice {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;      my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
     if ($target ne 'helper') {      if ($target ne 'helper') {
         return '';          return '';
     }      }
   
     $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',      my $computer = $token->[2]{'computer'};
                                                                $parser);      my $human = &Apache::lonxml::get_all_text('/choice',
                                                 $parser);
       my $nextstate = $token->[2]{'nextstate'};
       push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate];
       return '';
   }
   
   sub end_choice {
       return '';
 }  }
   
   sub render {
       # START HERE: Replace this with correct choices code.
       my $self = shift;
       my $var = $self->{'variable'};
       my $buttons = '';
       my $result = '';
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value) {
    for (i=0; i<document.forms.wizform.elements.length; i++) {
               document.forms.wizform.elements[i].checked=value;
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br />
   <input type="button" onclick="checkall(true)" value="Select All" />
   <input type="button" onclick="checkall(false)" value="Unselect All" />
   <br />
   BUTTONS
       }
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       $result .= $buttons;
   
       $result .= "<table>\n\n";
   
       my $type = "radio";
       if ($self->{'multichoice'}) { $type = 'checkbox'; }
       my $checked = 0;
       foreach my $choice (@{$self->{CHOICES}}) {
           $result .= "<tr>\n<td width='20'>&nbsp;</td>\n";
           $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
               . "' value='" . 
               HTML::Entities::encode($choice->[1]) 
               . "'";
           if (!$self->{'multichoice'} && !$checked) {
               $result .= " checked ";
               $checked = 1;
           }
           $result .= "/></td><td> " . $choice->[0] . "</td></tr>\n";
       }
       $result .= "</table>\n\n\n";
       $result .= $buttons;
   
       return $result;
   }
   
   # If a NEXTSTATE was given or a nextstate for this choice was
   # given, switch to it
   sub postprocess {
       my $self = shift;
       my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
   
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
           
 sub end_message_text { return 1; }      foreach my $choice (@{$self->{CHOICES}}) {
           if ($choice->[1] eq $chosenValue) {
               if (defined($choice->[2])) {
                   $helper->changeState($choice->[2]);
               }
           }
       }
   }
   1;
   
   package Apache::lonhelper::date;
   
   =pod
   
   =head2 Element: date
   
   Date elements allow the selection of a date with a drop down list.
   
   Date elements can take two attributes:
   
   =over 4
   
   =item * B<variable>: The name of the variable to store the chosen
           date in. Required.
   
   =item * B<hoursminutes>: If a true value, the date will show hours
           and minutes, as well as month/day/year. If false or missing,
           the date will only show the month, day, and year.
   
   =back
   
   Date elements contain only an option <nextstate> tag to determine
   the next state.
   
   Example:
   
    <date variable="DUE_DATE" hoursminutes="1">
      <nextstate>choose_why</nextstate>
      </date>
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   use Time::localtime;
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::date',
                                 ('date'));
   }
   
   # Don't need to override the "new" from element
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   my @months = ("January", "February", "March", "April", "May", "June", "July",
         "August", "September", "October", "November", "December");
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_date {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
   }
   
   sub end_date {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::date->new();
       return '';
   }
   
 sub render {  sub render {
     my $self = shift;      my $self = shift;
       my $result = "";
       my $var = $self->{'variable'};
   
       my $date;
       
       # Default date: The current hour.
       $date = localtime();
       $date->min(0);
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       # Month
       my $i;
       $result .= "<select name='${var}month'>\n";
       for ($i = 0; $i < 12; $i++) {
           if ($i == $date->mon) {
               $result .= "<option value='$i' selected>";
           } else {
               $result .= "<option value='$i'>";
           }
           $result .= $months[$i] . "</option>\n";
       }
       $result .= "</select>\n";
   
       # Day
       $result .= "<select name='${var}day'>\n";
       for ($i = 1; $i < 32; $i++) {
           if ($i == $date->mday) {
               $result .= '<option selected>';
           } else {
               $result .= '<option>';
           }
           $result .= "$i</option>\n";
       }
       $result .= "</select>,\n";
   
       # Year
       $result .= "<select name='${var}year'>\n";
       for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
           if ($date->year + 1900 == $i) {
               $result .= "<option selected>";
           } else {
               $result .= "<option>";
           }
           $result .= "$i</option>\n";
       }
       $result .= "</select>,\n";
   
       # Display Hours and Minutes if they are called for
       if ($self->{'hoursminutes'}) {
           # Build hour
           $result .= "<select name='${var}hour'>\n";
           $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
               " value='0'>midnight</option>\n";
           for ($i = 1; $i < 12; $i++) {
               if ($date->hour == $i) {
                   $result .= "<option selected value='$i'>$i a.m.</option>\n";
               } else {
                   $result .= "<option value='$i'>$i a.m</option>\n";
               }
           }
           $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
               " value='12'>noon</option>\n";
           for ($i = 13; $i < 24; $i++) {
               my $printedHour = $i - 12;
               if ($date->hour == $i) {
                   $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
               } else {
                   $result .= "<option value='$i'>$printedHour p.m.</option>\n";
               }
           }
   
           $result .= "</select> :\n";
   
           $result .= "<select name='${var}minute'>\n";
           for ($i = 0; $i < 60; $i++) {
               my $printedMinute = $i;
               if ($i < 10) {
                   $printedMinute = "0" . $printedMinute;
               }
               if ($date->min == $i) {
                   $result .= "<option selected>";
               } else {
                   $result .= "<option>";
               }
               $result .= "$printedMinute</option>\n";
           }
           $result .= "</select>\n";
       }
   
       return $result;
   
     return $self->{MESSAGE_TEXT};  
 }  }
 # If a NEXT_STATE was given, switch to it  # If a NEXTSTATE was given, switch to it
 sub postprocess {  sub postprocess {
     my $self = shift;      my $self = shift;
     if (defined($self->{NEXT_STATE})) {      my $var = $self->{'variable'};
         $helper->changeState($self->{NEXT_STATE});      my $month = $ENV{'form.' . $var . 'month'}; 
       my $day = $ENV{'form.' . $var . 'day'}; 
       my $year = $ENV{'form.' . $var . 'year'}; 
       my $min = 0; 
       my $hour = 0;
       if ($self->{'hoursminutes'}) {
           $min = $ENV{'form.' . $var . 'minute'};
           $hour = $ENV{'form.' . $var . 'hour'};
       }
   
       my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
       # Check to make sure that the date was not automatically co-erced into a 
       # valid date, as we want to flag that as an error
       # This happens for "Feb. 31", for instance, which is coerced to March 2 or
       # 3, depending on if it's a leapyear
       my $checkDate = localtime($chosenDate);
   
       if ($checkDate->mon != $month || $checkDate->mday != $day ||
           $checkDate->year + 1900 != $year) {
           $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
               . "date because it doesn't exist. Please enter a valid date.";
           return;
       }
   
       $helper->{VARS}->{$var} = $chosenDate;
   
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
     }      }
 }  }
 1;  1;
   
   package Apache::lonhelper::resource;
   
   =pod
   
   =head2 Element: resource
   
   <resource> elements allow the user to select one or multiple resources
   from the current course. You can filter out which resources they can view,
   and filter out which resources they can select. The course will always
   be displayed fully expanded, because of the difficulty of maintaining
   selections across folder openings and closings. If this is fixed, then
   the user can manipulate the folders.
   
   <resource> takes the standard variable attribute to control what helper
   variable stores the results. It also takes a "multichoice" attribute,
   which controls whether the user can select more then one resource.
   
   B<SUB-TAGS>
   
   =over 4
   
   =item * <filterfunc>: If you want to filter what resources are displayed
     to the user, use a filter func. The <filterfunc> tag should contain
     Perl code that when wrapped with "sub { my $res = shift; " and "}" is 
     a function that returns true if the resource should be displayed, 
     and false if it should be skipped. $res is a resource object. 
     (See Apache::lonnavmaps documentation for information about the 
     resource object.)
   
   =item * <choicefunc>: Same as <filterfunc>, except that controls whether
     the given resource can be chosen. (It is almost always a good idea to
     show the user the folders, for instance, but you do not always want to 
     let the user select them.)
   
   =item * <nextstate>: Standard nextstate behavior.
   
   =item * <valuefunc>: This function controls what is returned by the resource
     when the user selects it. Like filterfunc and choicefunc, it should be
     a function fragment that when wrapped by "sub { my $res = shift; " and
     "}" returns a string representing what you want to have as the value. By
     default, the value will be the resource ID of the object ($res->{ID}).
   
   =back
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::resource',
                                 ('resource', 'filterfunc', 
                                  'choicefunc', 'valuefunc'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   # CONSTRUCTION: Construct the message element from the XML
   sub start_resource {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       return '';
   }
   
   sub end_resource {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       if (!defined($paramHash->{FILTER_FUNC})) {
           $paramHash->{FILTER_FUNC} = sub {return 1;};
       }
       if (!defined($paramHash->{CHOICE_FUNC})) {
           $paramHash->{CHOICE_FUNC} = sub {return 1;};
       }
       if (!defined($paramHash->{VALUE_FUNC})) {
           $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
       }
       Apache::lonhelper::resource->new();
       return '';
   }
   
   sub start_filterfunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/filterfunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{FILTER_FUNC} = eval $contents;
   }
   
   sub end_filterfunc { return ''; }
   
   sub start_choicefunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/choicefunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{CHOICE_FUNC} = eval $contents;
   }
   
   sub end_choicefunc { return ''; }
   
   sub start_valuefunc {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/valuefunc',
                                                   $parser);
       $contents = 'sub { my $res = shift; ' . $contents . '}';
       $paramHash->{VALUE_FUNC} = eval $contents;
   }
   
   sub end_valuefunc { return ''; }
   
   # A note, in case I don't get to this before I leave.
   # If someone complains about the "Back" button returning them
   # to the previous folder state, instead of returning them to
   # the previous helper state, the *correct* answer is for the helper
   # to keep track of how many times the user has manipulated the folders,
   # and feed that to the history.go() call in the helper rendering routines.
   # If done correctly, the helper itself can keep track of how many times
   # it renders the same states, so it doesn't go in just this state, and
   # you can lean on the browser back button to make sure it all chains
   # correctly.
   # Right now, though, I'm just forcing all folders open.
   
   sub render {
       my $self = shift;
       my $result = "";
       my $var = $self->{'variable'};
       my $curVal = $helper->{VARS}->{$var};
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       my $filterFunc = $self->{FILTER_FUNC};
       my $choiceFunc = $self->{CHOICE_FUNC};
       my $valueFunc = $self->{VALUE_FUNC};
   
       # Create the composite function that renders the column on the nav map
       # have to admit any language that lets me do this can't be all bad
       #  - Jeremy (Pythonista) ;-)
       my $checked = 0;
       my $renderColFunc = sub {
           my ($resource, $part, $params) = @_;
           
           if (!&$choiceFunc($resource)) {
               return '<td>&nbsp;</td>';
           } else {
               my $col = "<td><input type='radio' name='${var}.forminput' ";
               if (!$checked) {
                   $col .= "checked ";
                   $checked = 1;
               }
               $col .= "value='" . 
                   HTML::Entities::encode(&$valueFunc($resource)) 
                   . "' /></td>";
               return $col;
           }
       };
   
       $ENV{'form.condition'} = 1;
       $result .= 
           &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, 
                                                     Apache::lonnavmaps::resource()],
                                          'showParts' => 0,
                                          'url' => $helper->{URL},
                                          'filterFunc' => $filterFunc,
                                          'resource_no_folder_link' => 1 }
                                          );
                                                   
       return $result;
   }
       
   sub postprocess {
       my $self = shift;
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   }
   
   1;
   
   package Apache::lonhelper::student;
   
   =pod
   
   =head2 Element: student
   
   Student elements display a choice of students enrolled in the current
   course. Currently it is primitive; this is expected to evolve later.
   
   Student elements take two attributes: "variable", which means what
   it usually does, and "multichoice", which if true allows the user
   to select multiple students.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::student',
                                 ('student'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_student {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
   }    
   
   sub end_student {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       Apache::lonhelper::student->new();
   }
   
   sub render {
       my $self = shift;
       my $result = '';
       my $buttons = '';
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value) {
    for (i=0; i<document.forms.wizform.elements.length; i++) {
               document.forms.wizform.elements[i].checked=value;
           }
       }
   </script>
   SCRIPT
           $buttons = <<BUTTONS;
   <br />
   <input type="button" onclick="checkall(true)" value="Select All" />
   <input type="button" onclick="checkall(false)" value="Unselect All" />
   <br />
   BUTTONS
       }
   
       if (defined $self->{ERROR_MSG}) {
           $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
       }
   
       # Load up the students
       my $choices = &Apache::loncoursedata::get_classlist();
   
       my @keys = keys %{$choices};
   
       # Constants
       my $section = Apache::loncoursedata::CL_SECTION();
       my $fullname = Apache::loncoursedata::CL_FULLNAME();
   
       # Sort by: Section, name
       @keys = sort {
           if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
               return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
           }
           return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
       } @keys;
   
       my $type = 'radio';
       if ($self->{'multichoice'}) { $type = 'checkbox'; }
       $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
       $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
           "<td align='center'><b>Section</b></td></tr>";
   
       my $checked = 0;
       foreach (@keys) {
           $result .= "<tr><td><input type='$type' name='" .
               $self->{'variable'} . '.forminput' . "'";
               
           if (!$self->{'multichoice'} && !$checked) {
               $result .= " checked ";
               $checked = 1;
           }
           $result .=
               " value='" . HTML::Entities::encode($_)
               . "' /></td><td>"
               . HTML::Entities::encode($choices->{$_}->[$fullname])
               . "</td><td align='center'>" 
               . HTML::Entities::encode($choices->{$_}->[$section])
               . "</td></tr>\n";
       }
   
       $result .= "</table>\n\n";
       $result .= $buttons;    
       
       return $result;
   }
   
   1;
   
   package Apache::lonhelper::files;
   
   =pod
   
   =head2 Element: files
   
   files allows the users to choose files from a given directory on the
   server. It is always multichoice and stores the result as a triple-pipe
   delimited entry in the helper variables. 
   
   Since it is extremely unlikely that you can actually code a constant
   representing the directory you wish to allow the user to search, <files>
   takes a subroutine that returns the name of the directory you wish to
   have the user browse.
   
   files accepts the attribute "variable" to control where the files chosen
   are put. It accepts the attribute "multichoice" as the other attribute,
   defaulting to false, which if true will allow the user to select more
   then one choice. 
   
   <files> accepts three subtags. One is the "nextstate" sub-tag that works
   as it does with the other tags. Another is a <filechoice> sub tag that
   is Perl code that, when surrounded by "sub {" and "}" will return a
   string representing what directory on the server to allow the user to 
   choose files from. Finally, the <filefilter> subtag should contain Perl
   code that when surrounded by "sub { my $filename = shift; " and "}",
   returns a true value if the user can pick that file, or false otherwise.
   The filename passed to the function will be just the name of the file, 
   with no path info.
   
   =cut
   
   no strict;
   @ISA = ("Apache::lonhelper::element");
   use strict;
   
   BEGIN {
       &Apache::lonxml::register('Apache::lonhelper::files',
                                 ('files', 'filechoice', 'filefilter'));
   }
   
   sub new {
       my $ref = Apache::lonhelper::element->new();
       bless($ref);
   }
   
   sub start_files {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       $paramHash->{'variable'} = $token->[2]{'variable'};
       $helper->declareVar($paramHash->{'variable'});
       $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
   }    
   
   sub end_files {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       if (!defined($paramHash->{FILTER_FUNC})) {
           $paramHash->{FILTER_FUNC} = sub { return 1; };
       }
       Apache::lonhelper::files->new();
   }    
   
   sub start_filechoice {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
       $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
                                                                 $parser);
   }
   
   sub end_filechoice { return ''; }
   
   sub start_filefilter {
       my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   
       if ($target ne 'helper') {
           return '';
       }
   
       my $contents = Apache::lonxml::get_all_text('/filefilter',
                                                   $parser);
       $contents = 'sub { my $filename = shift; ' . $contents . '}';
       $paramHash->{FILTER_FUNC} = eval $contents;
   }
   
   sub end_filefilter { return ''; }
   
   sub render {
       my $self = shift;
       my $result = '';
       my $var = $self->{'variable'};
       
       my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
       my $subdir = &$subdirFunc();
   
       my $filterFunc = $self->{FILTER_FUNC};
       my $buttons = '';
   
       if ($self->{'multichoice'}) {
           $result = <<SCRIPT;
   <script>
       function checkall(value) {
    for (i=0; i<document.forms.wizform.elements.length; i++) {
               ele = document.forms.wizform.elements[i];
               if (ele.type == "checkbox") {
                   document.forms.wizform.elements[i].checked=value;
               }
           }
       }
   </script>
   SCRIPT
           my $buttons = <<BUTTONS;
   <br /> &nbsp;
   <input type="button" onclick="checkall(true)" value="Select All" />
   <input type="button" onclick="checkall(false)" value="Unselect All" />
   <br /> &nbsp;
   BUTTONS
       }
   
       # Get the list of files in this directory.
       my @fileList;
   
       # If the subdirectory is in local CSTR space
       if ($subdir =~ m|/home/([^/]+)/public_html|) {
           my $user = $1;
           my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
           @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
       } else {
           # local library server resource space
           @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
       }
   
       $result .= $buttons;
   
       $result .= '<table border="0" cellpadding="1" cellspacing="1">';
   
       # Keeps track if there are no choices, prints appropriate error
       # if there are none. 
       my $choices = 0;
       my $type = 'radio';
       if ($self->{'multichoice'}) {
           $type = 'checkbox';
       }
       # Print each legitimate file choice.
       for my $file (@fileList) {
           $file = (split(/&/, $file))[0];
           if ($file eq '.' || $file eq '..') {
               next;
           }
           my $fileName = $subdir .'/'. $file;
           if (&$filterFunc($file)) {
               $result .= '<tr><td align="right">' .
                   "<input type='$type' name='" . $var
               . ".forminput' value='" . HTML::Entities::encode($fileName) .
                   "'";
               if (!$self->{'multichoice'} && $choices == 0) {
                   $result .= ' checked';
               }
               $result .= "/></td><td>" . $file . "</td></tr>\n";
               $choices++;
           }
       }
   
       $result .= "</table>\n";
   
       if (!$choices) {
           $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
       }
   
       $result .= $buttons;
   
       return $result;
   }
   
   sub postprocess {
       my $self = shift;
       if ($self->{'multichoice'}) {
           $self->process_multiple_choices($self->{'variable'}.'.forminput',
                                           $self->{'variable'});
       }
       if (defined($self->{NEXTSTATE})) {
           $helper->changeState($self->{NEXTSTATE});
       }
   }
   
   1;
   
 __END__  __END__
   

Removed from v.1.4  
changed lines
  Added in v.1.5


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>