--- loncom/interface/lonhelper.pm 2003/04/17 17:21:24 1.12 +++ loncom/interface/lonhelper.pm 2003/04/30 15:18:36 1.13 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # .helper XML handler to implement the LON-CAPA helper # -# $Id: lonhelper.pm,v 1.12 2003/04/17 17:21:24 bowersj2 Exp $ +# $Id: lonhelper.pm,v 1.13 2003/04/30 15:18:36 bowersj2 Exp $ # # Copyright Michigan State University Board of Trustees # @@ -84,6 +84,73 @@ Of course this does nothing. In order fo necessary to put actual elements into the wizard. Documentation for each of these elements follows. +=head2 Creating a Helper With Code, Not XML + +In some situations, such as the printing wizard (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. + +Creating a helper via code is more like issuing commands to create +a helper then normal code writing. For instance, elements will automatically +be added to the last state created, so it's important to create the +states in the correct order. + +First, create a new helper: + + use Apache::lonhelper; + + my $helper = Apache::lonhelper::new->("Helper Title"); + +Next you'll need to manually add states to the helper: + + Apache::lonhelper::state->new("STATE_NAME", "State's Human Title"); + +You don't need to save a reference to it because all elements up until +the next state creation will automatically be added to this state. + +Elements are created by populating the $paramHash in +Apache::lonhelper::paramhash. To prevent namespace issues, retrieve +a reference to that has with getParamHash: + + my $paramHash = Apache::lonhelper::getParamHash(); + +You will need to do this for each state you create. + +Populate the $paramHash with the parameters for the element you wish +to add next; the easiest way to find out what those entries are is +to read the code. Some common ones are 'variable' to record the variable +to store the results in, and NEXTSTATE to record a next state transition. + +Then create your element: + + $paramHash->{MESSAGETEXT} = "This is a message."; + Apache::lonhelper::message->new(); + +The creation will take the $paramHash and bless it into a +Apache::lonhelper::message object. To create the next element, you need +to get a reference to the new, empty $paramHash: + + $paramHash = Apache::lonhelper::getParamHash(); + +and you can repeat creating elements that way. You can add states +and elements as needed. + +See lonprintout.pm, subroutine printHelper for an example of this, where +we dynamically add some states to prevent security problems, for instance. + +Normally the machinery in the XML format is sufficient; dynamically +adding states can easily be done by wrapping the state in a +tag. This should only be used when the code dominates the XML content, +the code is so complicated that it is difficult to get access to +all of the information you need because of scoping issues, or so much +of the information used is persistent because would-be or + blocks that using the {DATA} mechanism results in hard-to-read +and -maintain code. + +It is possible to do some of the work with an XML fragment parsed by +lonxml; again, see lonprintout.pm for an example. + =cut package Apache::lonhelper; @@ -122,10 +189,15 @@ my $substate; # end of the element tag is located. my $paramHash; +# For debugging purposes, one can send a second parameter into this +# function, the 'uri' of the helper you wish to have rendered, and +# call this from other handlers. sub handler { my $r = shift; - $ENV{'request.uri'} = $r->uri(); - my $filename = '/home/httpd/html' . $r->uri(); + my $uri = shift; + if (!defined($uri)) { $uri = $r->uri(); } + $ENV{'request.uri'} = $uri; + my $filename = '/home/httpd/html' . $uri; my $fh = Apache::File->new($filename); my $file; read $fh, $file, 100000000; @@ -154,10 +226,24 @@ sub handler { # xml parsing &Apache::lonxml::xmlparse($r, 'helper', $file); + $helper->process(); + $r->print($helper->display()); return OK; } +sub registerHelperTags { + for my $tagList (@helperTags) { + Apache::lonxml::register($tagList->[0], $tagList->[1]); + } +} + +sub unregisterHelperTags { + for my $tagList (@helperTags) { + Apache::lonxml::deregister($tagList->[0], $tagList->[1]); + } +} + sub start_helper { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; @@ -165,11 +251,9 @@ sub start_helper { return ''; } - for my $tagList (@helperTags) { - Apache::lonxml::register($tagList->[0], $tagList->[1]); - } - - $helper = Apache::lonhelper::helper->new($token->[2]{'title'}); + registerHelperTags(); + + Apache::lonhelper::helper->new($token->[2]{'title'}); return ''; } @@ -180,9 +264,7 @@ sub end_helper { return ''; } - for my $tagList (@helperTags) { - Apache::lonxml::deregister($tagList->[0], $tagList->[1]); - } + unregisterHelperTags(); return ''; } @@ -194,11 +276,22 @@ sub start_state { return ''; } - $state = Apache::lonhelper::state->new($token->[2]{'name'}, - $token->[2]{'title'}); + Apache::lonhelper::state->new($token->[2]{'name'}, + $token->[2]{'title'}); return ''; } +# Use this to get the param hash from other files. +sub getParamHash { + return $paramHash; +} + +# Use this to get the helper, if implementing elements in other files +# (like lonprintout.pm) +sub getHelper { + return $helper; +} + # don't need this, so ignore it sub end_state { return ''; @@ -287,6 +380,11 @@ sub new { # for an example. $self->{DATA} = {}; + $helper = $self; + + # Establish the $paramHash + $paramHash = {}; + bless($self, $class); return $self; } @@ -347,23 +445,15 @@ sub registerState { $self->{STATES}{$stateName} = $state; } -# Done in four 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 { +sub process { my $self = shift; - my $result = ""; - # 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 "Next ->") { my $prevState = $self->{STATES}{$self->{STATE}}; - $prevState->postprocess(); + $prevState->postprocess(); } # Note, to handle errors in a state's input that a user must correct, @@ -374,11 +464,10 @@ sub display { my $startState = $self->{STATE}; my $state = $self->{STATES}{$startState}; - # Error checking; it is intended that the developer will have - # checked all paths and the user can't see this! + # For debugging, print something here to determine if you're going + # to an undefined state. if (!defined($state)) { - $result .="Error! The state ". $startState ." is not defined."; - return $result; + return; } $state->preprocess(); @@ -391,6 +480,21 @@ sub display { $state->preprocess(); } + return; +} + +# 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 $state = $self->{STATES}{$self->{STATE}}; + + my $result = ""; + # Phase 4: Display. my $stateTitle = $state->title(); my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'',''); @@ -430,9 +534,9 @@ HEADER $result .= "\n"; } - foreach my $key (keys %{$self->{VARS}}) { - $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; - } + #foreach my $key (keys %{$self->{VARS}}) { + # $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; + #} $result .= < @@ -474,6 +578,8 @@ sub new { $helper->registerState($self); + $state = $self; + return $self; } @@ -517,7 +623,16 @@ sub postprocess { } } +# Override the form if any element wants to. +# two elements overriding the form will make a mess, but that should +# be considered helper author error ;-) sub overrideForm { + my $self = shift; + for my $element (@{$self->{ELEMENTS}}) { + if ($element->overrideForm()) { + return 1; + } + } return 0; } @@ -616,6 +731,10 @@ sub render { return ''; } +sub overrideForm { + return 0; +} + sub process_multiple_choices { my $self = shift; my $formname = shift; @@ -766,6 +885,11 @@ B For example, Bobby McDormik. + can take a parameter "eval", which if set to + a true value, will cause the contents of the tag to be + evaluated as it would be in an tag; see tag + below. + may optionally contain a 'nextstate' attribute, which will be the state transisitoned to if the choice is made, if the choice is not multichoice. @@ -846,7 +970,9 @@ sub start_choice { my $human = &Apache::lonxml::get_all_text('/choice', $parser); my $nextstate = $token->[2]{'nextstate'}; - push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate]; + my $evalFlag = $token->[2]{'eval'}; + push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate, + $evalFlag]; return ''; } @@ -900,7 +1026,14 @@ BUTTONS $result .= " checked "; $checked = 1; } - $result .= "/> " . $choice->[0] . "\n"; + my $choiceLabel = $choice->[0]; + if ($choice->[4]) { # if we need to evaluate this choice + $choiceLabel = "sub { my $helper = shift; my $state = shift;" . + $choiceLabel . "}"; + $choiceLabel = eval($choiceLabel); + $choiceLabel = &$choiceLabel($helper, $self); + } + $result .= "/> " . $choiceLabel . "\n"; } $result .= "\n\n\n"; $result .= $buttons; @@ -1192,6 +1325,9 @@ B "}" returns a string representing what you want to have as the value. By default, the value will be the resource ID of the object ($res->{ID}). +=item * : If the URL of a map is given here, only that map + will be displayed, instead of the whole course. + =back =cut @@ -1203,7 +1339,8 @@ use strict; BEGIN { &Apache::lonhelper::register('Apache::lonhelper::resource', ('resource', 'filterfunc', - 'choicefunc', 'valuefunc')); + 'choicefunc', 'valuefunc', + 'mapurl')); } sub new { @@ -1288,6 +1425,20 @@ sub start_valuefunc { sub end_valuefunc { return ''; } +sub start_mapurl { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + + my $contents = Apache::lonxml::get_all_text('/mapurl', + $parser); + $paramHash->{MAP_URL} = eval $contents; +} + +sub end_mapurl { return ''; } + # A note, in case I don't get to this before I leave. # If someone complains about the "Back" button returning them # to the previous folder state, instead of returning them to @@ -1313,6 +1464,7 @@ sub render { my $filterFunc = $self->{FILTER_FUNC}; my $choiceFunc = $self->{CHOICE_FUNC}; my $valueFunc = $self->{VALUE_FUNC}; + my $mapUrl = $self->{MAP_URL}; # Create the composite function that renders the column on the nav map # have to admit any language that lets me do this can't be all bad @@ -1341,9 +1493,9 @@ sub render { &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc, Apache::lonnavmaps::resource()], 'showParts' => 0, - 'url' => $helper->{URL}, 'filterFunc' => $filterFunc, - 'resource_no_folder_link' => 1 } + 'resource_no_folder_link' => 1, + 'iterator_map' => $mapUrl } ); return $result; @@ -1929,7 +2081,148 @@ sub end_eval { Apache::lonhelper::message->new(); } +1; + +package Apache::lonhelper::parmwizfinal; + +# This is the final state for the parmwizard. It is not generally useful, +# so it is not perldoc'ed. It does its own processing. +# It is represented with , and +# should later be moved to lonparmset.pm . + +no strict; +@ISA = ('Apache::lonhelper::element'); +use strict; +BEGIN { + &Apache::lonhelper::register('Apache::lonhelper::parmwizfinal', + ('parmwizfinal')); +} + +use Time::localtime; + +sub new { + my $ref = Apache::lonhelper::choices->new(); + bless ($ref); +} + +sub start_parmwizfinal { return ''; } + +sub end_parmwizfinal { + my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + + if ($target ne 'helper') { + return ''; + } + Apache::lonhelper::parmwizfinal->new(); +} + +# Renders a form that, when submitted, will form the input to lonparmset.pm +sub render { + my $self = shift; + my $vars = $helper->{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 = "
\n"; + $result .= '

Confirm that this information is correct, then click "Finish Wizard" to complete setting the parameter.

    '; + my $affectedResourceId = ""; + my $parm_name = $parmTypeHash{$vars->{ACTION_TYPE}}; + my $level = ""; + + # Print the type of manipulation: + $result .= '
  • Setting the ' . $dateTypeHash{$vars->{ACTION_TYPE}} + . "
  • \n"; + if ($vars->{ACTION_TYPE} eq 'due_date' || + $vars->{ACTION_TYPE} eq 'answer_date') { + # for due dates, we default to "date end" type entries + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; + } elsif ($vars->{ACTION_TYPE} eq 'open_date') { + $result .= "\n"; + $result .= "\n"; + $result .= "\n"; + } + + # Print the granularity, depending on the action + if ($vars->{GRANULARITY} eq 'whole_course') { + $result .= '
  • for all resources in the course
  • '; + $level = 9; # general course, see lonparmset.pm perldoc + $affectedResourceId = "0.0"; + } elsif ($vars->{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($vars->{RESOURCE_ID}); + my $title = $res->compTitle(); + $navmap->untieHashes(); + $result .= "
  • for the map named $title
  • "; + $level = 8; + $affectedResourceId = $vars->{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($vars->{RESOURCE_ID}); + my $title = $res->compTitle(); + $navmap->untieHashes(); + $result .= "
  • for the resource named $title
  • "; + $level = 7; + $affectedResourceId = $vars->{RESOURCE_ID}; + } + + # Print targets + if ($vars->{TARGETS} eq 'course') { + $result .= '
  • for all students in course
  • '; + } elsif ($vars->{TARGETS} eq 'section') { + my $section = $vars->{SECTION_NAME}; + $result .= "
  • for section $section
  • "; + $level -= 3; + $result .= "\n"; + } else { + # FIXME: This is probably wasteful! Store the name! + my $classlist = Apache::loncoursedata::get_classlist(); + my $name = $classlist->{$vars->{USER_NAME}}->[6]; + $result .= "
  • for $name
  • "; + $level -= 6; + my ($uname, $udom) = split /:/, $vars->{USER_NAME}; + $result .= "\n"; + $result .= "\n"; + } + + # Print value + $result .= "
  • to " . ctime($vars->{PARM_DATE}) . " (" . + Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}) + . ")
  • \n"; + + # print pres_marker + $result .= "\n\n"; + + $result .= "

    \n"; + + return $result; +} + +sub overrideForm { + return 1; +} 1;