--- loncom/interface/lonhelper.pm 2006/08/01 15:12:32 1.158
+++ loncom/interface/lonhelper.pm 2010/03/10 21:25:50 1.179
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# .helper XML handler to implement the LON-CAPA helper
#
-# $Id: lonhelper.pm,v 1.158 2006/08/01 15:12:32 albertel Exp $
+# $Id: lonhelper.pm,v 1.179 2010/03/10 21:25:50 droeschl Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -81,25 +81,28 @@ State tags are also required to have an
human name of the state, and will be displayed as the header on top of
the screen for the user.
+State tags may also optionally have an attribute "help" which should be
+the filename of a help file, this will add a blue ? to the title.
+
=head2 Example Helper Skeleton
An example of the tags so far:
-
+
-Of course this does nothing. In order for the wizard to do something, it is
-necessary to put actual elements into the wizard. Documentation for each
+Of course this does nothing. In order for the helper to do something, it is
+necessary to put actual elements into the helper. Documentation for each
of these elements follows.
=head1 Creating a Helper With Code, Not XML
-In some situations, such as the printing wizard (see lonprintout.pm),
+In some situations, such as the printing helper (see lonprintout.pm),
writing the helper in XML would be too complicated, because of scope
issues or the fact that the code actually outweighs the XML. It is
possible to create a helper via code, though it is a little odd.
@@ -185,6 +188,8 @@ use Apache::lonlocal;
use Apache::lonnet;
use Apache::longroup;
use Apache::lonselstudent;
+
+
use LONCAPA;
# Register all the tags with the helper, so the helper can
@@ -334,7 +339,8 @@ sub start_state {
}
Apache::lonhelper::state->new($token->[2]{'name'},
- $token->[2]{'title'});
+ $token->[2]{'title'},
+ $token->[2]{'help'});
return '';
}
@@ -525,7 +531,7 @@ sub process {
# Phase 1: Post processing for state of previous screen (which is actually
# the "current state" in terms of the helper variables), if it wasn't the
# beginning state.
- if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next ->")) {
+ if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next")) {
my $prevState = $self->{STATES}{$self->{STATE}};
$prevState->postprocess();
}
@@ -577,80 +583,81 @@ sub display {
# Phase 4: Display.
my $stateTitle=&mt($state->title());
+ my $stateHelp= $state->help();
my $browser_searcher_js =
'';
- $result .= &Apache::loncommon::start_page($self->{TITLE},
- $browser_searcher_js);
-
- my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
- my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
- # FIXME: This should be parameterized, not concatenated - Jeremy
+ # Breadcrumbs
+ my $brcrum = [{'href' => '',
+ 'text' => 'Helper'}];
+ # FIXME: Dynamically add context sensitive breadcrumbs
+ # depending on the caller,
+ # e.g. printing, parametrization, etc.
+ # FIXME: Add breadcrumbs to reflect current helper state
+ $result .= &Apache::loncommon::start_page($self->{TITLE},
+ $browser_searcher_js,
+ {'bread_crumbs' => $brcrum,});
- if (!$state->overrideForm()) { $result.="
FOOTER
@@ -680,6 +687,7 @@ sub new {
$self->{NAME} = shift;
$self->{TITLE} = shift;
+ $self->{HELP} = shift;
$self->{ELEMENTS} = [];
bless($self, $class);
@@ -701,6 +709,11 @@ sub title {
return $self->{TITLE};
}
+sub help {
+ my $self = shift;
+ return $self->{HELP};
+}
+
sub preprocess {
my $self = shift;
for my $element (@{$self->{ELEMENTS}}) {
@@ -792,7 +805,7 @@ the element. How this value is interpret
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
+This is also intended for things like the course initialization helper, 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
@@ -994,6 +1007,9 @@ sub start_message {
if (defined($token->[2]{'nextstate'})) {
$paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
}
+ if (defined($token->[2]{'type'})) {
+ $paramHash->{TYPE} = $token->[2]{'type'};
+ }
return '';
}
@@ -1009,8 +1025,16 @@ sub end_message {
sub render {
my $self = shift;
-
- return &mtn($self->{MESSAGE_TEXT});
+
+ if ($self->{TYPE} =~ /^\s*warning\s*$/i) {
+ $self->{MESSAGE_TEXT} =
+ ''. $self->{MESSAGE_TEXT}.'';
+ }
+ if ($self->{TYPE} =~ /^\s*error\s*$/i) {
+ $self->{MESSAGE_TEXT} =
+ ''. $self->{MESSAGE_TEXT}.'';
+ }
+ return $self->{MESSAGE_TEXT};
}
# If a NEXTSTATE was given, switch to it
sub postprocess {
@@ -1023,6 +1047,100 @@ sub postprocess {
}
1;
+package Apache::lonhelper::helpicon;
+
+=pod
+
+=head1 Elements
+
+=head2 Element: helpiconX
+
+Helpicon elements add a help icon at the current location.
+Example:
+
+
+ General Help
+
+
+In this example will generate a help icon to the Help.hlp url with a
+description of 'General Help'. The description is not required and if
+left out (Example: only the icon will be
+added.)
+
+=head3 Localization
+
+The description text will be run through the normalize_string function
+and that will be used as a call to &mt.
+
+=cut
+
+no strict;
+@ISA = ("Apache::lonhelper::element");
+use strict;
+use Apache::lonlocal;
+
+BEGIN {
+ &Apache::lonhelper::register('Apache::lonhelper::helpicon',
+ ('helpicon'));
+}
+
+sub new {
+ my $ref = Apache::lonhelper::element->new();
+ bless($ref);
+}
+
+# CONSTRUCTION: Construct the message element from the XML
+sub start_helpicon {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+
+ $paramHash->{HELP_TEXT} = &mtn(&Apache::lonxml::get_all_text('/helpicon',
+ $parser));
+
+ $paramHash->{HELP_TEXT} =~s/^\s+//;
+ $paramHash->{HELP_TEXT} =~s/\s+$//;
+
+ if (defined($token->[2]{'file'})) {
+ $paramHash->{HELP_FILE} = $token->[2]{'file'};
+ }
+ return '';
+}
+
+sub end_helpicon {
+ my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
+
+ if ($target ne 'helper') {
+ return '';
+ }
+ Apache::lonhelper::helpicon->new();
+ return '';
+}
+
+sub render {
+ my $self = shift;
+
+ my $text;
+ if ( $self->{HELP_TEXT} ne '') {
+ $text=&mtn($self->{HELP_TEXT});
+ }
+
+ return &Apache::loncommon::help_open_topic($self->{HELP_FILE},
+ $text);
+}
+sub postprocess {
+ my $self = shift;
+ if (defined($self->{NEXTSTATE})) {
+ $helper->changeState($self->{NEXTSTATE});
+ }
+
+ return 1;
+}
+
+1;
+
package Apache::lonhelper::skip;
=pod
@@ -1369,7 +1487,7 @@ BUTTONS
$choiceLabel = &$choiceLabel($helper, $self);
}
$result .= "/>
".qq{
";
+ $choiceLabel. "";
if ($choice->[4]) {
$result .='
{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
}
@@ -1606,7 +1727,7 @@ no strict;
use strict;
use Apache::lonlocal; # A localization nightmare
use Apache::lonnet;
-use Time::localtime;
+use DateTime;
BEGIN {
&Apache::lonhelper::register('Apache::lonhelper::date',
@@ -1656,14 +1777,13 @@ sub render {
my $time=time;
my ($anytime,$onclick);
-
# first check VARS for a valid new value from the user
# then check DEFAULT_VALUE for a valid default time value
# otherwise pick now as reasonably good time
if (defined($helper->{VARS}{$var})
&& $helper->{VARS}{$var} > 0) {
- $date = localtime($helper->{VARS}{$var});
+ $date = &get_date_object($helper->{VARS}{$var});
} elsif (defined($self->{DEFAULT_VALUE})) {
my $valueFunc = eval($self->{DEFAULT_VALUE});
die('Error in default value code for variable ' .
@@ -1671,17 +1791,17 @@ sub render {
$time = &$valueFunc($helper, $self);
if (lc($time) eq 'anytime') {
$anytime=1;
- $date = localtime(time);
+ $date = &get_date_object(time);
$date->min(0);
} elsif (defined($time) && $time ne 0) {
- $date = localtime($time);
+ $date = &get_date_object($time);
} else {
# leave date undefined so it'll default to now
}
}
if (!defined($date)) {
- $date = localtime(time);
+ $date = &get_date_object(time);
$date->min(0);
}
@@ -1698,12 +1818,12 @@ sub render {
my $i;
$result .= "\n";
@@ -1722,7 +1842,7 @@ sub render {
# Year
$result .= "\n";
}
+ $result .= ' '.$date->time_zone_short_name().' ';
if ($self->{'anytime'}) {
$result.=(<
@@ -1804,7 +1925,8 @@ sub postprocess {
if ($env{'form.' . $var . 'anytime'}) {
$helper->{VARS}->{$var} = undef;
} else {
- my $month = $env{'form.' . $var . 'month'};
+ my $month = $env{'form.' . $var . 'month'};
+ $month ++;
my $day = $env{'form.' . $var . 'day'};
my $year = $env{'form.' . $var . 'year'};
my $min = 0;
@@ -1814,25 +1936,40 @@ sub postprocess {
$hour = $env{'form.' . $var . 'hour'};
}
- my $chosenDate;
- eval {$chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);};
+ my ($chosenDate,$checkDate);
+ my $timezone = &Apache::lonlocal::gettimezone();
+ my $dt;
+ eval {
+ $dt = DateTime->new( year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $min,
+ second => 0,
+ time_zone => $timezone,
+ );
+ };
+
my $error = $@;
+ if (!$error) {
+ $chosenDate = $dt->epoch;
+ $checkDate = &get_date_object($chosenDate);
+ }
# Check to make sure that the date was not automatically co-erced into a
# valid date, as we want to flag that as an error
# This happens for "Feb. 31", for instance, which is coerced to March 2 or
# 3, depending on if it's a leap year
- my $checkDate = localtime($chosenDate);
if ($error || $checkDate->mon != $month || $checkDate->mday != $day ||
- $checkDate->year + 1900 != $year) {
+ $checkDate->year != $year) {
unless (Apache::lonlocal::current_language()== ~/^en/) {
$self->{ERROR_MSG} = &mt("Invalid date entry");
return 0;
}
# LOCALIZATION FIXME: Needs to be parameterized
- $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
- . "date because it doesn't exist. Please enter a valid date.";
+ $self->{ERROR_MSG} = "Can't use ".$months[$env{'form.'.$var.'month'}]. " $day, $year as a ".
+ "date because it doesn't exist. Please enter a valid date.";
return 0;
}
@@ -1855,6 +1992,20 @@ sub postprocess {
return 1;
}
+
+sub get_date_object {
+ my ($epoch) = @_;
+ my $dt = DateTime->from_epoch(epoch => $epoch)
+ ->set_time_zone(&Apache::lonlocal::gettimezone());
+ my $lang = Apache::lonlocal::current_language();
+ if ($lang ne '') {
+ eval {
+ $dt->set_locale($lang);
+ };
+ }
+ return $dt;
+}
+
1;
package Apache::lonhelper::resource;
@@ -1881,7 +2032,8 @@ folders that have all of their contained
be filtered out. The 'addstatus' attribute, if true, will add the icon
and long status display columns to the display. The 'addparts'
attribute will add in a part selector beside problems that have more
-than 1 part.
+than 1 part. The 'includecourse' attribute if true, will include
+the toplevel default.sequence in the results.
=head3 SUB-TAGS
@@ -1914,6 +2066,49 @@ than 1 part.
evaluated with "sub { my $helper = shift; my $state = shift;" and
"}", with the return value used as the mapurl.
+=item * : Allows you to add optional elements to the
+ resource chooser currently these can be a checkbox, or a text entry
+ or hidden (see the 'type' attribute below).
+ the following attributes are supported by this tag:
+
+=over 4
+
+=item * type=control-type : determines the type of control displayed.
+ This can be one of the following types: 'checkbox' provides a true/false
+ checkbox. 'text' provides a text entry control. 'hidden' provides a
+ hidden form element that returns the name of the resource for each
+ element of the text box.
+
+=item * text=header-text : provides column header text for the option.
+
+=item * variable=helpervar : provides a helper variable to contain the
+ value of the input control for each resource. In general, the result
+ will be a set of values separated by ||| for the checkbox the value between
+ the |||'s will either be empty, if the box is not checked, or the resource
+ name if checked. For the text entry, the values will be the text in the
+ text box. This could be empty. Hidden elements unconditionally provide
+ the resource name for each row of the chooser and allow you to therefore
+ correlate text entries to their resources.
+ The helper variable can be initialized by the user code to pre-load values
+ into the controls:
+
+=over 4
+
+
+=item * Preloading checkboxes : Set the helper variable to the value you
+ would have gotten from the control if it had been manually set as desired.
+
+=item * Preloading text entries : Set the helper variable to triple pipe
+ separated values where each value is of the form resource-name=value
+
+=item * Preloading hidden fields : These cannot be pre-loaded and will always
+ be pipe separated resource names.
+
+=back
+
+
+=back
+
=back
=cut
@@ -1954,6 +2149,7 @@ sub start_resource {
$helper->declareVar($paramHash->{'variable'}.'_part');
}
$paramHash->{'closeallpages'} = $token->[2]{'closeallpages'};
+ $paramHash->{'include_top_level_map'} = $token->[2]{'includecourse'};
return '';
}
@@ -2042,20 +2238,42 @@ sub start_option {
if (!defined($paramHash->{OPTION_TEXTS})) {
$paramHash->{OPTION_TEXTS} = [ ];
$paramHash->{OPTION_VARS} = [ ];
+ $paramHash->{OPTION_TYPES} = [ ];
}
+ # We can have an attribute: type which can have the
+ # values: "checkbox" or "text" which defaults to
+ # checkbox allowing us to change the type of input
+ # for the option:
+ #
+ my $input_widget_type = 'checkbox';
+ if(defined($token->[2]{'type'})) {
+ my $widget_type = $token->[2]{'type'};
+ if ($widget_type eq 'text') { # only accept legal alternatives
+ $input_widget_type = $widget_type; # Illegals are checks.
+ } elsif ($widget_type eq 'hidden') {
+ $input_widget_type = $widget_type;
+ }
+ }
+
# OPTION_TEXTS is a list of the text attribute
# values used to create column headings.
# OPTION_VARS is a list of the variable names, used to create the checkbox
# inputs.
+ # OPTION_TYPES is a list of the option types:
+ #
# We're ok with empty elements. as place holders
# Although the 'variable' element should really exist.
#
+
my $option_texts = $paramHash->{OPTION_TEXTS};
my $option_vars = $paramHash->{OPTION_VARS};
+ my $option_types = $paramHash->{OPTION_TYPES};
push(@$option_texts, $token->[2]{'text'});
push(@$option_vars, $token->[2]{'variable'});
+ push(@$option_types, $input_widget_type);
+
# Need to create and declare the option variables as well to make them
# persistent.
@@ -2131,6 +2349,7 @@ BUTTONS
my $multichoice = $self->{'multichoice'};
my $option_vars = $self->{OPTION_VARS};
my $option_texts = $self->{OPTION_TEXTS};
+ my $option_types = $self->{OPTION_TYPES};
my $addparts = $self->{'addparts'};
my $headings_done = 0;
@@ -2189,17 +2408,52 @@ BUTTONS
my $resource_name =
HTML::Entities::encode($raw_name,"<>&\"'");
if($option_vars) {
+ my $option_num = 0;
foreach my $option_var (@$option_vars) {
+ my $option_type = $option_types->[$option_num];
+ $option_num++;
my $var_value = "\|\|\|" . $helper->{VARS}->{$option_var} .
"\|\|\|";
my $checked ="";
if($var_value =~ /\Q|||$raw_name|||\E/) {
$checked = "checked='checked'";
}
- $col .=
- "
";
+ if ($option_type eq 'text') {
+ #
+ # For text's the variable value is a ||| separated set of
+ # resource_name=value
+ #
+ my @values = split(/\|\|\|/, $helper->{VARS}->{$option_var});
+
+ # Normal practice would be to toss this in a hash but
+ # the only thing that saves is the compare in the loop
+ # below and for all but one case we'll break out of the loop
+ # before it completes.
+
+ my $text_value = ''; # In case there's no match.
+ foreach my $value (@values) {
+ my ($res, $skip) = split(/=/, $value);
+ if($res eq $resource_name) {
+ $text_value = $skip;
+ last;
+ }
+ }
+ # TODO: add an attribute to