[prev in list] [next in list] [prev in thread] [next in thread]
List: lon-capa-cvs
Subject: [LON-CAPA-cvs] cvs: loncom /interface lonwizard.pm
From: bowersj2 <lon-capa-cvs () mail ! lon-capa ! org>
Date: 2003-01-30 19:34:24
Message-ID: cvsbowersj21043955264 () cvsserver
[Download RAW message or body]
This is a MIME encoded message
bowersj2 Thu Jan 30 14:34:24 2003 EDT
Modified files:
/loncom/interface lonwizard.pm
Log:
Adding in the wizard code to the database.
["bowersj2-20030130143424.txt" (text/plain)]
Index: loncom/interface/lonwizard.pm
diff -u /dev/null loncom/interface/lonwizard.pm:1.3
--- /dev/null Thu Jan 30 14:34:24 2003
+++ loncom/interface/lonwizard.pm Thu Jan 30 14:34:24 2003
@@ -0,0 +1,1265 @@
+# This is the LON-CAPA HTML Wizard framework, for wrapping easy
+# functionality easily.
+
+package Apache::lonwizard;
+
+use Apache::Constants qw(:common :http);
+use Apache::loncommon;
+
+=head1 lonwizard - HTML "Wizard" framework for LON-CAPA
+
+I know how most developers feel about Wizards, but the fact is they are a \
well-established UI widget that users feel comfortable with. It can take a \
complicated multi-dimensional problem the user has (such as the canonical Course \
Parameter example) and turn in into a series of bite-sized one-dimensional questions. \
Or take the some four-question form and put it in a Wizard, and present the same user \
with the same form outside of the Wizard, and the user will *think* the Wizard is \
easier. +
+For the developer, wizards do provide an easy way to bundle easy bits of \
functionality for the user. It can be easier to write a Wizard then provide another \
custom interface. +
+All classes are in the Apache::lonwizard namespace.
+
+(For a perldoc'ed example of a wizard you can use as an example, see \
loncourseparmwizard.pm.) +
+=cut
+
+# To prevent runaway file counts, this file has lonwizard,
+# lonwizstate, and other wizard classes.
+use strict;
+
+use HTML::Entities;
+
+=pod
+
+=head1 Class: lonwizard
+
+=head2 lonwizard Attributes
+
+=over 4
+
+=item B<STATE>: The string name of the current state.
+
+=item B<TITLE>: The human-readable title of the wizard
+
+=item B<STATES>: A hash mapping the string names of states to references to the \
actual states. +
+=item B<VARS>: Hash that maintains the persistent variable values.
+
+=item B<HISTORY>: An array containing the names of the previous states. Used for \
"back" functionality. +
+=item B<DONE>: A boolean value, true if the wizard has completed.
+
+=back
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+
+ # If there is a state from the previous form, use that. If there is no
+ # state, use the start state parameter.
+ if (defined $ENV{"form.CURRENT_STATE"})
+ {
+ $self->{STATE} = $ENV{"form.CURRENT_STATE"};
+ }
+ else
+ {
+ $self->{STATE} = "START";
+ }
+
+ # set up return URL: Return the user to the referer page, unless the
+ # form has stored a value.
+ if (defined $ENV{"form.RETURN_PAGE"})
+ {
+ $self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};
+ }
+ else
+ {
+ $self->{RETURN_PAGE} = $ENV{REFERER};
+ }
+
+ $self->{TITLE} = shift;
+ $self->{STATES} = {};
+ $self->{VARS} = {};
+ $self->{HISTORY} = {};
+ $self->{DONE} = 0;
+ bless($self, $class);
+ return $self;
+}
+
+=pod
+
+=head2 lonwizard methods
+
+=over 2
+
+=item * B<new>(title): Returns a new instance of the given wizard type. "title" is \
the human-readable name of the wizard. A new wizard always starts on the B<START> \
state name. +
+=item * B<declareVars>(varList): Call this function to declare the var names you \
want the wizard to maintain for you. The wizard will automatically output the hidden \
form fields and parse the values for you on the next call. This is a bulk \
declaration. +
+=over 2
+
+=item Note that these variables are reserved for the wizard; if you output other \
form values in your state, you must use other names. For example, declaring "student" \
will cause the wizard to emit a form value with the name "student"; if your state \
emits form entries, do not name them "student". +
+=back
+
+=cut
+
+sub declareVars {
+ my $self = shift;
+ my $varlist = shift;
+
+ # for each string in the passed in list,
+ foreach my $element ( @{$varlist} )
+ {
+ # assign the var the default of ""
+ $self->{VARS}{$element} = "";
+
+ # if there's a form in the env, use that instead
+ my $envname = "form." . $element;
+ if (defined ($ENV{$envname}))
+ {
+ $self->{VARS}->{$element} = $ENV{$envname};
+ }
+ }
+}
+
+# Private function; takes all of the declared vars and returns a string
+# corresponding to the hidden input fields that will re-construct the
+# variables.
+sub _saveVars {
+ my $self = shift;
+ my $result = "";
+ foreach my $varname (keys %{$self->{VARS}})
+ {
+ $result .= '<input type="hidden" name="' .
+ HTML::Entities::encode($varname) . '" value="' .
+ HTML::Entities::encode($self->{VARS}{$varname}) .
+ "\" />\n";
+ }
+
+ # also save state & return page
+ $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
+ HTML::Entities::encode($self->{STATE}) . '" />' . "\n";
+ $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
+ HTML::Entities::encode($self->{RETURN_PAGE}) . '" />' . "\n";
+
+ return $result;
+}
+
+=pod
+
+=item B<registerState>(referenceToStateObj): Registers a state as part of the \
wizard, so the wizard can use it. The 'referenceToStateObj' should be a reference to \
an instantiated lonwizstate object. This is normally called at the end of the \
lonwizstate constructor. +
+=cut
+
+sub registerState {
+ my $self = shift;
+ my $state = shift;
+
+ my $stateName = $state->name();
+ $self->{STATES}{$stateName} = $state;
+}
+
+=pod
+
+=item B<changeState>(stateName): Given a string representing the name of some \
registered state, this causes the wizard to change to that state. Generally, states \
will call this. +
+=cut
+
+sub changeState {
+ my $self = shift;
+ $self->{STATE} = shift;
+}
+
+=pod
+
+=item B<display>(): This is the main method that the handler using the wizard calls.
+
+=cut
+
+# Done in five phases
+# 1: Do the post processing for the previous state.
+# 2: Do the preprocessing for the current state.
+# 3: Check to see if state changed, if so, postprocess current and move to next.
+# Repeat until state stays stable.
+# 4: Render the current state to the screen as an HTML page.
+sub display {
+ my $self = shift;
+
+ my $result = "";
+
+ # Phase 1: Post processing for state of previous screen (which is actually
+ # the current state), if it wasn't the beginning state.
+ if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->")
+ {
+ my $prevState = $self->{STATES}{$self->{STATE}};
+ $prevState->postprocess();
+ }
+
+ # Note, to handle errors in a state's input that a user must correct,
+ # do not transition in the postprocess, and force the user to correct
+ # the error.
+
+ # Phase 2: Preprocess current state
+ my $startState = $self->{STATE};
+ my $state = $self->{STATES}{$startState};
+
+ # Error checking
+ if (!defined($state)) {
+ $result .="Error! The state ". $startState ." is not defined.";
+ return $result;
+ }
+ $state->preprocess();
+
+ # Phase 3: While the current state is different from the previous state,
+ # keep processing.
+ while ( $startState ne $self->{STATE} )
+ {
+ $startState = $self->{STATE};
+ $state = $self->{STATES}{$startState};
+ $state->preprocess();
+ }
+
+ # Phase 4: Display.
+ my $stateTitle = $state->title();
+ my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
+
+ $result .= <<HEADER;
+<html>
+ <head>
+ <title>LON-CAPA Wizard: $self->{TITLE}</title>
+ </head>
+ $bodytag
+HEADER
+ if (!$state->overrideForm()) { $result.="<form method='GET'>"; }
+ $result .= <<HEADER;
+ <table border="0"><tr><td>
+ <h2><i>$stateTitle</i></h2>
+HEADER
+
+ if (!$state->overrideForm()) {
+ $result .= $self->_saveVars();
+ }
+ $result .= $state->render() . "<p> </p>";
+
+ if (!$state->overrideForm()) {
+ $result .= '<center>';
+ if ($self->{STATE} ne $self->{START_STATE})
+ {
+ #$result .= '<input name="SUBMIT" type="submit" value="<- Previous" \
/> '; + }
+ if ($self->{DONE})
+ {
+ my $returnPage = $self->{RETURN_PAGE};
+ $result .= "<a href=\"$returnPage\">End Wizard</a>";
+ }
+ else
+ {
+ $result .= '<input name="SUBMIT" type="submit" value="Next ->" />';
+ }
+ $result .= "</center>\n";
+ }
+
+ $result .= <<FOOTER;
+ </td>
+ </tr>
+ </table>
+ </form>
+ </body>
+</html>
+FOOTER
+
+ return $result;
+}
+
+=pod
+
+=item B<name>([name]): Returns the name of the wizard. If a parameter is passed, \
that will be saved as the name. +
+=cut
+
+# Returns/sets the name of this wizard, i.e., "Assignment Parameter"
+sub title {
+ my $self = shift;
+ if (@_) { $self->{TITLE} = shift};
+ return $self->{TITLE};
+}
+
+=pod
+
+=item B<getVars>(): Returns a hash reference containing the stored vars for this \
wizard. The states use this for variables maintained across states. Example: C<my \
%vars = %{$wizard-E<gt>getVars()};> This provides read-only access, apparently. +
+=cut
+
+sub getVars {
+ my $self = shift;
+ return ($self->{VARS});
+}
+
+=pod
+
+=item B<setVar>(key, val): Sets the var named "key" to "val" in the wizard's form \
array. +
+=cut
+
+# This may look trivial, but it's here as a hook for possible later processing
+sub setVar {
+ my $self = shift;
+ my $key = shift;
+ my $val = shift;
+ $self->{VARS}{$key} = $val;
+}
+
+=pod
+
+=item B<setDone>(): If a state calls this, the wizard will consider itself \
completed. The state should display a friendly "Done" message, and the wizard will \
display a link returning the user to the invoking page, rather then a "Next" button. \
+ +=cut
+
+
+# A temp function for debugging
+sub handler {
+ my $r = shift;
+
+ Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
+
+ if ($r->header_only) {
+ if ($ENV{'browser.mathml'}) {
+ $r->content_type('text/xml');
+ } else {
+ $r->content_type('text/html');
+ }
+ $r->send_http_header;
+ return OK;
+ }
+
+ # Send header, don't cache this page
+ if ($ENV{'browser.mathml'}) {
+ $r->content_type('text/xml');
+ } else {
+ $r->content_type('text/html');
+ }
+ &Apache::loncommon::no_cache($r);
+ $r->send_http_header;
+ $r->rflush();
+
+ my $mes = <<WIZBEGIN;
+<p>This wizard will allow you to</p>
+
+<ul>
+ <li>Change assignment parameters, such as due date or open date...</li>
+ <li>... for a whole class</li>
+ <li>... for a whole section</li>
+ <li>... for an individual student</li>
+ <li>... by folder</li>
+ <li>... by individual assignment</li>
+</ul>
+
+<p>After the wizard is done, you will be shown where in the advanced interface you \
would have gone to change the parameter you have chosen, so in the future you can do \
it directly.</p> +WIZBEGIN
+
+ my $wizard = Apache::lonwizard->new("Course Parameter Wizard");
+ $wizard->declareVars(['ACTION_TYPE', 'GRANULARITY', 'TARGETS', 'PARM_DATE', \
'RESOURCE_ID', 'USER_NAME', 'SECTION_NAME']); + my %dateTypeHash = ('open_date' => \
"Opening Date", + 'due_date' => "Due Date",
+ 'answer_date' => "Answer Date");
+
+ Apache::lonwizard::message_state->new($wizard, "START", "Welcome to the \
Assignment Parameter Wizard", $mes, "CHOOSE_ACTION"); + \
Apache::lonwizard::switch_state->new($wizard, "CHOOSE_ACTION", "What do you want to \
do?", "ACTION_TYPE", [ + ["open_date", "Set an Open Date for a problem", \
"CHOOSE_LEVEL"], + ["due_date", "Set a Due Date for a problem", \
"CHOOSE_LEVEL"], + ["answer_date", "Set an Answer Open Date for a problem", \
"CHOOSE_LEVEL" ] ]); + Apache::lonwizard::switch_state->new($wizard, \
"CHOOSE_LEVEL", "Parameter Granularity", "GRANULARITY", [ + ["whole_course", \
"Set for Whole Course", "CHOOSE_STUDENT_LEVEL"], + ["map", "Set for a \
Folder/Map", "CHOOSE_FOLDER"], + ["resource", "Set for a Particular Problem", \
"CHOOSE_RESOURCE"]], + "How general should \
this setting be?"); + Apache::lonwizard::resource_choice->new($wizard, \
"CHOOSE_FOLDER", "Select Folder", "", "", "CHOOSE_STUDENT_LEVEL", "RESOURCE_ID", sub \
{my $res = shift; return $res->is_map();}); + \
Apache::lonwizard::resource_choice->new($wizard, "CHOOSE_RESOURCE", "Select \
Resource", "", "", "CHOOSE_STUDENT_LEVEL", "RESOURCE_ID", sub {my $res = shift; \
return $res->is_map() || $res->is_problem();}, sub {my $res = shift; return \
$res->is_problem(); }); + Apache::lonwizard::switch_state->new($wizard, \
"CHOOSE_STUDENT_LEVEL", "Parameter Targets", "TARGETS", [ + ["course", "Set for \
All Students in Course", "CHOOSE_DATE"], + ["section", "Set for Section", \
"CHOOSE_SECTION"], + ["student", "Set for an Individual Student", \
"CHOOSE_STUDENT"]], + "Whom should this setting \
affect?"); +
+ my $dateType = $dateTypeHash{$wizard->{VARS}->{ACTION_TYPE}};
+ Apache::lonwizard::choose_section->new($wizard, "CHOOSE_SECTION", "Select \
Section", "Please select the section you wish to set the $dateType for:", "", \
"CHOOSE_DATE", "SECTION_NAME"); + Apache::lonwizard::choose_student->new($wizard, \
"CHOOSE_STUDENT", "Select Student", "Please select the student you wish to set the \
$dateType for:", "", "CHOOSE_DATE", "USER_NAME"); + \
Apache::lonwizard::date_state->new($wizard, "CHOOSE_DATE", "Set Date", "PARM_DATE", \
"FINISH", "What should the $dateType be set to?"); + \
Apache::lonwizard::parmwizfinal->new($wizard, "FINISH", "Confirm Selection"); +
+ $r->print($wizard->display());
+
+ return OK;
+}
+
+
+
+1;
+
+=head1 Class: lonwizstate
+
+A "lonwizstate" object represents a lonwizard state. A "state" is basically what is \
visible on the screen. For instance, a state may display a radio button dialog with \
three buttons, and wait for the user to choose one. +
+Several pre-prepared child classes are include in lonwizard. If you create a new \
wizard type, be sure to add it to lonwizard.pm so others can use it too. +
+It is importent to remember when constructing states that the user may use the \
"Previous" button to go back and revisit a state previously filled out. Thus, states \
should consult the wizard variables they are intended to set to see if the user has \
already selected something, and when displaying themselves should reselect the same \
values, such that the user paging from the end to the beginning, back to the end, \
will not change any settings. +
+None of the pre-packaged states correctly handle there being B<no> input, as the \
wizard does not currently have any protection against errors in the states \
themselves. (The closest thing you can do is set the wizard to be done and display an \
error message, which should be adequate.) +
+=head2 lonwizstate methods
+
+These methods should be overridden in derived states, except B<new> which may be \
sufficient. +
+=over 2
+
+=item B<new> (parentLonWizReference, stateName, stateTitle): Creates a new state and \
returns it. The first argument is a reference to the parent wizard. The second is the \
name of the state, which I<must> be unique. The third is the title, which will be \
displayed on the screen to the human. +
+=item B<preprocess>(): preprocess sets up all of the information the state needs to \
do its job, such as querying data bases to obtain lists of choices, and sets up data \
for the render method. If preprocess decides to jump to a new state, it is \
responsible for manually running post-process, if it so desires. +
+=over 2
+
+=item If this method calls the parent lonwizard's B<changeState> method to another \
state, then the state will never be rendered on the screen, and the wizard will move \
to the specified state. This is useful if the state may only be necessary to clarify \
an ambiguous input, such as selecting a part from a multi-part problem, which isn't \
necessary if the problem only has one state. +
+=back
+
+=item B<render>(): render returns a string of itself to be rendered to the screen, \
which the wizard will display. +
+=back
+
+=cut
+
+package Apache::lonwizard::state;
+
+use strict;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+ $self->{WIZARD} = shift;
+ $self->{NAME} = shift;
+ $self->{TITLE} = shift;
+
+ bless($self);
+
+ $self->{WIZARD}->registerState($self);
+ return $self;
+}
+
+sub name {
+ my $self = shift;
+ if (@_) { $self->{NAME} = shift};
+ return $self->{NAME};
+}
+
+sub title {
+ my $self = shift;
+ if (@_) { $self->{TITLE} = shift};
+ return $self->{TITLE};
+}
+
+sub preprocess {
+ return 1;
+}
+
+sub render {
+ return "This is the empty state. If you can see this, it's a bug.\n"
+}
+
+sub postprocess {
+ return 1;
+}
+
+# If this is 1, the wizard assumes the state will override the
+# wizard's form, useful for some final states
+sub overrideForm {
+ return 0;
+}
+
+1;
+
+=pod
+
+=back
+
+=head1 Prepackaged States
+
+lonwizard provides several pre-packaged states that you can drop into your Wizard \
and obtain common functionality. +
+=head2 Class: message_state
+
+message_state is a state the simply displays a message. It does not do any pre- or \
postprocessing. It makes a good initial state, which traditionally is a short message \
telling the user what they are about to accomplish, and may contain warnings or \
preconditions that should be fulfilled before using the wizard. +
+=over 4
+
+=item overridden method B<new>(parentLonWizReference, stateName, stateTitle, \
message, nextState): Two new parameters "message" will be the HTML message displayed \
to the user, and "nextState" is the name of the next state. +
+=back
+
+=cut
+
+package Apache::lonwizard::message_state;
+
+no strict;
+@ISA = ("Apache::lonwizard::state");
+use strict;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+
+ # This cute looking statement correctly handles subclassing
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ $self->{MESSAGE} = shift;
+ $self->{NEXT_STATE} = shift;
+
+ return $self;
+}
+
+sub postprocess {
+ my $self = shift;
+ $self->{WIZARD}->changeState($self->{NEXT_STATE});
+ return 1;
+}
+
+sub render {
+ my $self = shift;
+ return $self->{MESSAGE};
+}
+
+1;
+
+package Apache::lonwizard::choice_state;
+
+no strict;
+@ISA = ("Apache::lonwizard::state");
+use strict;
+
+=pod
+
+=head2 Class: choice_state
+
+Choice state provides a single choice to the user as a text selection box. You pass \
it a message and hash containing [human_name] -> [computer_name] entries, and it will \
display the choices and store the result in the provided variable. +
+If there is only one choice, the state will automatically make it and go to the next \
state. +
+=over 4
+
+=item overridden method B<new>(parentLonWizReference, stateName, stateTitle, \
messageBefore, messageAfter, nextState, varName, choiceHash): messageBefore is the \
HTML text that will be displayed before the choice display, messageAfter will display \
after. Keys will be sorted according to human name. nextState is the state to proceed \
to after the choice. varName is the name of the wizard var to store the computer_name \
answer in. choiceHash is the hash described above. It is optional because you may \
override it. +
+=back
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ $self->{MESSAGE_BEFORE} = shift;
+ $self->{MESSAGE_AFTER} = shift;
+ $self->{NEXT_STATE} = shift;
+ $self->{VAR_NAME} = shift;
+ $self->{CHOICE_HASH} = shift;
+ $self->{NO_CHOICES} = 0;
+
+ return $self;
+}
+
+sub preprocess {
+ my $self = shift;
+ my $choices = $self->{CHOICE_HASH};
+ if (!defined($self->{CHOICE_HASH})) {
+ $choices = $self->{CHOICE_HASH} = $self->determineChoices();
+ }
+ my $wizvars = $self->{WIZARD}->getVars();
+
+ my @keys = keys(%$choices);
+ @keys = sort @keys;
+
+ if (scalar(@keys) == 0)
+ {
+ # No choices... so prepare to display error message and cancel further execution.
+ $self->{NO_CHOICES} = 1;
+ $self->{WIZARD}->{DONE} = 1;
+ return;
+ }
+ if (scalar(@keys) == 1)
+ {
+ # If there is only one choice, pick it and move on.
+ $wizvars->{$self->{VAR_NAME}} = $choices->{$keys[0]};
+ $self->{WIZARD}->changeState($self->{NEXT_STATE});
+ return;
+ }
+
+ # Otherwise, do normal processing in the render routine.
+
+ return;
+}
+
+sub determineChoices {
+ return {"NO_CHOICE" => "No choices were given."};
+}
+
+sub render {
+ my $self = shift;
+ my $result = "";
+ my $var = $self->{VAR_NAME};
+
+ if (defined $self->{ERROR_MSG}) {
+ $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br \
/>'; + }
+
+ if (defined $self->{MESSAGE_BEFORE})
+ {
+ $result .= $self->{MESSAGE_BEFORE} . '<br /><br />';
+ }
+
+ my $choices = $self->{CHOICE_HASH};
+ my @keys = keys (%$choices);
+
+ $result .= "<select name=\"$var.forminput\" size=\"10\">\n";
+ foreach (@keys)
+ {
+ $result .= "<option value=\"" . HTML::Entities::encode($choices->{$_})
+ . "\">" . HTML::Entities::encode($_) . "\n";
+ }
+ $result .= "</select>\n\n";
+
+ if (defined $self->{MESSAGE_AFTER})
+ {
+ $result .= '<br /><br />' . $self->{MESSAGE_AFTER};
+ }
+
+ return $result;
+}
+
+sub postprocess {
+ my $self = shift;
+ my $wizard = $self->{WIZARD};
+ my $formvalue = $ENV{'form.' . $self->{VAR_NAME} . '.forminput'};
+ if ($formvalue) {
+ $wizard->setVar($self->{VAR_NAME}, $formvalue);
+ $wizard->changeState($self->{NEXT_STATE});
+ } else {
+ $self->{ERROR_MSG} = "Can't continue the wizard because you must make"
+ . ' a selection to continue.';
+ }
+ return 1;
+}
+
+package Apache::lonwizard::switch_state;
+
+no strict;
+@ISA = ("Apache::lonwizard::state");
+use strict;
+
+=pod
+
+=head2 Class; switch_state
+
+Switch state provides the ability to present the user with several radio-button \
choices. The state can store the user response in a wizard variable, and can also \
send the user to a different state for each selection, which is the intended primary \
purpose. +
+Each choice may have arbitrary HTML associated with it, which will be used as the \
label. The first choice will be selected by default. +
+=over 4
+
+=item overridden method B<new>(parentLonWizReference, stateName, stateTitle, \
varName, choiceList, messageBefore, messageAfter): varName is the name of the wizard \
variable the state will set with the choice made. choiceHash is list reference of a \
list of list references to three element lists, where the first element is what the \
wizard var varName will be set to, the second is the HTML that will be displayed for \
that choice, and the third is the destination state. messageBefore is an optional \
HTML string that will be placed before the message, messageAfter an optional HTML \
string that will be placed before. +
+An example of a legit choiceList: C<my $choicelist = [ ["flunk", "Flunk Student", \
"FLUNK_STATE"], ["pass", "Pass Student", "PASS_STATE"] ];> +
+=back
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ $self->{VAR_NAME} = shift;
+ $self->{CHOICE_LIST} = shift;
+ $self->{MESSAGE_BEFORE} = shift;
+ $self->{MESSAGE_AFTER} = shift;
+
+ return $self;
+}
+
+# Don't need a preprocess step; we assume we know the choices
+
+sub render {
+ my $self = shift;
+ my $result = "";
+ my $var = $self->{VAR_NAME};
+ my @choices = @{$self->{CHOICE_LIST}};
+ my $curVal = $self->{WIZARD}->{VARS}->{$var};
+
+ $result .= $self->{MESSAGE_BEFORE} if (defined $self->{MESSAGE_BEFORE});
+
+ if (!$curVal) {
+ $curVal = $self->{CHOICE_LIST}->[0]->[0]; # top is default
+ }
+
+ $result .= "<table>\n\n";
+
+ foreach my $choice (@choices)
+ {
+ my $value = $choice->[0];
+ my $text = $choice->[1];
+
+ $result .= "<tr>\n<td width='20'> </td>\n<td>";
+ $result .= "<td valign=\"top\"><input type=\"radio\" name=\"$var.forminput\"";
+ $result .= " checked" if ($value eq $curVal);
+ $result .= " value=\"$value\"></td>\n<td>$text</td>\n</tr>\n\n";
+ }
+
+ $result .= "<table>\n\n";
+
+ $result .= $self->{MESSAGE_AFTER} if (defined $self->{MESSAGE_AFTER});
+
+ return $result;
+}
+
+sub postprocess {
+ my $self = shift;
+ my $wizard = $self->{WIZARD};
+ my $chosenValue = $ENV{"form." . $self->{VAR_NAME} . '.forminput'};
+ $wizard->setVar($self->{VAR_NAME}, $chosenValue)
+ if (defined ($self->{VAR_NAME}));
+
+ foreach my $choice (@{$self->{CHOICE_LIST}})
+ {
+ if ($choice->[0] eq $chosenValue)
+ {
+ $wizard->changeState($choice->[2]);
+ }
+ }
+}
+
+# If there is only one choice, make it and move on
+sub preprocess {
+ my $self = shift;
+ my $choiceList = $self->{CHOICE_LIST};
+ my $wizard = $self->{WIZARD};
+
+ if (scalar(@{$choiceList}) == 1)
+ {
+ my $choice = $choiceList->[0];
+ my $chosenVal = $choice->[0];
+ my $nextState = $choice->[2];
+
+ $wizard->setVar($self->{VAR_NAME}, $chosenVal)
+ if (defined ($self->{VAR_NAME}));
+ $wizard->changeState($nextState);
+ }
+}
+
+1;
+
+package Apache::lonwizard::date_state;
+
+use Time::localtime;
+use Time::Local;
+use Time::tm;
+
+no strict;
+@ISA = ("Apache::lonwizard::state");
+use strict;
+
+my @months = ("January", "February", "March", "April", "May", "June", "July",
+ "August", "September", "October", "November", "December");
+
+=pod
+
+=head2 Class: date_state
+
+Date state provides a state for selecting a date/time, as seen in the course parmset \
wizard.. You can choose to display date entry if that's what you need. +
+=over 4
+
+=item overriddent method B<new>(parentLonWizReference, stateName, stateTitle, \
varName, nextState, messageBefore, messageAfter, displayJustDate): varName is where \
the date/time will be stored as seconds since the epoch. messageBefore and \
messageAfter as other states. displayJustDate is a flag defaulting to false that if \
true, will only display the date selection (defaulting to midnight on that date). \
Otherwise, minutes and hours will be shown. +
+=back
+
+=cut
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ $self->{VAR_NAME} = shift;
+ $self->{NEXT_STATE} = shift;
+ $self->{MESSAGE_BEFORE} = shift;
+ $self->{MESSAGE_AFTER} = shift;
+ $self->{DISPLAY_JUST_DATE} = shift;
+ if (!defined($self->{DISPLAY_JUST_DATE})) {$self->{DISPLAY_JUST_DATE} = 0;}
+ return $self;
+}
+
+sub render {
+ my $self = shift;
+ my $result = "";
+ my $var = $self->{VAR_NAME};
+ my $name = $self->{NAME};
+ my $wizvars = $self->{WIZARD}->getVars();
+
+ my $date;
+
+ # Pick default date: Now, or previous choice
+ if (defined ($wizvars->{$var}) && $wizvars->{$var} ne "")
+ {
+ $date = localtime($wizvars->{$var});
+ }
+ else
+ {
+ $date = localtime();
+ }
+
+ if (defined $self->{ERROR_MSG}) {
+ $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br \
/>'; + }
+
+ if (defined ($self->{MESSAGE_BEFORE})) {
+ $result .= $self->{MESSAGE_BEFORE};
+ $result .= "<br /><br />\n\n";
+ }
+
+ # Month
+ my $i;
+ $result .= "<select name='$self->{VAR_NAME}month'>\n";
+ for ($i = 0; $i < 12; $i++) {
+ if ($i == $date->mon) {
+ $result .= "<option value='$i' selected>";
+ } else {
+ $result .= "<option value='$i'>";
+ }
+ $result .= @months[$i] . "\n";
+ }
+ $result .= "</select>\n";
+
+ # Day
+ $result .= "<select name='$self->{VAR_NAME}day'>\n";
+ for ($i = 1; $i < 32; $i++) {
+ if ($i == $date->mday) {
+ $result .= '<option selected>';
+ } else {
+ $result .= '<option>';
+ }
+ $result .= "$i\n";
+ }
+ $result .= "</select>,\n";
+
+ # Year
+ $result .= "<select name='$self->{VAR_NAME}year'>\n";
+ for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
+ if ($date->year + 1900 == $i) {
+ $result .= "<option selected>";
+ } else {
+ $result .= "<option>";
+ }
+ $result .= "$i\n";
+ }
+ $result .= "</select>,\n";
+
+ # Display Hours and Minutes if they are called for
+ if (!$self->{DISPLAY_JUST_DATE}) {
+ $result .= "<select name='$self->{VAR_NAME}hour'>\n";
+ if ($date->hour == 12) { $result .= "<option selected>12\n"; }
+ else { $result .= "<option>12\n" }
+ for ($i = 1; $i < 12; $i++) {
+ if (($date->hour) % 12 == $i % 12) {
+ $result .= "<option selected>";
+ } else {
+ $result .= "<option>";
+ }
+ $result .= "$i\n";
+ }
+ $result .= "</select> :\n";
+
+ $result .= "<select name='$self->{VAR_NAME}minute'>\n";
+ for ($i = 0; $i < 60; $i++) {
+ if ($date->min == $i) {
+ $result .= "<option selected>";
+ } else {
+ $result .= "<option>";
+ }
+ $result .= "$i\n";
+ }
+ $result .= "</select>\n";
+
+ $result .= "<select name='$self->{VAR_NAME}meridian'>\n";
+ if ($date->hour < 12) {
+ $result .= "<option selected>A.M.\n<option>P.M.\n";
+ } else {
+ $result .= "<option>A.M.\n<option selected>P.M.\n";
+ }
+ $result .= "</select>";
+ }
+
+ if (defined ($self->{MESSAGE_AFTER})) {
+ $result .= "<br /><br />" . $self->{MESSAGE_AFTER};
+ }
+
+ return $result;
+}
+
+# Stick the date stored into the chosen variable.
+sub postprocess {
+ my $self = shift;
+ my $wizard = $self->{WIZARD};
+
+ my $month = $ENV{'form.' . $self->{VAR_NAME} . 'month'};
+ my $day = $ENV{'form.' . $self->{VAR_NAME} . 'day'};
+ my $year = $ENV{'form.' . $self->{VAR_NAME} . 'year'};
+ my $min = 0;
+ my $hour = 0;
+ if (!$self->{DISPLAY_JUST_DATE}) {
+ $min = $ENV{'form.' . $self->{VAR_NAME} . 'minute'};
+ $hour = $ENV{'form.' . $self->{VAR_NAME} . 'hour'};
+ }
+
+ my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
+ # 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 leapyear
+ my $checkDate = localtime($chosenDate);
+
+ if ($checkDate->mon != $month || $checkDate->mday != $day ||
+ $checkDate->year + 1900 != $year) {
+ $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
+ . "date because it doesn't exist. Please enter a valid date.";
+ return;
+ }
+
+ $wizard->setVar($self->{VAR_NAME}, $chosenDate);
+
+ $wizard->changeState($self->{NEXT_STATE});
+}
+
+1;
+
+package Apache::lonwizard::parmwizfinal;
+
+# This is the final state for the parmwizard. It is not generally useful,
+# so it is not perldoc'ed. It does it's own processing.
+
+no strict;
+@ISA = ('Apache::lonwizard::state');
+use strict;
+
+use Time::localtime;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ # No other variables because it gets it all from the wizard.
+}
+
+# Renders a form that, when submitted, will form the input to lonparmset.pm
+sub render {
+ my $self = shift;
+ my $wizard = $self->{WIZARD};
+ my $wizvars = $wizard->{VARS};
+
+ # FIXME: Unify my designators with the standard ones
+ my %dateTypeHash = ('open_date' => "Opening Date",
+ 'due_date' => "Due Date",
+ 'answer_date' => "Answer Date");
+ my %parmTypeHash = ('open_date' => "0_opendate",
+ 'due_date' => "0_duedate",
+ 'answer_date' => "0_answerdate");
+
+ my $result = "<form method='get' action='/adm/parmset'>\n";
+ $result .= '<p>Confirm that this information is correct, then click "Finish \
Wizard" to complete setting the parameter.<ul>'; + my $affectedResourceId = \
""; + my $parm_name = $parmTypeHash{$wizvars->{ACTION_TYPE}};
+ my $level = "";
+
+ # Print the type of manipulation:
+ $result .= '<li>Setting the <b>' . $dateTypeHash{$wizvars->{ACTION_TYPE}}
+ . "</b></li>\n";
+ if ($wizvars->{ACTION_TYPE} eq 'due_date' ||
+ $wizvars->{ACTION_TYPE} eq 'answer_date') {
+ # for due dates, we default to "date end" type entries
+ $result .= "<input type='hidden' name='recent_date_end' " .
+ "value='" . $wizvars->{PARM_DATE} . "' />\n";
+ $result .= "<input type='hidden' name='pres_value' " .
+ "value='" . $wizvars->{PARM_DATE} . "' />\n";
+ $result .= "<input type='hidden' name='pres_type' " .
+ "value='date_end' />\n";
+ } elsif ($wizvars->{ACTION_TYPE} eq 'open_date') {
+ $result .= "<input type='hidden' name='recent_date_start' ".
+ "value='" . $wizvars->{PARM_DATE} . "' />\n";
+ $result .= "<input type='hidden' name='pres_value' " .
+ "value='" . $wizvars->{PARM_DATE} . "' />\n";
+ $result .= "<input type='hidden' name='pres_type' " .
+ "value='date_start' />\n";
+ }
+
+ # Print the granularity, depending on the action
+ if ($wizvars->{GRANULARITY} eq 'whole_course') {
+ $result .= '<li>for <b>all resources in the course</b></li>';
+ $level = 9; # general course, see lonparmset.pm perldoc
+ $affectedResourceId = "0.0";
+ } elsif ($wizvars->{GRANULARITY} eq 'map') {
+ my $navmap = Apache::lonnavmaps::navmap->new(
+ $ENV{"request.course.fn"}.".db",
+ $ENV{"request.course.fn"}."_parms.db", 0, 0);
+ my $res = $navmap->getById($wizvars->{RESOURCE_ID});
+ my $title = $res->compTitle();
+ $navmap->untieHashes();
+ $result .= "<li>for the map named <b>$title</b></li>";
+ $level = 8;
+ $affectedResourceId = $wizvars->{RESOURCE_ID};
+ } else {
+ my $navmap = Apache::lonnavmaps::navmap->new(
+ $ENV{"request.course.fn"}.".db",
+ $ENV{"request.course.fn"}."_parms.db", 0, 0);
+ my $res = $navmap->getById($wizvars->{RESOURCE_ID});
+ my $title = $res->compTitle();
+ $navmap->untieHashes();
+ $result .= "<li>for the resource named <b>$title</b></li>";
+ $level = 7;
+ $affectedResourceId = $wizvars->{RESOURCE_ID};
+ }
+
+ # Print targets
+ if ($wizvars->{TARGETS} eq 'course') {
+ $result .= '<li>for <b>all students in course</b></li>';
+ } elsif ($wizvars->{TARGETS} eq 'section') {
+ my $section = $wizvars->{SECTION_NAME};
+ $result .= "<li>for section <b>$section</b></li>";
+ $level -= 3;
+ $result .= "<input type='hidden' name='csec' value='" .
+ HTML::Entities::encode($section) . "' />\n";
+ } else {
+ # FIXME: This is probably wasteful!
+ my $classlist = Apache::loncoursedata::get_classlist();
+ my $name = $classlist->{$wizvars->{USER_NAME}}->[6];
+ $result .= "<li>for <b>$name</b></li>";
+ $level -= 6;
+ my ($uname, $udom) = split /:/, $wizvars->{USER_NAME};
+ $result .= "<input type='hidden' name='uname' value='".
+ HTML::Entities::encode($uname) . "' />\n";
+ $result .= "<input type='hidden' name='udom' value='".
+ HTML::Entities::encode($udom) . "' />\n";
+ }
+
+ # Print value
+ $result .= "<li>to <b>" . ctime($wizvars->{PARM_DATE}) . "</b> (" .
+ Apache::lonnavmaps::timeToHumanString($wizvars->{PARM_DATE})
+ . ")</li>\n";
+
+ # print pres_marker
+ $result .= "\n<input type='hidden' name='pres_marker'" .
+ " value='$affectedResourceId&$parm_name&$level' />\n";
+
+ $result .= "<br /><br /><center><input type='submit' value='Finish Wizard' \
/></center></form>\n"; +
+ return $result;
+}
+
+sub overrideForm {
+ return 1;
+}
+
+1;
+
+package Apache::lonwizard::resource_choice;
+
+=pod
+
+=head2 Class: resource_choice
+
+folder_choice gives the user an opportunity to select one resource from the current \
course, and will stick the ID of that choice (#.#) into the desired variable. +
+Note this state will not automatically advance if there is only one choice, because \
it might confuse the user in this case. +
+=over 4
+
+=item overriddent method B<new>(parentLonWizReference, stateName, stateTitle, \
messageBefore, messageAfter, nextState, varName, filterFunction, choiceFunction): \
messageBefore and messageAfter appear before and after the state choice, \
respectively. nextState is the state to proceed to after the choice. varName is the \
wizard variable to store the choice in. +
+filterFunction is a function reference that receives the current resource as an \
argument, and returns 1 if it should be displayed, and 0 if it should not be \
displayed. By default, the class will use sub {return 1;}, which will show all \
resources. choiceFunction is a reference to a function that receives the resource \
object as a parameter and returns 1 if it should be a *selectable choice*, and 0 if \
not. By default, this is the same as the filterFunction, which means all displayed \
choices will be choosable. See parm wizard for an example of this in the resource \
selection routines. +
+=back
+
+=cut
+
+no strict;
+@ISA = ("Apache::lonwizard::state");
+use strict;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift);
+
+ $self->{MESSAGE_BEFORE} = shift;
+ $self->{MESSAGE_AFTER} = shift;
+ $self->{NEXT_STATE} = shift;
+ $self->{VAR_NAME} = shift;
+ $self->{FILTER_FUNC} = shift;
+ if (!defined($self->{FILTER_FUNC})) {
+ $self->{FILTER_FUNC} = sub {return 1;};
+ }
+ $self->{CHOICE_FUNC} = shift;
+ if (!defined($self->{CHOICE_FUNC})) {
+ $self->{CHOICE_FUNC} = $self->{FILTER_FUNC};
+ }
+}
+
+sub postprocess {
+ my $self = shift;
+ my $wizard = $self->{WIZARD};
+ my $chosenValue = $ENV{"form." . $self->{VAR_NAME} . ".forminput"};
+ $wizard->setVar($self->{VAR_NAME}, $chosenValue)
+ if (defined($self->{VAR_NAME}));
+
+ $wizard->changeState($self->{NEXT_STATE});
+}
+
+sub render {
+ my $self = shift;
+ my $result = "";
+ my $var = $self->{VAR_NAME};
+ my $curVal = $self->{WIZARD}->{VARS}->{$var};
+
+ $result .= $self->{MESSAGE_BEFORE} if (defined $self->{MESSAGE_BEFORE});
+
+ # Get the course nav map
+ my $navmap = Apache::lonnavmaps::navmap->new(
+ $ENV{"request.course.fn"}.".db",
+ $ENV{"request.course.fn"}."_parms.db", 0, 0);
+
+ if (!defined($navmap)) {
+ return "<font color='red' size='+1'>Something has gone wrong with the map \
selection feature. Please contact your administrator.</font>"; + }
+
+ my $iterator = $navmap->getIterator(undef, undef, undef, 1, 0);
+ my $depth = 1;
+ $iterator->next(); # discard first BEGIN_MAP
+ my $curRes = $iterator->next();
+ my $i;
+ my $padding = " ";
+ my $isChoosable = 0;
+ my $filterFunc = $self->{FILTER_FUNC};
+ my $choiceFunc = $self->{CHOICE_FUNC};
+
+ $result .= "<table border='0'>\n";
+
+ while ($depth > 0) {
+ if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
+ if ($curRes == $iterator->END_MAP()) { $depth--; }
+
+ if (ref($curRes) && &$filterFunc($curRes)) {
+ $result .= "<tr><td>";
+
+ if (&$choiceFunc($curRes)) {
+ if (!$curVal) {
+ # Set this to the first one if they have no previous
+ # selection.
+ $curVal = $curRes->{ID};
+ }
+
+ $isChoosable = 1;
+ $result .= "<input type='radio' name='${var}.forminput'";
+ if ($curRes->{ID} eq $curVal) {
+ $result .= " checked";
+ }
+ $result .= ' value="' . $curRes->{ID} . '" />';
+ }
+
+ $result .= "</td><td>";
+
+ for ($i = 0; $i < $depth; $i++) {
+ $result .= $padding;
+ }
+
+ #$result .= "<img border='0' \
src='/adm/lonIcons/navmap.folder.open.gif'>"; + $result .= \
$curRes->compTitle . "</td></tr>\n"; + }
+
+ $curRes = $iterator->next();
+ }
+
+ $result .= "</table>\n";
+
+ $navmap->untieHashes();
+
+ if (!$isChoosable) {
+ # FIXME: Check all the wiz vars for this.
+ $result .= "<p><font color='#ff0000'>There are no valid resources to select \
in this course.</font> The entire course will be selected by default (as if 1you \
selected "Set for Whole Course" on the previous screen).</p>"; + \
$result .= "<input type='hidden' name='${var}.forminput' value='0.0' />\n"; + }
+
+ $result .= "<p>(Note: I need to add the icons in.)</p>";
+ $result .= $self->{MESSAGE_AFTER} if (defined $self->{MESSAGE_AFTER});
+
+ return $result;
+}
+
+1;
+
+package Apache::lonwizard::choose_student;
+
+no strict;
+@ISA = ("Apache::lonwizard::choice_state");
+use strict;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift, shift,
+ shift, shift, shift);
+ return $self;
+}
+
+sub determineChoices {
+ my %choices;
+
+ my $classlist = Apache::loncoursedata::get_classlist();
+ foreach (keys %$classlist) {
+ $choices{$classlist->{$_}->[6]} = $_;
+ }
+
+ return \%choices;
+}
+
+1;
+
+package Apache::lonwizard::choose_section;
+
+no strict;
+@ISA = ("Apache::lonwizard::choice_state");
+use strict;
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = bless $proto->SUPER::new(shift, shift, shift, shift,
+ shift, shift, shift);
+ return $self;
+}
+
+sub determineChoices {
+ my %choices;
+
+ my $classlist = Apache::loncoursedata::get_classlist();
+ foreach (keys %$classlist) {
+ my $sectionName = $classlist->{$_}->[5];
+ if (!$sectionName) {
+ $choices{"No section assigned"} = "";
+ } else {
+ $choices{$sectionName} = $sectionName;
+ }
+ }
+
+ return \%choices;
+}
+
+1;
+
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic