[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>&nbsp;</p>";
+
+    if (!$state->overrideForm()) {
+        $result .= '<center>';
+        if ($self->{STATE} ne $self->{START_STATE})
+        {
+            #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" \
/>&nbsp;&nbsp;'; +        }
+        if ($self->{DONE})
+        {
+            my $returnPage = $self->{RETURN_PAGE};
+            $result .= "<a href=\"$returnPage\">End Wizard</a>";
+        }
+        else
+        {
+            $result .= '<input name="SUBMIT" type="submit" value="Next -&gt;" />';
+        }
+        $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'>&nbsp;</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 &quot;Finish \
Wizard&quot; 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 = "&nbsp;&nbsp;";
+    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 &quot;Set for Whole Course&quot; 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