--- loncom/interface/lonnavmaps.pm 2002/11/08 19:10:58 1.100
+++ loncom/interface/lonnavmaps.pm 2002/12/02 14:19:38 1.119
@@ -2,7 +2,7 @@
# The LearningOnline Network with CAPA
# Navigate Maps Handler
#
-# $Id: lonnavmaps.pm,v 1.100 2002/11/08 19:10:58 bowersj2 Exp $
+# $Id: lonnavmaps.pm,v 1.119 2002/12/02 14:19:38 bowersj2 Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -37,19 +37,22 @@
# 3/1/1,6/1,17/1,29/1,30/1,2/8,9/21,9/24,9/25 Gerd Kortemeyer
# YEAR=2002
# 1/1 Gerd Kortemeyer
-#
+# Oct-Nov Jeremy Bowers
package Apache::lonnavmaps;
use strict;
use Apache::Constants qw(:common :http);
-use Apache::lonnet();
use Apache::loncommon();
-use GDBM_File;
use POSIX qw (floor strftime);
sub handler {
my $r = shift;
+ real_handler($r);
+}
+
+sub real_handler {
+ my $r = shift;
&Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
@@ -73,7 +76,7 @@ sub handler {
&Apache::loncommon::no_cache($r);
$r->send_http_header;
- # Create the nav map the nav map
+ # Create the nav map
my $navmap = Apache::lonnavmaps::navmap->new(
$ENV{"request.course.fn"}.".db",
$ENV{"request.course.fn"}."_parms.db", 1, 1);
@@ -85,14 +88,19 @@ sub handler {
return HTTP_NOT_ACCEPTABLE;
}
+ $r->print("
\n");
+ $r->print("Navigate Course Contents");
+
# Header
- $r->print(&Apache::loncommon::bodytag('Navigate Course Map','',
+ $r->print(&Apache::loncommon::bodytag('Navigate Course Contents','',
''));
$r->print('');
- my $desc=$ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+
$r->print('');
my $date=localtime;
$r->print('Key: | ');
+
+ # Print discussions and feedback header
if ($navmap->{LAST_CHECK}) {
$r->print(' '.
' New discussion since '.
@@ -107,15 +115,64 @@ sub handler {
' | ');
}
$r->print('
');
+
my $condition = 0;
if ($ENV{'form.condition'}) {
$condition = 1;
}
+ # Determine where the "here" marker is and where the screen jumps to.
+ my $SYMB = 1; my $URL = 2; my $NOTHING = 3; # symbolic constants
+ my $hereType; # the type of marker, $SYMB, $URL, or $NOTHING
+ my $here; # the actual URL or SYMB for the here marker
+ my $jumpType; # The type of the thing we have a jump for, $SYMB or $URL
+ my $jump; # the SYMB/URL of the resource we need to jump to
+
+ if ( $ENV{'form.alreadyHere'} ) { # we came from a user's manipulation of the nav page
+ # If this is a click on a folder or something, we want to preserve the "here"
+ # from the querystring, and get the new "jump" marker
+ $hereType = $ENV{'form.hereType'};
+ $here = $ENV{'form.here'};
+ $jumpType = $ENV{'form.jumpType'} || $NOTHING;
+ $jump = $ENV{'form.jump'};
+ } else { # the user is visiting the nav map from the remote
+ # We're coming from the remote. We have either a url, a symb, or nothing,
+ # and we need to figure out what.
+ # Preference: Symb
+
+ if ($ENV{'form.symb'}) {
+ $hereType = $jumpType = $SYMB;
+ $here = $jump = $ENV{'form.symb'};
+ } elsif ($ENV{'form.postdata'}) {
+ # couldn't find a symb, is there a URL?
+ my $currenturl = $ENV{'form.postdata'};
+ $currenturl=~s/^http\:\/\///;
+ $currenturl=~s/^[^\/]+//;
+
+ $hereType = $jumpType = $URL;
+ $here = $jump = $currenturl;
+ } else {
+ # Nothing
+ $hereType = $jumpType = $NOTHING;
+ }
+ }
+
+
+ # 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 = "&alreadyHere=1";
+
if ($condition) {
- $r->print('Close All Folders');
+ $r->print("Close All Folders");
} else {
- $r->print('Open All Folders');
+ $r->print("Open All Folders");
}
$r->print('
');
@@ -135,14 +192,17 @@ sub handler {
# is technically not proper, but should be harmless
my $res = $navmap->firstResource();
+ # These are some data tables, which make it easy to change some of
+ # of the specific visualization parameters if desired.
+
# Defines a status->color mapping, null string means don't color
my %colormap =
( $res->NETWORK_FAILURE => '',
$res->CORRECT => '',
- $res->EXCUSED => '#BBBBFF',
+ $res->EXCUSED => '#3333FF',
$res->PAST_DUE_ANSWER_LATER => '',
$res->PAST_DUE_NO_ANSWER => '',
- $res->ANSWER_OPEN => '#CCFFCC',
+ $res->ANSWER_OPEN => '#006600',
$res->OPEN_LATER => '',
$res->TRIES_LEFT => '',
$res->INCORRECT => '',
@@ -152,6 +212,8 @@ sub handler {
# is not yet done and due in less then 24 hours
my $hurryUpColor = "#FF0000";
+ # Keep these mappings in sync with lonquickgrades, which uses the colors
+ # instead of the icons.
my %statusIconMap =
( $res->NETWORK_FAILURE => '',
$res->NOTHING_SET => '',
@@ -187,18 +249,6 @@ sub handler {
# 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
# four cols: resource + indent, chat+feedback, icon, text string
@@ -210,35 +260,35 @@ sub handler {
# 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 $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
my $found = 0;
my $depth = 1;
- my $currentUrlIndex = 0; # keeps track of when the current resource is found,
+ my $currentJumpIndex = 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
+ my $currentJumpDelta = 2; # change this to change how many resources are displayed
+ # before the current resource when using #current
$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 $foundJump = ($jumpType == $NOTHING); # look for jump point if we have one
+ my $looped = 0;
- 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.
+ $r->print($hereType);
- $currentUrlIndex = $counter;
+ # We only need to do this if we need to open the maps to show the
+ # current position. This will change the counter so we can't count
+ # for the jump marker with this loop.
+ while ($depth > 0 && !$ENV{'form.alreadyHere'}) {
+ if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $mapIterator->END_MAP()) { $depth--; }
+ $r->print($curRes . ' ');
+ if (ref($curRes) && !$ENV{'form.alreadyHere'} &&
+ ($hereType == $SYMB && $curRes->symb() eq $here) ||
+ (ref($curRes) && $hereType == $URL && $curRes->src() eq $here)) {
+ my $mapStack = $mapIterator->getStack();
+ # Ensure the parent maps are open
for my $map (@{$mapStack}) {
if ($condition) {
undef $filterHash{$map->map_pc()};
@@ -248,16 +298,33 @@ sub handler {
}
$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;
- }
+ $looped = 1;
+
+ $curRes = $mapIterator->next();
+ }
+
+ $mapIterator = $navmap->getIterator(undef, undef, \%filterHash, 0);
+ $depth = 1;
+ $mapIterator->next();
+ $curRes = $mapIterator->next();
+
+ while ($depth > 0 && !$foundJump) {
+ if ($curRes == $mapIterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $mapIterator->END_MAP()) { $depth--; }
+ if (ref($curRes)) { $counter++; }
+
+ if (ref($curRes) &&
+ (($jumpType == $SYMB && $curRes->symb() eq $jump) ||
+ ($jumpType == $URL && $curRes->src() eq $jump))) {
+ # If this is the correct resource, be sure to
+ # show it by making sure the containing maps
+ # are open.
+
+ # This is why we have to use the main iterator instead of the
+ # potentially faster DFS: The count has to be the same, so
+ # the order has to be the same, which DFS won't give us.
+ $currentJumpIndex = $counter;
+ $foundJump = 1;
}
$curRes = $mapIterator->next();
@@ -271,6 +338,7 @@ sub handler {
my $now = time();
my $in24Hours = $now + 24 * 60 * 60;
my $displayedHereMarker = 0;
+ my $displayedJumpMarker = 0;
# We know the first thing is a BEGIN_MAP (see "$self->{STARTED}"
# code in iterator->next), so ignore the first one
@@ -278,7 +346,6 @@ sub handler {
$condition);
$mapIterator->next();
$curRes = $mapIterator->next();
- my $deltadepth = 0;
$depth = 1;
my @backgroundColors = ("#FFFFFF", "#F6F6F6");
@@ -287,19 +354,6 @@ sub handler {
$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;
- }
-
if ($curRes == $mapIterator->BEGIN_MAP() ||
$curRes == $mapIterator->BEGIN_BRANCH()) {
$indentLevel++;
@@ -316,16 +370,24 @@ sub handler {
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?
+ # Is this resource being ignored because it is in a random-out
+ # map and it was not selected?
if (ref($curRes) && !advancedUser() && $curRes->randomout()) {
$curRes = $mapIterator->next();
- next; # and totally ignore this resource
+ next; # if yes, then just ignore this resource
}
- if (ref($curRes) && $curRes->src()) {
+ if (ref($curRes)) {
+
+ my $deltalevel = $isNewBranch? 1 : 0; # reserves space for branch icon
+
+ if ($indentLevel - $deltalevel < 0) {
+ # If this would be at a negative depth (top-level maps in
+ # new-style courses, we want to suppress their title display)
+ # then ignore it.
+ $curRes = $mapIterator->next();
+ next;
+ }
# Step one: Decide which parts to show
my @parts = @{$curRes->parts()};
@@ -333,14 +395,15 @@ sub handler {
my $condensed = 0;
if ($curRes->is_problem()) {
- # Is it multipart?
+ # 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
+ # no parts are open, display as one part
+ @parts = ("0");
$condensed = 1;
} else {
# Otherwise, only display part 0 if we want to
@@ -348,9 +411,6 @@ sub handler {
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]);
@@ -391,11 +451,11 @@ sub handler {
} else {
$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, and it simplifies the looping
}
# Is it a multipart problem with a single part, now in
- # @parts with "0" filtered out? If so, forget it's a multi-part
+ # @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;
@@ -403,41 +463,38 @@ sub handler {
# 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.
+ # status, but if it's multipart, we're lost, since we can't
+ # retreive the metadata to count the parts
if ($curRes->{RESOURCE_ERROR}) {
@parts = ("0");
}
- # Step two: Show the parts
+ # Step Two: Print the actual data.
+
+ # For each part we intend to display...
foreach my $part (@parts) {
- my $deltalevel = 0; # for inserting the branch icon
my $nonLinkedText = ""; # unlinked stuff after title
- # For each thing we're displaying...
-
my $stack = $mapIterator->getStack();
my $src = getLinkForResource($stack);
+ # Pass the correct symb on the querystring, so the
+ # remote will figure out where we are if we click a link
my $srcHasQuestion = $src =~ /\?/;
my $link = $src.
($srcHasQuestion?'&':'?') .
'symb='.&Apache::lonnet::escape($curRes->symb()).
'"';
- my $title = $curRes->title();
- if (!$title) {
- $title = $curRes->src();
- $title = substr ($title, rindex($title, "/") + 1);
- }
+
+ my $title = $curRes->compTitle();
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 = "";
$isNewBranch = 0;
- $deltalevel = 1;
}
# links to open and close the folders
@@ -465,7 +522,10 @@ sub handler {
$linkopen .= ($nowOpen xor $condition) ?
addToFilter(\%filterHash, $mapId) :
removeFromFilter(\%filterHash, $mapId);
- $linkopen .= "&condition=$condition&$queryAdd\">";
+ $linkopen .= "&condition=$condition&$queryAdd" .
+ "&hereType=$hereType&here=" .
+ Apache::lonnet::escape($here) . "&jumpType=$SYMB&" .
+ "jump=" . Apache::lonnet::escape($curRes->symb()) ."\">";
$linkclose = "";
}
@@ -473,27 +533,13 @@ sub handler {
my $colorizer = "";
my $color;
if ($curRes->is_problem()) {
- my $status = $curRes->status($part);
- $color = $colormap{$status};
+ $color = $colormap{$curRes->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()) {
+ if (dueInLessThen24Hours($curRes, $part) ||
+ lastTry($curRes, $part)) {
$color = $hurryUpColor;
}
+
if ($color ne "") {
$colorizer = "bgcolor=\"$color\"";
}
@@ -507,18 +553,16 @@ sub handler {
my $backgroundColor = $backgroundColors[$rowNum % scalar(@backgroundColors)];
# FIRST COL: The resource indentation, branch icon, name, and anchor
- $r->print(" \n");
+ $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 the anchor if necessary
+ if ($counter == $currentJumpIndex - $currentJumpDelta ) {
+ $r->print('');
+ $displayedJumpMarker = 1;
}
# print indentation
- for (my $i = 0; $i < $indentLevel - $deltalevel + $deltadepth; $i++) {
+ for (my $i = 0; $i < $indentLevel - $deltalevel; $i++) {
$r->print($indentString);
}
@@ -528,8 +572,10 @@ sub handler {
my $curMarkerEnd = "";
# Is this the current resource?
- if ($curRes->src() eq $currenturl && !$displayedHereMarker) {
- $curMarkerBegin = '> ';
+ if (!$displayedHereMarker &&
+ (($hereType == $SYMB && $curRes->symb eq $here) ||
+ ($hereType == $URL && $curRes->src eq $here))) {
+ $curMarkerBegin = '> ';
$curMarkerEnd = ' <';
$displayedHereMarker = 1;
}
@@ -543,18 +589,17 @@ sub handler {
}
$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 = "";
+ #if ($curRes->{RESOURCE_ERROR}) {
+ # $r->print(&Apache::loncommon::help_open_topic ("Navmap_Host_Down",
+ # 'Host down'));
+ # }
+
+ $r->print(" | \n");
# SECOND COL: Is there text, feedback, errors??
+ my $discussionHTML = ""; my $feedbackHTML = "";
+
if ($curRes->hasDiscussion()) {
$discussionHTML = $linkopen .
'' .
@@ -577,7 +622,8 @@ sub handler {
# Is this the first displayed part of a multi-part problem
# that has not been condensed, so we should suppress these two
- # columns?
+ # columns so we don't display useless status info about part
+ # "0"?
my $firstDisplayed = !$condensed && $multipart && $part eq "0";
# THIRD COL: Problem status icon
@@ -595,7 +641,6 @@ sub handler {
}
# FOURTH COL: Text description
- #$r->print("\n");
$r->print(" | \n");
if ($curRes->kind() eq "res" &&
@@ -610,12 +655,22 @@ sub handler {
}
$r->print(" |
\n");
+
+ if (!($counter % 20)) { $r->rflush(); }
+ if ($counter == 2) { $r->rflush(); }
}
}
$curRes = $mapIterator->next();
}
- $r->print("