[LON-CAPA-cvs] cvs: loncom /interface lonhelper.pm

bowersj2 lon-capa-cvs@mail.lon-capa.org
Wed, 30 Apr 2003 15:18:36 -0000


This is a MIME encoded message

--bowersj21051715916
Content-Type: text/plain

bowersj2		Wed Apr 30 11:18:36 2003 EDT

  Modified files:              
    /loncom/interface	lonhelper.pm 
  Log:
  Make it possible to create helpers manually, by exposing the functionality
  necessary to do that.
  
  
--bowersj21051715916
Content-Type: text/plain
Content-Disposition: attachment; filename="bowersj2-20030430111836.txt"

Index: loncom/interface/lonhelper.pm
diff -u loncom/interface/lonhelper.pm:1.12 loncom/interface/lonhelper.pm:1.13
--- loncom/interface/lonhelper.pm:1.12	Thu Apr 17 13:21:24 2003
+++ loncom/interface/lonhelper.pm	Wed Apr 30 11:18:36 2003
@@ -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 @@
 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 <condition>
+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 <exec> or 
+<eval> 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 @@
 # 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 @@
     # 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 @@
         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 @@
         return '';
     }
 
-    for my $tagList (@helperTags) {
-        Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
-    }
+    unregisterHelperTags();
 
     return '';
 }
@@ -194,11 +276,22 @@
         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 @@
     # for an example.
     $self->{DATA} = {};
 
+    $helper = $self;
+
+    # Establish the $paramHash
+    $paramHash = {};
+
     bless($self, $class);
     return $self;
 }
@@ -347,23 +445,15 @@
     $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 @@
     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 @@
 	$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 @@
         $result .= "</center>\n";
     }
 
-    foreach my $key (keys %{$self->{VARS}}) {
-        $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
-    }
+    #foreach my $key (keys %{$self->{VARS}}) {
+    #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
+    #}
 
     $result .= <<FOOTER;
               </td>
@@ -474,6 +578,8 @@
 
     $helper->registerState($self);
 
+    $state = $self;
+
     return $self;
 }
 
@@ -517,7 +623,16 @@
     }
 }
 
+# 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 @@
     return '';
 }
 
+sub overrideForm {
+    return 0;
+}
+
 sub process_multiple_choices {
     my $self = shift;
     my $formname = shift;
@@ -766,6 +885,11 @@
       For example,  
       <choice computer='234-12-7312'>Bobby McDormik</choice>.
 
+      <choice> 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 <eval> tag; see <eval> tag
+      below.
+
 <choice> 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 @@
     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 @@
             $result .= " checked ";
             $checked = 1;
         }
-        $result .= "/></td><td> " . $choice->[0] . "</td></tr>\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 .= "/></td><td> " . $choiceLabel . "</td></tr>\n";
     }
     $result .= "</table>\n\n\n";
     $result .= $buttons;
@@ -1192,6 +1325,9 @@
   "}" 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 * <mapurl>: 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 @@
 BEGIN {
     &Apache::lonhelper::register('Apache::lonhelper::resource',
                               ('resource', 'filterfunc', 
-                               'choicefunc', 'valuefunc'));
+                               'choicefunc', 'valuefunc',
+                               'mapurl'));
 }
 
 sub new {
@@ -1288,6 +1425,20 @@
 
 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 @@
     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 @@
         &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 @@
     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 <parmwizfinal />, 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 = "<form name='wizform' 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{$vars->{ACTION_TYPE}};
+    my $level = "";
+    
+    # Print the type of manipulation:
+    $result .= '<li>Setting the <b>' . $dateTypeHash{$vars->{ACTION_TYPE}}
+               . "</b></li>\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 .= "<input type='hidden' name='recent_date_end' " .
+            "value='" . $vars->{PARM_DATE} . "' />\n";
+        $result .= "<input type='hidden' name='pres_value' " . 
+            "value='" . $vars->{PARM_DATE} . "' />\n";
+        $result .= "<input type='hidden' name='pres_type' " .
+            "value='date_end' />\n";
+    } elsif ($vars->{ACTION_TYPE} eq 'open_date') {
+        $result .= "<input type='hidden' name='recent_date_start' ".
+            "value='" . $vars->{PARM_DATE} . "' />\n";
+        $result .= "<input type='hidden' name='pres_value' " .
+            "value='" . $vars->{PARM_DATE} . "' />\n";
+        $result .= "<input type='hidden' name='pres_type' " .
+            "value='date_start' />\n";
+    } 
+    
+    # Print the granularity, depending on the action
+    if ($vars->{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 ($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 .= "<li>for the map named <b>$title</b></li>";
+        $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 .= "<li>for the resource named <b>$title</b></li>";
+        $level = 7;
+        $affectedResourceId = $vars->{RESOURCE_ID};
+    }
+
+    # Print targets
+    if ($vars->{TARGETS} eq 'course') {
+        $result .= '<li>for <b>all students in course</b></li>';
+    } elsif ($vars->{TARGETS} eq 'section') {
+        my $section = $vars->{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! Store the name!
+        my $classlist = Apache::loncoursedata::get_classlist();
+        my $name = $classlist->{$vars->{USER_NAME}}->[6];
+        $result .= "<li>for <b>$name</b></li>";
+        $level -= 6;
+        my ($uname, $udom) = split /:/, $vars->{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($vars->{PARM_DATE}) . "</b> (" .
+        Apache::lonnavmaps::timeToHumanString($vars->{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 Helper' /></center></form>\n";
+
+    return $result;
+}
+    
+sub overrideForm {
+    return 1;
+}
 
 1;
 

--bowersj21051715916--