[system] / branches / rel-2-4-patches / pg / macros / compoundProblem.pl Repository:
ViewVC logotype

View of /branches/rel-2-4-patches/pg/macros/compoundProblem.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5730 - (download) (as text) (annotate)
Tue Jun 24 00:44:55 2008 UTC (4 years, 10 months ago) by gage
Original Path: branches/rel-2-4-dev/pg/macros/compoundProblem.pl
File size: 25894 byte(s)
merging with HEAD 6/23/2008  see Value.pm for
list of significant changes

    1 sub _compoundProblem_init {};   # don't reload this file
    2 
    3 ######################################################################
    4 #
    5 #  This package implements a method of handling multi-part problems
    6 #  that show only a single part at any one time.  The students can
    7 #  work on one part at a time, and then when they get it right (or
    8 #  under other circumstances deterimed by the professor), they can
    9 #  move on to the next part.  Students can not return to earlier parts
   10 #  once they have been completed.  The score for problem as a whole is
   11 #  made up from the scores on the individual parts, and the relative
   12 #  weighting of the various parts can be specified by the problem
   13 #  author.
   14 #
   15 #  To use the compoundProblem library, use
   16 #
   17 #      loadMacros("compoundProblem.pl");
   18 #
   19 #  at the top of your file, and then create a compoundProblem object
   20 #  via the command
   21 #
   22 #      $cp = new compoundProblem(options)
   23 #
   24 #  where '$cp' is the name of a variable that you will use to
   25 #  refer to the compound problem, and 'options' can include:
   26 #
   27 #    parts => n                The number of parts in the problem.
   28 #                                Default: 1
   29 #
   30 #    weights => [n1,...,nm]    The relative weights to give to each
   31 #                              part in the problem.  For example,
   32 #                                  weights => [2,1,1]
   33 #                              would cause the first part to be worth 50%
   34 #                              of the points (twice the amount for each of
   35 #                              the other two), while the second and third
   36 #                              part would be worth 25% each.  If weights
   37 #                              are not supplied, the parts are weighted
   38 #                              by the number of answer blanks in each part
   39 #                              (and you must provide the total number of
   40 #                              blanks in all the parts by supplying the
   41 #                              totalAnswers option).
   42 #
   43 #    totalAnswers => n         The total number of answer blanks in all
   44 #                              the parts put together (this is used when
   45 #                              computing the per-part scores, if part
   46 #                              weights are not provided).
   47 #
   48 #    saveAllAnswers => 0 or 1  Usually, the contents of named answer blanks
   49 #                              from previous parts are made available to
   50 #                              later parts using variables with the
   51 #                              same name as the answer blank.  Setting
   52 #                              saveAllAnswers to 1 will cause ALL answer
   53 #                              blanks to be available (via variables
   54 #                              like $AnSwEr1, and so on).
   55 #                                 Default:  0
   56 #
   57 #    parserValues => 0 or 1    Determines whether the answers from previous
   58 #                              parts are returned as MathObjects (like
   59 #                              those returned from Real(), Vector(), etc)
   60 #                              or as strings (the unparsed contents of the
   61 #                              student answer).  If you intend to use the
   62 #                              previous answers as numbers, for example,
   63 #                              you would want to set this to 1 so that you
   64 #                              would get the final result of any formula
   65 #                              the student typed, rather than the formula
   66 #                              itself as a character string.
   67 #                                 Default:  0
   68 #
   69 #    nextVisible => type       Tells when the "go on to the next part" option
   70 #                              is available to the student.  The possible
   71 #                              types include:
   72 #
   73 #                                 'ifCorrect'   next is available only when
   74 #                                               all the answers are correct.
   75 #
   76 #                                 'Always'      next is always available
   77 #                                               (but remember that students
   78 #                                               can't go back once they go
   79 #                                               on.)
   80 #
   81 #                                 'Never'       next is never allowed (the
   82 #                                               problem will control going
   83 #                                               on to the next part itself).
   84 #
   85 #                                Default:  'ifCorrect'
   86 #
   87 #    nextStyle => type         Determines the style of "next" indicator to display
   88 #                              (when it is available).  The type can be one of:
   89 #
   90 #                                 'CheckBox'    a checkbox that allows the students
   91 #                                               to go on to the next part when they
   92 #                                               submit their answers.
   93 #
   94 #                                 'Button'      a button that submits their answers
   95 #                                               and goes on to the next part.
   96 #
   97 #                                 'Forced'      forces the student to go on to the
   98 #                                               next part the next time they submit
   99 #                                               answers.
  100 #
  101 #                                 'HTML'        allows you to provide an arbitrary
  102 #                                               HTML string of your own.
  103 #
  104 #                                Default:  'Checkbox'
  105 #
  106 #    nextLabel => string       Specifies the string to use as the label for the checkbox,
  107 #                              the name of the button, the text of the message indicating
  108 #                              that the next submit will move to the next part, or the
  109 #                              HTML string, depending on the setting of nextStyle above.
  110 #
  111 #    nextNoChange => 0 or 1    Since the students must submit their answers again to go on
  112 #                              to the next part, it is possible for them to change their
  113 #                              answers before they submit, and if nextVisible is 'ifCorrect'
  114 #                              they might go on to the next without having correct answers
  115 #                              stored.  This option lets you control whether the answers
  116 #                              are checked against the previous ones before going on to the
  117 #                              next part.  If the answers don't match, a warning is issued
  118 #                              and they are not allowed to move on.
  119 #                                Default:  1
  120 #
  121 #    allowReset => 0 or 1      Determines whether a "Go back to the first part" checkbox
  122 #                              is provided on parts 2 and later.  This is intended for
  123 #                              the professor during testing of the problem (otherwise
  124 #                              it would be impossible to go back to earlier parts).
  125 #                                Default:  0
  126 #
  127 #    resetLabel => string      The string used to label the reset checkbox.
  128 #
  129 #  Once you have created a compoundProblem object, you can use $cp->part to
  130 #  determine the part that the student is working on, and use 'if' statements
  131 #  to display the proper information for the given part.  The compoundProblem
  132 #  object takes care of maintaining the data as the parts change.  (See the
  133 #  compoundProblem.pg file for an example of a compound problem.)
  134 #
  135 #  In order to handle the scoring of the problem as a whole when only part is
  136 #  showing, the compoundProblem object uses its own problem grader to manage
  137 #  the scores, and calls your own grader from there.  The default is to use
  138 #  the one that was installed before the compoundProblem object was created,
  139 #  or avg_problem_grader if none was installed.  You can specify a different
  140 #  one using the $cp->useGrader() method (see below).  It is important that
  141 #  you NOT call install_problem_grader() yourself once you have created the
  142 #  compoundProblem object, as that would disable the special grader, causing
  143 #  the compound problem to fail to work properly.
  144 #
  145 #  You may call the following methods once you have a compoundProblem:
  146 #
  147 #    $cp->part                   Returns the part the student is working on.
  148 #    $cp->part(n)                Sets the part to be part n, as long as the
  149 #                                student has finished the preceeding parts.
  150 #                                If not, the part is set to the highest
  151 #                                one the student hasn't completed, and he
  152 #                                can work up to the given part.  (The
  153 #                                nextVisible option is set to 'ifCorrect' if
  154 #                                it was 'Never' so that students can go on
  155 #                                once they finish the earlier parts.)
  156 #
  157 #    $cp->useGrader(code_ref)    Supplies your own grader to use in
  158 #                                place of the default one.  For example:
  159 #                                  $cp->useGrader(~~&std_problem_grader);
  160 #
  161 #    $cp->score                  Returns the (weighted) score for this part.
  162 #                                Note that this is the score shown at the bottom
  163 #                                of the page on which the student pressed submit
  164 #                                (not the score for the answers the student is
  165 #                                submitting -- that is not available until
  166 #                                after the body of the problem has been created).
  167 #
  168 #    $cp->scoreRaw               Returns the unweighted score for this part.
  169 #
  170 #    $cp->scoreOverall           Returns the overall score for the problem
  171 #                                so far.
  172 #
  173 #    $cp->addAnswers(list)       Make additional answer blanks be available
  174 #                                from one part to another.  E.g.,
  175 #                                   $cp->addAnswers('AnSwEr1');
  176 #                                would make the first unnamed blank be available
  177 #                                in later parts as well.  (This command should
  178 #                                be issued only when the part containing the
  179 #                                given answer blank is displayed.)
  180 #
  181 #    $cp->nextCheckbox(label)    Returns the HTML string for the "go on to next
  182 #                                part" checkbox so you can use it in the body of
  183 #                                the problem if you wish.  This should not be
  184 #                                inserted when the $displayMode is 'TeX'.  If the
  185 #                                label is not given or is blank, the default label
  186 #                                is used.
  187 #
  188 #    $cp->nextButton(label)      Returns the HTML string for the "go on to next
  189 #                                part" button so you can use it in the body of
  190 #                                the problem if you wish.  This should not be
  191 #                                inserted when the $displayMode is 'TeX'.  If the
  192 #                                label is not given or is blank, the default label
  193 #                                is used.
  194 #
  195 #    $cp->nextForces(label)      Returns the HTML string for the forced "go on to
  196 #                                next part" so you can use it in the body of
  197 #                                the problem if you wish.  This should not be
  198 #                                inserted when the $displayMode is 'TeX'.  If the
  199 #                                label is not given or is blank, the default label
  200 #                                is used.
  201 #
  202 #    $cp->reset                  Go back to part 1, clearing the answers
  203 #                                and score.  (Best used when debugging problems.)
  204 #
  205 #    $cp->resetCheckbox(label)   Returns the HTML string for the reset checkbox
  206 #                                so that you can provide one within the body
  207 #                                of the problem if you wish.  This should not be
  208 #                                inserted when the $displayMode is 'TeX'.  If the
  209 #                                label is not given or is blank, the default label
  210 #                                will be used.
  211 #
  212 
  213 ######################################################################
  214 
  215 
  216 package compoundProblem;
  217 
  218 #
  219 #  The state data that is stored between invocations of
  220 #  the problem.
  221 #
  222 our %defaultStatus = (
  223   part => 1,                # the current part
  224   answers => "",            # answer labels from previous parts
  225   new_answers => "",        # answer labels for THIS part
  226   ans_rule_count => 0,      # the ans_rule count from previous parts
  227   new_ans_rule_count => 0,  # the ans_rule count from THIS part
  228   score => 0,               # the (weighted) score on this part
  229   total => 0,               # the total on previous parts
  230   raw => 0,                 # raw score on this part
  231 );
  232 
  233 #
  234 #  Create a new instance of the compound Problem and initialize
  235 #  it.  This includes reading the status from the previous
  236 #  parts, defining the variables from the answers to previous parts,
  237 #  and setting up the grader so that the current data can be saved.
  238 #
  239 sub new {
  240   my $self = shift; my $class = ref($self) || $self;
  241   my $cp = bless {
  242     parts => 1,
  243     totalAnswers => undef,
  244     weights => undef,            # array of weights per part
  245     saveAllAnswers => 0,         # usually only save named answers
  246     parserValues => 0,           # make Parser objects from the answers?
  247     nextVisible => "ifCorrect",  # or "Always" or "Never"
  248     nextStyle   => "Checkbox",   # or "Button", "Forced", or "HTML"
  249     nextLabel   => undef,        # Checkbox text or button name or HTML
  250     nextNoChange => 1,           # true if answer can't change for new part
  251     allowReset => 0,             # true to show "back to part 1" button
  252     resetLabel => undef,         # label for reset button
  253     grader => $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} || \&main::avg_problem_grader,
  254     @_,
  255     status => $defaultStatus,
  256   }, $class;
  257   die "You must provide either the totalAnswers or weights"
  258     unless $cp->{totalAnswers} || $cp->{weights};
  259   $cp->getTotalWeight if $cp->{weights};
  260   main::loadMacros("Parser.pl") if $cp->{parserValues};
  261   $cp->reset if $cp->{allowReset} && $main::inputs_ref->{_reset};
  262   $cp->getStatus;
  263   $cp->initPart;
  264   return $cp;
  265 }
  266 
  267 #
  268 #  Compute the total of the weights so that the parts can
  269 #  be properly scaled.
  270 #
  271 sub getTotalWeight {
  272   my $self = shift;
  273   $self->{totalWeight} = 0; $self->{totalAnswers} = 1;
  274   foreach my $w (@{$self->{weights}}) {$self->{totalWeight} += $w}
  275   $self->{totalWeight} = 1 if $self->{totalWeight} == 0;
  276 }
  277 
  278 #
  279 #  Look up the status from the previous invocation
  280 #  and see if we need to go on to the next part.
  281 #
  282 sub getStatus {
  283   my $self = shift;
  284   main::RECORD_FORM_LABEL("_next");
  285   main::RECORD_FORM_LABEL("_status");
  286   $self->{status} = $self->decode;
  287   $self->{isNew} = $main::inputs_ref->{_next} || ($main::inputs_ref->{submitAnswers} &&
  288      $main::inputs_ref->{submitAnswers} eq ($self->{nextLabel} || "Go on to Next Part"));
  289   if ($self->{isNew}) {
  290     $self->checkAnswers;
  291     $self->incrementPart unless $self->{nextNoChange} && $self->{answersChanged};
  292   }
  293 }
  294 
  295 #
  296 #  Initialize the current part by setting the ans_rule
  297 #  count (so that later parts will get unique answer names),
  298 #  installing the grader (to save the data), and setting
  299 #  the variables for previous answers.
  300 #
  301 sub initPart {
  302   my $self = shift;
  303   $main::ans_rule_count = $self->{status}{ans_rule_count};
  304   main::install_problem_grader(\&compoundProblem::grader);
  305   $main::PG_FLAGS{compoundProblem} = $self;
  306   $self->initAnswers($self->{status}{answers});
  307 }
  308 
  309 #
  310 #  Look through the list of answer labels and set
  311 #  the variables for them to be the associated student
  312 #  answer.  Make it a Parser value if requested.
  313 #  Record the value so that is will be available
  314 #  again on the next invocation.
  315 #
  316 sub initAnswers {
  317   my $self = shift; my $answers = shift;
  318   foreach my $id (split(/;/,$answers)) {
  319     my $value = $main::inputs_ref->{$id}; $value = '' unless defined($value);
  320     if ($self->{parserValues}) {
  321       my $parser = Parser::Formula($value);
  322       $parser = Parser::Evaluate($parser) if $parser && $parser->isConstant;
  323       $value = $parser if $parser;
  324     }
  325     ${"main::$id"} = $value unless $id =~ m/$main::ANSWER_PREFIX/o;
  326     $value = quoteHTML($value);
  327     main::TEXT(qq!<input type="hidden" name="$id" value="$value" />!);
  328     main::RECORD_FORM_LABEL($id);
  329   }
  330 }
  331 
  332 #
  333 #  Look to see is any answers have changed on this
  334 #  invocation of the problem.
  335 #
  336 sub checkAnswers {
  337   my $self = shift;
  338   foreach my $id (keys(%{$main::inputs_ref})) {
  339     if ($id =~ m/^previous_(.*)$/) {
  340       if ($main::inputs_ref->{$id} ne $main::inputs_ref->{$1}) {
  341   $self->{answersChanged} = 1;
  342   $self->{isNew} = 0 if $self->{nextNoChange};
  343   return;
  344       }
  345     }
  346   }
  347 }
  348 
  349 #
  350 #  Go on to the next part, updating the status
  351 #  to include the data from the old part so that
  352 #  it will be properly preserved when the next
  353 #  part is showing.
  354 #
  355 sub incrementPart {
  356   my $self = shift;
  357   my $status = $self->{status};
  358   if ($status->{part} < $self->{parts}) {
  359     $status->{part}++;
  360     $status->{answers} .= ';' if $status->{answers};
  361     $status->{answers} .= $status->{new_answers};
  362     $status->{ans_rule_count} = $status->{new_ans_rule_count};
  363     $status->{total} += $status->{score};
  364     $status->{score} = $status->{raw} = 0;
  365     $status->{new_answers} = '';
  366   }
  367 }
  368 
  369 ######################################################################
  370 
  371 #
  372 #  Encode all the status information so that it can be
  373 #  maintained as the student submits answers.  Since this
  374 #  state information includes things like the score from
  375 #  the previous parts, it is "encrypted" using a dumb
  376 #  hex encoding (making it harder for a student to recognize
  377 #  it as valuable data if they view the page source).
  378 #
  379 sub encode {
  380   my $self = shift; my $status = shift || $self->{status};
  381   my @data = (); my $data = "";
  382   foreach my $id (main::lex_sort(keys(%defaultStatus))) {push(@data,$status->{$id})}
  383   foreach my $c (split(//,join('|',@data))) {$data .= toHex($c)}
  384   return $data;
  385 }
  386 
  387 #
  388 #  Decode the data and break it into the status hash.
  389 #
  390 sub decode {
  391   my $self = shift; my $status = shift || $main::inputs_ref->{_status};
  392   return {%defaultStatus} unless $status;
  393   my @data = (); foreach my $hex (split(/(..)/,$status)) {push(@data,fromHex($hex)) if $hex ne ''}
  394   @data = split('\\|',join('',@data)); $status = {%defaultStatus};
  395   foreach my $id (main::lex_sort(keys(%defaultStatus))) {$status->{$id} = shift(@data)}
  396   return $status;
  397 }
  398 
  399 
  400 #
  401 #  Hex encoding is shifted by 10 to obfuscate it further.
  402 #  (shouldn't be a problem since the status will be made of
  403 #  printable characters, so they are all above ASCII 32)
  404 #
  405 sub toHex {main::spf(ord(shift)-10,"%X")}
  406 sub fromHex {main::spf(hex(shift)+10,"%c")}
  407 
  408 
  409 #
  410 #  Make sure the data can be properly preserved within
  411 #  an HTML <INPUT TYPE="HIDDEN"> tag.
  412 #
  413 sub quoteHTML {
  414   my $string = shift;
  415   $string =~ s/&/\&amp;/g; $string =~ s/"/\&quot;/g;
  416   $string =~ s/>/\&gt;/g;  $string =~ s/</\&lt;/g;
  417   return $string;
  418 }
  419 
  420 ######################################################################
  421 
  422 #
  423 #  Set the grader for this part to the specified one.
  424 #
  425 sub useGrader {
  426   my $self = shift;
  427   $self->{grader} = shift;
  428 }
  429 
  430 #
  431 #  Make additional answer blanks from the current part
  432 #  be preserved for use in future parts.
  433 #
  434 sub addAnswers {
  435   my $self = shift;
  436   $self->{extraAnswers} = [] unless $self->{extraAnswers};
  437   push(@{$self->{extraAnswers}},@_);
  438 }
  439 
  440 #
  441 #  Go back to part 1 and clear the answers and scores.
  442 #
  443 sub reset {
  444   my $self = shift;
  445   if ($main::inputs_ref->{_status}) {
  446     my $status = $self->decode($main::inputs_ref->{_status});
  447     foreach my $id (split(/;/,$status->{answers})) {delete $main::inputs_ref->{$id}}
  448     foreach my $id (1..$status->{ans_rule_count})
  449       {delete $main::inputs_ref->{"${main::QUIZ_PREFIX}${main::ANSWER_PREFIX}$id"}}
  450   }
  451   $main::inputs_ref->{_status} = $self->encode(\%defaultStatus);
  452   $main::inputs_ref->{_next} = 0;
  453 }
  454 
  455 #
  456 #  Return the HTML for the "Go back to part 1" checkbox.
  457 #
  458 sub resetCheckbox {
  459   my $self = shift;
  460   my $label = shift || " <b>Go back to Part 1</b> (when you submit your answers).";
  461   my $par = shift; $par = ($par ? $main::PAR : '');
  462   qq'$par<input type="checkbox" name="_reset" value="1" />$label';
  463 }
  464 
  465 #
  466 #  Return the HTML for the "next part" checkbox.
  467 #
  468 sub nextCheckbox {
  469   my $self = shift;
  470   my $label = shift || " <b>Go on to next part</b> (when you submit your answers).";
  471   my $par = shift; $par = ($par ? $main::PAR : '');
  472   $self->{nextInserted} = 1;
  473   qq!$par<input type="checkbox" name="_next" value="next" />$label!;
  474 }
  475 
  476 #
  477 #  Return the HTML for the "next part" button.
  478 #
  479 sub nextButton {
  480   my $self = shift;
  481   my $label = quoteHTML(shift || "Go on to Next Part");
  482   my $par = shift; $par = ($par ? $main::PAR : '');
  483   $par . qq!<input type="submit" name="submitAnswers" value="$label" !
  484        .      q!onclick="document.getElementById('_next').value=1" />!;
  485 }
  486 
  487 #
  488 #  Return the HTML for when going to the next part is forced.
  489 #
  490 sub nextForced {
  491   my $self = shift;
  492   my $label = shift || "<b>Submit your answers again to go on to the next part.</b>";
  493   $label = $main::PAR . $label if shift;
  494   $self->{nextInserted} = 1;
  495   qq!$label<input type="hidden" name="_next" id="_next" value="Next" />!;
  496 }
  497 
  498 #
  499 #  Return the raw HTML provided
  500 #
  501 sub nextHTML {shift; shift}
  502 
  503 ######################################################################
  504 
  505 #
  506 #  Return the current part, or try to set the part to the given
  507 #  part (returns the part actually set, which may be earlier if
  508 #  the student didn't complete an earlier part).
  509 #
  510 sub part {
  511   my $self = shift; my $status = $self->{status};
  512   my $part = shift;
  513   return $status->{part} unless defined $part && $main::displayMode ne 'TeX';
  514   $part = 1 if $part < 1; $part = $self->{parts} if $part > $self->{parts};
  515   if ($part > $status->{part} && !$main::inputs_ref->{_noadvance}) {
  516     unless ((lc($self->{nextVisible}) eq 'ifcorrect' && $status->{raw} < 1) ||
  517              lc($self->{nextVisible}) eq 'never') {
  518       $self->initAnswers($status->{new_answers});
  519       $self->incrementPart; $self->{isNew} = 1;
  520     }
  521   }
  522   if ($part != $status->{part}) {
  523     main::TEXT('<input type="hidden" name="_noadvance" value="1" />');
  524     $self->{nextVisible} = 'IfCorrect' if lc($self->{nextVisible}) eq 'never';
  525   }
  526   return $status->{part};
  527 }
  528 
  529 #
  530 #  Return the various scores
  531 #
  532 sub score {shift->{status}{score}}
  533 sub scoreRaw {shift->{status}{raw}}
  534 sub scoreOverall {
  535   my $self = shift;
  536   return $self->{status}{score} + $self->{status}{total};
  537 }
  538 
  539 ######################################################################
  540 #
  541 #  The custom grader that does the work of computing the scores
  542 #  and saving the data.
  543 #
  544 sub grader {
  545   my $self = $main::PG_FLAGS{compoundProblem};
  546 
  547   #
  548   #  Get the answer names and the weight for the current part.
  549   #
  550   my @answers = keys(%{$_[0]});
  551   my $weight = scalar(@answers)/$self->{totalAnswers};
  552   $weight = $self->{weights}[$self->{status}{part}-1]/$self->{totalWeight}
  553     if $self->{weights} && defined($self->{weights}[$self->{status}{part}-1]);
  554   @answers = grep(!/$main::ANSWER_PREFIX/o,@answers) unless $self->{saveAllAnswers};
  555   push(@answers,@{$self->{extraAnswers}}) if $self->{extraAnswers};
  556   my $space = '<img src="about:blank" style="height:1px; width:3em; visibility:hidden" />';
  557 
  558   #
  559   #  Call the original grader, but put back the old recorded_score
  560   #  (the grader will have updated it based on the score for the PART,
  561   #  not the problem as a whole).
  562   #
  563   my $oldScore = ($_[1])->{recorded_score};
  564   my ($result,$state) = &{$self->{grader}}(@_);
  565   $state->{recorded_score} = $oldScore;
  566 
  567   #
  568   #  Update that state information and encode it.
  569   #
  570   my $status = $self->{status};
  571   $status->{raw}   = $result->{score};
  572   $status->{score} = $result->{score}*$weight;
  573   $status->{new_ans_rule_count} = $main::ans_rule_count;
  574   $status->{new_answers} = join(';',@answers);
  575   my $data = quoteHTML($self->encode);
  576 
  577   #
  578   #  Update the recorded score
  579   #
  580   my $newScore = $status->{total} + $status->{score};
  581   $state->{recorded_score} = $newScore if $newScore > $oldScore;
  582   $state->{recorded_score} = 0 if $self->{allowReset} && $main::inputs_ref->{_reset};
  583 
  584   #
  585   #  Add the compoundProblem message and data
  586   #
  587   $result->{type} = "compoundProblem ($result->{type})";
  588   $result->{msg} .= '</i><p><b>Note:</b> <i>' if $result->{msg};
  589   $result->{msg} .= 'This problem has more than one part.'
  590                  .  '<br/>'.$space.'<small>Your score for this attempt is for this part only;</small>'
  591      .  '<br/>'.$space.'<small>your overall score is for all the parts combined.</small>'
  592                  .  qq!<input type="hidden" name="_status" value="$data" />!;
  593 
  594   #
  595   #  Warn if the answers changed when they shouldn't have
  596   #
  597   $result->{msg} .= '<p><b>You may not change your answers when going on to the next part!</b>'
  598     if $self->{nextNoChange} && $self->{answersChanged};
  599 
  600   #
  601   #  Include the "next part" checkbox, button, or whatever.
  602   #
  603   my $par = 1;
  604   if ($self->{parts} > $status->{part} && !$main::inputs_ref->{previewAnswers}) {
  605     if (lc($self->{nextVisible}) eq 'always' ||
  606        (lc($self->{nextVisible}) eq 'ifcorrect' && $result->{score} >= 1)) {
  607       my $method = "next".$self->{nextStyle}; $par = 0;
  608       $result->{msg} .= $self->$method($self->{nextLabel},1).'<br/>';
  609     }
  610   }
  611 
  612   #
  613   #  Add the reset checkbox, if needed
  614   #
  615   $result->{msg} .= $self->resetCheckbox($self->{resetLabel},$par)
  616     if $self->{allowReset} && $status->{part} > 1;
  617 
  618   #
  619   #  Make sure we don't go on unless the next button really is checked
  620   #
  621   $result->{msg} .= '<input type="hidden" name="_next" value="0" />'
  622     unless $self->{nextInserted};
  623 
  624   return ($result,$state);
  625 }

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9