From 96c9cd8808203c9f3bfb6aa21793d0b3e741a910 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 17 Apr 2024 20:48:48 -0500 Subject: [PATCH] Add a special case to feedback to show correct answers only. If the translation options `showCorrectAnswers`, `forceShowAttemptResults`, and `forceScaffoldsOpen` are all true, and the options `showAttemptAnswers`, `showAttemptPreviews`, and `showMessages` are all false, then correct answers will be shown with no other content in the feedback popover except a close button. Furthermore the popover will open automatically on page load. Obviously scaffold will all be open (since the `forceScaffoldsOpen` option is true). Otherwise there would be a problem with popovers opening immediately from inside a closed scaffold. A corresponding pull request to webwork2 will utilize this. --- htdocs/js/Feedback/feedback.js | 11 +++-- htdocs/js/MathQuill/mqeditor.js | 6 +++ htdocs/js/Problem/problem.scss | 25 ++++++++-- lib/WeBWorK/PG.pm | 10 +++- macros/PG.pl | 87 ++++++++++++++++++++++----------- macros/core/PGessaymacros.pl | 1 + 6 files changed, 103 insertions(+), 37 deletions(-) diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js index ad7413cde6..4562ac8199 100644 --- a/htdocs/js/Feedback/feedback.js +++ b/htdocs/js/Feedback/feedback.js @@ -40,9 +40,7 @@ moveToFront(); // Make a click on the popover header close the popover. - feedbackPopover.tip - ?.querySelector('.popover-header .btn-close') - ?.addEventListener('click', () => feedbackPopover.hide()); + feedbackPopover.tip?.querySelector('.btn-close')?.addEventListener('click', () => feedbackPopover.hide()); if (feedbackPopover.tip) feedbackPopover.tip.dataset.iframeHeight = '1'; @@ -68,6 +66,13 @@ }); } }); + + if (feedbackBtn.dataset.showCorrectOnly) { + setTimeout(() => { + feedbackBtn.click(); + setTimeout(() => feedbackPopover.update(), 100); + }, 0); + } }; // Setup feedback popovers already on the page. diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 4ce4058098..53b3356bdc 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -36,6 +36,9 @@ if (input.classList.contains('incorrect')) answerQuill.classList.add('incorrect'); if (input.classList.contains('partially-correct')) answerQuill.classList.add('partially-correct'); + // Find the feedback button for this input if there is one on the page. + const feedbackBtn = document.querySelector(`button[data-answer-label="${answerLabel}"`); + // Default options. const cfgOptions = { spaceBehavesLikeTab: true, @@ -261,6 +264,9 @@ answerQuill.input.value = ''; answerQuill.latexInput.value = ''; } + + // If the feedback popover is open, then update its position. + if (feedbackBtn) bootstrap.Popover.getInstance(feedbackBtn)?.update(); }; input.after(answerQuill); diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index f580ed0140..d94f68b1dd 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -224,7 +224,6 @@ --bs-popover-max-width: 600px; --bs-popover-zindex: 17; position: absolute; - min-width: 200px; .popover-header { text-align: center; @@ -260,11 +259,29 @@ } } + &:not(.correct-only) { + min-width: 200px; + + .popover-body { + .card { + border-top-left-radius: 0; + border-top-right-radius: 0; + --bs-card-spacer-y: 0.5rem; + } + } + } + + &.correct-only { + .popover-body { + .card { + --bs-card-spacer-x: 0.25rem; + --bs-card-spacer-y: 0.25rem; + } + } + } + .popover-body { .card { - border-top-left-radius: 0; - border-top-right-radius: 0; - --bs-card-spacer-y: 0.5rem; --bs-card-cap-bg: #ddd; .card-header { diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index fb6c6d1a34..07cd5acf3f 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -487,7 +487,8 @@ the value of this option. =item showAttemptAnswers (boolean, default: 1) -Determines if the student's evaluated (i.e. "Entered") answers will be shown in feedback. +Determines if the student's evaluated (i.e. "Entered") answers will be shown in +feedback. =item showAttemptPreviews (boolean, default: 1) @@ -520,6 +521,13 @@ shown. If set to 1, then correct answers are shown but hidden, and a "Reveal" button is shown at first. If that button is clicked, then the answer is shown. If set to 2, then correct answers are shown immediately. +There is one special case that needs extra explanation. If this is true +(greater than zero), C is true, C +is true, and C, C, and C +are all false, then correct answers will be shown with no other content in the +feedback popover except a close button, and the popover will open automatically +on page load. + =item answerPrefix (string, default: '') A prefix to prepend to all answer labels. Note that other prefixes may be diff --git a/macros/PG.pl b/macros/PG.pl index d2201492eb..8e3ba56574 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1027,6 +1027,14 @@ sub ENDDOCUMENT { my @answerNames = keys %{ $PG->{PG_ANSWERS_HASH} }; + my $showCorrectOnly = + $rh_envir->{showCorrectAnswers} + && $rh_envir->{forceScaffoldsOpen} + && $rh_envir->{forceShowAttemptResults} + && !$rh_envir->{showAttemptAnswers} + && !$rh_envir->{showAttemptPreviews} + && !$rh_envir->{showMessages}; + for my $answerLabel (@answerNames) { my $response_obj = $PG->{PG_ANSWERS_HASH}{$answerLabel}->response_obj; my $ansHash = $PG->{PG_ANSWERS_HASH}{$answerLabel}{ans_eval}{rh_ans}; @@ -1072,11 +1080,12 @@ sub ENDDOCUMENT { push(@{ $options{feedbackElements} }, @$elements); } - my $showResults = ($rh_envir->{showAttemptResults} && $PG->{flags}{showPartialCorrectAnswers}) - || $rh_envir->{forceShowAttemptResults}; - - if ($showResults) { - if ($answerScore >= 1) { + if (($rh_envir->{showAttemptResults} && $PG->{flags}{showPartialCorrectAnswers}) + || $rh_envir->{forceShowAttemptResults}) + { + if ($showCorrectOnly) { + $options{resultClass} = 'correct-only'; + } elsif ($answerScore >= 1) { $options{resultTitle} = maketext('Correct'); $options{resultClass} = 'correct'; $options{btnClass} = 'btn-success'; @@ -1111,6 +1120,8 @@ sub ENDDOCUMENT { || $ansHash->{ans_message} || $rh_envir->{showCorrectAnswers}); + next if $showCorrectOnly && !$options{showCorrect}; + # Find an element to insert the button in or around if one has not been provided. unless ($options{insertElement}) { # Use the last feedback element by default. @@ -1144,8 +1155,7 @@ sub ENDDOCUMENT { if $options{insertElement} && $options{insertElement}->attr->{'data-feedback-insert-method'}; } - # Add the correct/incorrect/partially-correct class and - # aria-described by attribute to the feedback elements. + # Add the correct/incorrect/partially-correct class to the feedback elements. for (@{ $options{feedbackElements} }) { $_->attr(class => join(' ', $options{resultClass}, $_->attr->{class} || ())) if $options{resultClass}; @@ -1183,8 +1193,7 @@ sub ENDDOCUMENT { my $answerPreview = $previewAnswer->($ansHash->{preview_latex_string}, $options{wrapPreviewInTex}); - # Create the screen reader only span holding the aria description, create the feedback button and - # popover, and insert the button at the requested location. + # Create the feedback button and popover, and insert the button at the requested location. my $feedback = Mojo::DOM->new_tag( 'button', type => 'button', @@ -1196,27 +1205,30 @@ sub ENDDOCUMENT { : $options{resultTitle} ), data => { - bs_title => Mojo::DOM->new_tag( - 'div', - class => 'd-flex align-items-center justify-content-between', - 'data-bs-theme' => 'dark', - sub { - Mojo::DOM->new_tag('span', style => 'width:20.4px') - . Mojo::DOM->new_tag('span', class => 'mx-3', $options{resultTitle}) - . Mojo::DOM->new_tag( - 'button', - type => 'button', - class => 'btn-close', - 'aria-label' => maketext('Close') - ); - } - )->to_string, + $showCorrectOnly ? (show_correct_only => 1) : ( + bs_title => Mojo::DOM->new_tag( + 'div', + class => 'd-flex align-items-center justify-content-between', + 'data-bs-theme' => 'dark', + sub { + Mojo::DOM->new_tag('span', style => 'width:20.4px') + . Mojo::DOM->new_tag('span', class => 'mx-3', $options{resultTitle}) + . Mojo::DOM->new_tag( + 'button', + type => 'button', + class => 'btn-close', + 'aria-label' => maketext('Close') + ); + } + )->to_string + ), + answer_label => $answerLabel, bs_toggle => 'popover', bs_trigger => 'click', - bs_placement => 'bottom', + bs_placement => $showCorrectOnly ? 'right' : 'bottom', bs_html => 'true', bs_custom_class => join(' ', 'ww-feedback-popover', $options{resultClass} || ()), - bs_fallback_placements => '[]', + bs_fallback_placements => $showCorrectOnly ? '["left","top","bottom"]' : '[]', bs_content => Mojo::DOM->new_tag( 'div', id => "$answerLabel-feedback", @@ -1257,10 +1269,27 @@ sub ENDDOCUMENT { $options{wrapPreviewInTex}, $ansHash->{correct_ans} ); - $feedbackLine->( + $showCorrectOnly + ? $feedbackLine->( + '', + Mojo::DOM->new_tag( + 'div', + class => + 'd-flex justify-content-between align-items-center gap-1', + sub { + $correctAnswer + . Mojo::DOM->new_tag( + 'button', + type => 'button', + class => 'btn-close', + 'aria-label' => maketext('Close') + ); + } + ) + ) + : $feedbackLine->( maketext('Correct Answer'), - $rh_envir->{showCorrectAnswers} > 1 - ? $correctAnswer + $rh_envir->{showCorrectAnswers} > 1 ? $correctAnswer : Mojo::DOM->new_tag( 'button', type => 'button', diff --git a/macros/core/PGessaymacros.pl b/macros/core/PGessaymacros.pl index 4f49c3043c..06bab7919f 100644 --- a/macros/core/PGessaymacros.pl +++ b/macros/core/PGessaymacros.pl @@ -61,6 +61,7 @@ sub essay_cmp { $options->{manuallyGraded} = 1; if ($envir{needs_grading} + || !defined $ansHash->{ans_label} || !defined $inputs_ref->{"previous_$ansHash->{ans_label}"} || $inputs_ref->{ $ansHash->{ans_label} } ne $inputs_ref->{"previous_$ansHash->{ans_label}"}) {