--- loncom/interface/lonnavmaps.pm 2003/04/25 18:54:36 1.183
+++ loncom/interface/lonnavmaps.pm 2003/05/14 20:16:56 1.190
@@ -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.190 2003/05/14 20:16:56 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
@@ -687,6 +737,14 @@ returns a true or false value. If true,
false, it is simply skipped in the display. By default, all resources
are shown.
+=item * B:
+
+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.
+
=item * B:
If true, will not display Navigate Content resources. Default to
@@ -1173,6 +1231,43 @@ sub render {
$args->{'indentString'} = setDefault($args->{'indentString'}, "");
$args->{'displayedHereMarker'} = 0;
+ # If we're suppressing empty sequences, look for them here. Use DFS for speed,
+ # since structure actually doesn't matter, except what map has what resources.
+ if ($args->{'suppressEmptySequences'}) {
+ my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
+ $it->{FIRST_RESOURCE},
+ $it->{FINISH_RESOURCE},
+ {}, undef, 1);
+ $depth = 0;
+ $dfsit->next();
+ my $curRes = $dfsit->next();
+ while ($depth > -1) {
+ if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $dfsit->END_MAP()) { $depth--; }
+
+ if (ref($curRes)) {
+ # Parallel pre-processing: Do sequences have non-filtered-out children?
+ if ($curRes->is_sequence()) {
+ $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
+ # Sequences themselves do not count as visible children,
+ # unless those sequences also have visible children.
+ # This means if a sequence appears, there's a "promise"
+ # that there's something under it if you open it, somewhere.
+ } else {
+ # Not a sequence: if it's filtered, ignore it, otherwise
+ # rise up the stack and mark the sequences as having children
+ if (&$filterFunc($curRes)) {
+ for my $sequence (@{$dfsit->getStack()}) {
+ $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
+ }
+ }
+ }
+ }
+ } continue {
+ $curRes = $dfsit->next();
+ }
+ }
+
my $displayedJumpMarker = 0;
# Set up iteration.
$depth = 1;
@@ -1216,6 +1311,12 @@ sub render {
next;
}
+ # If this is an empty sequence and we're filtering them, continue on
+ if ($curRes->is_sequence() && $args->{'suppressEmptySequences'} &&
+ !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {
+ next;
+ }
+
# If we're suppressing navmaps and this is a navmap, continue on
if ($suppressNavmap && $curRes->src() =~ /^\/adm\/navmaps/) {
next;
@@ -2144,7 +2245,8 @@ sub new {
$curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
- }
+ }
+ } continue {
$curRes = $iterator->next();
}
}
@@ -2521,6 +2623,31 @@ sub next {
return $self->{HERE};
}
+# Identical to the full iterator methods of the same name. Hate to copy/paste
+# but I also hate to "inherit" either iterator from the other.
+
+sub getStack {
+ my $self=shift;
+
+ my @stack;
+
+ $self->populateStack(\@stack);
+
+ return \@stack;
+}
+
+# Private method: Calls the iterators recursively to populate the stack.
+sub populateStack {
+ my $self=shift;
+ my $stack = shift;
+
+ push @$stack, $self->{HERE} if ($self->{HERE});
+
+ if ($self->{RECURSIVE_ITERATOR_FLAG}) {
+ $self->{RECURSIVE_ITERATOR}->populateStack($stack);
+ }
+}
+
1;
package Apache::lonnavmaps::resource;
@@ -2544,6 +2671,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 +2886,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 +3063,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 +3180,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 +3218,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 +3252,7 @@ sub extractParts {
if (!$metadata) {
$self->{RESOURCE_ERROR} = 1;
$self->{PARTS} = [];
+ $self->{PART_TYPE} = {};
return;
}
foreach (split(/\,/,$metadata)) {
@@ -3107,6 +3274,46 @@ sub extractParts {
my @sortedParts = sort keys %parts;
$self->{PARTS} = \@sortedParts;
+
+ my %responseIdHash;
+ my %responseTypeHash;
+
+
+ # Init the responseIdHash
+ foreach (@{$self->{PARTS}}) {
+ $responseIdHash{$_} = [];
+ }
+
+ # 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);
+ push @{$responseIdHash{$partIdSoFar}}, $responseId;
+ $responseTypeHash{$partIdSoFar} = $responseType;
+ last;
+ }
+ }
+ }
+ }
+
+ $self->{RESPONSE_IDS} = \%responseIdHash;
+ $self->{RESPONSE_TYPES} = \%responseTypeHash;
}
return;
@@ -3310,7 +3517,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 +3535,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 +3597,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 +3621,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 +3667,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