Skip to content

Commit 040c8b7

Browse files
authored
Merge pull request openwebwork#2921 from somiaj/add-points-to-grader
Add point input to ProblemGrader.
2 parents dc307b0 + 9d35e20 commit 040c8b7

File tree

5 files changed

+136
-36
lines changed

5 files changed

+136
-36
lines changed

htdocs/js/ProblemGrader/problemgrader.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
11
'use strict';
22

33
(() => {
4+
const setPointInputValue = (pointInput, score) =>
5+
(pointInput.value = parseFloat(
6+
(Math.round((score * pointInput.max) / 100 / pointInput.step) * pointInput.step).toFixed(2)
7+
));
8+
9+
// Update problem score if point value changes and is a valid value.
10+
for (const pointInput of document.querySelectorAll('.problem-points')) {
11+
pointInput.addEventListener('input', () => {
12+
const userId = pointInput.id.replace(/\.points$/, '');
13+
if (pointInput.checkValidity()) {
14+
const scoreInput = document.getElementById(`${userId}.score`);
15+
if (scoreInput) {
16+
scoreInput.classList.remove('is-invalid');
17+
scoreInput.value = Math.round((100 * pointInput.value) / pointInput.max);
18+
}
19+
pointInput.classList.remove('is-invalid');
20+
} else {
21+
pointInput.classList.add('is-invalid');
22+
}
23+
});
24+
}
25+
26+
// Update problem points if score changes and is a valid value.
27+
for (const scoreInput of document.querySelectorAll('.problem-score')) {
28+
scoreInput.addEventListener('input', () => {
29+
const userId = scoreInput.id.replace(/\.score$/, '');
30+
if (scoreInput.checkValidity()) {
31+
const pointInput = document.getElementById(`${userId}.points`);
32+
if (pointInput) {
33+
pointInput.classList.remove('is-invalid');
34+
pointInput.value = setPointInputValue(pointInput, scoreInput.value);
35+
}
36+
scoreInput.classList.remove('is-invalid');
37+
} else {
38+
scoreInput.classList.add('is-invalid');
39+
}
40+
});
41+
}
42+
443
const userSelect = document.getElementById('student_selector');
544
if (!userSelect) return;
645

lib/WeBWorK/ConfigValues.pm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -776,12 +776,12 @@ sub getConfigValues ($ce) {
776776
},
777777
{
778778
var => 'problemGraderScore',
779-
doc => x('Method to enter problem scores in the single problem manual grader'),
779+
doc => x('Method to enter problem scores in the manual problem graders'),
780780
doc2 => x(
781-
'This configures if the single problem manual grader has inputs to enter problem scores as '
782-
. 'a percent, a point value, or both. Note, the problem score is always saved as a '
783-
. 'percent, so when using a point value, the problem score will be rounded to the '
784-
. 'nearest whole percent.'
781+
'This configures if the manual problem grader or single problem grader has inputs to enter '
782+
. 'problem scores as a percent, a point value, or both. Note, the problem score is always '
783+
. 'saved as a percent, so when using a point value, the problem score will be rounded to '
784+
. 'the nearest whole percent.'
785785
),
786786
values => [qw(Percent Point Both)],
787787
type => 'popuplist'

lib/WeBWorK/Utils.pm

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ our @EXPORT_OK = qw(
3232
generateURLs
3333
formatEmailSubject
3434
getAssetURL
35+
points_stepsize
36+
round_nearest_stepsize
3537
x
3638
);
3739

@@ -533,6 +535,28 @@ sub getAssetURL ($ce, $file, $isThemeFile = 0) {
533535
return "$ce->{webworkURLs}{htdocs}/$file";
534536
}
535537

538+
sub points_stepsize ($points) {
539+
my $stepsize;
540+
if ($points == 1) {
541+
$stepsize = 0.01;
542+
} elsif ($points <= 5) {
543+
$stepsize = 0.05;
544+
} elsif ($points <= 10) {
545+
$stepsize = 0.1;
546+
} elsif ($points <= 25) {
547+
$stepsize = 0.25;
548+
} elsif ($points <= 50) {
549+
$stepsize = 0.5;
550+
} else {
551+
$stepsize = int(($points - 1) / 100) + 1;
552+
}
553+
return $stepsize;
554+
}
555+
556+
sub round_nearest_stepsize ($score, $stepsize) {
557+
return wwRound(2, wwRound(0, $score / $stepsize) * $stepsize);
558+
}
559+
536560
sub x (@args) { return @args }
537561

538562
1;
@@ -763,6 +787,22 @@ Returns the URL for the asset specified in C<$file>. If C<$isThemeFile> is
763787
true, then the asset will be assumed to be located in a theme directory. The
764788
parameter C<$ce> must be a valid C<WeBWorK::CourseEnvironment> object.
765789
790+
=head2 points_stepsize
791+
792+
Usage: C<points_stepsize($points)>
793+
794+
Returns a reasonable stepsize that best converts between a whole percent and
795+
a point value. The stepsize is the point value that is close to but greater
796+
than or equal to 1% per step. This is done by first using preset values of
797+
0.01, 0.05, 0.1, 0.25, or 0.5, then using only whole point values, such that
798+
the stepsize is greater than or equal to 1% of total points.
799+
800+
=head2 round_nearest_stepsize
801+
802+
Usage: C<round_nearest_stepsize($score, $stepsize)>
803+
804+
Returns the value of the score rounded to the nearest stepsize.
805+
766806
=head2 x
767807
768808
Usage: C<x(@args)>

templates/ContentGenerator/Instructor/ProblemGrader.html.ep

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
% use WeBWorK::Utils qw(wwRound getAssetURL);
1+
% use WeBWorK::Utils qw(wwRound getAssetURL points_stepsize round_nearest_stepsize);
22
% require WeBWorK::PG;
33
%
44
% content_for js => begin
@@ -122,11 +122,17 @@
122122
<%= check_box 'select-all' => 'on', id => 'select-all', class => 'select-all form-check-input',
123123
data => { select_group => 'mark_correct' } =%>
124124
</th>
125-
<th id="score-header"><%= maketext('Score (%)') %></th>
125+
% unless ($ce->{problemGraderScore} eq 'Point') {
126+
<th id="score-header"><%= maketext('Score (%)') %></th>
127+
% }
128+
% unless ($ce->{problemGraderScore} eq 'Percent') {
129+
<th id="point-header"><%= maketext('Points (0 - [_1])', $problem->value) %></th>
130+
% }
126131
<th id="comment-header"><%= maketext('Comment') %></th>
127132
</tr>
128133
</thead>
129134
<tbody>
135+
% my $stepSize = points_stepsize($problem->value);
130136
% for my $user (@$users) {
131137
% my $userID = $user->user_id;
132138
%
@@ -206,14 +212,46 @@
206212
class => 'mark_correct form-check-input',
207213
'aria-labelledby' => 'mark-all-correct-header' =%>
208214
</td>
209-
<td class="restricted-width-col">
210-
% param("$userID.$versionID.score", undef);
211-
<%= number_field "$userID.$versionID.score" =>
212-
wwRound(0, $_->{problem}->status * 100),
213-
class => 'score-selector form-control form-control-sm restricted-width-col',
214-
style => 'width:6.5rem;', min => 0, max => 100, autocomplete => 'off',
215-
'aria-labelledby' => 'score-header' =%>
216-
</td>
215+
% unless ($ce->{problemGraderScore} eq 'Point') {
216+
<td class="restricted-width-col">
217+
% param("$userID.$versionID.score", undef);
218+
<%= number_field "$userID.$versionID.score" => wwRound(0, $_->{problem}->status * 100),
219+
id => "$userID.$versionID.score",
220+
class => 'problem-score form-control form-control-sm restricted-width-col',
221+
style => 'width:6.5rem;', min => 0, max => 100, step => 1,
222+
autocomplete => 'off', 'aria-labelledby' => 'score-header' =%>
223+
</td>
224+
% }
225+
% unless ($ce->{problemGraderScore} eq 'Percent') {
226+
% my $problemValue = ($_->{problem}->value ne '' ? $_->{problem} : $problem)->value;
227+
% my $useUserValue = $problemValue != $problem->value;
228+
% my $thisStepSize = $useUserValue ? points_stepsize($problemValue) : $stepSize;
229+
<td class="restricted-width-col">
230+
% if ($ce->{problemGraderScore} eq 'Point') {
231+
% param("$userID.$versionID.score", wwRound(0, $_->{problem}->status * 100));
232+
<%= hidden_field "$userID.$versionID.score" => 0,
233+
id => "$userID.$versionID.score" %>
234+
% }
235+
% param("$userID.$versionID.points", undef);
236+
<%= number_field "$userID.$versionID.points" => round_nearest_stepsize(
237+
$_->{problem}->status * $problemValue, $thisStepSize),
238+
id => "$userID.$versionID.points",
239+
class => 'problem-points form-control form-control-sm restricted-width-col',
240+
style => 'width:6.5rem;',
241+
min => 0,
242+
max => $problemValue,
243+
step => $thisStepSize,
244+
autocomplete => 'off',
245+
'aria-labelledby' => $useUserValue
246+
? "point-label-$userID.$versionID"
247+
: 'point-header' =%>
248+
% if ($useUserValue) {
249+
<div id="point-label-<%= "$userID.$versionID" =%>" class="fw-bold">
250+
<%= maketext('Points (0 - [_1])', $problemValue) =%>
251+
</div>
252+
% }
253+
</td>
254+
% }
217255
<td class="grader-comment-column">
218256
% if (defined $_->{past_answer}) {
219257
<%= text_area "$userID.$versionID.comment" => $_->{past_answer}->comment_string,

templates/HTML/SingleProblemGrader/grader.html.ep

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
% use WeBWorK::Utils 'wwRound';
1+
% use WeBWorK::Utils qw(wwRound points_stepsize round_nearest_stepsize);
22
%
33
% if (!stash->{jsInserted}) {
44
% stash->{jsInserted} = 1;
@@ -82,28 +82,11 @@
8282
%
8383
% # Total point value. Show only if configured to.
8484
% unless ($ce->{problemGraderScore} eq 'Percent') {
85-
% # Compute a reasonable step size based on point value.
86-
% # First use some preset nice values, then only use whole
87-
% # point values, such that the step size >= 1% of total.
88-
% my $stepSize;
89-
% if ($grader->{problem_value} == 1) {
90-
% $stepSize = 0.01;
91-
% } elsif ($grader->{problem_value} <= 5) {
92-
% $stepSize = 0.05;
93-
% } elsif ($grader->{problem_value} <= 10) {
94-
% $stepSize = 0.1;
95-
% } elsif ($grader->{problem_value} <= 25) {
96-
% $stepSize = 0.25;
97-
% } elsif ($grader->{problem_value} <= 50) {
98-
% $stepSize = 0.5;
99-
% } else {
100-
% $stepSize = int(($grader->{problem_value} - 1) / 100) + 1;
101-
% }
102-
% # Round point score to the nearest $stepSize.
85+
% my $stepSize = points_stepsize($grader->{problem_value});
10386
% my $recordedPoints =
104-
% wwRound(2, wwRound(0, $grader->{recorded_score} * $grader->{problem_value} / $stepSize) * $stepSize);
87+
% round_nearest_stepsize($grader->{recorded_score} * $grader->{problem_value}, $stepSize);
10588
% my $currentPoints =
106-
% wwRound(2, wwRound(0, $rawCurrentScore / 100 * $grader->{problem_value} / $stepSize) * $stepSize);
89+
% round_nearest_stepsize($rawCurrentScore / 100 * $grader->{problem_value}, $stepSize);
10790
% param('grader-problem-points', $recordedPoints);
10891
<div class="row align-items-center mb-2">
10992
<%= label_for "score_problem$grader->{problem_id}_points",

0 commit comments

Comments
 (0)