--- loncom/interface/lonnavmaps.pm 2003/05/14 20:16:56 1.190 +++ loncom/interface/lonnavmaps.pm 2003/05/16 17:54:21 1.192 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.190 2003/05/14 20:16:56 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.192 2003/05/16 17:54:21 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -47,6 +47,7 @@ use Apache::Constants qw(:common :http); use Apache::loncommon(); use Apache::lonmenu(); use POSIX qw (floor strftime); +use Data::Dumper; # for debugging, not always used # symbolic constants sub SYMB { return 1; } @@ -200,8 +201,10 @@ sub real_handler { } } + my $jumpToFirstHomework = 0; # Check to see if the student is jumping to next open, do-able problem if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { + $jumpToFirstHomework = 1; # Find the next homework problem that they can do. my $iterator = $navmap->getIterator(undef, undef, undef, 1); my $depth = 1; @@ -216,9 +219,7 @@ sub real_handler { if (ref($curRes) && $curRes->is_problem()) { my $status = $curRes->status(); - if (($status == $curRes->OPEN || - $status == $curRes->TRIES_LEFT()) && - $curRes->getCompletionStatus() != $curRes->ATTEMPTED()) { + if ($curRes->completable()) { $problemRes = $curRes; $foundDoableProblem = 1; @@ -246,15 +247,45 @@ sub real_handler { "Go To My First Homework Problem
"); } - # renderer call - my $render = render({ 'cols' => [0,1,2,3], - 'url' => '/adm/navmaps', - 'navmap' => $navmap, - 'suppressNavmap' => 1, - 'r' => $r}); + my $suppressEmptySequences = 0; + my $filterFunc = undef; + # Display only due homework. + my $showOnlyHomework = 0; + if ($ENV{QUERY_STRING} eq 'showOnlyHomework') { + $showOnlyHomework = 1; + $suppressEmptySequences = 1; + $filterFunc = sub { my $res = shift; + return $res->completable() || $res->is_sequence(); + }; + $r->print("

Uncompleted Homework

"); + $ENV{'form.filter'} = ''; + $ENV{'form.condition'} = 1; + } else { + $r->print("" . + "Show Only Uncompleted Homework
"); + } + # renderer call + my $renderArgs = { 'cols' => [0,1,2,3], + 'url' => '/adm/navmaps', + 'navmap' => $navmap, + 'suppressNavmap' => 1, + 'suppressEmptySequences' => $suppressEmptySequences, + 'filterFunc' => $filterFunc, + 'r' => $r}; + my $render = render($renderArgs); $navmap->untieHashes(); + # If no resources were printed, print a reassuring message so the + # user knows there was no error. + if ($renderArgs->{'counter'} == 0) { + if ($showOnlyHomework) { + $r->print("

All homework is currently completed.

"); + } else { # both jumpToFirstHomework and normal use the same: course must be empty + $r->print("

This course is empty.

"); + } + } + $r->print(""); $r->rflush(); @@ -808,7 +839,7 @@ sub render_resource { my $icon = ""; if ($resource->is_problem()) { - if ($part eq "" || $params->{'condensed'}) { + if ($part eq '0' || $params->{'condensed'}) { $icon = ''; } else { $icon = $params->{'indentString'}; @@ -878,7 +909,7 @@ sub render_resource { $params->{'displayedHereMarker'} = 1; } - if ($resource->is_problem() && $part ne "" && + if ($resource->is_problem() && $part ne '0' && !$params->{'condensed'}) { $partLabel = " (Part $part)"; $title = ""; @@ -970,7 +1001,7 @@ sub render_long_status { $params->{'multipart'} && $part eq "0"; my $color; - if ($resource->is_problem()) { + if ($resource->is_problem() && ($resource->countParts() <= 1 || $part ne '') ) { $color = $colormap{$resource->status}; if (dueInLessThen24Hours($resource, $part) || @@ -1041,6 +1072,19 @@ sub render { } } + # Filter: Remember filter function and add our own filter: Refuse + # to show hidden resources unless the user can see them. + my $userCanSeeHidden = advancedUser(); + my $filterFunc = setDefault($args->{'filterFunc'}, + sub {return 1;}); + if (!$userCanSeeHidden) { + # Without renaming the filterfunc, the server seems to go into + # an infinite loop + my $oldFilterFunc = $filterFunc; + $filterFunc = sub { my $res = shift; return !$res->randomout() && + &$oldFilterFunc($res);}; + } + my $condition = 0; if ($ENV{'form.condition'}) { $condition = 1; @@ -1137,6 +1181,8 @@ sub render { } # (re-)Locate the jump point, if any + # Note this does not take filtering or hidden into account... need + # to be fixed? my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0); my $depth = 1; $mapIterator->next(); @@ -1169,8 +1215,6 @@ sub render { my $printKey = $args->{'printKey'}; my $printCloseAll = $args->{'printCloseAll'}; if (!defined($printCloseAll)) { $printCloseAll = 1; } - my $filterFunc = setDefault($args->{'filterFunc'}, - sub {return 1;}); # Print key? if ($printKey) { @@ -1303,8 +1347,6 @@ sub render { next; } - $args->{'counter'}++; - # If this has been filtered out, continue on if (!(&$filterFunc($curRes))) { $args->{'isNewBranch'} = 0; # Don't falsely remember this @@ -1322,6 +1364,8 @@ sub render { next; } + $args->{'counter'}++; + # Does it have multiple parts? $args->{'multipart'} = 0; $args->{'condensed'} = 0; @@ -1330,7 +1374,7 @@ sub render { # Decide what parts to show. if ($curRes->is_problem() && $showParts) { @parts = @{$curRes->parts()}; - $args->{'multipart'} = scalar(@parts) > 1; + $args->{'multipart'} = $curRes->multipart(); if ($condenseParts) { # do the condensation if (!$curRes->opendate("0")) { @@ -1339,13 +1383,13 @@ sub render { } if (!$args->{'condensed'}) { # Decide whether to condense based on similarity - my $status = $curRes->status($parts[1]); - my $due = $curRes->duedate($parts[1]); - my $open = $curRes->opendate($parts[1]); + my $status = $curRes->status($parts[0]); + my $due = $curRes->duedate($parts[0]); + my $open = $curRes->opendate($parts[0]); my $statusAllSame = 1; my $dueAllSame = 1; my $openAllSame = 1; - for (my $i = 2; $i < scalar(@parts); $i++) { + for (my $i = 1; $i < scalar(@parts); $i++) { if ($curRes->status($parts[$i]) != $status){ $statusAllSame = 0; } @@ -1366,7 +1410,7 @@ sub render { if (($statusAllSame && defined($condenseStatuses{$status})) || ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)|| ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){ - @parts = (); + @parts = ($parts[0]); $args->{'condensed'} = 1; } @@ -1377,14 +1421,14 @@ sub render { # If the multipart problem was condensed, "forget" it was multipart if (scalar(@parts) == 1) { $args->{'multipart'} = 0; + } else { + # Add part 0 so we display it correctly. + unshift @parts, '0'; } # Now, we've decided what parts to show. Loop through them and # show them. - foreach my $part ('', @parts) { - if ($part eq '0') { - next; - } + foreach my $part (@parts) { $rownum ++; my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)]; @@ -3177,9 +3221,13 @@ each part. Filtering part 0 if you want Returns the number of parts of the problem a student can answer. Thus, for single part problems, returns 1. For multipart, it returns the number of parts in the problem, not including psuedo-part 0. Thus, -B may return an array with fewer parts in it then countParts +B may return an array with more parts in it then countParts might lead you to believe. +=item * B(): + +Returns true if the problem is multipart, false otherwise. + =item * B($part): Returns the response type of the part, without the word "response" on the @@ -3196,7 +3244,7 @@ Retreives the response ID for the given sub parts { my $self = shift; - if ($self->ext) { return ['0']; } + if ($self->ext) { return []; } $self->extractParts(); return $self->{PARTS}; @@ -3206,16 +3254,23 @@ sub countParts { my $self = shift; my $parts = $self->parts(); - my $delta = 0; - for my $part (@$parts) { - if ($part eq '0') { $delta--; } - } + + # If I left this here, then it's not necessary. + #my $delta = 0; + #for my $part (@$parts) { + # if ($part eq '0') { $delta--; } + #} if ($self->{RESOURCE_ERROR}) { return 0; } - return scalar(@{$parts}) + $delta; + return scalar(@{$parts}); # + $delta; +} + +sub multipart { + my $self = shift; + return $self->countParts() > 1; } sub responseType { @@ -3532,7 +3587,9 @@ B Along with directly returning the date or completion status, the resource object includes a convenience function B() that will combine the two status tidbits into one composite status that can -represent the status of the resource as a whole. The precise logic is +represent the status of the resource as a whole. This method represents +the concept of the thing we want to display to the user on the nav maps +screen, which is a combination of completion and open status. The precise logic is documented in the comments of the status method. The following results may be returned, all available as methods on the resource object ($res->NETWORK_FAILURE): In addition to the return values that match @@ -3675,6 +3732,48 @@ sub status { } =pod + +B + +The completable method represents the concept of I. If the student can do the problem, which means +that it is open, there are tries left, and if the problem is manually graded +or the grade is suppressed via problemstatus, the student has not tried it +yet, then the method returns 1. Otherwise, it returns 0, to indicate that +either the student has tried it and there is no feedback, or that for +some reason it is no longer completable (not open yet, successfully completed, +out of tries, etc.). As an example, this is used as the filter for the +"Uncompleted Homework" option for the nav maps. + +If this does not quite meet your needs, do not fiddle with it (unless you are +fixing it to better match the student's conception of "completable" because +it's broken somehow)... make a new method. + +=cut + +sub completable { + my $self = shift; + if (!$self->is_problem()) { return 0; } + my $partCount = $self->countParts(); + + foreach my $part (@{$self->parts()}) { + if ($part eq '0' && $partCount != 1) { next; } + my $status = $self->status($part); + # "If any of the parts are open, or have tries left (implies open), + # and it is not "attempted" (manually graded problem), it is + # not "complete" + if (!(($status == OPEN() || $status == TRIES_LEFT()) + && $self->getCompletionStatus($part) != ATTEMPTED() + && $status != ANSWER_SUBMITTED())) { + return 0; + } + } + + # If all the parts were complete, so was this problem. + return 1; +} + +=pod =head2 Resource/Nav Map Navigation