$stateTitle
@@ -653,6 +684,7 @@ sub addElement {
push @{$self->{ELEMENTS}}, $element;
}
+use Data::Dumper;
sub render {
my $self = shift;
my @results = ();
@@ -660,6 +692,7 @@ sub render {
for my $element (@{$self->{ELEMENTS}}) {
push @results, $element->render();
}
+
return join("\n", @results);
}
@@ -672,29 +705,41 @@ package Apache::lonhelper::element;
=head2 Element Base Class
-The Apache::lonhelper::element base class provides support methods for
-the elements to use, such as a multiple value processer.
+The Apache::lonhelper::element base class provides support for elements
+and defines some generally useful tags for use in elements.
-B:
+B
-=over 4
-
-=item * process_multiple_choices(formName, varName): Process the form
-element named "formName" and place the selected items into the helper
-variable named varName. This is for things like checkboxes or
-multiple-selection listboxes where the user can select more then
-one entry. The selected entries are delimited by triple pipes in
-the helper variables, like this:
+Each element can contain a "finalcode" tag that, when the special FINAL
+helper state is used, will be executed, surrounded by "sub { my $helper = shift;"
+and "}". It is expected to return a string describing what it did, which
+may be an empty string. See course initialization helper for an example. This is
+generally intended for helpers like the course initialization helper, which consist
+of several panels, each of which is performing some sort of bite-sized functionality.
+
+B
+
+Each element that accepts user input can contain a "defaultvalue" tag that,
+when surrounded by "sub { my $helper = shift; my $state = shift; " and "}",
+will form a subroutine that when called will provide a default value for
+the element. How this value is interpreted by the element is specific to
+the element itself, and possibly the settings the element has (such as
+multichoice vs. single choice for tags).
+
+This is also intended for things like the course initialization wizard, where the
+user is setting various parameters. By correctly grabbing current settings
+and including them into the helper, it allows the user to come back to the
+helper later and re-execute it, without needing to worry about overwriting
+some setting accidentally.
- CHOICE_1|||CHOICE_2|||CHOICE_3
-
-=back
+Again, see the course initialization helper for examples.
=cut
BEGIN {
&Apache::lonhelper::register('Apache::lonhelper::element',
- ('nextstate'));
+ ('nextstate', 'finalcode',
+ 'defaultvalue'));
}
# Because we use the param hash, this is often a sufficent
@@ -729,6 +774,36 @@ sub start_nextstate {
sub end_nextstate { return ''; }
+sub start_finalcode {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+
+ $paramHash->{FINAL_CODE} = &Apache::lonxml::get_all_text('/finalcode',
+ $parser);
+ return '';
+}
+
+sub end_finalcode { return ''; }
+
+sub start_defaultvalue {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+
+ $paramHash->{DEFAULT_VALUE} = &Apache::lonxml::get_all_text('/defaultvalue',
+ $parser);
+ $paramHash->{DEFAULT_VALUE} = 'sub { my $helper = shift; my $state = shift;' .
+ $paramHash->{DEFAULT_VALUE} . '}';
+ return '';
+}
+
+sub end_defaultvalue { return ''; }
+
sub preprocess {
return 1;
}
@@ -745,28 +820,6 @@ sub overrideForm {
return 0;
}
-sub process_multiple_choices {
- my $self = shift;
- my $formname = shift;
- my $var = shift;
-
- # Must extract values from data directly, as there
- # may be more then one.
- my @values;
- for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
- my ($name, $value) = split(/=/, $formparam);
- if ($name ne $formname) {
- next;
- }
- $value =~ tr/+/ /;
- $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
- push @values, $value;
- }
- $helper->{VARS}->{$var} = join('|||', @values);
-
- return;
-}
-
1;
package Apache::lonhelper::message;
@@ -875,6 +928,10 @@ the result is stored in.
takes an attribute "multichoice" which, if set to a true
value, will allow the user to select multiple choices.
+ takes an attribute "allowempty" which, if set to a true
+value, will allow the user to select none of the choices without raising
+an error message.
+
B
can have the following subtags:
@@ -924,6 +981,18 @@ You can mix and match methods of creatin
"push" onto the choice list, rather then wiping it out. (You can even
remove choices programmatically, but that would probably be bad form.)
+B
+
+Choices supports default values both in multichoice and single choice mode.
+In single choice mode, have the defaultvalue tag's function return the
+computer value of the box you want checked. If the function returns a value
+that does not correspond to any of the choices, the default behavior of selecting
+the first choice will be preserved.
+
+For multichoice, return a string with the computer values you want checked,
+delimited by triple pipes. Note this matches how the result of the
+tag is stored in the {VARS} hash.
+
=cut
no strict;
@@ -949,9 +1018,10 @@ sub start_choices {
}
# Need to initialize the choices list, so everything can assume it exists
- $paramHash->{'variable'} = $token->[2]{'variable'};
+ $paramHash->{'variable'} = $token->[2]{'variable'} if (!defined($paramHash->{'variable'}));
$helper->declareVar($paramHash->{'variable'});
$paramHash->{'multichoice'} = $token->[2]{'multichoice'};
+ $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
$paramHash->{CHOICES} = [];
return '';
}
@@ -1007,6 +1077,11 @@ sub render {
}
SCRIPT
+ }
+
+ # Only print "select all" and "unselect all" if there are five or
+ # more choices; fewer then that and it looks silly.
+ if ($self->{'multichoice'} && scalar(@{$self->{CHOICES}}) > 4) {
$buttons = <
@@ -1023,18 +1098,52 @@ BUTTONS
$result .= "\n\n";
+ my %checkedChoices;
+ my $checkedChoicesFunc;
+
+ if (defined($self->{DEFAULT_VALUE})) {
+ $checkedChoicesFunc = eval ($self->{DEFAULT_VALUE});
+ die 'Error in default value code for variable ' .
+ {'variable'} . ', Perl said:' . $@ if $@;
+ } else {
+ $checkedChoicesFunc = sub { return ''; };
+ }
+
+ # Process which choices should be checked.
+ if ($self->{'multichoice'}) {
+ for my $selectedChoice (split(/\|\|\|/, (&$checkedChoicesFunc($helper, $self)))) {
+ $checkedChoices{$selectedChoice} = 1;
+ }
+ } else {
+ # single choice
+ my $selectedChoice = &$checkedChoicesFunc($helper, $self);
+
+ my $foundChoice = 0;
+
+ # check that the choice is in the list of choices.
+ for my $choice (@{$self->{CHOICES}}) {
+ if ($choice->[1] eq $selectedChoice) {
+ $checkedChoices{$choice->[1]} = 1;
+ $foundChoice = 1;
+ }
+ }
+
+ # If we couldn't find the choice, pick the first one
+ if (!$foundChoice) {
+ $checkedChoices{$self->{CHOICES}->[0]->[1]} = 1;
+ }
+ }
+
my $type = "radio";
if ($self->{'multichoice'}) { $type = 'checkbox'; }
- my $checked = 0;
foreach my $choice (@{$self->{CHOICES}}) {
$result .= "\n \n";
$result .= " {'multichoice'} && !$checked) {
+ if ($checkedChoices{$choice->[1]}) {
$result .= " checked ";
- $checked = 1;
}
my $choiceLabel = $choice->[0];
if ($choice->[4]) { # if we need to evaluate this choice
@@ -1057,15 +1166,14 @@ sub postprocess {
my $self = shift;
my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
- if (!$chosenValue) {
+ if (!defined($chosenValue) && !$self->{'allowempty'}) {
$self->{ERROR_MSG} = "You must choose one or more choices to" .
" continue.";
return 0;
}
- if ($self->{'multichoice'}) {
- $self->process_multiple_choices($self->{'variable'}.'.forminput',
- $self->{'variable'});
+ if (ref($chosenValue)) {
+ $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
}
if (defined($self->{NEXTSTATE})) {
@@ -1311,7 +1419,10 @@ variable stores the results. It also tak
which controls whether the user can select more then one resource. The
"toponly" attribute controls whether the resource display shows just the
resources in that sequence, or recurses into all sub-sequences, defaulting
-to false.
+to false. The "suppressEmptySequences" attribute reflects the
+suppressEmptySequences argument to the render routine, which will cause
+folders that have all of their contained resources filtered out to also
+be filtered out.
B
@@ -1372,6 +1483,7 @@ sub start_resource {
$paramHash->{'variable'} = $token->[2]{'variable'};
$helper->declareVar($paramHash->{'variable'});
$paramHash->{'multichoice'} = $token->[2]{'multichoice'};
+ $paramHash->{'suppressEmptySequences'} = $token->[2]{'suppressEmptySequences'};
$paramHash->{'toponly'} = $token->[2]{'toponly'};
return '';
}
@@ -1540,6 +1652,7 @@ BUTTONS
'showParts' => 0,
'filterFunc' => $filterFunc,
'resource_no_folder_link' => 1,
+ 'suppressEmptySequences' => $self->{'suppressEmptySequences'},
'iterator_map' => $mapUrl }
);
@@ -1551,11 +1664,6 @@ BUTTONS
sub postprocess {
my $self = shift;
- if ($self->{'multichoice'}) {
- $self->process_multiple_choices($self->{'variable'}.'.forminput',
- $self->{'variable'});
- }
-
if ($self->{'multichoice'} && !$helper->{VARS}->{$self->{'variable'}}) {
$self->{ERROR_MSG} = 'You must choose at least one resource to continue.';
return 0;
@@ -1713,10 +1821,6 @@ sub postprocess {
return 0;
}
- if ($self->{'multichoice'}) {
- $self->process_multiple_choices($self->{'variable'}.'.forminput',
- $self->{'variable'});
- }
if (defined($self->{NEXTSTATE})) {
$helper->changeState($self->{NEXTSTATE});
}
@@ -1835,6 +1939,10 @@ sub render {
my $filterFunc = $self->{FILTER_FUNC};
my $buttons = '';
+ my $type = 'radio';
+ if ($self->{'multichoice'}) {
+ $type = 'checkbox';
+ }
if ($self->{'multichoice'}) {
$result = <
-
-
+BUTTONS
+
+ if ($helper->{VARS}->{'construction'}) {
+ $buttons .= <
+
BUTTONS
+ }
}
# Get the list of files in this directory.
@@ -1892,10 +2005,6 @@ BUTTONS
# Keeps track if there are no choices, prints appropriate error
# if there are none.
my $choices = 0;
- my $type = 'radio';
- if ($self->{'multichoice'}) {
- $type = 'checkbox';
- }
# Print each legitimate file choice.
for my $file (@fileList) {
$file = (split(/&/, $file))[0];
@@ -1904,9 +2013,36 @@ BUTTONS
}
my $fileName = $subdir .'/'. $file;
if (&$filterFunc($file)) {
- (my $status, my $color) = @{fileState($subdir, $file)};
+ my $status;
+ my $color;
+ if ($helper->{VARS}->{'construction'}) {
+ ($status, $color) = @{fileState($subdir, $file)};
+ } else {
+ $status = '';
+ $color = '';
+ }
+
+ # Netscape 4 is stupid and there's nowhere to put the
+ # information on the input tag that the file is Published,
+ # Unpublished, etc. In *real* browsers we can just say
+ # "class='Published'" and check the className attribute of
+ # the input tag, but Netscape 4 is too stupid to understand
+ # that attribute, and un-comprehended attributes are not
+ # reflected into the object model. So instead, what I do
+ # is either have or don't have an "onclick" handler that
+ # does nothing, give Published files the onclick handler, and
+ # have the checker scripts check for that. Stupid and clumsy,
+ # and only gives us binary "yes/no" information (at least I
+ # couldn't figure out how to reach into the event handler's
+ # actual code to retreive a value), but it works well enough
+ # here.
+
+ my $onclick = '';
+ if ($status eq 'Published' && $helper->{VARS}->{'construction'}) {
+ $onclick = 'onclick="a=1" ';
+ }
$result .= '" .
- " {'multichoice'} && $choices == 0) {
@@ -1969,10 +2105,6 @@ sub postprocess {
return 0;
}
- if ($self->{'multichoice'}) {
- $self->process_multiple_choices($self->{'variable'}.'.forminput',
- $self->{'variable'});
- }
if (defined($self->{NEXTSTATE})) {
$helper->changeState($self->{NEXTSTATE});
}
@@ -2189,6 +2321,101 @@ sub end_eval {
1;
+package Apache::lonhelper::final;
+
+=pod
+
+=head2 Element: final
+
+ is a special element that works with helpers that use the
+tag. It goes through all the states and elements, executing the
+snippets and collecting the results. Finally, it takes the user out of the
+helper, going to a provided page.
+
+=cut
+
+no strict;
+@ISA = ("Apache::lonhelper::element");
+use strict;
+
+BEGIN {
+ &Apache::lonhelper::register('Apache::lonhelper::final',
+ ('final', 'exitpage'));
+}
+
+sub new {
+ my $ref = Apache::lonhelper::element->new();
+ bless($ref);
+}
+
+sub start_final { return ''; }
+
+sub end_final {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+
+ Apache::lonhelper::final->new();
+
+ return '';
+}
+
+sub start_exitpage {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+
+ $paramHash->{EXIT_PAGE} = &Apache::lonxml::get_all_text('/exitpage',
+ $parser);
+
+ return '';
+}
+
+sub end_exitpage { return ''; }
+
+sub render {
+ my $self = shift;
+
+ my @results;
+
+ # Collect all the results
+ for my $stateName (keys %{$helper->{STATES}}) {
+ my $state = $helper->{STATES}->{$stateName};
+
+ for my $element (@{$state->{ELEMENTS}}) {
+ if (defined($element->{FINAL_CODE})) {
+ # Compile the code.
+ my $code = 'sub { my $helper = shift; ' . $element->{FINAL_CODE} .
+ '}';
+ $code = eval($code);
+ die 'Error while executing final code for element with var ' .
+ $element->{'variable'} . ', Perl said: ' . $@ if $@;
+
+ my $result = &$code($helper);
+ if ($result) {
+ push @results, $result;
+ }
+ }
+ }
+ }
+
+ if (scalar(@results) == 0) {
+ return '';
+ }
+
+ my $result = "\n";
+ for my $re (@results) {
+ $result .= ' ' . $re . " \n";
+ }
+ return $result . ' ';
+}
+
+1;
+
package Apache::lonhelper::parmwizfinal;
# This is the final state for the parmwizard. It is not generally useful,
@@ -2236,60 +2463,72 @@ sub render {
'due_date' => "0_duedate",
'answer_date' => "0_answerdate");
- my $result = "\n";