[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--