--- loncom/interface/lonnavmaps.pm 2003/04/25 18:54:36 1.183 +++ loncom/interface/lonnavmaps.pm 2003/05/14 18:33:28 1.188 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.183 2003/04/25 18:54:36 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.188 2003/05/14 18:33:28 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -71,7 +71,8 @@ my %statusIconMap = $resObj->TRIES_LEFT => 'navmap.open.gif', $resObj->INCORRECT => 'navmap.wrong.gif', $resObj->OPEN => 'navmap.open.gif', - $resObj->ATTEMPTED => 'navmap.open.gif' ); + $resObj->ATTEMPTED => 'navmap.open.gif', + $resObj->ANSWER_SUBMITTED => '' ); my %iconAltTags = ( 'navmap.correct.gif' => 'Correct', @@ -199,6 +200,52 @@ sub real_handler { } } + # Check to see if the student is jumping to next open, do-able problem + if ($ENV{QUERY_STRING} eq 'jumpToFirstHomework') { + # Find the next homework problem that they can do. + my $iterator = $navmap->getIterator(undef, undef, undef, 1); + my $depth = 1; + $iterator->next(); + my $curRes = $iterator->next(); + my $foundDoableProblem = 0; + my $problemRes; + + while ($depth > 0 && !$foundDoableProblem) { + if ($curRes == $iterator->BEGIN_MAP()) { $depth++; } + if ($curRes == $iterator->END_MAP()) { $depth--; } + + if (ref($curRes) && $curRes->is_problem()) { + my $status = $curRes->status(); + if (($status == $curRes->OPEN || + $status == $curRes->TRIES_LEFT()) && + $curRes->getCompletionStatus() != $curRes->ATTEMPTED()) { + $problemRes = $curRes; + $foundDoableProblem = 1; + + # Pop open all previous maps + my $stack = $iterator->getStack(); + pop @$stack; # last resource in the stack is the problem + # itself, which we don't need in the map stack + my @mapPcs = map {$_->map_pc()} @$stack; + $ENV{'form.filter'} = join(',', @mapPcs); + + # Mark as both "here" and "jump" + $ENV{'form.postsymb'} = $curRes->symb(); + } + } + } continue { + $curRes = $iterator->next(); + } + + # If we found no problems, print a note to that effect. + if (!$foundDoableProblem) { + $r->print("All homework assignments have been completed.

"); + } + } else { + $r->print("" . + "Go To My First Homework Problem
"); + } + # renderer call my $render = render({ 'cols' => [0,1,2,3], 'url' => '/adm/navmaps', @@ -319,6 +366,9 @@ sub getDescription { return "No due date $triesString"; } } + if ($status == $res->ANSWER_SUBMITTED) { + return 'Answer submitted'; + } } # Convenience function, so others can use it: Is the problem due in less then @@ -2544,6 +2594,12 @@ You will probably never need to instanti Apache::lonnavmaps::navmap, and use the "start" method to obtain the starting resource. +Resource objects respect the parameter_hiddenparts, which suppresses +various parts according to the wishes of the map author. As of this +writing, there is no way to override this parameter, and suppressed +parts will never be returned, nor will their response types or ids be +stored. + =head2 Public Members resource objects have a hash called DATA ($resourceRef->{DATA}) that @@ -2753,7 +2809,10 @@ sub is_sequence { sub parmval { my $self = shift; my $what = shift; - my $part = shift || "0"; + my $part = shift; + if (!defined($part)) { + $part = '0'; + } return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb()); } @@ -2927,6 +2986,10 @@ sub opendate { } return $self->parmval("opendate"); } +sub problemstatus { + (my $self, my $part) = @_; + return $self->parmval("problemstatus", $part); +} sub sig { (my $self, my $part) = @_; return $self->parmval("sig", $part); @@ -3040,6 +3103,15 @@ number of parts in the problem, not incl B may return an array with fewer parts in it then countParts might lead you to believe. +=item * B($part): + +Returns the response type of the part, without the word "response" on the +end. Example return values: 'string', 'essay', 'numeric', etc. + +=item * B($part): + +Retreives the response ID for the given part, which may be an empty string. + =back =cut @@ -3069,7 +3141,24 @@ sub countParts { return scalar(@{$parts}) + $delta; } -# Private function: Extracts the parts information and saves it +sub responseType { + my $self = shift; + my $part = shift; + + $self->extractParts(); + return $self->{RESPONSE_TYPE}->{$part}; +} + +sub responseId { + my $self = shift; + my $part = shift; + + $self->extractParts(); + return $self->{RESPONSE_IDS}->{$part}; +} + +# Private function: Extracts the parts information, both part names and +# part types, and saves it. sub extractParts { my $self = shift; @@ -3086,6 +3175,7 @@ sub extractParts { if (!$metadata) { $self->{RESOURCE_ERROR} = 1; $self->{PARTS} = []; + $self->{PART_TYPE} = {}; return; } foreach (split(/\,/,$metadata)) { @@ -3107,6 +3197,43 @@ sub extractParts { my @sortedParts = sort keys %parts; $self->{PARTS} = \@sortedParts; + + my %responseIdHash; + my %responseTypeHash; + + # Now, the unfortunate thing about this is that parts, part name, and + # response if are delimited by underscores, but both the part + # name and response id can themselves have underscores in them. + # So we have to use our knowlege of part names to figure out + # where the part names begin and end, and even then, it is possible + # to construct ambiguous situations. + foreach (split /,/, $metadata) { + if ($_ =~ /^([a-zA-Z]+)response_(.*)/) { + my $responseType = $1; + my $partStuff = $2; + my $partIdSoFar = ''; + my @partChunks = split /_/, $partStuff; + my $i = 0; + + for ($i = 0; $i < scalar(@partChunks); $i++) { + if ($partIdSoFar) { $partIdSoFar .= '_'; } + $partIdSoFar .= $partChunks[$i]; + if ($parts{$partIdSoFar}) { + my @otherChunks = @partChunks[$i+1..$#partChunks]; + my $responseId = join('_', @otherChunks); + if (!defined($responseIdHash{$partIdSoFar})) { + $responseIdHash{$partIdSoFar} = []; + } + push @{$responseIdHash{$partIdSoFar}}, $responseId; + $responseTypeHash{$partIdSoFar} = $responseType; + last; + } + } + } + } + + $self->{RESPONSE_IDS} = \%responseIdHash; + $self->{RESPONSE_TYPES} = \%responseTypeHash; } return; @@ -3310,7 +3437,7 @@ sub queryRestoreHash { my $self = shift; my $hashentry = shift; my $part = shift; - $part = "0" if (!defined($part)); + $part = "0" if (!defined($part) || $part eq ''); return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE}); $self->getReturnHash(); @@ -3328,7 +3455,10 @@ combine the two status tidbits into one represent the status of the resource as a whole. 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): +($res->NETWORK_FAILURE): In addition to the return values that match +the date or completion status, this function can return "ANSWER_SUBMITTED" +if that problemstatus parameter value is set to No, suppressing the +incorrect/correct feedback. =over 4 @@ -3387,11 +3517,16 @@ The item is open and not yet tried. The problem has been attempted. +=item * B: + +An answer has been submitted, but the student should not see it. + =back =cut -sub TRIES_LEFT { return 10; } +sub TRIES_LEFT { return 20; } +sub ANSWER_SUBMITTED { return 21; } sub status { my $self = shift; @@ -3406,10 +3541,12 @@ sub status { if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } + my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no'; + # There are a few whole rows we can dispose of: if ($completionStatus == CORRECT || $completionStatus == CORRECT_BY_OVERRIDE ) { - return CORRECT; + return $suppressFeedback? ANSWER_SUBMITTED : CORRECT; } if ($completionStatus == ATTEMPTED) { @@ -3450,7 +3587,7 @@ sub status { if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) { return TRIES_LEFT; } - return INCORRECT; # otherwise, return orange; student can't fix this + return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this } # Otherwise, it's untried and open