[LON-CAPA-cvs] cvs: loncom /interface lonnavmaps.pm
jms
lon-capa-cvs-allow@mail.lon-capa.org
Tue, 18 Nov 2008 20:18:38 -0000
This is a MIME encoded message
--jms1227039518
Content-Type: text/plain
jms Tue Nov 18 20:18:38 2008 EDT
Modified files:
/loncom/interface lonnavmaps.pm
Log:
Reworked some POD documentation
--jms1227039518
Content-Type: text/plain
Content-Disposition: attachment; filename="jms-20081118201838.txt"
Index: loncom/interface/lonnavmaps.pm
diff -u loncom/interface/lonnavmaps.pm:1.415 loncom/interface/lonnavmaps.pm:1.416
--- loncom/interface/lonnavmaps.pm:1.415 Fri Oct 10 16:07:16 2008
+++ loncom/interface/lonnavmaps.pm Tue Nov 18 20:18:37 2008
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.415 2008/10/10 16:07:16 bisitz Exp $
+# $Id: lonnavmaps.pm,v 1.416 2008/11/18 20:18:37 jms Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -27,407 +27,25 @@
#
###
-package Apache::lonnavmaps;
-
-use strict;
-use GDBM_File;
-use Apache::loncommon();
-use Apache::lonenc();
-use Apache::lonlocal;
-use Apache::lonnet;
-use POSIX qw (floor strftime);
-use Time::HiRes qw( gettimeofday tv_interval );
-use LONCAPA;
-use DateTime();
-
-# symbolic constants
-sub SYMB { return 1; }
-sub URL { return 2; }
-sub NOTHING { return 3; }
-
-# Some data
-
-my $resObj = "Apache::lonnavmaps::resource";
-
-# Keep these mappings in sync with lonquickgrades, which uses the colors
-# instead of the icons.
-my %statusIconMap =
- (
- $resObj->CLOSED => '',
- $resObj->OPEN => 'navmap.open.gif',
- $resObj->CORRECT => 'navmap.correct.gif',
- $resObj->PARTIALLY_CORRECT => 'navmap.partial.gif',
- $resObj->INCORRECT => 'navmap.wrong.gif',
- $resObj->ATTEMPTED => 'navmap.ellipsis.gif',
- $resObj->ERROR => ''
- );
-
-my %iconAltTags =
- ( 'navmap.correct.gif' => 'Correct',
- 'navmap.wrong.gif' => 'Incorrect',
- 'navmap.open.gif' => 'Open' );
-
-# Defines a status->color mapping, null string means don't color
-my %colormap =
- ( $resObj->NETWORK_FAILURE => '',
- $resObj->CORRECT => '',
- $resObj->EXCUSED => '#3333FF',
- $resObj->PAST_DUE_ANSWER_LATER => '',
- $resObj->PAST_DUE_NO_ANSWER => '',
- $resObj->ANSWER_OPEN => '#006600',
- $resObj->OPEN_LATER => '',
- $resObj->TRIES_LEFT => '',
- $resObj->INCORRECT => '',
- $resObj->OPEN => '',
- $resObj->NOTHING_SET => '',
- $resObj->ATTEMPTED => '',
- $resObj->ANSWER_SUBMITTED => '',
- $resObj->PARTIALLY_CORRECT => '#006600'
- );
-# And a special case in the nav map; what to do when the assignment
-# is not yet done and due in less than 24 hours
-my $hurryUpColor = "#FF0000";
-
-sub close {
- if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
- return(<<ENDCLOSE);
-<script type="text/javascript">
-window.status='Accessing Nav Control';
-menu=window.open("/adm/rat/empty.html","loncapanav",
- "height=600,width=400,scrollbars=1");
-window.status='Closing Nav Control';
-menu.close();
-window.status='Done.';
-</script>
-ENDCLOSE
-}
-
-sub update {
- if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
- if (!$env{'request.course.id'}) { return ''; }
- if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
- return(<<ENDUPDATE);
-<form name="navform"></form>
-<script type="text/javascript">
-this.document.navform.action='/adm/navmaps#curloc';
-this.document.navform.target='loncapanav';
-this.document.navform.submit();
-</script>
-ENDUPDATE
-}
-
-# Convenience functions: Returns a string that adds or subtracts
-# the second argument from the first hash, appropriate for the
-# query string that determines which folders to recurse on
-sub addToFilter {
- my $hashIn = shift;
- my $addition = shift;
- my %hash = %$hashIn;
- $hash{$addition} = 1;
-
- return join (",", keys(%hash));
-}
-
-sub removeFromFilter {
- my $hashIn = shift;
- my $subtraction = shift;
- my %hash = %$hashIn;
-
- delete $hash{$subtraction};
- return join(",", keys(%hash));
-}
-
-# Convenience function: Given a stack returned from getStack on the iterator,
-# return the correct src() value.
-sub getLinkForResource {
- my $stack = shift;
- my $res;
-
- # Check to see if there are any pages in the stack
- foreach $res (@$stack) {
- if (defined($res)) {
- my $anchor;
- if ($res->is_page()) {
- foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } }
- $anchor=&escape($anchor->shown_symb());
- return ($res->link(),$res->shown_symb(),$anchor);
- }
- # in case folder was skipped over as "only sequence"
- my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
- if ($map=~/\.page$/) {
- my $url=&Apache::lonnet::clutter($map);
- $anchor=&escape($src->shown_symb());
- return ($url,$res->shown_symb(),$anchor);
- }
- }
- }
-
- # Failing that, return the src of the last resource that is defined
- # (when we first recurse on a map, it puts an undefined resource
- # on the bottom because $self->{HERE} isn't defined yet, and we
- # want the src for the map anyhow)
- foreach my $item (@$stack) {
- if (defined($item)) { $res = $item; }
- }
-
- if ($res) {
- return ($res->link(),$res->shown_symb());
- }
- return;
-}
-
-# Convenience function: This separates the logic of how to create
-# the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
-# etc.) into a separate function. It takes a resource object as the
-# first parameter, and the part number of the resource as the second.
-# It's basically a big switch statement on the status of the resource.
-
-sub getDescription {
- my $res = shift;
- my $part = shift;
- my $status = $res->status($part);
-
- my $open = $res->opendate($part);
- my $due = $res->duedate($part);
- my $answer = $res->answerdate($part);
-
- if ($status == $res->NETWORK_FAILURE) {
- return &mt("Having technical difficulties; please check status later");
- }
- if ($status == $res->NOTHING_SET) {
- return &mt("Not currently assigned.");
- }
- if ($status == $res->OPEN_LATER) {
- return &mt("Open ") .timeToHumanString($open,'start');
- }
- if ($status == $res->OPEN) {
- if ($due) {
- if ($res->is_practice()) {
- return &mt("Closes ")." " .timeToHumanString($due,'start');
- } else {
- return &mt("Due")." " .timeToHumanString($due,'end');
- }
- } else {
- return &mt("Open, no due date");
- }
- }
- if ($status == $res->PAST_DUE_ANSWER_LATER) {
- return &mt("Answer open")." " .timeToHumanString($answer,'start');
- }
- if ($status == $res->PAST_DUE_NO_ANSWER) {
- if ($res->is_practice()) {
- return &mt("Closed")." " . timeToHumanString($due,'start');
- } else {
- return &mt("Was due")." " . timeToHumanString($due,'end');
- }
- }
- if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
- && $res->handgrade($part) ne 'yes') {
- return &mt("Answer available");
- }
- if ($status == $res->EXCUSED) {
- return &mt("Excused by instructor");
- }
- if ($status == $res->ATTEMPTED) {
- return &mt("Answer submitted, not yet graded");
- }
- if ($status == $res->TRIES_LEFT) {
- my $tries = $res->tries($part);
- my $maxtries = $res->maxtries($part);
- my $triesString = "";
- if ($tries && $maxtries) {
- $triesString = '<font size="-1"><i>('.&mt('[_1] of [_2] tries used',$tries,$maxtries).')</i></font>';
- if ($maxtries > 1 && $maxtries - $tries == 1) {
- $triesString = "<b>$triesString</b>";
- }
- }
- if ($due) {
- return &mt("Due")." " . timeToHumanString($due,'end') .
- " $triesString";
- } else {
- return &mt("No due date")." $triesString";
- }
- }
- if ($status == $res->ANSWER_SUBMITTED) {
- return &mt('Answer submitted');
- }
-}
-
-# Convenience function, so others can use it: Is the problem due in less than
-# 24 hours, and still can be done?
-
-sub dueInLessThan24Hours {
- my $res = shift;
- my $part = shift;
- my $status = $res->status($part);
-
- return ($status == $res->OPEN() ||
- $status == $res->TRIES_LEFT()) &&
- $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
- $res->duedate($part) > time();
-}
-
-# Convenience function, so others can use it: Is there only one try remaining for the
-# part, with more than one try to begin with, not due yet and still can be done?
-sub lastTry {
- my $res = shift;
- my $part = shift;
-
- my $tries = $res->tries($part);
- my $maxtries = $res->maxtries($part);
- return $tries && $maxtries && $maxtries > 1 &&
- $maxtries - $tries == 1 && $res->duedate($part) &&
- $res->duedate($part) > time();
-}
-
-# This puts a human-readable name on the env variable.
-
-sub advancedUser {
- return $env{'request.role.adv'};
-}
-
-
-# timeToHumanString takes a time number and converts it to a
-# human-readable representation, meant to be used in the following
-# manner:
-# print "Due $timestring"
-# print "Open $timestring"
-# print "Answer available $timestring"
-# Very, very, very, VERY English-only... goodness help a localizer on
-# this func...
-
-
-sub timeToHumanString {
- my ($time,$type,$format) = @_;
-
- # zero, '0' and blank are bad times
- if (!$time) {
- return &mt('never');
- }
- unless (&Apache::lonlocal::current_language()=~/^en/) {
- return &Apache::lonlocal::locallocaltime($time);
- }
- my $now = time();
-
- # Positive = future
- my $delta = $time - $now;
-
- my $minute = 60;
- my $hour = 60 * $minute;
- my $day = 24 * $hour;
- my $week = 7 * $day;
- my $inPast = 0;
-
- # Logic in comments:
- # Is it now? (extremely unlikely)
- if ( $delta == 0 ) {
- return "this instant";
- }
-
- if ($delta < 0) {
- $inPast = 1;
- $delta = -$delta;
- }
-
- if ( $delta > 0 ) {
-
- my $tense = $inPast ? " ago" : "";
- my $prefix = $inPast ? "" : "in ";
-
- # Less than a minute
- if ( $delta < $minute ) {
- if ($delta == 1) { return "${prefix}1 second$tense"; }
- return "$prefix$delta seconds$tense";
- }
-
- # Less than an hour
- if ( $delta < $hour ) {
- # If so, use minutes
- my $minutes = floor($delta / 60);
- if ($minutes == 1) { return "${prefix}1 minute$tense"; }
- return "$prefix$minutes minutes$tense";
- }
-
- # Is it less than 24 hours away? If so,
- # display hours + minutes
- if ( $delta < $hour * 24) {
- my $hours = floor($delta / $hour);
- my $minutes = floor(($delta % $hour) / $minute);
- my $hourString = "$hours hours";
- my $minuteString = ", $minutes minutes";
- if ($hours == 1) {
- $hourString = "1 hour";
- }
- if ($minutes == 1) {
- $minuteString = ", 1 minute";
- }
- if ($minutes == 0) {
- $minuteString = "";
- }
- return "$prefix$hourString$minuteString$tense";
- }
-
- my $dt = DateTime->from_epoch(epoch => $time)
- ->set_time_zone(&Apache::lonlocal::gettimezone());
-
- # If there's a caller supplied format, use it.
-
- if ($format ne '') {
- my $timeStr = $dt->strftime($format);
- return $timeStr.' ('.$dt->time_zone_short_name().')';
- }
-
- # Less than 5 days away, display day of the week and
- # HH:MM
-
- if ( $delta < $day * 5 ) {
- my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");
- $timeStr =~ s/12:00 am/00:00/;
- $timeStr =~ s/12:00 pm/noon/;
- return ($inPast ? "last " : "this ") .
- $timeStr;
- }
-
- my $conjunction='on';
- if ($type eq 'start') {
- $conjunction='at';
- } elsif ($type eq 'end') {
- $conjunction='by';
- }
- # Is it this year?
- my $dt_now = DateTime->from_epoch(epoch => $now)
- ->set_time_zone(&Apache::lonlocal::gettimezone());
- if ( $dt->year() == $dt_now->year()) {
- # Return on Month Day, HH:MM meridian
- my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");
- $timeStr =~ s/12:00 am/00:00/;
- $timeStr =~ s/12:00 pm/noon/;
- return $timeStr;
- }
-
- # Not this year, so show the year
- my $timeStr =
- $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");
- $timeStr =~ s/12:00 am/00:00/;
- $timeStr =~ s/12:00 pm/noon/;
- return $timeStr;
- }
-}
-
-
=pod
=head1 NAME
-Apache::lonnavmap - Subroutines to handle and render the navigation
- maps
+Apache::lonnavmaps.pm
=head1 SYNOPSIS
+Handles navigational maps.
+
The main handler generates the navigational listing for the course,
the other objects export this information in a usable fashion for
other modules.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+
=head1 OVERVIEW
X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
@@ -511,241 +129,719 @@
argument hash passed to the renderer, and returns a string that will
be inserted into the HTML representation as it.
-All other parameters are ways of either changing how the columns
-are printing, or which rows are shown.
+All other parameters are ways of either changing how the columns
+are printing, or which rows are shown.
+
+The pre-packaged column names are refered to by constants in the
+Apache::lonnavmaps namespace. The following currently exist:
+
+=over 4
+
+=item * B<Apache::lonnavmaps::resource>:
+
+The general info about the resource: Link, icon for the type, etc. The
+first column in the standard nav map display. This column provides the
+indentation effect seen in the B<NAV> screen. This column also accepts
+the following parameters in the renderer hash:
+
+=over 4
+
+=item * B<resource_nolink>: default false
+
+If true, the resource will not be linked. By default, all non-folder
+resources are linked.
+
+=item * B<resource_part_count>: default true
+
+If true, the resource will show a part count B<if> the full
+part list is not displayed. (See "condense_parts" later.) If false,
+the resource will never show a part count.
+
+=item * B<resource_no_folder_link>:
+
+If true, the resource's folder will not be clickable to open or close
+it. Default is false. True implies printCloseAll is false, since you
+can't close or open folders when this is on anyhow.
+
+=back
+
+=item * B<Apache::lonnavmaps::communication_status>:
+
+Whether there is discussion on the resource, email for the user, or
+(lumped in here) perl errors in the execution of the problem. This is
+the second column in the main nav map.
+
+=item * B<Apache::lonnavmaps::quick_status>:
+
+An icon for the status of a problem, with five possible states:
+Correct, incorrect, open, awaiting grading (for a problem where the
+computer's grade is suppressed, or the computer can't grade, like
+essay problem), or none (not open yet, not a problem). The
+third column of the standard navmap.
+
+=item * B<Apache::lonnavmaps::long_status>:
+
+A text readout of the details of the current status of the problem,
+such as "Due in 22 hours". The fourth column of the standard navmap.
+
+=item * B<Apache::lonnavmaps::part_status_summary>:
+
+A text readout summarizing the status of the problem. If it is a
+single part problem, will display "Correct", "Incorrect",
+"Not yet open", "Open", "Attempted", or "Error". If there are
+multiple parts, this will output a string that in HTML will show a
+status of how many parts are in each status, in color coding, trying
+to match the colors of the icons within reason.
+
+Note this only makes sense if you are I<not> showing parts. If
+C<showParts> is true (see below), this column will not output
+anything.
+
+=back
+
+If you add any others please be sure to document them here.
+
+An example of a column renderer that will show the ID number of a
+resource, along with the part name if any:
+
+ sub {
+ my ($resource, $part, $params) = @_;
+ if ($part) { return '<td>' . $resource->{ID} . ' ' . $part . '</td>'; }
+ return '<td>' . $resource->{ID} . '</td>';
+ }
+
+Note these functions are responsible for the TD tags, which allow them
+to override vertical and horizontal alignment, etc.
+
+=head2 Parameters
+
+Minimally, you should be
+able to get away with just using 'cols' (to specify the columns
+shown), 'url' (necessary for the folders to link to the current screen
+correctly), and possibly 'queryString' if your app calls for it. In
+that case, maintaining the state of the folders will be done
+automatically.
+
+=over 4
+
+=item * B<iterator>: default: constructs one from %env
+
+A reference to a fresh ::iterator to use from the navmaps. The
+rendering will reflect the options passed to the iterator, so you can
+use that to just render a certain part of the course, if you like. If
+one is not passed, the renderer will attempt to construct one from
+env{'form.filter'} and env{'form.condition'} information, plus the
+'iterator_map' parameter if any.
+
+=item * B<iterator_map>: default: not used
+
+If you are letting the renderer do the iterator handling, you can
+instruct the renderer to render only a particular map by passing it
+the source of the map you want to process, like
+'/res/103/jerf/navmap.course.sequence'.
+
+=item * B<include_top_level_map>: default: false
+
+If you need to include the top level map (meaning the course) in the
+rendered output set this to true
+
+=item * B<navmap>: default: constructs one from %env
+
+A reference to a navmap, used only if an iterator is not passed in. If
+this is necessary to make an iterator but it is not passed in, a new
+one will be constructed based on env info. This is useful to do basic
+error checking before passing it off to render.
+
+=item * B<r>: default: must be passed in
+
+The standard Apache response object. This must be passed to the
+renderer or the course hash will be locked.
+
+=item * B<cols>: default: empty (useless)
+
+An array reference
+
+=item * B<showParts>:default true
+
+A flag. If true, a line for the resource itself, and a line
+for each part will be displayed. If not, only one line for each
+resource will be displayed.
+
+=item * B<condenseParts>: default true
+
+A flag. If true, if all parts of the problem have the same
+status and that status is Nothing Set, Correct, or Network Failure,
+then only one line will be displayed for that resource anyhow. If no,
+all parts will always be displayed. If showParts is 0, this is
+ignored.
+
+=item * B<jumpCount>: default: determined from %env
+
+A string identifying the URL to place the anchor 'curloc' at.
+It is the responsibility of the renderer user to
+ensure that the #curloc is in the URL. By default, determined through
+the use of the env{} 'jump' information, and should normally "just
+work" correctly.
+
+=item * B<here>: default: empty string
+
+A Symb identifying where to place the 'here' marker. The empty
+string means no marker.
+
+=item * B<indentString>: default: 25 pixel whitespace image
+
+A string identifying the indentation string to use.
+
+=item * B<queryString>: default: empty
+
+A string which will be prepended to the query string used when the
+folders are opened or closed. You can use this to pass
+application-specific values.
+
+=item * B<url>: default: none
+
+The url the folders will link to, which should be the current
+page. Required if the resource info column is shown, and you
+are allowing the user to open and close folders.
+
+=item * B<currentJumpIndex>: default: no jumping
+
+Describes the currently-open row number to cause the browser to jump
+to, because the user just opened that folder. By default, pulled from
+the Jump information in the env{'form.*'}.
+
+=item * B<printKey>: default: false
+
+If true, print the key that appears on the top of the standard
+navmaps.
+
+=item * B<printCloseAll>: default: true
+
+If true, print the "Close all folders" or "open all folders"
+links.
+
+=item * B<filterFunc>: default: sub {return 1;} (accept everything)
+
+A function that takes the resource object as its only parameter and
+returns a true or false value. If true, the resource is displayed. If
+false, it is simply skipped in the display.
+
+=item * B<suppressEmptySequences>: default: false
+
+If you're using a filter function, and displaying sequences to orient
+the user, then frequently some sequences will be empty. Setting this to
+true will cause those sequences not to display, so as not to confuse the
+user into thinking that if the sequence is there there should be things
+under it; for example, see the "Show Uncompleted Homework" view on the
+B<NAV> screen.
+
+=item * B<suppressNavmaps>: default: false
+
+If true, will not display Navigate Content resources.
+
+=back
+
+=head2 Additional Info
+
+In addition to the parameters you can pass to the renderer, which will
+be passed through unchange to the column renderers, the renderer will
+generate the following information which your renderer may find
+useful:
+
+=over 4
+
+=item * B<counter>:
+
+Contains the number of rows printed. Useful after calling the render
+function, as you can detect whether anything was printed at all.
+
+=item * B<isNewBranch>:
+
+Useful for renderers: If this resource is currently the first resource
+of a new branch, this will be true. The Resource column (leftmost in the
+navmaps screen) uses this to display the "new branch" icon
+
+=back
+
+=cut
-The pre-packaged column names are refered to by constants in the
-Apache::lonnavmaps namespace. The following currently exist:
-=over 4
+=head1 SUBROUTINES
-=item * B<Apache::lonnavmaps::resource>:
+=over
-The general info about the resource: Link, icon for the type, etc. The
-first column in the standard nav map display. This column provides the
-indentation effect seen in the B<NAV> screen. This column also accepts
-the following parameters in the renderer hash:
+=item update()
-=over 4
+=item addToFilter()
-=item * B<resource_nolink>: default false
+Convenience functions: Returns a string that adds or subtracts
+the second argument from the first hash, appropriate for the
+query string that determines which folders to recurse on
-If true, the resource will not be linked. By default, all non-folder
-resources are linked.
+=item removeFromFilter()
-=item * B<resource_part_count>: default true
+=item getLinkForResource()
-If true, the resource will show a part count B<if> the full
-part list is not displayed. (See "condense_parts" later.) If false,
-the resource will never show a part count.
+Convenience function: Given a stack returned from getStack on the iterator,
+return the correct src() value.
-=item * B<resource_no_folder_link>:
+=item getDescription()
-If true, the resource's folder will not be clickable to open or close
-it. Default is false. True implies printCloseAll is false, since you
-can't close or open folders when this is on anyhow.
+Convenience function: This separates the logic of how to create
+the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
+etc.) into a separate function. It takes a resource object as the
+first parameter, and the part number of the resource as the second.
+It's basically a big switch statement on the status of the resource.
-=back
+=item dueInLessThan24Hours()
-=item * B<Apache::lonnavmaps::communication_status>:
+Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done?
-Whether there is discussion on the resource, email for the user, or
-(lumped in here) perl errors in the execution of the problem. This is
-the second column in the main nav map.
+=item lastTry()
-=item * B<Apache::lonnavmaps::quick_status>:
+Convenience function, so others can use it: Is there only one try remaining for the
+part, with more than one try to begin with, not due yet and still can be done?
-An icon for the status of a problem, with five possible states:
-Correct, incorrect, open, awaiting grading (for a problem where the
-computer's grade is suppressed, or the computer can't grade, like
-essay problem), or none (not open yet, not a problem). The
-third column of the standard navmap.
+=item advancedUser()
-=item * B<Apache::lonnavmaps::long_status>:
+This puts a human-readable name on the env variable.
-A text readout of the details of the current status of the problem,
-such as "Due in 22 hours". The fourth column of the standard navmap.
+=item timeToHumanString()
-=item * B<Apache::lonnavmaps::part_status_summary>:
+timeToHumanString takes a time number and converts it to a
+human-readable representation, meant to be used in the following
+manner:
-A text readout summarizing the status of the problem. If it is a
-single part problem, will display "Correct", "Incorrect",
-"Not yet open", "Open", "Attempted", or "Error". If there are
-multiple parts, this will output a string that in HTML will show a
-status of how many parts are in each status, in color coding, trying
-to match the colors of the icons within reason.
+=over 4
-Note this only makes sense if you are I<not> showing parts. If
-C<showParts> is true (see below), this column will not output
-anything.
+=item * print "Due $timestring"
+
+=item * print "Open $timestring"
+
+=item * print "Answer available $timestring"
=back
-If you add any others please be sure to document them here.
+Very, very, very, VERY English-only... goodness help a localizer on
+this func...
-An example of a column renderer that will show the ID number of a
-resource, along with the part name if any:
+=item
- sub {
- my ($resource, $part, $params) = @_;
- if ($part) { return '<td>' . $resource->{ID} . ' ' . $part . '</td>'; }
- return '<td>' . $resource->{ID} . '</td>';
- }
+=item
-Note these functions are responsible for the TD tags, which allow them
-to override vertical and horizontal alignment, etc.
+=item
-=head2 Parameters
+=item
-Minimally, you should be
-able to get away with just using 'cols' (to specify the columns
-shown), 'url' (necessary for the folders to link to the current screen
-correctly), and possibly 'queryString' if your app calls for it. In
-that case, maintaining the state of the folders will be done
-automatically.
+=item
-=over 4
+=item
-=item * B<iterator>: default: constructs one from %env
+=item
-A reference to a fresh ::iterator to use from the navmaps. The
-rendering will reflect the options passed to the iterator, so you can
-use that to just render a certain part of the course, if you like. If
-one is not passed, the renderer will attempt to construct one from
-env{'form.filter'} and env{'form.condition'} information, plus the
-'iterator_map' parameter if any.
+=item
-=item * B<iterator_map>: default: not used
+=item
-If you are letting the renderer do the iterator handling, you can
-instruct the renderer to render only a particular map by passing it
-the source of the map you want to process, like
-'/res/103/jerf/navmap.course.sequence'.
+=item
-=item * B<include_top_level_map>: default: false
+=item
-If you need to include the top level map (meaning the course) in the
-rendered output set this to true
+=item
-=item * B<navmap>: default: constructs one from %env
+=item
-A reference to a navmap, used only if an iterator is not passed in. If
-this is necessary to make an iterator but it is not passed in, a new
-one will be constructed based on env info. This is useful to do basic
-error checking before passing it off to render.
+=back
-=item * B<r>: default: must be passed in
+=cut
-The standard Apache response object. This must be passed to the
-renderer or the course hash will be locked.
+package Apache::lonnavmaps;
-=item * B<cols>: default: empty (useless)
+use strict;
+use GDBM_File;
+use Apache::loncommon();
+use Apache::lonenc();
+use Apache::lonlocal;
+use Apache::lonnet;
+use POSIX qw (floor strftime);
+use Time::HiRes qw( gettimeofday tv_interval );
+use LONCAPA;
+use DateTime();
-An array reference
+# symbolic constants
+sub SYMB { return 1; }
+sub URL { return 2; }
+sub NOTHING { return 3; }
-=item * B<showParts>:default true
+# Some data
-A flag. If true, a line for the resource itself, and a line
-for each part will be displayed. If not, only one line for each
-resource will be displayed.
+my $resObj = "Apache::lonnavmaps::resource";
-=item * B<condenseParts>: default true
+# Keep these mappings in sync with lonquickgrades, which uses the colors
+# instead of the icons.
+my %statusIconMap =
+ (
+ $resObj->CLOSED => '',
+ $resObj->OPEN => 'navmap.open.gif',
+ $resObj->CORRECT => 'navmap.correct.gif',
+ $resObj->PARTIALLY_CORRECT => 'navmap.partial.gif',
+ $resObj->INCORRECT => 'navmap.wrong.gif',
+ $resObj->ATTEMPTED => 'navmap.ellipsis.gif',
+ $resObj->ERROR => ''
+ );
-A flag. If true, if all parts of the problem have the same
-status and that status is Nothing Set, Correct, or Network Failure,
-then only one line will be displayed for that resource anyhow. If no,
-all parts will always be displayed. If showParts is 0, this is
-ignored.
+my %iconAltTags =
+ ( 'navmap.correct.gif' => 'Correct',
+ 'navmap.wrong.gif' => 'Incorrect',
+ 'navmap.open.gif' => 'Open' );
-=item * B<jumpCount>: default: determined from %env
+# Defines a status->color mapping, null string means don't color
+my %colormap =
+ ( $resObj->NETWORK_FAILURE => '',
+ $resObj->CORRECT => '',
+ $resObj->EXCUSED => '#3333FF',
+ $resObj->PAST_DUE_ANSWER_LATER => '',
+ $resObj->PAST_DUE_NO_ANSWER => '',
+ $resObj->ANSWER_OPEN => '#006600',
+ $resObj->OPEN_LATER => '',
+ $resObj->TRIES_LEFT => '',
+ $resObj->INCORRECT => '',
+ $resObj->OPEN => '',
+ $resObj->NOTHING_SET => '',
+ $resObj->ATTEMPTED => '',
+ $resObj->ANSWER_SUBMITTED => '',
+ $resObj->PARTIALLY_CORRECT => '#006600'
+ );
+# And a special case in the nav map; what to do when the assignment
+# is not yet done and due in less than 24 hours
+my $hurryUpColor = "#FF0000";
-A string identifying the URL to place the anchor 'curloc' at.
-It is the responsibility of the renderer user to
-ensure that the #curloc is in the URL. By default, determined through
-the use of the env{} 'jump' information, and should normally "just
-work" correctly.
+sub close {
+ if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
+ return(<<ENDCLOSE);
+<script type="text/javascript">
+window.status='Accessing Nav Control';
+menu=window.open("/adm/rat/empty.html","loncapanav",
+ "height=600,width=400,scrollbars=1");
+window.status='Closing Nav Control';
+menu.close();
+window.status='Done.';
+</script>
+ENDCLOSE
+}
-=item * B<here>: default: empty string
+sub update {
+ if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
+ if (!$env{'request.course.id'}) { return ''; }
+ if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
+ return(<<ENDUPDATE);
+<form name="navform"></form>
+<script type="text/javascript">
+this.document.navform.action='/adm/navmaps#curloc';
+this.document.navform.target='loncapanav';
+this.document.navform.submit();
+</script>
+ENDUPDATE
+}
+
+# Convenience functions: Returns a string that adds or subtracts
+# the second argument from the first hash, appropriate for the
+# query string that determines which folders to recurse on
+sub addToFilter {
+ my $hashIn = shift;
+ my $addition = shift;
+ my %hash = %$hashIn;
+ $hash{$addition} = 1;
+
+ return join (",", keys(%hash));
+}
+
+sub removeFromFilter {
+ my $hashIn = shift;
+ my $subtraction = shift;
+ my %hash = %$hashIn;
+
+ delete $hash{$subtraction};
+ return join(",", keys(%hash));
+}
+
+# Convenience function: Given a stack returned from getStack on the iterator,
+# return the correct src() value.
+sub getLinkForResource {
+ my $stack = shift;
+ my $res;
+
+ # Check to see if there are any pages in the stack
+ foreach $res (@$stack) {
+ if (defined($res)) {
+ my $anchor;
+ if ($res->is_page()) {
+ foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; } }
+ $anchor=&escape($anchor->shown_symb());
+ return ($res->link(),$res->shown_symb(),$anchor);
+ }
+ # in case folder was skipped over as "only sequence"
+ my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
+ if ($map=~/\.page$/) {
+ my $url=&Apache::lonnet::clutter($map);
+ $anchor=&escape($src->shown_symb());
+ return ($url,$res->shown_symb(),$anchor);
+ }
+ }
+ }
+
+ # Failing that, return the src of the last resource that is defined
+ # (when we first recurse on a map, it puts an undefined resource
+ # on the bottom because $self->{HERE} isn't defined yet, and we
+ # want the src for the map anyhow)
+ foreach my $item (@$stack) {
+ if (defined($item)) { $res = $item; }
+ }
+
+ if ($res) {
+ return ($res->link(),$res->shown_symb());
+ }
+ return;
+}
+
+# Convenience function: This separates the logic of how to create
+# the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
+# etc.) into a separate function. It takes a resource object as the
+# first parameter, and the part number of the resource as the second.
+# It's basically a big switch statement on the status of the resource.
-A Symb identifying where to place the 'here' marker. The empty
-string means no marker.
+sub getDescription {
+ my $res = shift;
+ my $part = shift;
+ my $status = $res->status($part);
-=item * B<indentString>: default: 25 pixel whitespace image
+ my $open = $res->opendate($part);
+ my $due = $res->duedate($part);
+ my $answer = $res->answerdate($part);
-A string identifying the indentation string to use.
+ if ($status == $res->NETWORK_FAILURE) {
+ return &mt("Having technical difficulties; please check status later");
+ }
+ if ($status == $res->NOTHING_SET) {
+ return &mt("Not currently assigned.");
+ }
+ if ($status == $res->OPEN_LATER) {
+ return &mt("Open ") .timeToHumanString($open,'start');
+ }
+ if ($status == $res->OPEN) {
+ if ($due) {
+ if ($res->is_practice()) {
+ return &mt("Closes ")." " .timeToHumanString($due,'start');
+ } else {
+ return &mt("Due")." " .timeToHumanString($due,'end');
+ }
+ } else {
+ return &mt("Open, no due date");
+ }
+ }
+ if ($status == $res->PAST_DUE_ANSWER_LATER) {
+ return &mt("Answer open")." " .timeToHumanString($answer,'start');
+ }
+ if ($status == $res->PAST_DUE_NO_ANSWER) {
+ if ($res->is_practice()) {
+ return &mt("Closed")." " . timeToHumanString($due,'start');
+ } else {
+ return &mt("Was due")." " . timeToHumanString($due,'end');
+ }
+ }
+ if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
+ && $res->handgrade($part) ne 'yes') {
+ return &mt("Answer available");
+ }
+ if ($status == $res->EXCUSED) {
+ return &mt("Excused by instructor");
+ }
+ if ($status == $res->ATTEMPTED) {
+ return &mt("Answer submitted, not yet graded");
+ }
+ if ($status == $res->TRIES_LEFT) {
+ my $tries = $res->tries($part);
+ my $maxtries = $res->maxtries($part);
+ my $triesString = "";
+ if ($tries && $maxtries) {
+ $triesString = '<font size="-1"><i>('.&mt('[_1] of [_2] tries used',$tries,$maxtries).')</i></font>';
+ if ($maxtries > 1 && $maxtries - $tries == 1) {
+ $triesString = "<b>$triesString</b>";
+ }
+ }
+ if ($due) {
+ return &mt("Due")." " . timeToHumanString($due,'end') .
+ " $triesString";
+ } else {
+ return &mt("No due date")." $triesString";
+ }
+ }
+ if ($status == $res->ANSWER_SUBMITTED) {
+ return &mt('Answer submitted');
+ }
+}
-=item * B<queryString>: default: empty
+# Convenience function, so others can use it: Is the problem due in less than
+# 24 hours, and still can be done?
-A string which will be prepended to the query string used when the
-folders are opened or closed. You can use this to pass
-application-specific values.
+sub dueInLessThan24Hours {
+ my $res = shift;
+ my $part = shift;
+ my $status = $res->status($part);
-=item * B<url>: default: none
+ return ($status == $res->OPEN() ||
+ $status == $res->TRIES_LEFT()) &&
+ $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
+ $res->duedate($part) > time();
+}
-The url the folders will link to, which should be the current
-page. Required if the resource info column is shown, and you
-are allowing the user to open and close folders.
+# Convenience function, so others can use it: Is there only one try remaining for the
+# part, with more than one try to begin with, not due yet and still can be done?
+sub lastTry {
+ my $res = shift;
+ my $part = shift;
-=item * B<currentJumpIndex>: default: no jumping
+ my $tries = $res->tries($part);
+ my $maxtries = $res->maxtries($part);
+ return $tries && $maxtries && $maxtries > 1 &&
+ $maxtries - $tries == 1 && $res->duedate($part) &&
+ $res->duedate($part) > time();
+}
-Describes the currently-open row number to cause the browser to jump
-to, because the user just opened that folder. By default, pulled from
-the Jump information in the env{'form.*'}.
+# This puts a human-readable name on the env variable.
-=item * B<printKey>: default: false
+sub advancedUser {
+ return $env{'request.role.adv'};
+}
-If true, print the key that appears on the top of the standard
-navmaps.
-=item * B<printCloseAll>: default: true
+# timeToHumanString takes a time number and converts it to a
+# human-readable representation, meant to be used in the following
+# manner:
+# print "Due $timestring"
+# print "Open $timestring"
+# print "Answer available $timestring"
+# Very, very, very, VERY English-only... goodness help a localizer on
+# this func...
-If true, print the "Close all folders" or "open all folders"
-links.
-=item * B<filterFunc>: default: sub {return 1;} (accept everything)
+sub timeToHumanString {
+ my ($time,$type,$format) = @_;
-A function that takes the resource object as its only parameter and
-returns a true or false value. If true, the resource is displayed. If
-false, it is simply skipped in the display.
+ # zero, '0' and blank are bad times
+ if (!$time) {
+ return &mt('never');
+ }
+ unless (&Apache::lonlocal::current_language()=~/^en/) {
+ return &Apache::lonlocal::locallocaltime($time);
+ }
+ my $now = time();
-=item * B<suppressEmptySequences>: default: false
+ # Positive = future
+ my $delta = $time - $now;
-If you're using a filter function, and displaying sequences to orient
-the user, then frequently some sequences will be empty. Setting this to
-true will cause those sequences not to display, so as not to confuse the
-user into thinking that if the sequence is there there should be things
-under it; for example, see the "Show Uncompleted Homework" view on the
-B<NAV> screen.
+ my $minute = 60;
+ my $hour = 60 * $minute;
+ my $day = 24 * $hour;
+ my $week = 7 * $day;
+ my $inPast = 0;
-=item * B<suppressNavmaps>: default: false
+ # Logic in comments:
+ # Is it now? (extremely unlikely)
+ if ( $delta == 0 ) {
+ return "this instant";
+ }
-If true, will not display Navigate Content resources.
+ if ($delta < 0) {
+ $inPast = 1;
+ $delta = -$delta;
+ }
-=back
+ if ( $delta > 0 ) {
-=head2 Additional Info
+ my $tense = $inPast ? " ago" : "";
+ my $prefix = $inPast ? "" : "in ";
+
+ # Less than a minute
+ if ( $delta < $minute ) {
+ if ($delta == 1) { return "${prefix}1 second$tense"; }
+ return "$prefix$delta seconds$tense";
+ }
-In addition to the parameters you can pass to the renderer, which will
-be passed through unchange to the column renderers, the renderer will
-generate the following information which your renderer may find
-useful:
+ # Less than an hour
+ if ( $delta < $hour ) {
+ # If so, use minutes
+ my $minutes = floor($delta / 60);
+ if ($minutes == 1) { return "${prefix}1 minute$tense"; }
+ return "$prefix$minutes minutes$tense";
+ }
+
+ # Is it less than 24 hours away? If so,
+ # display hours + minutes
+ if ( $delta < $hour * 24) {
+ my $hours = floor($delta / $hour);
+ my $minutes = floor(($delta % $hour) / $minute);
+ my $hourString = "$hours hours";
+ my $minuteString = ", $minutes minutes";
+ if ($hours == 1) {
+ $hourString = "1 hour";
+ }
+ if ($minutes == 1) {
+ $minuteString = ", 1 minute";
+ }
+ if ($minutes == 0) {
+ $minuteString = "";
+ }
+ return "$prefix$hourString$minuteString$tense";
+ }
-=over 4
+ my $dt = DateTime->from_epoch(epoch => $time)
+ ->set_time_zone(&Apache::lonlocal::gettimezone());
-=item * B<counter>:
+ # If there's a caller supplied format, use it.
-Contains the number of rows printed. Useful after calling the render
-function, as you can detect whether anything was printed at all.
+ if ($format ne '') {
+ my $timeStr = $dt->strftime($format);
+ return $timeStr.' ('.$dt->time_zone_short_name().')';
+ }
-=item * B<isNewBranch>:
+ # Less than 5 days away, display day of the week and
+ # HH:MM
-Useful for renderers: If this resource is currently the first resource
-of a new branch, this will be true. The Resource column (leftmost in the
-navmaps screen) uses this to display the "new branch" icon
+ if ( $delta < $day * 5 ) {
+ my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");
+ $timeStr =~ s/12:00 am/00:00/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return ($inPast ? "last " : "this ") .
+ $timeStr;
+ }
+
+ my $conjunction='on';
+ if ($type eq 'start') {
+ $conjunction='at';
+ } elsif ($type eq 'end') {
+ $conjunction='by';
+ }
+ # Is it this year?
+ my $dt_now = DateTime->from_epoch(epoch => $now)
+ ->set_time_zone(&Apache::lonlocal::gettimezone());
+ if ( $dt->year() == $dt_now->year()) {
+ # Return on Month Day, HH:MM meridian
+ my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");
+ $timeStr =~ s/12:00 am/00:00/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return $timeStr;
+ }
-=back
+ # Not this year, so show the year
+ my $timeStr =
+ $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");
+ $timeStr =~ s/12:00 am/00:00/;
+ $timeStr =~ s/12:00 pm/noon/;
+ return $timeStr;
+ }
+}
-=cut
sub resource { return 0; }
sub communication_status { return 1; }
--jms1227039518--