[LON-CAPA-cvs] cvs: rat /client parameter.html loncom/homework grades.pm inputtags.pm lonhomework.pm structuretags.pm loncom/interface loncommon.pm loncoursedata.pm lonnavmaps.pm lonparmset.pm lonquickgrades.pm loncom/interface/statistics lonstudentassessment.pm loncom/misc releaseslist.xml loncom/publisher packages.tab
raeburn
raeburn at source.lon-capa.org
Sat Jun 28 10:35:14 EDT 2025
raeburn Sat Jun 28 14:35:14 2025 EDT
Modified files:
/loncom/interface loncommon.pm loncoursedata.pm lonnavmaps.pm
lonparmset.pm lonquickgrades.pm
/loncom/interface/statistics lonstudentassessment.pm
/loncom/homework grades.pm inputtags.pm lonhomework.pm
structuretags.pm
/loncom/publisher packages.tab
/loncom/misc releaseslist.xml
/rat/client parameter.html
Log:
- Bug 6623. Grace period after due date during which problem can be viewed
and submissions can be made for partial credit. Work in progress.
-------------- next part --------------
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1477 loncom/interface/loncommon.pm:1.1478
--- loncom/interface/loncommon.pm:1.1477 Mon Mar 31 13:55:07 2025
+++ loncom/interface/loncommon.pm Sat Jun 28 14:34:46 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1477 2025/03/31 13:55:07 raeburn Exp $
+# $Id: loncommon.pm,v 1.1478 2025/06/28 14:34:46 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -8763,7 +8763,12 @@
padding: 4px;
}
-fieldset.LC_delete_slot > legend {
+fieldset.LC_grace {
+ display:inline;
+}
+
+fieldset.LC_delete_slot > legend,
+fieldset.LC_grace > legend {
font-weight: normal;
}
Index: loncom/interface/loncoursedata.pm
diff -u loncom/interface/loncoursedata.pm:1.209 loncom/interface/loncoursedata.pm:1.210
--- loncom/interface/loncoursedata.pm:1.209 Sat Nov 4 00:06:00 2023
+++ loncom/interface/loncoursedata.pm Sat Jun 28 14:34:46 2025
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: loncoursedata.pm,v 1.209 2023/11/04 00:06:00 raeburn Exp $
+# $Id: loncoursedata.pm,v 1.210 2025/06/28 14:34:46 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -220,6 +220,8 @@
type => 'SMALLINT UNSIGNED' },
{ name => 'awarded',
type => 'REAL' },
+ { name => 'latefrac',
+ type => 'REAL' },
{ name => 'award',
type => 'TINYTEXT' },
{ name => 'awarddetail',
@@ -257,6 +259,8 @@
type => 'REAL' },
{ name => 'previous',
type => 'SMALLINT UNSIGNED' },
+ { name => 'latefrac',
+ type => 'REAL' },
# { name => 'regrader',
# type => 'TINYTEXT' },
# { name => 'afterduedate',
@@ -1186,7 +1190,7 @@
#
# Parameters
while (my ($parameter,$value) = each(%$param_hash)) {
- if ($parameter !~ /(timestamp|resource\.(.*)\.(solved|tries|awarded|award|awarddetail|previous))/) {
+ if ($parameter !~ /(timestamp|resource\.(.*)\.(solved|tries|awarded|award|awarddetail|previous|latefrac))/) {
my $sql_parameter = "('".join("','",
$symb_id,$student_id,
$parameter)."',".
Index: loncom/interface/lonnavmaps.pm
diff -u loncom/interface/lonnavmaps.pm:1.575 loncom/interface/lonnavmaps.pm:1.576
--- loncom/interface/lonnavmaps.pm:1.575 Tue May 27 23:31:49 2025
+++ loncom/interface/lonnavmaps.pm Sat Jun 28 14:34:46 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.575 2025/05/27 23:31:49 raeburn Exp $
+# $Id: lonnavmaps.pm,v 1.576 2025/06/28 14:34:46 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -637,6 +637,7 @@
my $open = $res->opendate($part);
my $due = $res->duedate($part);
+ my $overdue = $res->overduedate($part);
my $answer = $res->answerdate($part);
if ($status == $res->NETWORK_FAILURE) {
@@ -690,11 +691,20 @@
}
if ($status == $res->OPEN) {
if ($due) {
- if ($res->is_practice()) {
- return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo;
- } else {
- return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo;
- }
+ my $now = time;
+ if (($now >= $due) && ($overdue) && ($now < $overdue)) {
+ if ($res->is_practice()) {
+ return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($overdue,'start'),$res->symb(),'duedate',$part)).$slotinfo;
+ } else {
+ return &mt("Grace period ends [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($overdue,'end'),$res->symb(),'grace',$part)).$slotinfo;
+ }
+ } else {
+ if ($res->is_practice()) {
+ return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo;
+ } else {
+ return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo;
+ }
+ }
} else {
return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo;
}
@@ -5177,6 +5187,12 @@
if (!defined($part)) { $part = '0'; }
return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'};
}
+sub latefrac {
+ my $self = shift; my $part = shift;
+ $self->{NAV_MAP}->get_user_data();
+ if (!defined($part)) { $part = '0'; }
+ return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.latefrac'};
+}
sub taskversion {
my $self = shift; my $part = shift;
$self->{NAV_MAP}->get_user_data();
@@ -5266,6 +5282,71 @@
}
return $opendate;
}
+sub overduedate {
+ my ($self,$part) = @_;
+ my $duedate = $self->parmval("duedate", $part);
+ my $overduedate;
+ if ($duedate) {
+ my $grace = $self->parmval("grace", $part);
+ if ($grace) {
+ my $grace_end = (split(/,/,$grace))[-1];
+ my ($offset) = split(/:/,$grace_end,2);
+ if ($offset > 0) {
+ $overduedate = $offset + $duedate;
+ }
+ }
+ }
+ return $overduedate;
+}
+sub partial_credit_overdue {
+ my ($self,$part) = @_;
+ my $reduction;
+ my $duedate = $self->parmval("duedate", $part);
+ if ($duedate) {
+ my $grace = $self->parmval("grace",$part);
+ if ($grace) {
+ my $lateness = time - $duedate;
+ if ($lateness > 0) {
+ my ($start,$end,$startfrac,$endfrac,$usegrad);
+ $start = 0;
+ $startfrac = 1.0;
+ $usegrad = 0;
+ foreach my $item (split(/,/,$grace)) {
+ my ($offset,$frac,$grad) = split(/:/,$item);
+ if ($lateness > $offset) {
+ $start = $offset;
+ $startfrac = $frac;
+ next;
+ } elsif ($lateness <= $offset) {
+ $end = $offset;
+ $endfrac = $frac;
+ $usegrad = $grad;
+ last;
+ }
+ }
+ if ($end) {
+ if (($end == $start) || ($startfrac == $endfrac)) {
+ $reduction = $endfrac;
+ } elsif ($end - $start > 0) {
+ if (($endfrac <= 1.0) && ($endfrac >= 0)) {
+ $reduction = $endfrac;
+ if ($usegrad) {
+ my $decline = $startfrac - $endfrac;
+ my $fraction = ($lateness - $start)/($end - $start);
+ if (($fraction <= 1) && ($fraction >= 0)) {
+ my $value = $startfrac - ($decline*$fraction);
+ $reduction = sprintf("%.2f", $value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return $reduction;
+}
+
sub problemstatus {
(my $self, my $part) = @_;
my $problemstatus = $self->parmval("problemstatus", $part);
@@ -5844,6 +5925,7 @@
my $open = $self->opendate($part);
my $due = $self->duedate($part);
+ my $overdue = $self->overduedate($part);
my $answer = $self->answerdate($part);
if (!$open && !$due && !$answer) {
@@ -5853,6 +5935,7 @@
}
if (!$open || $now < $open) {return $self->OPEN_LATER}
if (!$due || $now < $due) {return $self->OPEN}
+ if ($overdue && $now < $overdue) {return $self->OPEN}
if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER}
if ($answer) { return $self->ANSWER_OPEN; }
return PAST_DUE_NO_ANSWER;
@@ -6121,6 +6204,7 @@
# If there's an answer date and we're past it, don't
# suppress the feedback; student should know
if ($self->duedate($part) && $self->duedate($part) < time() &&
+ (!$self->overduedate($part) || $self->overduedate($part) < time()) &&
$self->answerdate($part) && $self->answerdate($part) < time()) {
$suppressFeedback = 0;
}
Index: loncom/interface/lonparmset.pm
diff -u loncom/interface/lonparmset.pm:1.621 loncom/interface/lonparmset.pm:1.622
--- loncom/interface/lonparmset.pm:1.621 Fri Dec 22 13:38:02 2023
+++ loncom/interface/lonparmset.pm Sat Jun 28 14:34:46 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Handler to set parameters for assessments
#
-# $Id: lonparmset.pm,v 1.621 2023/12/22 13:38:02 raeburn Exp $
+# $Id: lonparmset.pm,v 1.622 2025/06/28 14:34:46 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -992,55 +992,46 @@
$result=' ';
}
} else {
- if ($type eq 'date_interval') {
- my ($totalsecs,$donesuffix) = split(/_/,$value,2);
- my ($usesdone,$donebuttontext,$proctor,$secretkey);
- if ($donesuffix =~ /^done\:([^\:]+)\:(.*)$/) {
- $donebuttontext = $1;
- (undef,$proctor,$secretkey) = split(/_/,$2);
- $usesdone = 'done';
- } elsif ($donesuffix =~ /^done(|_.+)$/) {
- $donebuttontext = &mt('Done');
- ($usesdone,$proctor,$secretkey) = split(/_/,$donesuffix);
- }
- my ($sec,$min,$hour,$mday,$mon,$year)=gmtime($totalsecs);
- my @timer;
- $year=$year-70;
- $mday--;
- if ($year) {
-# $result.=&mt('[quant,_1,yr]',$year).' ';
- push(@timer,&mt('[quant,_1,yr]',$year));
- }
- if ($mon) {
-# $result.=&mt('[quant,_1,mth]',$mon).' ';
- push(@timer,&mt('[quant,_1,mth]',$mon));
- }
- if ($mday) {
-# $result.=&mt('[quant,_1,day]',$mday).' ';
- push(@timer,&mt('[quant,_1,day]',$mday));
- }
- if ($hour) {
-# $result.=&mt('[quant,_1,hr]',$hour).' ';
- push(@timer,&mt('[quant,_1,hr]',$hour));
- }
- if ($min) {
-# $result.=&mt('[quant,_1,min]',$min).' ';
- push(@timer,&mt('[quant,_1,min]',$min));
- }
- if ($sec) {
-# $result.=&mt('[quant,_1,sec]',$sec).' ';
- push(@timer,&mt('[quant,_1,sec]',$sec));
- }
-# $result=~s/\s+$//;
- if (!@timer) { # Special case: all entries 0 -> display "0 secs" intead of empty field to keep this field editable
- push(@timer,&mt('[quant,_1,sec]',0));
- }
- $result.=join(", ", at timer);
- if ($usesdone eq 'done') {
- if ($secretkey) {
- $result .= ' '.&mt('+ "[_1]" with proctor key: [_2]',$donebuttontext,$secretkey);
+ if (($type eq 'date_interval') || ($type eq 'string_grace')) {
+ if ($type eq 'string_grace') {
+ my @items;
+ if ($value =~ /,/) {
+ @items = split(/,/,$value);
} else {
- $result .= ' + "'.$donebuttontext.'"';
+ @items = ($value);
+ }
+ foreach my $item (@items) {
+ if ($item =~ /^\d+:(0|1)\.?\d*:(0|1)$/) {
+ my ($totalsecs,$fraction,$grad) = split(/:/,$item);
+ $result .= &interval_to_humanstr($totalsecs);
+ if (($fraction >=0) && ($fraction <=1)) {
+ $result .= ' | '.$fraction.' '.&mt('pts');
+ if ($grad == 1) {
+ $result .= ' ('.&mt('gradual').')';
+ }
+ }
+ $result .= ', ';
+ }
+ }
+ $result =~ s/, $//;
+ } else {
+ my ($totalsecs,$donesuffix) = split(/_/,$value,2);
+ $result = &interval_to_humanstr($totalsecs);
+ my ($usesdone,$donebuttontext,$proctor,$secretkey);
+ if ($donesuffix =~ /^done\:([^\:]+)\:(.*)$/) {
+ $donebuttontext = $1;
+ (undef,$proctor,$secretkey) = split(/_/,$2);
+ $usesdone = 'done';
+ } elsif ($donesuffix =~ /^done(|_.+)$/) {
+ $donebuttontext = &mt('Done');
+ ($usesdone,$proctor,$secretkey) = split(/_/,$donesuffix);
+ }
+ if ($usesdone eq 'done') {
+ if ($secretkey) {
+ $result .= ' '.&mt('+ "[_1]" with proctor key: [_2]',$donebuttontext,$secretkey);
+ } else {
+ $result .= ' + "'.$donebuttontext.'"';
+ }
}
}
} elsif (&isdateparm($type)) {
@@ -1055,6 +1046,35 @@
return $result;
}
+sub interval_to_humanstr {
+ my ($totalsecs) = @_;
+ my ($sec,$min,$hour,$mday,$mon,$year)=gmtime($totalsecs);
+ my @timer;
+ $year=$year-70;
+ $mday--;
+ if ($year) {
+ push(@timer,&mt('[quant,_1,yr]',$year));
+ }
+ if ($mon) {
+ push(@timer,&mt('[quant,_1,mth]',$mon));
+ }
+ if ($mday) {
+ push(@timer,&mt('[quant,_1,day]',$mday));
+ }
+ if ($hour) {
+ push(@timer,&mt('[quant,_1,hr]',$hour));
+ }
+ if ($min) {
+ push(@timer,&mt('[quant,_1,min]',$min));
+ }
+ if ($sec) {
+ push(@timer,&mt('[quant,_1,sec]',$sec));
+ }
+ if (!@timer) { # Special case: all entries 0 -> display "0 secs" intead of empty field to keep this field editable
+ push(@timer,&mt('[quant,_1,sec]',0));
+ }
+ return '<span style="white-space:nowrap">'.join('</span>, <span style="white-space:nowrap">', at timer).'</span>';
+}
# Returns HTML containing a link on a parameter value, for table mode.
# The link uses the javascript function 'pjump'.
@@ -1245,6 +1265,7 @@
var ipRegExp = /^setip/;
var ipallowRegExp = /^setipallow_/;
var ipdenyRegExp = /^setipdeny_/;
+ var graceRegExp = /^setgrace_/;
var deeplinkRegExp = /^deeplink_/;
var dlListScopeRegExp = /^deeplink_(state|others|listing|scope)_/;
var dlLinkProtectRegExp = /^deeplink_protect_/;
@@ -1461,6 +1482,63 @@
}
}
}
+ } else if (graceRegExp.test(name)) {
+ var identifier = name.replace(graceRegExp,'');
+ var divElem = document.parmform.elements[i].closest('div');
+ var timeSels = divElem.getElementsByTagName("select");
+ var total = 0;
+ if (timeSels.length) {
+ for (var j=0; j<timeSels.length; j++) {
+ var sname = timeSels[j].getAttribute('name');
+ var poss = parseInt(timeSels[j].options[timeSels[j].selectedIndex].value);
+ if (sname == 'days_'+identifier) {
+ if ((poss > 0) && (poss <= 31)) {
+ total += (poss * 86400);
+ }
+ } else if (sname == 'hours_'+identifier) {
+ if ((poss > 0) && (poss < 24)) {
+ total += (poss * 3600);
+ }
+ } else if (sname == 'minutes_'+identifier) {
+ if ((poss > 0) && (poss < 60)) {
+ total += (poss * 60);
+ }
+ } else if (sname == 'seconds_'+identifier) {
+ if ((poss > 0) && (poss < 60)) {
+ total += poss;
+ }
+ }
+ }
+ }
+ var inputElems = divElem.getElementsByTagName("input");
+ var frac = '';
+ var grad = '';
+ if (inputElems.length) {
+ for (var j=0; j<inputElems.length; j++) {
+ var iname = inputElems[j].getAttribute('name');
+ if (iname == 'frac_'+identifier) {
+ var ival = inputElems[j].value;
+ ival.trim();
+ var poss = parseFloat(ival);
+ if ((typeof poss === 'number') && (!isNaN(poss))) {
+ if ((poss => 0) && (poss <= 1)) {
+ frac = poss;
+ }
+ }
+ } else if (iname == 'grad_'+identifier) {
+ if (inputElems[j].checked) {
+ grad = 1;
+ } else {
+ grad = 0;
+ }
+ }
+ }
+ }
+ document.parmform.elements[i].value = total+':'+frac+':'+grad;
+ if (document.parmform.elements['set_'+identifier].value) {
+ document.parmform.elements['set_'+identifier].value += ',';
+ }
+ document.parmform.elements['set_'+identifier].value += document.parmform.elements[i].value;
}
}
}
@@ -1513,6 +1591,45 @@
END
}
+sub grace_js {
+ my %lt = &grace_titles();
+ &js_escape(\%lt);
+ my $overdue = '<fieldset class="LC_grace"><legend>'.$lt{'sinc'}.'</legend>';
+ foreach my $which (['days', 86400, 31],
+ ['hours', 3600, 23],
+ ['minutes', 60, 59],
+ ['seconds', 1, 59]) {
+ my ($name, $factor, $max) = @{ $which };
+ my %select = ((map {$_ => $_} (0..$max)),
+ 'select_form_order' => [0..$max]);
+ my $selector = &Apache::loncommon::select_form('',$name."_'+identifier+'",
+ \%select);
+ $selector =~ s/([\r\n\f]+)//g;
+ $overdue .= $selector.' '.$lt{$name}.(' 'x2).' ';
+ }
+ $overdue .= '</fieldset>';
+ return <<"END";
+\$(document).ready(function() {
+ var wrapper = \$(".LC_string_grace_wrap");
+ var add_button = \$(".LC_add_grace_button");
+ var graceRegExp = /^LC_string_grace_/;
+
+ \$(add_button).click(function(e){
+ e.preventDefault();
+ var identifier = \$(this).closest("div").attr("id");
+ identifier = identifier.replace(graceRegExp,'');
+ \$(this).closest('div').find('.LC_string_grace_inner').append('<div><input type="hidden" name="setgrace_'+identifier+'" value="" />$overdue<fieldset class="LC_grace"><legend>$lt{scor}</legend><input type="text" size="3" name="frac_'+identifier+'" value="" /> <label><input type="checkbox" value="1" name="grad_'+identifier+'" />$lt{grad}</label></fieldset><a href="#" class="LC_remove_grace">$lt{remo}</a></div>');
+ });
+
+ \$(wrapper).delegate(".LC_remove_grace","click", function(e){
+ e.preventDefault(); \$(this).closest("div").remove();
+ })
+});
+
+
+END
+}
+
# Javascript function toggleSecret, for overview mode.
sub done_proctor_js {
my $defaultdone = &mt('Done');
@@ -2441,6 +2558,7 @@
'opendate' => 'time_settings',
'duedate' => 'time_settings',
'answerdate' => 'time_settings',
+ 'grace' => 'time_settings',
'interval' => 'time_settings',
'contentopen' => 'time_settings',
'contentclose' => 'time_settings',
@@ -3180,26 +3298,27 @@
return ('parameter_0_opendate' => 1,
'parameter_0_duedate' => 2,
'parameter_0_answerdate' => 3,
- 'parameter_0_interval' => 4,
- 'parameter_0_weight' => 5,
- 'parameter_0_maxtries' => 6,
- 'parameter_0_hinttries' => 7,
- 'parameter_0_contentopen' => 8,
- 'parameter_0_contentclose' => 9,
- 'parameter_0_type' => 10,
- 'parameter_0_problemstatus' => 11,
- 'parameter_0_hiddenresource' => 12,
- 'parameter_0_hiddenparts' => 13,
- 'parameter_0_display' => 14,
- 'parameter_0_ordered' => 15,
- 'parameter_0_tol' => 16,
- 'parameter_0_sig' => 17,
- 'parameter_0_turnoffunit' => 18,
- 'parameter_0_discussend' => 19,
- 'parameter_0_discusshide' => 20,
- 'parameter_0_discussvote' => 21,
- 'parameter_0_printstartdate' => 22,
- 'parameter_0_printenddate' => 23);
+ 'parameter_0_grace' => 4,
+ 'parameter_0_interval' => 5,
+ 'parameter_0_weight' => 6,
+ 'parameter_0_maxtries' => 7,
+ 'parameter_0_hinttries' => 8,
+ 'parameter_0_contentopen' => 9,
+ 'parameter_0_contentclose' => 10,
+ 'parameter_0_type' => 11,
+ 'parameter_0_problemstatus' => 12,
+ 'parameter_0_hiddenresource' => 13,
+ 'parameter_0_hiddenparts' => 14,
+ 'parameter_0_display' => 15,
+ 'parameter_0_ordered' => 16,
+ 'parameter_0_tol' => 17,
+ 'parameter_0_sig' => 18,
+ 'parameter_0_turnoffunit' => 19,
+ 'parameter_0_discussend' => 20,
+ 'parameter_0_discusshide' => 21,
+ 'parameter_0_discussvote' => 22,
+ 'parameter_0_printstartdate' => 23,
+ 'parameter_0_printenddate' => 24);
}
@@ -3712,7 +3831,7 @@
'date_interval','int','float','string','string_lenient',
'string_examcode','string_deeplink','string_discussvote',
'string_useslots','string_problemstatus','string_ip',
- 'string_questiontype','string_tex') {
+ 'string_questiontype','string_tex','string_grace') {
$r->print('<input type="hidden" value="'.
&HTML::Entities::encode($env{'form.recent_'.$item},'"&<>').
'" name="recent_'.$item.'" />');
@@ -4381,7 +4500,7 @@
# Stores parameter data, using form parameters directly.
#
# Uses the following form parameters. The variable part in the names is a resourcedata key (except for a modification for user data).
-# set_* (except settext, setipallow, setipdeny, setdeeplink) - set a parameter value
+# set_* (except settext, setipallow, setipdeny, setdeeplink, setgrace) - set a parameter value
# del_* - remove a parameter
# datepointer_* - set a date parameter (value is key_* refering to a set of other form parameters)
# dateinterval_* - set a date interval parameter (value refers to more form parameters)
@@ -4414,7 +4533,7 @@
my $cmd=$1;
my $thiskey=$2;
my ($altkey,$recursive,$tkey,$tkeyrec,$tkeynonrec);
- next if ($cmd eq 'rec' || $cmd eq 'settext' || $cmd eq 'setipallow' || $cmd eq 'setipdeny' || $cmd eq 'setdeeplink');
+ next if ($cmd eq 'rec' || $cmd eq 'settext' || $cmd eq 'setipallow' || $cmd eq 'setipdeny' || $cmd eq 'setdeeplink' || $cmd eq 'setgrace');
if ((($cmd eq 'set') || ($cmd eq 'datepointer') || ($cmd eq 'dateinterval') || ($cmd eq 'del')) &&
($thiskey =~ /(?:sequence|page)\Q___(all)\E/)) {
unless ($thiskey =~ /(encrypturl|hiddenresource)$/) {
@@ -5513,6 +5632,91 @@
return $output;
}
+sub string_grace_selector {
+ my ($thiskey, $showval, $readonly) = @_;
+ my $addmore;
+ unless ($readonly) {
+ $addmore = "\n".'<button class="LC_add_grace_button">'.&mt('Add more').'</button>';
+ }
+ my $output = '<input type="hidden" name="set_'.$thiskey.'" value="" />'.
+ '<div class="LC_string_grace_wrap" id="LC_string_grace_'.$thiskey.'">'."\n".
+ '<div class="LC_string_grace_inner">'."\n";
+ if ($showval ne '') {
+ my @current;
+ if ($showval =~ /,/) {
+ @current = split(/,/,$showval);
+ } else {
+ @current = ($showval);
+ }
+ my $num = scalar(@current);
+ foreach my $item (@current) {
+ my ($delta,$fraction,$gradational) = split(/:/,$item);
+ if (($delta =~ /^\d+$/) && ($fraction =~ /^(0|1)\.?\d*$/) &&
+ (($gradational eq 1) || ($gradational eq '0'))) {
+ my $gradchk = '';
+ if ($gradational) {
+ $gradchk = ' checked="checked"';
+ }
+ $output .= &grace_form($thiskey,$delta,$fraction,$gradchk,
+ $readonly);
+ }
+ }
+ } elsif (!$readonly) {
+ $output .= &grace_form($thiskey,'','','',$readonly);
+ }
+ $output .= '</div>'.$addmore.'</div>';
+ return $output;
+}
+
+sub grace_form {
+ my ($thiskey,$delta,$fraction,$gradchkon,$readonly) = @_;
+ my $disabled;
+ if ($readonly) {
+ $disabled = ' disabled="disabled"';
+ }
+ my %lt = &grace_titles();
+ my $output = '<div><input type="hidden" name="setgrace_'.$thiskey.'" value="" />'.
+ '<fieldset class="LC_grace"><legend>'.$lt{'sinc'}.'</legend>';
+ foreach my $which (['days', 86400, 31],
+ ['hours', 3600, 23],
+ ['minutes', 60, 59],
+ ['seconds', 1, 59]) {
+ my ($name, $factor, $max) = @{ $which };
+ my $amount;
+ if ($delta ne '') {
+ $amount = int($delta/$factor);
+ $delta %= $factor;
+ }
+ my %select = ((map {$_ => $_} (0..$max)),
+ 'select_form_order' => [0..$max]);
+ $output .= &Apache::loncommon::select_form($amount,$name.'_'.$thiskey,
+ \%select,'',$readonly);
+ $output .= ' '.$lt{$name}.' ';
+ }
+ $output .= '</fieldset>'.
+ '<fieldset class="LC_grace"><legend>'.$lt{'pcr'}.'</legend>'.
+ '<input type="text" size="3" name="frac_'.$thiskey.'" value="'.$fraction.'"'.$disabled.' />'.
+ ' <label><input type="checkbox" value="1" name="grad_'.$thiskey.'"'.$gradchkon.$disabled.' />'.
+ $lt{'grad'}.'</label></fieldset>';
+ unless ($readonly) {
+ $output .= '<a href="#" class="LC_remove_grace">'.$lt{'remo'}.'</a>';
+ }
+ $output .= '</div>'."\n";
+ return $output;
+}
+
+sub grace_titles {
+ return &Apache::lonlocal::texthash (
+ sinc => 'Time past due',
+ remo => 'Remove',
+ pcr => 'Partial credit',
+ grad => 'gradual',
+ days => 'days',
+ hours => 'hours',
+ minutes => 'minutes',
+ seconds => 'seconds',
+ );
+}
{ # block using some constants related to parameter types (overview mode)
@@ -5552,6 +5756,8 @@
'string_tex'
=> [['tth', 'tth (TeX to HTML)'],
['mathjax', 'MathJax']],
+ 'string_grace'
+ => [['on','Set grading scale and grace period for submissions after due date']],
);
@@ -5563,6 +5769,8 @@
['_denyfrom_','\!']],
'string_deeplink'
=> [['on','^(only|off|both)\,(hide|unhide)\,(full|absent|grades|details|datestatus)\,(res|map|rec)\,(none|key\:\w+|ltic\:\d+|ltid\:\d+)\,(\d+|)\,_(self|top),(yes|url|no)(|:[^:;\'",]+)$']],
+ 'string_grace'
+ => [['on','^\d+,(0|1)\.?\d*,(0|1)']],
);
my %stringtypes = (
@@ -5573,6 +5781,7 @@
examcode => 'string_examcode',
acc => 'string_ip',
deeplink => 'string_deeplink',
+ grace => 'string_grace',
texdisplay => 'string_tex',
);
@@ -5635,6 +5844,7 @@
($thistype eq 'string_ip') ||
($thistype eq 'string_deeplink') ||
($thistype eq 'string_tex') ||
+ ($thistype eq 'string_grace') ||
($name eq 'retrypartial')) {
my ($got_chostname,$chostname,$cmajor,$cminor);
foreach my $possibilities (@{ $strings{$thistype} }) {
@@ -5672,7 +5882,9 @@
}
if ($thistype eq 'string_ip') {
- return &string_ip_selector($thiskey,$showval,$readonly);
+ return &string_ip_selector($thiskey,$showval,$readonly);
+ } elsif ($thistype eq 'string_grace') {
+ return &string_grace_selector($thiskey,$showval,$readonly);
} elsif ($thistype eq 'string_deeplink') {
return &string_deeplink_selector($thiskey,$showval,$readonly);
}
@@ -6111,6 +6323,7 @@
&toggleparmtextbox_js()."\n".
&validateparms_js()."\n".
&ipacc_boxes_js()."\n".
+ &grace_js()."\n".
&done_proctor_js()."\n".
&deeplink_js()."\n".
'// ]]>
@@ -6339,6 +6552,7 @@
&toggleparmtextbox_js()."\n".
&validateparms_js()."\n".
&ipacc_boxes_js()."\n".
+ &grace_js()."\n".
&done_proctor_js()."\n".
&deeplink_js()."\n".
'// ]]>'."\n".
@@ -7628,6 +7842,9 @@
} else {
if (&isdateparm($istype{$parmname})) {
$showvalue = &Apache::lonlocal::locallocaltime($value);
+ } elsif (($istype{$parmname} eq 'string_grace') ||
+ ($istype{$parmname} eq 'string_ip')) {
+ $showvalue =~ s/,/, /g;
}
}
$output .= $showvalue;
Index: loncom/interface/lonquickgrades.pm
diff -u loncom/interface/lonquickgrades.pm:1.128 loncom/interface/lonquickgrades.pm:1.129
--- loncom/interface/lonquickgrades.pm:1.128 Tue Dec 10 04:52:30 2024
+++ loncom/interface/lonquickgrades.pm Sat Jun 28 14:34:46 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Quick Student Grades Display
#
-# $Id: lonquickgrades.pm,v 1.128 2024/12/10 04:52:30 raeburn Exp $
+# $Id: lonquickgrades.pm,v 1.129 2025/06/28 14:34:46 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -612,7 +612,8 @@
$totalAttempted += $partsAttempted;
}
} else {
- $score = &Apache::grades::compute_points($weight, $curRes->awarded($part));
+ $score = &Apache::grades::compute_points($weight, $curRes->awarded($part),
+ $curRes->latefrac($part));
}
$partsRight += $score;
$totalRight += $score;
Index: loncom/interface/statistics/lonstudentassessment.pm
diff -u loncom/interface/statistics/lonstudentassessment.pm:1.177 loncom/interface/statistics/lonstudentassessment.pm:1.178
--- loncom/interface/statistics/lonstudentassessment.pm:1.177 Fri Apr 7 16:46:44 2023
+++ loncom/interface/statistics/lonstudentassessment.pm Sat Jun 28 14:34:53 2025
@@ -1,6 +1,6 @@
# The LearningOnline Network with CAPA
#
-# $Id: lonstudentassessment.pm,v 1.177 2023/04/07 16:46:44 raeburn Exp $
+# $Id: lonstudentassessment.pm,v 1.178 2025/06/28 14:34:53 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -2089,8 +2089,12 @@
$awarded = 0 if (! $awarded);
$hasdata = 1;
}
+ my $latefrac;
+ if (exists($resource_data->{'resource.'.$part.'.latefrac'})) {
+ $latefrac = $resource_data->{'resource.'.$part.'.latefrac'};
+ }
#
- $partscore = &Apache::grades::compute_points($weight,$awarded);
+ $partscore = &Apache::grades::compute_points($weight,$awarded,$latefrac);
if (! defined($awarded)) {
$partscore = undef;
}
Index: loncom/homework/grades.pm
diff -u loncom/homework/grades.pm:1.810 loncom/homework/grades.pm:1.811
--- loncom/homework/grades.pm:1.810 Sat Jan 18 22:04:36 2025
+++ loncom/homework/grades.pm Sat Jun 28 14:35:00 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.810 2025/01/18 22:04:36 raeburn Exp $
+# $Id: grades.pm,v 1.811 2025/06/28 14:35:00 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -881,14 +881,20 @@
-# Given the score (as a number [0-1] and the weight) what is the final
-# point value? This function will round to the nearest tenth, third,
-# or quarter if one of those is within the tolerance of .00001.
+# Given the score (as a number [0-1], the weight, and a posible
+# reduction for submission between duedate and overduedate)
+# what is the final point value? This function will round to
+# the nearest tenth, third, or quarter if one of those is
+# within the tolerance of .00001.
sub compute_points {
- my ($score, $weight) = @_;
+ my ($score, $weight, $latefrac) = @_;
my $tolerance = .00001;
my $points = $score * $weight;
+ if (($latefrac ne '') &&
+ ($latefrac < 1) && ($latefrac >= 0)) {
+ $points = $points * $latefrac;
+ }
# Check for nearness to 1/x.
my $check_for_nearness = sub {
@@ -2951,7 +2957,8 @@
: '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
$wgt = ($wgt > 0 ? $wgt : '1');
my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
- '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
+ '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt,
+ $$record{'resource.'.$partid.'.latefrac'}));
my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
my $display_part= &get_display_part($partid,$symb);
my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
@@ -5419,6 +5426,7 @@
foreach my $apart (@$parts) {
my ($part,$type) = &split_part_type($apart);
my $score=$record{"resource.$part.$type"};
+ my $latefrac=$record{"resource.$part.latefrac"};
$result.='<td align="center">';
my ($aggtries,$totaltries);
unless (exists($aggregates{$part})) {
@@ -5435,7 +5443,7 @@
$aggregates{$part} = 1;
}
if ($type eq 'awarded') {
- my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
+ my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part},$latefrac);
$result.='<input type="hidden" name="'.
'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
$result.='<input type="text" name="'.
Index: loncom/homework/inputtags.pm
diff -u loncom/homework/inputtags.pm:1.369 loncom/homework/inputtags.pm:1.370
--- loncom/homework/inputtags.pm:1.369 Mon Mar 17 00:25:53 2025
+++ loncom/homework/inputtags.pm Sat Jun 28 14:35:00 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# input definitons
#
-# $Id: inputtags.pm,v 1.369 2025/03/17 00:25:53 raeburn Exp $
+# $Id: inputtags.pm,v 1.370 2025/06/28 14:35:00 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -1478,6 +1478,13 @@
if (!$Apache::lonhomework::scantronmode &&
$Apache::inputtags::status['-1'] ne 'CAN_ANSWER' &&
$Apache::inputtags::status['-1'] ne 'CANNOT_ANSWER') {
+ my $overduedate = &Apache::lonhomework::overdue_date($id);
+ if ($overduedate) {
+ if (time > $overduedate) {
+ $Apache::lonhomework::results{"resource.$id.aftergrace"}=$award;
+ return '';
+ }
+ }
$Apache::lonhomework::results{"resource.$id.afterduedate"}=$award;
return '';
} elsif ( $Apache::lonhomework::history{"resource.$id.awarded"} < 1
@@ -1496,6 +1503,9 @@
if ($Apache::lonhomework::history{"resource.$id.afterduedate"}) {
$Apache::lonhomework::results{"resource.$id.afterduedate"}='';
}
+ if ($Apache::lonhomework::history{"resource.$id.aftergrace"}) {
+ $Apache::lonhomework::results{"resource.$id.aftergrace"}='';
+ }
if ( $award eq 'ASSIGNED_SCORE') {
$Apache::lonhomework::results{"resource.$id.tries"} =
$Apache::lonhomework::history{"resource.$id.tries"} + 1;
@@ -1616,6 +1626,21 @@
$Apache::lonhomework::results{"resource.$id.hinttries"} = &Apache::lonnet::EXT("resource.$id.hinttries");
$Apache::lonhomework::results{"resource.$id.version"} = &Apache::lonnet::usedversion();
$Apache::lonhomework::results{"resource.$id.maxtries"} = &Apache::lonnet::EXT("resource.$id.maxtries");
+ if ($Apache::lonhomework::results{"resource.$id.duedate"} ne '') {
+ my $now = time();
+ if ($now > $Apache::lonhomework::results{"resource.$id.duedate"}) {
+ my $overduedate = &Apache::lonhomework::overdue_date($id);
+ if ($overduedate) {
+ $Apache::lonhomework::results{"resource.$id.endgrace"} = $overduedate;
+ if ($now <= $overduedate) {
+ my $fraction = &Apache::lonhomework::partial_credit_overdue($id);
+ if ($fraction ne '') {
+ $Apache::lonhomework::results{"resource.$id.latefrac"} = $fraction;
+ }
+ }
+ }
+ }
+ }
}
sub find_which_previous {
Index: loncom/homework/lonhomework.pm
diff -u loncom/homework/lonhomework.pm:1.394 loncom/homework/lonhomework.pm:1.395
--- loncom/homework/lonhomework.pm:1.394 Fri Jan 17 15:05:47 2025
+++ loncom/homework/lonhomework.pm Sat Jun 28 14:35:00 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Homework handler
#
-# $Id: lonhomework.pm,v 1.394 2025/01/17 15:05:47 raeburn Exp $
+# $Id: lonhomework.pm,v 1.395 2025/06/28 14:35:00 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -600,10 +600,12 @@
return ('SHOW_ANSWER');
}
}
- foreach my $temp ("opendate","duedate","answerdate") {
+ foreach my $temp ("opendate","duedate","overduedate","answerdate") {
$lastdate = $date;
if ($temp eq 'duedate') {
$date = &due_date($id,$symb);
+ } elsif ($temp eq 'overduedate') {
+ $date = &overdue_date($id,$symb);
} else {
$date = &Apache::lonnet::EXT("resource.$id.$temp",$symb);
}
@@ -644,6 +646,9 @@
} elsif ($type eq 'duedate') {
$status='CAN_ANSWER';
$datemsg = &mt('is due at [_1]',$date);
+ } elsif ($type eq 'overduedate') {
+ $status='CAN_ANSWER';
+ $datemsg = &mt('past-due grace period until [_1]',$date);
} elsif ($type eq 'answerdate') {
$status='CLOSED';
$datemsg = &mt('was due on [_1], and answers will be available on [_2]',
@@ -734,6 +739,76 @@
return $date;
}
+sub overdue_date {
+ my ($part_id,$symb,$udom,$uname)=@_;
+ my $date;
+ my $duedate= &Apache::lonnet::EXT("resource.$part_id.duedate",$symb,
+ $udom,$uname);
+ if ($duedate ne '') {
+ my $grace = &Apache::lonnet::EXT("resource.$part_id.grace",$symb,
+ $udom,$uname);
+ if ($grace) {
+ my $grace_end = (split(/,/,$grace))[-1];
+ my ($offset) = split(/:/,$grace_end,2);
+ if ($offset > 0) {
+ $date = $offset + $duedate;
+ }
+ }
+ }
+ return $date;
+}
+
+sub partial_credit_overdue {
+ my ($part_id,$symb,$udom,$uname)=@_;
+ my $reduction;
+ my $duedate = &Apache::lonnet::EXT("resource.$part_id.duedate",$symb,
+ $udom,$uname);
+ if ($duedate) {
+ my $grace = &Apache::lonnet::EXT("resource.$part_id.grace",$symb,
+ $udom,$uname);
+ if ($grace) {
+ my $lateness = time - $duedate;
+ if ($lateness > 0) {
+ my ($start,$end,$startfrac,$endfrac,$usegrad);
+ $start = 0;
+ $startfrac = 1.0;
+ $usegrad = 0;
+ foreach my $item (split(/,/,$grace)) {
+ my ($offset,$frac,$grad) = split(/:/,$item);
+ if ($lateness > $offset) {
+ $start = $offset;
+ $startfrac = $frac;
+ next;
+ } elsif ($lateness <= $offset) {
+ $end = $offset;
+ $endfrac = $frac;
+ $usegrad = $grad;
+ last;
+ }
+ }
+ if ($end) {
+ if (($end == $start) || ($startfrac == $endfrac)) {
+ $reduction = $endfrac;
+ } elsif ($end - $start > 0) {
+ if (($endfrac <= 1.0) && ($endfrac >= 0)) {
+ $reduction = $endfrac;
+ if ($usegrad) {
+ my $decline = $startfrac - $endfrac;
+ my $fraction = ($lateness - $start)/($end - $start);
+ if (($fraction <= 1) && ($fraction >= 0)) {
+ my $value = $startfrac - ($decline*$fraction);
+ $reduction = sprintf("%.2f", $value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return $reduction;
+}
+
sub seconds_to_human_length {
my ($length)=@_;
Index: loncom/homework/structuretags.pm
diff -u loncom/homework/structuretags.pm:1.591 loncom/homework/structuretags.pm:1.592
--- loncom/homework/structuretags.pm:1.591 Sun Mar 30 01:09:59 2025
+++ loncom/homework/structuretags.pm Sat Jun 28 14:35:00 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# definition of tags that give a structure to a document
#
-# $Id: structuretags.pm,v 1.591 2025/03/30 01:09:59 raeburn Exp $
+# $Id: structuretags.pm,v 1.592 2025/06/28 14:35:00 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -1313,7 +1313,14 @@
if (($record{'version'}) && (exists($record{"resource.$part.awarded"}))) {
my $awarded = $record{"resource.$part.awarded"};
if ($awarded) {
- $total += $weight * $awarded;
+ my $points = $weight * $awarded;
+ if (exists($record{"resource.$part.latefrac"})) {
+ my $latefrac = $record{"resource.$part.latefrac"};
+ if (($latefrac ne '') && ($latefrac >= 0) && ($latefrac < 1)) {
+ $points = $points * $latefrac;
+ }
+ }
+ $total += $points;
}
}
}
@@ -1328,7 +1335,14 @@
$possible += $weight;
my $awarded = $Apache::lonhomework::results{$key};
if ($awarded) {
- $total += $weight * $awarded;
+ my $points = $weight * $awarded;
+ if (exists($Apache::lonhomework::results{"resource.$part.latefrac"})) {
+ my $latefrac = $Apache::lonhomework::results{"resource.$part.latefrac"};
+ if (($latefrac ne '') && ($latefrac >= 0) && ($latefrac < 1)) {
+ $points = $points * $latefrac;
+ }
+ }
+ $total += $points;
}
}
}
Index: loncom/publisher/packages.tab
diff -u loncom/publisher/packages.tab:1.83 loncom/publisher/packages.tab:1.84
--- loncom/publisher/packages.tab:1.83 Fri Dec 22 13:38:02 2023
+++ loncom/publisher/packages.tab Sat Jun 28 14:35:04 2025
@@ -11,6 +11,8 @@
part&weight&display:Weight
part&weight&type:float_pos
part&weight&default:1
+part&grace&display:Grace Period Past-Due
+part&grace&type:string_grace
part&maxtries&display:Maximum Number of Tries
part&maxtries&type:int_pos
part&maxtries&default:99
Index: loncom/misc/releaseslist.xml
diff -u loncom/misc/releaseslist.xml:1.23 loncom/misc/releaseslist.xml:1.24
--- loncom/misc/releaseslist.xml:1.23 Fri Dec 22 19:44:38 2023
+++ loncom/misc/releaseslist.xml Sat Jun 28 14:35:06 2025
@@ -30,6 +30,7 @@
<parameter name="deeplink" valuematch="on">2.12</parameter>
<parameter name="texdisplay" value="tth">2.12</parameter>
<parameter name="texdisplay" value="mathjax">2.12</parameter>
+<parameter name="grace" valuematch="on">2.12</parameter>
<resourcetag name="responsetype" value="custom">2.1</resourcetag>
<resourcetag name="responsetype" value="math">2.2</resourcetag>
<resourcetag name="responsetype" value="functionplot">2.10</resourcetag>
Index: rat/client/parameter.html
diff -u rat/client/parameter.html:1.95 rat/client/parameter.html:1.96
--- rat/client/parameter.html:1.95 Sat Jun 28 13:55:43 2025
+++ rat/client/parameter.html Sat Jun 28 14:35:11 2025
@@ -5,7 +5,7 @@
The LearningOnline Network with CAPA
Parameter Input Window
//
-// $Id: parameter.html,v 1.95 2025/06/28 13:55:43 raeburn Exp $
+// $Id: parameter.html,v 1.96 2025/06/28 14:35:11 raeburn Exp $
//
// Copyright Michigan State University Board of Trustees
//
@@ -96,6 +96,8 @@
choicewrite('}');
choicewrite('table.LC_parmsel_table {font-size: 90%;}');
choicewrite('table.LC_parmsel_table tr td { padding: 5px; border: 1px solid #C8C8C8;}');
+ choicewrite('fieldset.LC_grace { display:inline; }');
+ choicewrite('fieldset.LC_grace > legend { font-weight: normal; }');
choicewrite('-->');
choicewrite('</style>');
choicewrite('</head>');
@@ -247,11 +249,22 @@
}
-function intminute() {
- var thisminutes=cmins;
+function intminute(mins) {
+ var thisminutes;
+ if ((typeof mins === 'number') && (!isNaN(mins))) {
+ thisminutes=mins;
+ } else {
+ thisminutes=cmins;
+ }
var i;
var result = '';
- result += '<select name="minutes" onchange="parent.intcalc();">';
+ var funcname = '';
+ if (pscat == 'grace') {
+ funcname = 'parent.gracestringeval()';
+ } else {
+ funcname = 'parent.intcalc()';
+ }
+ result += '<select name="minutes" onchange="'+funcname+';">';
for (i=0;i<=59;i++) {
result += '<option value="'+i+'"';
if (i==thisminutes) {
@@ -263,11 +276,22 @@
return result;
}
-function inthour() {
- var thishours=chours;
+function inthour(hours) {
+ var thishours;
+ if ((typeof hours === 'number') && (!isNaN(hours))) {
+ thishours=hours;
+ } else {
+ thishours=chours;
+ }
var i;
var result = '';
- result += '<select name="hours" onchange="parent.intcalc();">';
+ var funcname = '';
+ if (pscat == 'grace') {
+ funcname = 'parent.gracestringeval()';
+ } else {
+ funcname = 'parent.intcalc()';
+ }
+ result += '<select name="hours" onchange="'+funcname+';">';
for (i=0;i<=23;i++) {
result += '<option value="'+i+'"';
if (i==thishours) {
@@ -279,11 +303,22 @@
return result;
}
-function intsecond() {
- var thisseconds=csecs;
+function intsecond(secs) {
+ var thisseconds;
+ if ((typeof secs === 'number') && (!isNaN(secs))) {
+ thisseconds=secs;
+ } else {
+ thisseconds=csecs;
+ }
var i;
var result = '';
- result += '<select name="seconds" onchange="parent.intcalc();">';
+ var funcname = '';
+ if (pscat == 'grace') {
+ funcname = 'parent.gracestringeval()';
+ } else {
+ funcname = 'parent.intcalc()';
+ }
+ result += '<select name="seconds" onchange="'+funcname+';">';
for (i=0;i<=59;i++) {
result += '<option value="'+i+'"';
if (i==thisseconds) {
@@ -295,12 +330,22 @@
return result;
}
-
-function intday() {
- var thisdate=cdays;
+function intday(days) {
+ var thisdate;
+ if ((typeof days === 'number') && (!isNaN(days))) {
+ thisdate=days;
+ } else {
+ thisdate=cdays;
+ }
var i;
var result ='';
- result += '<select name="date" onchange="parent.intcalc();">';
+ var funcname = '';
+ if (pscat == 'grace') {
+ funcname = 'parent.gracestringeval()';
+ } else {
+ funcname = 'parent.intcalc()';
+ }
+ result += '<select name="date" onchange="'+funcname+';">';
for (i=0;i<=31;i++) {
result += '<option value="'+i+'"';
if (i==thisdate) {
@@ -663,6 +708,132 @@
return;
}
+function gracestringeval() {
+ var items = choices.document.getElementsByName('setgrace');
+ if (items.length) {
+ if (items.length > 0) {
+ svalue = '';
+ for (var i=0; i<items.length; i++) {
+ var graceDiv = items[i].closest('div');
+ var timeSels = graceDiv.getElementsByTagName("select");
+ var total = 0;
+ if (timeSels.length) {
+ for (var j=0; j<timeSels.length; j++) {
+ var sname = timeSels[j].getAttribute('name');
+ var poss = parseInt(timeSels[j].options[timeSels[j].selectedIndex].value);
+ if (sname == 'date') {
+ if ((poss > 0) && (poss <= 31)) {
+ total += (poss * 86400);
+ }
+ } else if (sname == 'hours') {
+ if ((poss > 0) && (poss < 24)) {
+ total += (poss * 3600);
+ }
+ } else if (sname == 'minutes') {
+ if ((poss > 0) && (poss < 60)) {
+ total += (poss * 60);
+ }
+ } else if (sname == 'seconds') {
+ if ((poss > 0) && (poss < 60)) {
+ total += poss;
+ }
+ }
+ }
+ }
+ var inputElems = graceDiv.getElementsByTagName("input");
+ var frac = '';
+ var grad = '';
+ if (inputElems.length) {
+ for (var j=0; j<inputElems.length; j++) {
+ var iname = inputElems[j].getAttribute('name');
+ if (iname == 'frac') {
+ var ival = inputElems[j].value;
+ ival.trim();
+ var poss = parseFloat(ival);
+ if ((typeof poss === 'number') && (!isNaN(poss))) {
+ if ((poss => 0) && (poss <= 1)) {
+ frac = poss;
+ }
+ }
+ } else if (iname == 'grad') {
+ if (inputElems[j].checked) {
+ grad = 1;
+ } else {
+ grad = 0;
+ }
+ }
+ }
+ }
+ if (svalue === '') {
+ svalue = total+':'+frac+':'+grad;
+ } else {
+ svalue += ','+total+':'+frac+':'+grad;
+ }
+ }
+ }
+ }
+}
+
+function graceitem(current) {
+ var gdays = 0;
+ var ghours = 0;
+ var gmins = 0;
+ var gsecs = 0;
+ var gfrac = '';
+ var checktext = '';
+ var patternGrace = /^\d+:(0|1).?\d*:(0|1)$/;
+ if ((current != '') && (current != 'undefined') && (patternGrace.test(current))) {
+ var graceItems = new Array;
+ graceItems = current.split(':');
+ gsecs=graceItems[0];
+ gdays=Math.floor(gsecs/86400);
+ gsecs -= gdays*86400;
+ ghours=Math.floor(gsecs/3600);
+ gsecs -= ghours*3600;
+ gmins=Math.floor(gsecs/60);
+ gsecs -= gmins*60;
+ gfrac = graceItems[1];
+ if (graceItems[2] == 1) {
+ checktext = ' checked="checked"';
+ }
+ }
+ return '<input type="hidden" name="setgrace" value="" />'+
+ '<fieldset class="LC_grace"><legend>Time past due</legend>'+
+ '<span style="white-space:nowrap">'+intday(gdays)+' days </span>'+
+ '<span style="white-space:nowrap">'+inthour(ghours)+' hours</span><br />'+
+ '<span style="white-space:nowrap">'+intminute(gmins)+' mins</span>'+
+ '<span style="white-space:nowrap">'+intsecond(gsecs)+' secs</span>'+
+ '</fieldset><fieldset class="LC_grace"><legend>Partial Credit</legend>'+
+ '<input type="text" size="3" name="frac" value="'+gfrac+'" onblur="parent.gracestringeval();" />'+
+ ' <label><input type="checkbox" value="1" name="grad"'+checktext+' onclick="parent.gracestringeval();" />'+
+ 'gradual</label></fieldset>'+
+ '<a href="#" onclick="parent.removeGrace(this);return false;">Remove</a><hr />';
+}
+
+function addGrace() {
+ var frame = window.frames["choices"];
+ if (frame.document.getElementById('LC_string_grace_inner')) {
+ var innerDiv = frame.document.getElementById('LC_string_grace_inner');
+ var graceDiv = frame.document.createElement('div');
+ graceDiv.innerHTML = graceitem();
+ innerDiv.appendChild(graceDiv);
+ }
+ return;
+}
+
+function removeGrace(caller) {
+ var frame = window.frames["choices"];
+ if (frame.document.getElementById('LC_string_grace_inner')) {
+ var innerDiv = frame.document.getElementById('LC_string_grace_inner');
+ var divToRemove = caller.closest('div');
+ if (divToRemove) {
+ innerDiv.removeChild(divToRemove);
+ gracestringeval();
+ }
+ }
+ return;
+}
+
function radiostringeval(newval) {
svalue=newval;
draw();
@@ -754,6 +925,8 @@
if (ptype=='string') {
if (pscat == 'ip') {
choicewrite(' action="javascript:ipstringeval();"');
+ } else if (pscat == 'grace') {
+ choicewrite(' action="javascript:gracestringeval();"');
} else {
choicewrite(' action="javascript:stringeval();"');
}
@@ -1517,6 +1690,35 @@
choicewrite('</span></div>');
choicewrite('</td></tr></table>');
}
+ if (pscat=='grace') {
+ tablestart('Grace period after due date');
+ choicewrite('<tr><td colspan="3" valign="top">'+
+ '<div id="LC_string_grace_wrap">'+
+ '<div id="LC_string_grace_inner">');
+ if ((svalue != '') && (typeof(svalue) != 'undefined')) {
+ var patternComma = /,/;
+ var patternGrace = /^\d+:(0|1).?\d*:(0|1)$/;
+ var current = new Array;
+ if (patternComma.test(svalue)) {
+ current = svalue.split(',');
+ } else {
+ current = [svalue];
+ }
+ for (var i=0; i<current.length; i++) {
+ if (patternGrace.test(current[i])) {
+ choicewrite('<div>');
+ choicewrite(graceitem(current[i]));
+ choicewrite('</div>');
+ }
+ }
+ } else {
+ choicewrite('<div>');
+ choicewrite(graceitem());
+ choicewrite('</div>');
+ }
+ choicewrite('</div><button onclick="parent.addGrace();return false;">Add another?</button>');
+ choicewrite('</div></td></tr></table>');
+ }
}
if (ptype=='color') {
@@ -1846,6 +2048,7 @@
else if (pscat == 'useslots') { sopt('useslots','Slots control access'); }
else if (pscat == 'deeplink') { sopt('deeplink','Deep-linked items'); }
else if (pscat == 'tex') { sopt('texdisplay','TeX File Display'); }
+ else if (pscat == 'grace') { sopt('grace','Grace period'); }
else { pscat = 'any'; }
if (pscat != 'deeplink') { sopt('any','String Value'); }
}
@@ -1915,6 +2118,13 @@
document.getElementById("LCparampopup").rows="60,*";
}
}
+ if (pscat == 'grace') {
+ if (psmap==1) {
+ document.getElementById("LCparampopup").rows="105,*";
+ } else {
+ document.getElementById("LCparampopup").rows="65,*";
+ }
+ }
draw();
}
More information about the LON-CAPA-cvs
mailing list