--- loncom/interface/lonnavmaps.pm 2002/10/01 07:20:31 1.58
+++ loncom/interface/lonnavmaps.pm 2002/11/03 21:05:03 1.97
@@ -1,7 +1,8 @@
+
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.58 2002/10/01 07:20:31 albertel Exp $
+# $Id: lonnavmaps.pm,v 1.97 2002/11/03 21:05:03 bowersj2 Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -44,8 +45,8 @@ use strict;
use Apache::Constants qw(:common :http);
use Apache::lonnet();
use Apache::loncommon();
-use HTML::TokeParser;
use GDBM_File;
+use POSIX qw (floor strftime);
# -------------------------------------------------------------- Module Globals
my %hash;
@@ -819,13 +820,10 @@ sub new_handle {
&Apache::loncommon::no_cache($r);
$r->send_http_header;
- # Header
- $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
- ''));
- # Initialize the nav map
+ # Create the nav map the nav map
my $navmap = Apache::lonnavmaps::navmap->new(
$ENV{"request.course.fn"}.".db",
- $ENV{"request.course.fn"}."_parms.db", 1);
+ $ENV{"request.course.fn"}."_parms.db", 1, 1);
if (!defined($navmap)) {
@@ -834,6 +832,45 @@ sub new_handle {
return HTTP_NOT_ACCEPTABLE;
}
+ # Header
+ $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
+ ''));
+ $r->print('');
+ my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+ $r->print('
');
+ my $condition = 0;
+ if ($ENV{'form.condition'}) {
+ $condition = 1;
+ }
+
+ if ($condition) {
+ $r->print('Close All Folders');
+ } else {
+ $r->print('Open All Folders');
+ }
+
+ $r->print('
');
+ $r->rflush();
+
+ # Now that we've displayed some stuff to the user, init the navmap
+ $navmap->init();
+
# Check that it's defined
if (!($navmap->courseMapDefined())) {
$r->print('Coursemap undefined.' .
@@ -848,16 +885,43 @@ sub new_handle {
# Defines a status->color mapping, null string means don't color
my %colormap =
( $res->NETWORK_FAILURE => '',
- $res->CORRECT => '#BBFFBB',
+ $res->CORRECT => '',
$res->EXCUSED => '#BBBBFF',
- $res->PAST_DUE_ANSWER_LATER => '#FFAA00',
- $res->PAST_DUE_NO_ANSWER => '#FFAA00',
- $res->ANSWER_OPEN => '#FF00AA',
+ $res->PAST_DUE_ANSWER_LATER => '',
+ $res->PAST_DUE_NO_ANSWER => '',
+ $res->ANSWER_OPEN => '#CCFFCC',
$res->OPEN_LATER => '',
- $res->TRIES_LEFT => '#FFFF00',
- $res->INCORRECT => '#FFAA00',
- $res->OPEN => '#FFFF88',
+ $res->TRIES_LEFT => '',
+ $res->INCORRECT => '',
+ $res->OPEN => '',
$res->NOTHING_SET => '' );
+ # And a special case in the nav map; what to do when the assignment
+ # is not yet done and due in less then 24 hours
+ my $hurryUpColor = "#FF0000";
+
+ my %statusIconMap =
+ ( $res->NETWORK_FAILURE => '',
+ $res->NOTHING_SET => '',
+ $res->CORRECT => 'navmap.correct.gif',
+ $res->EXCUSED => 'navmap.correct.gif',
+ $res->PAST_DUE_NO_ANSWER => 'navmap.wrong.gif',
+ $res->PAST_DUE_ANSWER_LATER => 'navmap.wrong.gif',
+ $res->ANSWER_OPEN => 'navmap.wrong.gif',
+ $res->OPEN_LATER => '',
+ $res->TRIES_LEFT => 'navmap.open.gif',
+ $res->INCORRECT => 'navmap.wrong.gif',
+ $res->OPEN => 'navmap.open.gif',
+ $res->ATTEMPTED => 'navmap.open.gif' );
+
+ my %iconAltTags =
+ ( 'navmap.correct.gif' => 'Correct',
+ 'navmap.wrong.gif' => 'Incorrect',
+ 'navmap.open.gif' => 'Open' );
+
+ my %condenseStatuses =
+ ( $res->NETWORK_FAILURE => 1,
+ $res->NOTHING_SET => 1,
+ $res->CORRECT => 1 );
my %filterHash;
# Figure out what we're not displaying
@@ -867,19 +931,122 @@ sub new_handle {
}
}
+ # Is this a new-style course? If so, we want to suppress showing the top-level
+ # maps in their own folders, in favor of "inlining" them.
+ my $topResource = $navmap->getById("0.0");
+ my $inlineTopLevelMaps = $topResource->src() =~ m|^/uploaded/.*default\.sequence$|;
+
+ my $currenturl = $ENV{'form.postdata'};
+ $currenturl=~s/^http\:\/\///;
+ $currenturl=~s/^[^\/]+//;
+ # alreadyHere allows us to only open the maps necessary to view
+ # the current location once, while at the same time remembering
+ # the current location. Without that check, the user would never
+ # be able to close those maps; the user would close it, and the
+ # currenturl scan would re-open it.
+ my $queryAdd = "postdata=" . &Apache::lonnet::escape($currenturl) .
+ "&alreadyHere=1";
+
# Begin the HTML table
- # two columns: the title/icon on the left, the info on the right
- $r->print('' ."\n");
+ # four cols: resource + indent, chat+feedback, icon, text string
+ $r->print('' ."\n");
- my $mapIterator = $navmap->getIterator(undef, undef, \%filterHash);
+ # This needs to be updated to use symbs from the remote,
+ # instead of uris. The changes to this and the main rendering
+ # loop should be obvious.
+ # Here's a simple example of the iterator.
+ # Preprocess the map: Look for current URL, force inlined maps to display
+
+ # This currently does very little...
+ my $mapEventualIterator = Apache::lonnavmaps::iterator->new($navmap, undef, undef, {},
+ undef, $condition);
+
+ my $mapIterator = $navmap->getIterator(undef, undef, {}, 1);
+ my $found = 0;
+ my $depth = 1;
+ my $currentUrlIndex = 0; # keeps track of when the current resource is found,
+ # so we can back up a few and put the anchor above the
+ # current resource
+ $mapIterator->next(); # discard the first BEGIN_MAP
my $curRes = $mapIterator->next();
+ my $counter = 0;
+
+ while ($depth > 0) {
+ if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $mapIterator->END_MAP()) { $depth--; }
+
+ if (ref($curRes)) { $counter++; }
+
+ my $mapStack = $mapIterator->getStack();
+ if ($currenturl && !$ENV{'form.alreadyHere'} && ref($curRes) &&
+ $curRes->src() eq $currenturl) {
+ # If this is the correct resource, be sure to
+ # show it by making sure the containing maps
+ # are open.
+
+ $currentUrlIndex = $counter;
+
+ for my $map (@{$mapStack}) {
+ if ($condition) {
+ undef $filterHash{$map->map_pc()};
+ } else {
+ $filterHash{$map->map_pc()} = 1;
+ }
+ }
+ $ENV{'form.alreadyHere'} = 1;
+ }
+
+ # Preprocessing: If we're inlining nav maps into the top-level display,
+ # make sure we show this map!
+ if ($inlineTopLevelMaps && ref($curRes) && $curRes->is_map &&
+ scalar(@{$mapStack}) == 1) {
+ if ($condition) {
+ undef $filterHash{$curRes->map_pc()};
+ } else {
+ $filterHash{$curRes->map_pc()} = 1;
+ }
+ }
+
+ $curRes = $mapIterator->next();
+ }
+
undef $res; # so we don't accidentally use it later
- my $indentLevel = -1;
- my $indentString = " ";
+ my $indentLevel = 0;
+ my $indentString = "";
my $isNewBranch = 0;
+ my $now = time();
+ my $in24Hours = $now + 24 * 60 * 60;
+ my $displayedHereMarker = 0;
+
+ # We know the first thing is a BEGIN_MAP (see "$self->{STARTED}"
+ # code in iterator->next), so ignore the first one
+ $mapIterator = $navmap->getIterator(undef, undef, \%filterHash,
+ $condition);
+ $mapIterator->next();
+ $curRes = $mapIterator->next();
+ my $deltadepth = 0;
+ $depth = 1;
+
+ my @backgroundColors = ("#FFFFFF", "#F6F6F6");
+ my $rowNum = 0;
+
+ $counter = 0;
+
+ while ($depth > 0) {
+ # If we're in a new style course, and this is a BEGIN_MAP, END_MAP, or
+ # map resource and the stack depth is only one, just plain ignore this resource
+ # entirely. (This has the effect of inlining the resources in that map
+ # in the nav map.)
+ if ($inlineTopLevelMaps && scalar(@{$mapIterator->getStack()}) == 1 &&
+ ref($curRes) && $curRes->is_map()) {
+ # We let the normal depth stuff occur, but we need to shift everything
+ # over by one to the left to make it look right.
+ $deltadepth = -1;
+ $curRes = $mapIterator->next();
+ next;
+ }
- while ($curRes != $mapIterator->END_NAV_MAP) {
if ($curRes == $mapIterator->BEGIN_MAP() ||
$curRes == $mapIterator->BEGIN_BRANCH()) {
$indentLevel++;
@@ -891,20 +1058,108 @@ sub new_handle {
if ($curRes == $mapIterator->BEGIN_BRANCH()) {
$isNewBranch = 1;
}
+ if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $mapIterator->END_MAP()) { $depth--; }
+
+ if (ref($curRes)) { $counter++; }
+
+ if ($depth == 1) { $deltadepth = 0; } # we're done shifting, because we're
+ # out of the inlined map
+
+ # Is this resource being blotted out?
+ if (ref($curRes) && !advancedUser() && $curRes->randomout()) {
+ $curRes = $mapIterator->next();
+ next; # and totally ignore this resource
+ }
if (ref($curRes) && $curRes->src()) {
- my @parts;
+ # Step one: Decide which parts to show
+ my @parts = @{$curRes->parts()};
+ my $multipart = scalar(@parts) > 1;
+ my $condensed = 0;
+
if ($curRes->is_problem()) {
- @parts = @{$curRes->parts()};
- if (scalar(@parts) > 1) { shift(@parts); }
+ # Is it multipart?
+
+ if ($multipart) {
+ # If it's multipart, see if part 0 is "open"
+ # if it is, display all parts, if it isn't,
+ # just display first
+ if (!$curRes->opendate("0")) {
+ @parts = ("0"); # just display the zero-th part
+ $condensed = 1;
+ } else {
+ # Otherwise, only display part 0 if we want to
+ # attach feedback or email information to it
+ if ($curRes->hasDiscussion() || $curRes->getFeedback()) {
+ shift @parts;
+ } else {
+ # If there's discussion or feedback, that counts
+ # as a difference, so display the parts.
+
+ # Now, we decide whether to condense the
+ # parts due to similarity
+ my $status = $curRes->status($parts[1]);
+ my $due = $curRes->duedate($parts[1]);
+ my $open = $curRes->opendate($parts[1]);
+ my $statusAllSame = 1;
+ my $dueAllSame = 1;
+ my $openAllSame = 1;
+ for (my $i = 2; $i < scalar(@parts); $i++) {
+ if ($curRes->status($parts[$i]) != $status){
+ $statusAllSame = 0;
+ }
+ if ($curRes->duedate($parts[$i]) != $due ) {
+ $dueAllSame = 0;
+ }
+ if ($curRes->opendate($parts[$i]) != $open) {
+ $openAllSame = 0;
+ }
+ }
+
+ # $allSame is true if all the statuses were
+ # the same. Now, if they are all the same and
+ # match one of the statuses to condense, or they
+ # are all open with the same due date, or they are
+ # all OPEN_LATER with the same open date, display the
+ # status of the first non-zero part (to get the 'correct'
+ # status right, since 0 is never 'correct' or 'open').
+ if (($statusAllSame && defined($condenseStatuses{$status})) ||
+ ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
+ ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
+ @parts = ($parts[1]);
+ $condensed = 1;
+ }
+ }
+ }
+ }
+
} else {
- @parts[0] = "0"; # this is to get past foreach loop below
+ $parts[0] = "0"; # this is to get past foreach loop below
# you can consider a non-problem resource as a resource
- # with only one part without loss
+ # with only one part without loss
+ }
+
+ # Is it a multipart problem with a single part, now in
+ # @parts with "0" filtered out? If so, forget it's a multi-part
+ # problem and treat it like a single-part problem.
+ if ( scalar(@parts) == 1 ) {
+ $multipart = 0;
+ }
+
+ # Display one part, in event of network error.
+ # If this is a single part, we can at least show the correct
+ # status, but if it's multipart, we're lost.
+ if ($curRes->{RESOURCE_ERROR}) {
+ @parts = ("0");
}
+ # Step two: Show the parts
foreach my $part (@parts) {
+
+ my $deltalevel = 0; # for inserting the branch icon
+ my $nonLinkedText = ""; # unlinked stuff after title
# For each thing we're displaying...
@@ -917,68 +1172,191 @@ sub new_handle {
'symb='.&Apache::lonnet::escape($curRes->symb()).
'"';
my $title = $curRes->title();
+ if (!$title) {
+ $title = $curRes->src();
+ $title = substr ($title, rindex($title, "/") + 1);
+ }
my $partLabel = "";
my $newBranchText = "";
# If this is a new branch, label it so
# (temporary, this should be an icon w/ alt text)
if ($isNewBranch) {
- $newBranchText = "NB -> ";
+ $newBranchText = "";
$isNewBranch = 0;
+ $deltalevel = 1;
}
# links to open and close the folders
my $linkopen = "";
my $linkclose = "";
- my $icon = "html.gif";
+ my $icon = "";
if ($curRes->is_problem()) {
- $icon = "problem.gif";
+ if ($part eq "0" || $condensed) {
+ $icon = '';
+ } else {
+ $icon = $indentString;
+ }
}
# Display the correct icon, link to open or shut map
if ($curRes->is_map()) {
my $mapId = $curRes->map_pc();
- my $nowOpen = !defined($filterHash{$mapId});
+ my $nowOpen = (!defined($filterHash{$mapId}));
+ if ($condition) {$nowOpen = !$nowOpen;}
$icon = $nowOpen ?
- "folder_opened.gif" : "folder_closed.gif";
+ "navmap.folder.closed.gif" : "navmap.folder.open.gif";
+ $icon = "";
$linkopen = "";
+ $linkopen .= "&condition=$condition&$queryAdd\">";
$linkclose = "";
-
+
}
my $colorizer = "";
+ my $color;
if ($curRes->is_problem()) {
my $status = $curRes->status($part);
- my $color = $colormap{$status};
+ $color = $colormap{$status};
+
+ # Special case in the navmaps: If in less then
+ # 24 hours, give it a bit of urgency
+ if (($status == $curRes->OPEN() || $status == $curRes->ATTEMPTED() ||
+ $status == $curRes->TRIES_LEFT())
+ && $curRes->duedate() &&
+ $curRes->duedate() < time()+(24*60*60) &&
+ $curRes->duedate() > time()) {
+ $color = $hurryUpColor;
+ }
+ # Special case: If this is the last try, and there is
+ # more then one available, and it's not due yet, give a bit of urgency
+ my $tries = $curRes->tries($part);
+ my $maxtries = $curRes->maxtries($part);
+ if ($tries && $maxtries && $maxtries > 1 &&
+ $maxtries - $tries == 1 && $curRes->duedate() &&
+ $curRes->duedate() > time()) {
+ $color = $hurryUpColor;
+ }
if ($color ne "") {
$colorizer = "bgcolor=\"$color\"";
}
}
- $r->print(" \n");
+ if ($curRes->randomout()) {
+ $nonLinkedText .= ' (hidden) ';
+ }
+
+ $rowNum++;
+ my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)];
+
+ # FIRST COL: The resource indentation, branch icon, name, and anchor
+ $r->print(" |
\n");
+
+ # anchor for current resource... - 5 is deliberate: If it's that
+ # high on the screen, don't bother focusing on it. Also this will
+ # print multiple anchors if this is an expanded multi-part problem...
+ # who cares?
+ if ($counter == $currentUrlIndex - 5) {
+ $r->print('');
+ }
# print indentation
- for (my $i = 0; $i < $indentLevel; $i++) {
+ for (my $i = 0; $i < $indentLevel - $deltalevel + $deltadepth; $i++) {
$r->print($indentString);
}
- $r->print(" ${newBranchText}${linkopen}${linkclose}\n");
+ $r->print(" ${newBranchText}${linkopen}$icon${linkclose}\n");
+
+ my $curMarkerBegin = "";
+ my $curMarkerEnd = "";
+
+ # Is this the current resource?
+ if ($curRes->src() eq $currenturl && !$displayedHereMarker) {
+ $curMarkerBegin = '> ';
+ $curMarkerEnd = ' <';
+ $displayedHereMarker = 1;
+ }
- if ($curRes->is_problem() && $part != "0") { $partLabel = " (Part $part)"; }
+ if ($curRes->is_problem() && $part ne "0" && !$condensed) {
+ $partLabel = " (Part $part)";
+ $title = "";
+ }
+ if ($multipart && $condensed) {
+ $nonLinkedText .= ' (' . $curRes->countParts() . ' parts)';
+ }
- $r->print(" $title$partLabel | ".
- "\n");
+ $r->print(" $curMarkerBegin$title$partLabel $curMarkerEnd $nonLinkedText");
+ $r->print(" TDV:" . $curRes->{DATA}->{TOP_DOWN_VAL}); # temp
+ $r->print(" BUV:" . $curRes->{DATA}->{BOT_UP_VAL}); # temp
+ $r->print(" DD:" . $curRes->{DATA}->{DISPLAY_DEPTH}); # temp
+
+ if ($curRes->{RESOURCE_ERROR}) {
+ $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down",
+ 'Host down'));
+ }
+
+ my $discussionHTML = ""; my $feedbackHTML = "";
+
+ # SECOND COL: Is there text, feedback, errors??
+ if ($curRes->hasDiscussion()) {
+ $discussionHTML = $linkopen .
+ '' .
+ $linkclose;
+ }
+
+ if ($curRes->getFeedback()) {
+ my $feedback = $curRes->getFeedback();
+ foreach (split(/\,/, $feedback)) {
+ if ($_) {
+ $feedbackHTML .= ' '
+ . '';
+ }
+ }
+ }
+
+ $r->print(" | $discussionHTML$feedbackHTML | ");
+
+ # Is this the first displayed part of a multi-part problem
+ # that has not been condensed, so we should suppress these two
+ # columns?
+ my $firstDisplayed = !$condensed && $multipart && $part eq "0";
+
+ # THIRD COL: Problem status icon
+ if ($curRes->is_problem() &&
+ !$firstDisplayed) {
+ my $icon = $statusIconMap{$curRes->status($part)};
+ my $alt = $iconAltTags{$icon};
+ if ($icon) {
+ $r->print("$linkopen$linkclose | \n");
+ } else {
+ $r->print(" | \n");
+ }
+ } else { # not problem, no icon
+ $r->print(" | \n");
+ }
+
+ # FOURTH COL: Text description
+ #$r->print("\n");
+ $r->print(" | \n");
- if ($curRes->kind() eq "res" and
- $curRes->is_problem() ) {
+ if ($curRes->kind() eq "res" &&
+ $curRes->is_problem() &&
+ !$firstDisplayed) {
+ $r->print ("") if ($color);
$r->print (getDescription($curRes, $part));
+ $r->print ("") if ($color);
+ }
+ if ($curRes->is_map() && advancedUser() && $curRes->randompick()) {
+ $r->print('(randomly select ' . $curRes->randompick() .')');
}
+
+ $r->print(" |
\n");
}
}
$curRes = $mapIterator->next();
@@ -986,6 +1364,8 @@ sub new_handle {
$r->print("