[system] / trunk / pg / macros / compoundProblem.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/compoundProblem.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5947 - (download) (as text) (annotate)
Mon Dec 22 05:18:26 2008 UTC (11 years, 1 month ago) by dpvc
File size: 26613 byte(s)
Added code to save/restore the images_created counts so that
on-the-fly graphics in more than one part will work properly.
Old data format is updated automatically to the new one, so problems
should continue to work even if they are already in use by students.

    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   images_created => 0,      # the image count from the precious parts
  229   new_images_created => 0,  # the image count from THIS part
  230   imageName => "",          # name of images_created image file
  231   score => 0,               # the (weighted) score on this part
  232   total => 0,               # the total on previous parts
  233   raw => 0,                 # raw score on this part
  234 );
  235 
  236 #
  237 #  Create a new instance of the compound Problem and initialize
  238 #  it.  This includes reading the status from the previous
  239 #  parts, defining the variables from the answers to previous parts,
  240 #  and setting up the grader so that the current data can be saved.
  241 #
  242 sub new {
  243   my $self = shift; my $class = ref($self) || $self;
  244   my $cp = bless {
  245     parts => 1,
  246     totalAnswers => undef,
  247     weights => undef,            # array of weights per part
  248     saveAllAnswers => 0,         # usually only save named answers
  249     parserValues => 0,           # make Parser objects from the answers?
  250     nextVisible => "ifCorrect",  # or "Always" or "Never"
  251     nextStyle   => "Checkbox",   # or "Button", "Forced", or "HTML"
  252     nextLabel   => undef,        # Checkbox text or button name or HTML
  253     nextNoChange => 1,           # true if answer can't change for new part
  254     allowReset => 0,             # true to show "back to part 1" button
  255     resetLabel => undef,         # label for reset button
  256     grader => $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} || \&main::avg_problem_grader,
  257     @_,
  258     status => $defaultStatus,
  259   }, $class;
  260   die "You must provide either the totalAnswers or weights"
  261     unless $cp->{totalAnswers} || $cp->{weights};
  262   $cp->getTotalWeight if $cp->{weights};
  263   main::loadMacros("Parser.pl") if $cp->{parserValues};
  264   $cp->reset if $cp->{allowReset} && $main::inputs_ref->{_reset};
  265   $cp->getStatus;
  266   $cp->initPart;
  267   return $cp;
  268 }
  269 
  270 #
  271 #  Compute the total of the weights so that the parts can
  272 #  be properly scaled.
  273 #
  274 sub getTotalWeight {
  275   my $self = shift;
  276   $self->{totalWeight} = 0; $self->{totalAnswers} = 1;
  277   foreach my $w (@{$self->{weights}}) {$self->{totalWeight} += $w}
  278   $self->{totalWeight} = 1 if $self->{totalWeight} == 0;
  279 }
  280 
  281 #
  282 #  Look up the status from the previous invocation
  283 #  and see if we need to go on to the next part.
  284 #
  285 sub getStatus {
  286   my $self = shift;
  287   main::RECORD_FORM_LABEL("_next");
  288   main::RECORD_FORM_LABEL("_status");
  289   $self->{status} = $self->decode;
  290   $self->{isNew} = $main::inputs_ref->{_next} || ($main::inputs_ref->{submitAnswers} &&
  291      $main::inputs_ref->{submitAnswers} eq ($self->{nextLabel} || "Go on to Next Part"));
  292   if ($self->{isNew}) {
  293     $self->checkAnswers;
  294     $self->incrementPart unless $self->{nextNoChange} && $self->{answersChanged};
  295   }
  296 }
  297 
  298 #
  299 #  Initialize the current part by setting the ans_rule
  300 #  count (so that later parts will get unique answer names),
  301 #  installing the grader (to save the data), and setting
  302 #  the variables for previous answers.
  303 #
  304 sub initPart {
  305   my $self = shift;
  306   $main::ans_rule_count = $self->{status}{ans_rule_count};
  307   $main::images_created{$self->{status}{imageName}} = $self->{status}{images_created}
  308     if $self->{status}{imageName};
  309   main::install_problem_grader(\&compoundProblem::grader);
  310   $main::PG_FLAGS{compoundProblem} = $self;
  311   $self->initAnswers($self->{status}{answers});
  312 }
  313 
  314 #
  315 #  Look through the list of answer labels and set
  316 #  the variables for them to be the associated student
  317 #  answer.  Make it a Parser value if requested.
  318 #  Record the value so that is will be available
  319 #  again on the next invocation.
  320 #
  321 sub initAnswers {
  322   my $self = shift; my $answers = shift;
  323   foreach my $id (split(/;/,$answers)) {
  324     my $value = $main::inputs_ref->{$id}; $value = '' unless defined($value);
  325     if ($self->{parserValues}) {
  326       my $parser = Parser::Formula($value);
  327       $parser = Parser::Evaluate($parser) if $parser && $parser->isConstant;
  328       $value = $parser if $parser;
  329     }
  330     ${"main::$id"} = $value unless $id =~ m/$main::ANSWER_PREFIX/o;
  331     $value = quoteHTML($value);
  332     main::TEXT(qq!<input type="hidden" name="$id" value="$value" />!);
  333     main::RECORD_FORM_LABEL($id);
  334   }
  335 }
  336 
  337 #
  338 #  Look to see is any answers have changed on this
  339 #  invocation of the problem.
  340 #
  341 sub checkAnswers {
  342   my $self = shift;
  343   foreach my $id (keys(%{$main::inputs_ref})) {
  344     if ($id =~ m/^previous_(.*)$/) {
  345       if ($main::inputs_ref->{$id} ne $main::inputs_ref->{$1}) {
  346   $self->{answersChanged} = 1;
  347   $self->{isNew} = 0 if $self->{nextNoChange};
  348   return;
  349       }
  350     }
  351   }
  352 }
  353 
  354 #
  355 #  Go on to the next part, updating the status
  356 #  to include the data from the old part so that
  357 #  it will be properly preserved when the next
  358 #  part is showing.
  359 #
  360 sub incrementPart {
  361   my $self = shift;
  362   my $status = $self->{status};
  363   if ($status->{part} < $self->{parts}) {
  364     $status->{part}++;
  365     $status->{answers} .= ';' if $status->{answers};
  366     $status->{answers} .= $status->{new_answers};
  367     $status->{ans_rule_count} = $status->{new_ans_rule_count};
  368     $status->{images_created} = $status->{new_images_created};
  369     $status->{total} += $status->{score};
  370     $status->{score} = $status->{raw} = 0;
  371     $status->{new_answers} = '';
  372   }
  373 }
  374 
  375 ######################################################################
  376 
  377 #
  378 #  Encode all the status information so that it can be
  379 #  maintained as the student submits answers.  Since this
  380 #  state information includes things like the score from
  381 #  the previous parts, it is "encrypted" using a dumb
  382 #  hex encoding (making it harder for a student to recognize
  383 #  it as valuable data if they view the page source).
  384 #
  385 sub encode {
  386   my $self = shift; my $status = shift || $self->{status};
  387   my @data = (); my $data = "";
  388   foreach my $id (main::lex_sort(keys(%defaultStatus))) {push(@data,$status->{$id})}
  389   foreach my $c (split(//,join('|',@data))) {$data .= toHex($c)}
  390   return $data;
  391 }
  392 
  393 #
  394 #  Decode the data and break it into the status hash.
  395 #
  396 sub decode {
  397   my $self = shift; my $status = shift || $main::inputs_ref->{_status};
  398   return {%defaultStatus} unless $status;
  399   my @data = (); foreach my $hex (split(/(..)/,$status)) {push(@data,fromHex($hex)) if $hex ne ''}
  400   @data = split('\\|',join('',@data)); $status = {%defaultStatus};
  401   if (scalar(@data) == 8) {
  402     # insert imageName, images_created, new_images_created, if missing
  403     splice(@data,2,0,"",0); splice(@data,6,0,0);
  404   }
  405   foreach my $id (main::lex_sort(keys(%defaultStatus))) {$status->{$id} = shift(@data)}
  406   return $status;
  407 }
  408 
  409 
  410 #
  411 #  Hex encoding is shifted by 10 to obfuscate it further.
  412 #  (shouldn't be a problem since the status will be made of
  413 #  printable characters, so they are all above ASCII 32)
  414 #
  415 sub toHex {main::spf(ord(shift)-10,"%X")}
  416 sub fromHex {main::spf(hex(shift)+10,"%c")}
  417 
  418 
  419 #
  420 #  Make sure the data can be properly preserved within
  421 #  an HTML <INPUT TYPE="HIDDEN"> tag.
  422 #
  423 sub quoteHTML {
  424   my $string = shift;
  425   $string =~ s/&/\&amp;/g; $string =~ s/"/\&quot;/g;
  426   $string =~ s/>/\&gt;/g;  $string =~ s/</\&lt;/g;
  427   return $string;
  428 }
  429 
  430 ######################################################################
  431 
  432 #
  433 #  Set the grader for this part to the specified one.
  434 #
  435 sub useGrader {
  436   my $self = shift;
  437   $self->{grader} = shift;
  438 }
  439 
  440 #
  441 #  Make additional answer blanks from the current part
  442 #  be preserved for use in future parts.
  443 #
  444 sub addAnswers {
  445   my $self = shift;
  446   $self->{extraAnswers} = [] unless $self->{extraAnswers};
  447   push(@{$self->{extraAnswers}},@_);
  448 }
  449 
  450 #
  451 #  Go back to part 1 and clear the answers and scores.
  452 #
  453 sub reset {
  454   my $self = shift;
  455   if ($main::inputs_ref->{_status}) {
  456     my $status = $self->decode($main::inputs_ref->{_status});
  457     foreach my $id (split(/;/,$status->{answers})) {delete $main::inputs_ref->{$id}}
  458     foreach my $id (1..$status->{ans_rule_count})
  459       {delete $main::inputs_ref->{"${main::QUIZ_PREFIX}${main::ANSWER_PREFIX}$id"}}
  460   }
  461   $main::inputs_ref->{_status} = $self->encode(\%defaultStatus);
  462   $main::inputs_ref->{_next} = 0;
  463 }
  464 
  465 #
  466 #  Return the HTML for the "Go back to part 1" checkbox.
  467 #
  468 sub resetCheckbox {
  469   my $self = shift;
  470   my $label = shift || " <b>Go back to Part 1</b> (when you submit your answers).";
  471   my $par = shift; $par = ($par ? $main::PAR : '');
  472   qq'$par<input type="checkbox" name="_reset" value="1" />$label';
  473 }
  474 
  475 #
  476 #  Return the HTML for the "next part" checkbox.
  477 #
  478 sub nextCheckbox {
  479   my $self = shift;
  480   my $label = shift || " <b>Go on to next part</b> (when you submit your answers).";
  481   my $par = shift; $par = ($par ? $main::PAR : '');
  482   $self->{nextInserted} = 1;
  483   qq!$par<input type="checkbox" name="_next" value="next" />$label!;
  484 }
  485 
  486 #
  487 #  Return the HTML for the "next part" button.
  488 #
  489 sub nextButton {
  490   my $self = shift;
  491   my $label = quoteHTML(shift || "Go on to Next Part");
  492   my $par = shift; $par = ($par ? $main::PAR : '');
  493   $par . qq!<input type="submit" name="submitAnswers" value="$label" !
  494        .      q!onclick="document.getElementById('_next').value=1" />!;
  495 }
  496 
  497 #
  498 #  Return the HTML for when going to the next part is forced.
  499 #
  500 sub nextForced {
  501   my $self = shift;
  502   my $label = shift || "<b>Submit your answers again to go on to the next part.</b>";
  503   $label = $main::PAR . $label if shift;
  504   $self->{nextInserted} = 1;
  505   qq!$label<input type="hidden" name="_next" id="_next" value="Next" />!;
  506 }
  507 
  508 #
  509 #  Return the raw HTML provided
  510 #
  511 sub nextHTML {shift; shift}
  512 
  513 ######################################################################
  514 
  515 #
  516 #  Return the current part, or try to set the part to the given
  517 #  part (returns the part actually set, which may be earlier if
  518 #  the student didn't complete an earlier part).
  519 #
  520 sub part {
  521   my $self = shift; my $status = $self->{status};
  522   my $part = shift;
  523   return $status->{part} unless defined $part && $main::displayMode ne 'TeX';
  524   $part = 1 if $part < 1; $part = $self->{parts} if $part > $self->{parts};
  525   if ($part > $status->{part} && !$main::inputs_ref->{_noadvance}) {
  526     unless ((lc($self->{nextVisible}) eq 'ifcorrect' && $status->{raw} < 1) ||
  527              lc($self->{nextVisible}) eq 'never') {
  528       $self->initAnswers($status->{new_answers});
  529       $self->incrementPart; $self->{isNew} = 1;
  530     }
  531   }
  532   if ($part != $status->{part}) {
  533     main::TEXT('<input type="hidden" name="_noadvance" value="1" />');
  534     $self->{nextVisible} = 'IfCorrect' if lc($self->{nextVisible}) eq 'never';
  535   }
  536   return $status->{part};
  537 }
  538 
  539 #
  540 #  Return the various scores
  541 #
  542 sub score {shift->{status}{score}}
  543 sub scoreRaw {shift->{status}{raw}}
  544 sub scoreOverall {
  545   my $self = shift;
  546   return $self->{status}{score} + $self->{status}{total};
  547 }
  548 
  549 ######################################################################
  550 #
  551 #  The custom grader that does the work of computing the scores
  552 #  and saving the data.
  553 #
  554 sub grader {
  555   my $self = $main::PG_FLAGS{compoundProblem};
  556 
  557   #
  558   #  Get the answer names and the weight for the current part.
  559   #
  560   my @answers = keys(%{$_[0]});
  561   my $weight = scalar(@answers)/$self->{totalAnswers};
  562   $weight = $self->{weights}[$self->{status}{part}-1]/$self->{totalWeight}
  563     if $self->{weights} && defined($self->{weights}[$self->{status}{part}-1]);
  564   @answers = grep(!/$main::ANSWER_PREFIX/o,@answers) unless $self->{saveAllAnswers};
  565   push(@answers,@{$self->{extraAnswers}}) if $self->{extraAnswers};
  566   my $space = '<img src="about:blank" style="height:1px; width:3em; visibility:hidden" />';
  567 
  568   #
  569   #  Call the original grader, but put back the old recorded_score
  570   #  (the grader will have updated it based on the score for the PART,
  571   #  not the problem as a whole).
  572   #
  573   my $oldScore = ($_[1])->{recorded_score};
  574   my ($result,$state) = &{$self->{grader}}(@_);
  575   $state->{recorded_score} = $oldScore;
  576 
  577   #
  578   #  Update that state information and encode it.
  579   #
  580   my $status = $self->{status};
  581   $status->{raw}   = $result->{score};
  582   $status->{score} = $result->{score}*$weight;
  583   $status->{new_ans_rule_count} = $main::ans_rule_count;
  584   if (defined(%main::images_created)) {
  585     $status->{imageName} = (keys %main::images_created)[0];
  586     $status->{new_images_created} = $main::images_created{$status->{imageName}};
  587   }
  588   $status->{new_answers} = join(';',@answers);
  589   my $data = quoteHTML($self->encode);
  590 
  591   #
  592   #  Update the recorded score
  593   #
  594   my $newScore = $status->{total} + $status->{score};
  595   $state->{recorded_score} = $newScore if $newScore > $oldScore;
  596   $state->{recorded_score} = 0 if $self->{allowReset} && $main::inputs_ref->{_reset};
  597 
  598   #
  599   #  Add the compoundProblem message and data
  600   #
  601   $result->{type} = "compoundProblem ($result->{type})";
  602   $result->{msg} .= '</i><p><b>Note:</b> <i>' if $result->{msg};
  603   $result->{msg} .= 'This problem has more than one part.'
  604                  .  '<br/>'.$space.'<small>Your score for this attempt is for this part only;</small>'
  605      .  '<br/>'.$space.'<small>your overall score is for all the parts combined.</small>'
  606                  .  qq!<input type="hidden" name="_status" value="$data" />!;
  607 
  608   #
  609   #  Warn if the answers changed when they shouldn't have
  610   #
  611   $result->{msg} .= '<p><b>You may not change your answers when going on to the next part!</b>'
  612     if $self->{nextNoChange} && $self->{answersChanged};
  613 
  614   #
  615   #  Include the "next part" checkbox, button, or whatever.
  616   #
  617   my $par = 1;
  618   if ($self->{parts} > $status->{part} && !$main::inputs_ref->{previewAnswers}) {
  619     if (lc($self->{nextVisible}) eq 'always' ||
  620        (lc($self->{nextVisible}) eq 'ifcorrect' && $result->{score} >= 1)) {
  621       my $method = "next".$self->{nextStyle}; $par = 0;
  622       $result->{msg} .= $self->$method($self->{nextLabel},1).'<br/>';
  623     }
  624   }
  625 
  626   #
  627   #  Add the reset checkbox, if needed
  628   #
  629   $result->{msg} .= $self->resetCheckbox($self->{resetLabel},$par)
  630     if $self->{allowReset} && $status->{part} > 1;
  631 
  632   #
  633   #  Make sure we don't go on unless the next button really is checked
  634   #
  635   $result->{msg} .= '<input type="hidden" name="_next" value="0" />'
  636     unless $self->{nextInserted};
  637 
  638   return ($result,$state);
  639 }
  640 
  641 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9