diff --git a/macros/core/scaffold.pl b/macros/core/scaffold.pl index 4070b432a2..83bf22b8ff 100644 --- a/macros/core/scaffold.pl +++ b/macros/core/scaffold.pl @@ -186,6 +186,21 @@ =head1 DESCRIPTION have the student open it by hand before anwering the questions. In this case, set this value to 0 (it is 1 by default). +=item C 0 or 1 >>> + +This determines whether Preview My Answers should be allowed to open +a new section. Set this to 0 to prevent the next section from opening +after Preview My Answers is clicked (it is 1 by default). + +=item C 0 or 1 >>> + +This determines whether Preview My Answers should be allowed to close +a section which was open before, if the prior answers were changed to be +incorrect. By default the setting is 0, so such section closures can +occur due to a Preview, while when set to 1 sections will not be closed by +Preview. + + =item C 0 or 1 >>> This determines whether each section is automatically numbered before @@ -316,6 +331,7 @@ sub Begin { unshift(@scaffolds, $self); $scaffold = $self; $scaffold_depth++; + $self->{key_last_open} = "ScAfFoLd_${PREFIX}_SC-" . $self->{number} . "_last_open"; $self->{previous_output} = [ splice(@{$PG_OUTPUT}, 0) ]; # get output and clear it without changing the array pointer $self->{output} = []; # the contents of the scaffold @@ -338,6 +354,12 @@ sub End { $self->hide_other_results(@{ $self->{open} }); # hide results of unnopened sections in the results table push(@$PG_OUTPUT, @{ $self->{previous_output} }, @{ $self->{output} }) ; # put back original output and scaffold output + + if (!$self->{isPreview}) { + # Record the last_open value for future renders + main::update_persistent_data($self->{key_last_open}, $self->{open}[-1] ? $self->{open}[-1] : 0); + } + delete $self->{previous_output}; delete $self->{output}; # don't need these any more shift(@scaffolds); @@ -372,6 +394,11 @@ sub new { hardcopy_is_open => "always", # open all possible sections in hardcopy open_first_section => 1, # 0 means don't open any sections initially numbered => 0, # 1 means sections will be printed with their number + + # control whether Preview should be allowed to open/close sections + allow_next_section_to_open_on_preview => 1, # can Preview open another section + prevent_close_by_preview => 0, # can Preview close previously open sections + @_, number => ++$scaffold_no, # the number for this scaffold depth => $scaffold_depth, # the nesting depth for this scaffold @@ -402,7 +429,7 @@ sub start_section { # # Add the content from the current section into the scaffold's output -# and remove the current_section (so we can tell that no sectionis open). +# and remove the current_section (so we can tell that no section is open). # sub end_section { my $self = shift; @@ -415,8 +442,10 @@ sub end_section { # keeping the scores for future reference. # sub section_answers { - my $self = shift; + my ($self, @this_section_answers) = @_; my %answers; + my $section_no = $self->{section_no}; + my $prior_last_open = main::get_persistent_data($self->{key_last_open}) // 0; # # MultiAnswer objects can set the answer hash score when the last answer is evaluated, # so save the hashes and look up the scores after they have all been called. @@ -425,8 +454,8 @@ sub section_answers { # to consider the score to be 1 (bug in PGessaymacros.pl prevents us from using # it for essay_cmp(), though). # - push(@{ $self->{ans_names} }, @_); - foreach my $name (@_) { + push(@{ $self->{ans_names} }, @this_section_answers); + foreach my $name (@this_section_answers) { my $input = $main::inputs_ref->{$name}; my $evaluator = $PG_ANSWERS_HASH->{$name}->ans_eval; Parser::Eval(sub { $answers{$name} = $evaluator->evaluate($input) }) if defined($input) && $input ne ""; @@ -434,12 +463,42 @@ sub section_answers { if $answers{$name} && (($answers{$name}{type} || "") eq "essay" || $answers{$name}{"scaffold_force"}); $evaluator->{rh_ans}{ans_message} = ""; delete $evaluator->{rh_ans}{error_message}; + # In sections which are not yet open, make that known to answer + # evaluators. For MultiAnswer using a single result, this allows + # hiding the structure of the answer during a preview. + $PG_ANSWERS_HASH->{$name}{ans_eval}{rh_ans}{scaffold_past_open} = 1 if ($section_no > $prior_last_open); + } + + my $myLastSet; # we save the name of the last answer field whose score was set + for my $name (@this_section_answers) { + if ($answers{$name}) { + $self->{scores}{$name} = $answers{$name}{score}; + $myLastSet = $name; + } + } + if ($myLastSet) { + $self->{isPreview} = $answers{$myLastSet}{isPreview} if (!defined($self->{isPreview})); + if ($self->{isPreview}) { # Only handle for Preview mode + if ( + ($section_no == $prior_last_open) # Only should consider modifying the last previously open section + && !$self->{allow_next_section_to_open_on_preview} + ) + { + # This should prevent the next section from opening on Preview + # if all answers to this section are correct, unless it would open anyway. + $self->{scores}{$myLastSet} = 0; + } elsif (($section_no < $prior_last_open) && $self->{prevent_close_by_preview}) { + # This section was open before - AVOID closing it due to a change in prior sections and a Preview + for my $name (@{ $self->{ans_names} }) { + $self->{scores}{$name} = 1.0 if ($answers{$name}); # to prevent it from being closed + } + } + } } - foreach my $name (@_) { $self->{scores}{$name} = $answers{$name}{score} if $answers{$name} } } # -# Add the given sections to the list of sections to be openned +# Add the given sections to the list of sections to be opened # for this scaffold # sub is_open { @@ -601,7 +660,7 @@ sub new { # Adds the necessary HTML around the content of the section. # # First, determine the is_correct and can_open status and save them. -# Then check if the section is to be openned, and if so, add it +# Then check if the section is to be opened, and if so, add it # to the open list of the scaffold. # # The $PG_OUTPUT variable holds just the contents of this section, @@ -745,11 +804,11 @@ sub new_answers { package Section::can_open; # -# Always can be openned +# Always can be opened # sub always { return 1 } # -# Can be openned when all the answers from previous sections are correct +# Can be opened when all the answers from previous sections are correct # sub when_previous_correct { my $section = shift; @@ -772,7 +831,7 @@ sub incorrect { return !$section->{is_correct}; } # -# Never can be openned +# Never can be opened # sub never { return 0 } diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 3d1af6e841..75f5cf43d4 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -292,6 +292,10 @@ sub single_check { $ans->{_filter_name} = "MultiAnswer Single Check"; my $inputs = $main::inputs_ref; $self->{ans}[0] = $self->{cmp}[0]->evaluate($ans->{student_ans}); + # Allow hiding the the structure of the answer for an + # answer in an unopened part of a scaffold problem. + my $hide_preview = 0; + $hide_preview = 1 if ($ans->{scaffold_past_open}); foreach my $i (1 .. $self->length - 1) { $self->{ans}[$i] = $self->{cmp}[$i]->evaluate($inputs->{ $self->ANS_NAME($i) }); } @@ -328,13 +332,18 @@ sub single_check { . ''; } if ($nonblank) { - $ans->{preview_latex_string} = - ( - defined($self->{tex_format}) - ? sprintf($self->{tex_format}, @latex) - : join($self->{tex_separator}, @latex)); - $ans->{preview_text_string} = - (defined($self->{format}) ? sprintf($self->{format}, @text) : join($self->{separator}, @text)); + if ($hide_preview) { + $ans->{preview_latex_string} = ""; + $ans->{preview_text_string} = ""; + } else { + $ans->{preview_latex_string} = + ( + defined($self->{tex_format}) + ? sprintf($self->{tex_format}, @latex) + : join($self->{tex_separator}, @latex)); + $ans->{preview_text_string} = + (defined($self->{format}) ? sprintf($self->{format}, @text) : join($self->{separator}, @text)); + } $ans->{student_ans} = (defined($self->{format}) ? sprintf($self->{format}, @student) : join($self->{separator}, @student)); }