File:  [LON-CAPA] / loncom / html / adm / help / tex / Developer_Tutorial.tex
Revision 1.6: download - view: text, annotated - select for diffs
Thu Apr 7 06:56:22 2005 UTC (19 years, 3 months ago) by albertel
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_99_0, version_2_9_1, version_2_9_0, version_2_8_X, version_2_8_99_1, version_2_8_99_0, version_2_8_2, version_2_8_1, version_2_8_0, version_2_7_X, version_2_7_99_1, version_2_7_99_0, version_2_7_1, version_2_7_0, version_2_6_X, version_2_6_99_1, version_2_6_99_0, version_2_6_3, version_2_6_2, version_2_6_1, version_2_6_0, version_2_5_X, version_2_5_99_1, version_2_5_99_0, version_2_5_2, version_2_5_1, version_2_5_0, version_2_4_X, version_2_4_99_0, version_2_4_2, version_2_4_1, version_2_4_0, version_2_3_X, version_2_3_99_0, version_2_3_2, version_2_3_1, version_2_3_0, version_2_2_X, version_2_2_99_1, version_2_2_99_0, version_2_2_2, version_2_2_1, version_2_2_0, version_2_1_X, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_11_0_RC2, version_2_11_0_RC1, version_2_10_X, version_2_10_1, version_2_10_0_RC2, version_2_10_0_RC1, version_2_10_0, version_2_0_X, version_2_0_99_1, version_2_0_2, version_2_0_1, version_2_0_0, version_1_99_3, version_1_99_2, version_1_99_1_tmcc, version_1_99_1, version_1_99_0_tmcc, version_1_99_0, loncapaMITrelate_1, language_hyphenation_merge, language_hyphenation, bz6209-base, bz6209, bz5969, bz2851, PRINT_INCOMPLETE_base, PRINT_INCOMPLETE, HEAD, GCI_3, GCI_2, GCI_1, BZ5971-printing-apage, BZ5434-fox, BZ4492-merge, BZ4492-feature_horizontal_radioresponse
- ENV -> env

    1: \label{Developer_Tutorial}
    2: 
    3: \section{Adding a Handler to LON-CAPA}
    4: 
    5: In this section, we will add a brand new {}``handler'' to LON-CAPA.
    6: A \textbf{handler\index{handler}} is code that gets loaded by the
    7: Apache web server that tells the web server how to handle a certain
    8: kind of request. For a complete discussion of this process, you will
    9: need to read the Apache documentation; we especially recommend the
   10: O'Reilly book {}``Writing Apache Modules with Perl and C''. In this
   11: chapter we will add a {}``Response'' handler; this is the most important
   12: handler in the system because it is directly responsible for what
   13: the user sees on the screen when they end up using the handler.
   14: 
   15: As a side-effect of installing a new handler from scratch, we will
   16: also show how to modify the files LON-CAPA uses and run LON-CAPA with
   17: the modified files. 
   18: 
   19: After completing this tutorial you should be able to more easily understand
   20: the structure of the vast majority of LON-CAPA code.
   21: 
   22: If you are brave and are not using Red Hat to run LON-CAPA, the \texttt{/etc/httpd}
   23: and \texttt{/var/log/httpd} directories may not exist, as your distribution
   24: may prefer to refer to them as \texttt{/etc/apache} and \texttt{/etc/log/apache},
   25: such as Gentoo does. In that case, I recommend symlinking \texttt{/etc/httpd}
   26: to \texttt{/etc/apache} (LON-CAPA depends on \texttt{/etc/httpd} existing),
   27: and either make the appropriate substitutions in the following directions
   28: for the log files, or make a symlink there, too. (\texttt{ln -s /etc/apache
   29: /etc/httpd})
   30: 
   31: 
   32: \subsection{Pre-requisites}
   33: 
   34: \begin{itemize}
   35: \item LON-CAPA must be installed and running.
   36: \item \textbf{Perl}: You're going to need to know some or you will not understand
   37: the code. You don't need to be intimately familiar with the many hundreds
   38: of libraries Perl has, though, which is much harder than learning
   39: the language.
   40: \item Knowledge of the Apache server is not required for this tutorial,
   41: but {}``real work'' may require more information then is given here.
   42: \end{itemize}
   43: 
   44: \subsection{Adding a New Handler}
   45: 
   46: You need to create a \emph{new Perl module} to serve as the handler.
   47: A {}``Perl module''\index{Perl module} is a file that has a name
   48: ending in {}``.pm''. For the sake of concreteness, I will refer
   49: to this file as \texttt{lontutorial.pm}%
   50: \footnote{Currently, almost all LON-CAPA Perl modules are prefixed with {}``lon''.
   51: This is a weak convention used inconsistently, and theoretically it
   52: will be dropped from the project someday. %
   53: }.
   54: 
   55: Create this new file where ever you'd like for now. 
   56: 
   57: 
   58: \subsubsection{Apache Handler Shell}
   59: 
   60: Put the following code into your Perl module: 
   61: 
   62: \begin{lyxcode}
   63: package~Apache::lontutorial;
   64: 
   65: ~
   66: 
   67: use~strict;
   68: 
   69: use~Apache::Constants~qw(:common);
   70: 
   71: use~Apache::loncommon;
   72: 
   73: ~
   74: 
   75: =pod
   76: 
   77: ~
   78: 
   79: =head1~NAME
   80: 
   81: ~
   82: 
   83: lontutorial~-~tutorial~code
   84: 
   85: ~
   86: 
   87: =head1~SYNOPSIS
   88: 
   89: ~
   90: 
   91: lontutorial~contains~the~code~from~the~tutorial,~and
   92: 
   93: does~tutorial~stuff.
   94: 
   95: ~
   96: 
   97: =cut
   98: 
   99: ~
  100: 
  101: sub~handler~\{
  102: 
  103: ~~~~my~\$r~=~shift;
  104: 
  105: ~
  106: 
  107: ~~~~\$r->print('Hello~world!');
  108: 
  109: 
  110: 
  111: ~~~~return~OK;
  112: 
  113: \}
  114: 
  115: ~
  116: 
  117: 1;
  118: \end{lyxcode}
  119: A line-by-line breakdown:
  120: 
  121: \begin{itemize}
  122: \item \texttt{\textbf{package Apache::lontutorial;}}: This puts everything
  123: in the file in the \texttt{Apache::lontutorial} namespace. Note that
  124: the \texttt{lontutorial} part should match your filename before the
  125: \texttt{.pm}. (The file may contain other packages; see \texttt{lonnavmaps.pm}
  126: or \texttt{lonhelper.pm} for an example. But this package should show
  127: up in the file or Apache may have trouble finding your file.)
  128: \item \texttt{\textbf{use strict;}}: Turns on strict variable checking,
  129: which is a Very Good Idea. If you intend to submit the code back to
  130: the main LON-CAPA project, please make sure to use this.
  131: \item \texttt{\textbf{use Apache::Constants qw(:common);}}: This loads certain
  132: constants used to communicate with Apache into the current namespace.
  133: \item \texttt{\textbf{use Apache::loncommon;}}: This loads some commonly-used
  134: functions we will use later. See \texttt{man Apache::loncommon} for
  135: more information.
  136: \item \texttt{\textbf{=pod}} through \texttt{\textbf{=cut}}: This contains
  137: inline documentation in the POD format. Please see \texttt{man perlpod}
  138: for more information. I encourage you to document your code. Note
  139: many current LON-CAPA modules do not contain good examples. Please
  140: consult \texttt{perldoc lonhelp.pm} in the \texttt{loncom/interface}
  141: directory of the CVS repository, or see the \texttt{lonhelp.pm} information
  142: later in the developer manual, for style considerations. The documentation
  143: so produced can be easily added to this Developer's Manual if it would
  144: be useful to others, so you're accomplishing the dual purpose of documenting
  145: in the file and the manual at the same time.
  146: \item \texttt{\textbf{sub handler \{}}: Apache will call the \texttt{handler}
  147: function in the namespace it is given for the handler in the \texttt{loncapa\_apache.conf}
  148: file; see below. Thus, we need to supply it, or Apache will fail and
  149: complain about a missing subroutine.
  150: \item \texttt{\textbf{my \$r = shift;}}: The first and only parameter to
  151: the handler function is the Apache Response object. We will partially
  152: cover this object in this tutorial, for more information consult outside
  153: documentation.
  154: \item \texttt{\textbf{\$r->print('Hello world!');}}: \texttt{\$r->print}
  155: is how we sent text to the user. This is the \emph{only} method to
  156: send text to the user's web browser, and in the end, all output ends
  157: up going through this function.
  158: \item \texttt{\textbf{return OK;}}: {}``OK'' is a constant we imported
  159: in our namespace in the use \texttt{Apache::Constants} line. It tells
  160: the server the page was successfully displayed, as opposed to a 404
  161: or other error. 
  162: \item \texttt{\textbf{1;}}: This tells Perl the module was successfully
  163: parsed, and is a required part of Perl modules.
  164: \end{itemize}
  165: 
  166: \subsubsection{Telling Apache About Your Handler}
  167: 
  168: In order for Apache to use your file, you need to tell Apache where
  169: to find it, and what URL(s) to use for it. Add the following to your
  170: \texttt{/etc/httpd/conf/loncapa\_apache.conf}, after the \texttt{PerlTransHandler}
  171: declaration:
  172: 
  173: \begin{lyxcode}
  174: <Location~/adm/tutorial>
  175: 
  176: PerlAccessHandler~~~~~~~Apache::lonacc
  177: 
  178: SetHandler~~~~~~~~~~~~~~perl-script
  179: 
  180: PerlHandler~~~~~~~~~~~~~Apache::lontutorial
  181: 
  182: ErrorDocument~~~~~~500~~/adm/errorhandler
  183: 
  184: </Location>
  185: \end{lyxcode}
  186: This will tell Apache to call Apache::lontutorial::handler when someone
  187: goes to /adm/tutorial to see what to do.
  188: 
  189: Note: The \texttt{Apache::} is not technically necessary in mod\_perl,
  190: it's just that LON-CAPA uses it. We may remove it from the system
  191: someday. In the meantime, it would be consistent with the rest of
  192: the system to continue use it. (If you don't understand what this
  193: is saying, don't worry about it.)
  194: 
  195: 
  196: \subsubsection{Putting The File Where Apache Can Find It}
  197: 
  198: Now you need to put that file where Apache can find it.
  199: 
  200: Apache will look for it in \texttt{/home/httpd/lib/perl/Apache}/,
  201: so copy it there.
  202: 
  203: \begin{lyxcode}
  204: cp~lontutorial~/home/httpd/lib/perl/Apache/
  205: \end{lyxcode}
  206: 
  207: \subsubsection{Running Apache}
  208: 
  209: You can now (re-)start the Apache server. 
  210: 
  211: \begin{lyxcode}
  212: /etc/init.d/httpd~restart
  213: \end{lyxcode}
  214: Also, if you are logged into LON-CAPA, log out (exit) before continuing.
  215: (Note re-starting the server does \emph{not} automatically log you
  216: out, which is really helpful while debugging so you don't have to
  217: continuously log back in.) 
  218: 
  219: If everything went well, you can now go to your server and go to \texttt{http://your.server/adm/tutorial},
  220: with \texttt{your.server} replaced by the domain name of your LON-CAPA
  221: server, of course. (I will use {}``your.server'' that way for the
  222: rest of this tutorial.)
  223: 
  224: And you should see\ldots{} {}``Bad Request''? What happened?
  225: 
  226: You don't know it yet, but we're making a handler for functionality
  227: that will require the user to be logged in for it to work, because
  228: it is going to depend on information about the user. Right now, nobody
  229: is logged in. The \texttt{PerlAccessHandler\index{PerlAccessHandler}}
  230: line in what we put in \texttt{loncapa\_apache.conf} tells Apache
  231: to use {}``Apache::lonacc::handler'' as an \textbf{access handler}\index{access handler}.
  232: An \textbf{access handler} determines whether the user has permission
  233: to perform the current action. lonacc is a handler written for LON-CAPA
  234: that among other things, ensures the user is logged in. This handler
  235: told Apache that this is a {}``Bad Request'', which is why you got
  236: the {}``Bad Request'' screen. Your handler code was actually \textbf{never
  237: even executed}; Apache aborted the request before getting to your
  238: code.
  239: 
  240: Log into LON-CAPA, and choose a role with a course. (You may need
  241: to create a course; please see the Course Management manual for more
  242: information.) Any role with a course, Student, Instructor, Course
  243: Coordinator or otherwise is fine. We won't be making any changes to
  244: the course data so it doesn't matter if it's a {}``real'' course.
  245: Once you've gotten to the first page of the course (normal entry),
  246: change your URL to read \texttt{http://your.server/adm/tutorial}.
  247: 
  248: You should now see {}``Hello world!'' on the screen. 
  249: 
  250: 
  251: \paragraph{Apache can't find \texttt{/adm/tutorial}:}
  252: 
  253: If Apache is claiming it can't find the file named {}``/adm/tutorial'',
  254: something is wrong with your \texttt{loncapa\_apache.conf} file. Please
  255: double check the entry to make sure it is in the right place, that
  256: it is typed correctly, and that all the lines are present. If it is
  257: still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
  258: list, including a copy of your \texttt{loncapa\_apache.conf} file
  259: in the email; either something is wrong that is beyond the scope of
  260: this document(other system configuration issues) or you really can't
  261: find the error.
  262: 
  263: 
  264: \paragraph{{}``Something went wrong, please help us find out what''}
  265: 
  266: If you get an icon of the LON-CAPA logo with a cracked screen and
  267: the preceding message, something is wrong with your Perl code. This
  268: is the standard screen that asks the user to send us more information
  269: about what happened, so we can try to diagnose the problem. Since
  270: you're a developer, you can ignore that screen and actually collect
  271: the information and act on it directly. 
  272: 
  273: The Perl error landed in \texttt{/var/log/httpd/error\_log}, in the
  274: last few lines. Take a look at what was in there, and try to fix it. 
  275: 
  276: \begin{lyxcode}
  277: tail~/var/log/httpd/error\_log
  278: \end{lyxcode}
  279: Since I can't guess what error you encountered, I'm afraid I can't
  280: help you out here. But I do know the code I wrote above worked, so
  281: as a last resort try to figure out what is different. If you are absolutely
  282: stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
  283: list, including a copy of your \texttt{lontutorial.pm} file in the
  284: email; either something is wrong that is beyond the scope of this
  285: document (other system configuration issues) or you really can't find
  286: the error.
  287: 
  288: Actually, if you have the time, I recommend adding a syntax error
  289: to your \texttt{lontutorial.pm}, even if it worked the first time,
  290: so you can see what happens. This will not be the last time there's
  291: an error in your code\ldots{}
  292: 
  293: 
  294: \subsubsection{Modifying The Module}
  295: 
  296: When Apache is started, it reads all the Perl modules in LON-CAPA
  297: into memory, and after that, does not use the \texttt{.pm} files in
  298: \texttt{/home/httpd/lib/perl/Apache}%
  299: \footnote{Slight exaggeration here: Actually it only loads the ones mentioned
  300: in startup.pl, and since your module isn't in that file, it won't
  301: be loaded at startup. But this really doesn't matter much.%
  302: }. To get Apache to use your newly-edited files, you need to copy them
  303: to \texttt{/home/httpd/lib/perl/Apache} and restart the Apache server.
  304: 
  305: It is generally not a good idea to directly modify the files in \texttt{/home/httpd/lib/perl/Apache}
  306: while developing, because of the difficulty you will have later propagating
  307: changes made there back to more useful places, such as in the LON-CAPA
  308: CVS repository. To alleviate the annoyance of constantly copying the
  309: file to that directory and restarting the server, compose a single
  310: command line that does all of it at once. I often use something like
  311: the following:
  312: 
  313: \begin{lyxcode}
  314: pushd~.;~cd~YOUR\_DIRECTORY;~cp~lontutorial.pm
  315: 
  316: ~/home/httpd/lib/perl/Apache;~/etc/init.d/httpd~restart;
  317: 
  318: ~popd
  319: \end{lyxcode}
  320: where YOUR\_DIRECTORY is replaced by the directory containing the
  321: \texttt{lontutorial.pm} file, and that is all one line.
  322: 
  323: The {}``pushd'' and {}``popd'' commands are built-in to the Bash
  324: shell; other shells may or may not contain such commands.
  325: 
  326: Once you've created this command line, you can just use the Up arrow
  327: in your shell to get it back. You can add whatever you want to it
  328: as needed; that's why I wrap it in the {}``pushd'' and {}``popd''
  329: commands, so you can {}``cd'' without affecting your current directory.
  330: 
  331: If you had a syntax error in your Perl module, fix it now. If you
  332: didn't, I'd recommend make a small change to the printed string or
  333: something and making sure you have figured out how to change the code
  334: in the Apache server correctly before continuing. 
  335: 
  336: Once again, notice that even after re-starting the server, you are
  337: still logged in with the same settings you were using before the server
  338: re-start.
  339: 
  340: 
  341: \subsection{Making The Handler Do Something}
  342: 
  343: 
  344: \subsubsection{A Bit More Boilerplate}
  345: 
  346: At this point, your handler isn't doing much. If you look carefully,
  347: you'll notice that the {}``Hello World'' on the screen is in a mono-spaced
  348: font, not the proportional font used by default in HTML. That's because
  349: \emph{all} you sent was {}``Hello World!''; the browser looks at
  350: the header of the HTTP response to see what kind of response it is
  351: getting back, but you didn't even \emph{send} a header. Technically,
  352: that's not a legal HTTP response, but the browser was accommodating
  353: and displayed what you got back anyhow as text. (Consult documentation
  354: on the HTTP protocol for more information about this; generally you
  355: will not need to worry about this in LON-CAPA beyond the boilerplate
  356: presented here.)
  357: 
  358: To send back a proper HTTP response, add this code after the \texttt{my
  359: \$r = shift;}:
  360: 
  361: \begin{lyxcode}
  362: ~~~~\#~Handle~header-only~request
  363: 
  364: ~~~~if~(\$r->header\_only)~\{
  365: 
  366: ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{
  367: 
  368: ~~~~~~~~~~~~\$r->content\_type('text/xml');
  369: 
  370: ~~~~~~~~\}~else~\{
  371: 
  372: ~~~~~~~~~~~~\$r->content\_type('text/html');
  373: 
  374: ~~~~~~~~\}
  375: 
  376: ~~~~~~~~\$r->send\_http\_header;
  377: 
  378: ~~~~~~~~return~OK;
  379: 
  380: ~~~~\}
  381: 
  382: ~~~~\#~Send~header,~don't~cache~this~page
  383: 
  384: ~~~~if~(\$env\{'browser.mathml'\})~\{
  385: 
  386: ~~~~~~~~\$r->content\_type('text/xml');
  387: 
  388: ~~~~\}~else~\{
  389: 
  390: ~~~~~~~~\$r->content\_type('text/html');
  391: 
  392: ~~~~\}
  393: 
  394: ~~~~\&Apache::loncommon::no\_cache(\$r);
  395: 
  396: ~~~~\$r->send\_http\_header;
  397: \end{lyxcode}
  398: This is boilerplate that appears at the top of many LON-CAPA handlers.
  399: It handles the case where the user is requesting that only headers
  400: be sent; unusual, but worth handling. It sets the content type appropriately,
  401: and tells the client not to cache this page, which is appropriate
  402: since it will be dynamic.
  403: 
  404: For efficiency, when you use \texttt{\$r->print()} to send something
  405: to the user, Apache assumes you know what you are doing and will immediately%
  406: \footnote{Actually, if you send a lot of little things, Apache is smart enough
  407: to bundle them together into one big network packet for efficiency.
  408: If you absolutely want Apache to send everything it has to the user
  409: \emph{right now}, use \texttt{\$r->rflush()}\ldots{} but sparingly,
  410: as repeated use can cause a \emph{huge} slowdown.%
  411: } send what you printed across the network. Since the HTTP header is
  412: supposed to precede all content, it is important that you send the
  413: header before you send any other content across the network, so we
  414: ask the Apache server to go ahead and send the header with \texttt{\$r->send\_http\_header()}. 
  415: 
  416: Since the \texttt{Apache::loncommon::no\_cache()} call affects the
  417: header, it must be called before we send it out, because once the
  418: header is sent there's no getting it back.
  419: 
  420: All code that follows this code will send the output to the user directly.
  421: 
  422: Finally, let's add two more lines to make it fit into the system better.
  423: Wrap the following three lines around the {}``Hello World'' line,
  424: as shown:
  425: 
  426: \begin{lyxcode}
  427: \$r->print('<html><head><title>Tutorial~Page</title></head>');
  428: 
  429: \$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','',''));
  430: 
  431: \$r->print('Hello~world!');
  432: 
  433: \$r->print('</body></html>');
  434: \end{lyxcode}
  435: Now use your command line to copy \texttt{lontutorial.pm} and restart
  436: the server. Go to \texttt{/adm/tutorial}, and you'll see a nice looking
  437: page saying {}``Hello world!''.
  438: 
  439: Congratulations, you now have a handler ready to do some real work
  440: with!
  441: 
  442: 
  443: \subsubsection{Rending Navmaps}
  444: 
  445: To start with, let's go ahead and display the navmap for the current
  446: course. 
  447: 
  448: Under \texttt{use strict;}, add this line:
  449: 
  450: \begin{lyxcode}
  451: use~Apache::lonnavmaps;
  452: \end{lyxcode}
  453: Remove the {}``Hello world!'' line and replace it with this:
  454: 
  455: \begin{lyxcode}
  456: \$env\{'form.condition'\}~=~1;
  457: 
  458: my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource{]},
  459: 
  460: ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
  461: 
  462: ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
  463: 
  464: Apache::lonnavmaps::render(\$renderArgs);
  465: \end{lyxcode}
  466: Line by line:
  467: 
  468: \begin{itemize}
  469: \item \texttt{\textbf{use Apache::lonnavmaps;}}: \texttt{Apache::lonnavmaps}
  470: contains routines that help render navmaps. For more information,
  471: see later in this manual or type \texttt{man Apache::lonnavmaps}.
  472: This ensures these routines are loaded into memory.
  473: \item \texttt{\textbf{\$env\{'form.condition'\} = 1;}}: This is an an argument
  474: being passed to the Apache::lonnavmaps::render routine in a rather
  475: unorthodox way. This will cause the navmap to render all of the resources,
  476: by default, except for what we explicitly exclude. Since we're not
  477: going to exclude anything (which we would do with \texttt{\$env\{'form.filter'\}}),
  478: all resources will be shown.
  479: \item \texttt{\textbf{my \$renderArgs\ldots{}\$r\};}}: Since the \texttt{render}
  480: routine takes a \emph{lot} of arguments, the \texttt{render} routine
  481: takes in a hash reference instead of a traditional list of arguments.
  482: For full information about what that function takes, consult the documentation.
  483: \texttt{'cols'} will tell the render function what to render in the
  484: navmap; {}``Apache::lonnavmaps::resource'' is a constant that
  485: indicates the standard listing of the resource with a link 
  486: to the resource, which is the first column of the \textbf{NAV} display
  487: you are used to. \texttt{'showParts' => 0} tells the render function
  488: not to show individual parts, which will not be useful to us. \texttt{'r'
  489: => \$r} passes the Apache response object to the \texttt{render} function,
  490: which it uses to provide periodic output to the user by using the
  491: \texttt{rflush} method on the object.
  492: \item \texttt{\textbf{Apache::lonnavmaps::render(\$renderArgs);}}: Performs
  493: the actual rendering of the navmaps. Since we passed it \texttt{\$r},
  494: it will take care of displaying the navmaps for us. If we didn't pass
  495: it \texttt{\$r}, it would return a string containing the rendered
  496: HTML, which we would be responsible for \texttt{\$r->print}ing.
  497: \end{itemize}
  498: Save this and use your command line to re-load the server. Make sure
  499: you've selected a specific course role before trying to visit \texttt{/adm/tutorial}.
  500: You may need to create a course with a few resources in it first,
  501: depending on your setup.
  502: 
  503: 
  504: \subsubsection{Goal}
  505: 
  506: In order to exercise storing data on the LON-CAPA network, we're going
  507: to do something a little silly, but pedagogically useful. Let's add
  508: two columns to the navmap, one which will provide a checkbox which
  509: we can click, and another which will show how many times we've selected
  510: that particular resource and submitted a form. While this is somewhat
  511: silly, the general principle of doing something for some selection
  512: of resources is a fairly common operation in LON-CAPA, as is storing
  513: or loading data from the network.
  514: 
  515: In doing this, we'll learn:
  516: 
  517: \begin{itemize}
  518: \item How to store data on the LON-CAPA network correctly.
  519: \item How to process incoming form input in LON-CAPA.
  520: \item How to work with and identify resources in LON-CAPA.
  521: \end{itemize}
  522: 
  523: \subsection{Adding A Form For Input}
  524: 
  525: The first thing we need to do to accomplish this is to add an HTML
  526: form to the screen so we have something to submit. Just above the
  527: \texttt{\$env\{'form.condition'\}} line, add the following:
  528: 
  529: \begin{lyxcode}
  530: \$r->print(\char`\"{}<form~method='post'><input~type='hidden'~name='submission'~value='1'~/>\char`\"{});
  531: \end{lyxcode}
  532: This will print out a form element, and a hidden form element named
  533: {}``submission'' with a value of {}``1''. Strictly speaking, this
  534: is not necessary, but this serves as a {}``sentinel'', which makes
  535: it easy to check later whether or not we need to process input with
  536: a single \texttt{if} statement.
  537: 
  538: Just before \texttt{\$r->print({}``</body></html>'');}, add this:
  539: 
  540: \begin{lyxcode}
  541: \$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}Increment\char`\"{}~/></form>');
  542: \end{lyxcode}
  543: If you now use your command line to re-load the server, and you examine
  544: the source code of the page on /adm/tutorial, you should now see a
  545: form, with a hidden value and a submit button, though pressing the
  546: submit button won't do much.
  547: 
  548: 
  549: \subsubsection{Seeing The Incoming Data}
  550: 
  551: LON-CAPA automatically processing incoming POST data and exposes it
  552: to you in the \texttt{\%env} hash. The data will appear in \texttt{\$env\{'form.\$varname'\}},
  553: where \texttt{\$varname} is the variable name of the HTML form element.
  554: In this case, since we have an element named {}``submission'', a
  555: {}``1'' will appear in \texttt{\$env\{'form.submission'\}} when
  556: we hit the {}``Increment'' button. To see this, add the following
  557: after the \texttt{bodytag} call:
  558: 
  559: \begin{lyxcode}
  560: if~(\$env\{'form.submission'\})~\{
  561: 
  562: ~~~~\$r->print('<p>Form~submitted.</p>');
  563: 
  564: \}~else~\{
  565: 
  566: ~~~~\$r->print('<p>No~form~information~submitted.</p>');
  567: 
  568: \}
  569: \end{lyxcode}
  570: Reload the tutorial code into the server. Now, when you type in \texttt{/adm/tutorial},
  571: {}``No form information submitted.'' will appear on the screen.
  572: When you hit {}``Increment'', {}``Form submitted.'' will appear.
  573: 
  574: Note this only applies to POST'ed data. If you use GET, the data will
  575: appear on the query string. For your code, this will show up in \texttt{\$env\{QUERY\_STRING\}}.
  576: If you want to invoke LON-CAPA's processing on that string, so you
  577: see the variables in \texttt{\%env}, use
  578: \texttt{Apache::loncommon::get\_unprocessed\_cgi(\$env\{QUERY\_STRING\});}.
  579: This is particularly useful for cases where input may be coming in via
  580: either POST or GET.
  581: 
  582: 
  583: \subsubsection{Adding Checkboxes for Input}
  584: 
  585: Now we want to add a checkbox column into the navmap rendering so
  586: we have a place to input our selections. In order to do that, we need
  587: to provide the rendering code with a subroutine that will take an
  588: \texttt{Apache::lonnavmaps::resource} object and return the string
  589: we want to print, including the \texttt{td} and 
  590: \texttt{/td} tags. (For more information,
  591: consult the \texttt{Apache::lonnavmaps::render} documentation.) Add
  592: the following after the second and last \texttt{send\_http\_header}
  593: line:
  594: 
  595: \begin{lyxcode}
  596: my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;
  597: 
  598: ~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~'~.
  599: 
  600: ~~~~'name=\char`\"{}symb.'~.
  601: 
  602: ~~~~Apache::lonnet::escape(\$resource->symb())~.~
  603: 
  604: ~~~~'\char`\"{}~/></td>';
  605: 
  606: \};
  607: \end{lyxcode}
  608: A \textbf{symb} is a string which uniquely identifies a resource within
  609: a course, since the same resource can show up in multiple sequences,
  610: or multiple times in the same sequence. Thus, that makes a good ID
  611: for the checkboxes. Given an \texttt{Apache::lonnavmaps::resource}
  612: object, the {}``symb'' method will return the symb. \texttt{Apache::lonnet::escape}
  613: is a function that does URL-escaping on the resulting string, so it's
  614: safe to pass into an HTML attribute. (XML-type escaping would work
  615: too.)
  616: 
  617: Go ahead and load this into the server, so you can see the HTML output
  618: by viewing the source.
  619: 
  620: 
  621: \subsubsection{Getting The Checked Resources}
  622: 
  623: Let's make sure we can retrieve the checked resources before continuing.
  624: Add the following code before the \texttt{\$r->print({}``<form}\ldots{}
  625: code:
  626: 
  627: \begin{lyxcode}
  628: foreach~(keys~\%env)~\{
  629: 
  630: ~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
  631: 
  632: ~~~~~~~~my~\$symb~=~substr(\$\_,~10);
  633: 
  634: ~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
  635: 
  636: ~~~~~~~~\$r->print(\char`\"{}\$symb<br~/>\char`\"{});
  637: 
  638: ~~~~\}
  639: 
  640: \}
  641: \end{lyxcode}
  642: That will print each symb that we select the checkbox for on the screen.
  643: You may remove this later if you find it unsightly.
  644: 
  645: Re-load the code into the server and make sure it's still working.
  646: Try checking a few boxes and hitting {}``Increment'' to make sure
  647: the symbs appear at the top of the screen.
  648: 
  649: 
  650: \subsection{Storing Data on the LON-CAPA Network}
  651: 
  652: Let's suppose you want to record how often you submit a given checkbox.
  653: You can't write to the current server's hard drive, because there's
  654: no guarantee that next time you log on, that you'll get the same server.
  655: (Of course, if you're on a single development system, you can know
  656: that, but if you want your code to be generally useful for LON-CAPA,
  657: you can't depend on being on the same system.)
  658: 
  659: Let's go ahead and retrieve the hash we're going to store. If it doesn't
  660: already exist, it will end up getting created implicitly. Before the
  661: \texttt{foreach (keys \%env)}, add 
  662: 
  663: \begin{lyxcode}
  664: my~\%countHash~=~Apache::lonnet::restore('tutorial');
  665: 
  666: my~\%newHash~=~();
  667: \end{lyxcode}
  668: \texttt{\%countHash} will contain what we've stored. \texttt{\%newHash}
  669: will be used to store new data into the stored hash; please see the
  670: documentation on lonnet to see why this is necessary. 
  671: 
  672: \texttt{'tutorial'} identifies the hash we wish to store; if we make
  673: a later \texttt{restore} call with that parameter we'll get this hash
  674: back. Normally you'll want to choose the name of the hash more carefully,
  675: for instance including the course ID into it, unless you really mean
  676: to do something for all courses. For simplicity, we are leaving that
  677: out.
  678: 
  679: Replace the \texttt{foreach} loop that is printing out the symbs with
  680: this:
  681: 
  682: \begin{lyxcode}
  683: foreach~(keys~\%env)~\{
  684: 
  685: ~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
  686: 
  687: ~~~~~~~~my~\$symb~=~substr(\$\_,~10);
  688: 
  689: ~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
  690: 
  691: ~~~~~~~~\$countHash\{\$symb\}++;
  692: 
  693: ~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
  694: 
  695: ~~~~\}
  696: 
  697: \}
  698: \end{lyxcode}
  699: That will increment the count for each symb that we checked a box
  700: for. We need to store the data the user changed, so just before \texttt{return
  701: OK;}, add the following line of code:
  702: 
  703: \begin{lyxcode}
  704: Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
  705: \end{lyxcode}
  706: 
  707: \subsubsection{Displaying The Count}
  708: 
  709: Finally, we need a column that will display the count of times we've
  710: checked the box. Right after the \texttt{foreach} loop we just modified,
  711: add
  712: 
  713: \begin{lyxcode}
  714: my~\$countCode~=~sub~\{~my~\$resource~=~shift;
  715: 
  716: ~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
  717: 
  718: ~~~~\$countHash\{\$resource->symb()\}~.
  719: 
  720: ~~~~\char`\"{}</td>\char`\"{};
  721: 
  722: ~~\};
  723: \end{lyxcode}
  724: Add \texttt{\$countCode} to the 'cols' list in the \texttt{\$renderArgs}: 
  725: 
  726: \begin{lyxcode}
  727: my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
  728: 
  729: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
  730: 
  731: ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
  732: 
  733: ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
  734: \end{lyxcode}
  735: Viola! That should do it.
  736: 
  737: 
  738: \subsection{Internationalization}
  739: 
  740: In order for your handler to be able to submitted to LON-CAPA, we'd
  741: really appreciate it if your handler was already
  742: internationalized. ``Internationalization'' refers to the process of
  743: adding hooks to code to make it easy to ``localize''. ``Localizing'' a
  744: program means taking advantage of those hooks and create a file that
  745: describes to the LON-CAPA web system how to display LON-CAPA in a
  746: language other than English.
  747: 
  748: For more complete information about Internationalization and
  749: Localization, please refer to the Internationalization chapter. For
  750: now, suffice it to say we need to wrap the text strings we are using
  751: in the program in calls to the hook subroutine, which will be named
  752: \texttt{\&mt}. 
  753: 
  754: First, at the top of your handler, near the other \texttt{use}
  755: commands, add \texttt{use Apache::lonlocal;}.
  756: 
  757: Second, find where all the strings are in your program and wrap them
  758: in calls to \texttt{\&mt}. I see the following:
  759: 
  760: \begin{enumerate}
  761: 
  762: \item In the \texttt{bodytag} call, the title:
  763: 
  764: \begin{lyxcode}
  765: \$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
  766: 
  767: \end{lyxcode}
  768: 
  769: \item In the two messages printed out showing the form submissions:
  770:   (Of course in this case these are really just debugging messages
  771:   we'd remove before actually using this handler. But let's localize
  772:   them for practice)
  773: 
  774: \begin{lyxcode}
  775: if~(\$env{'form.submission'})~{
  776: 
  777: ~~~~\$r->print('<p>'.\&mt('Form submitted').'</p>');
  778: 
  779: }~else~{
  780: 
  781: ~~~~\$r->print('<p>'.\&mt('No form information submitted.').'</p>');
  782: 
  783: }
  784: 
  785: \end{lyxcode}
  786: 
  787: Note we do \emph{not} generally want to wrap HTML tags unless we are
  788: absolutely forced to; those are constant across human languages and
  789: would only burder the translators with stuff they should not need to
  790: deal with.
  791: 
  792: \item The label of the button we've created:
  793: 
  794: \begin{lyxcode}
  795: \$r->print('<input type=\char`\"{}submit\char`\"{} value=\char`\"{}'~.
  796: 
  797: ~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
  798: 
  799: \end{lyxcode}
  800: 
  801: \end{enumerate}
  802: 
  803: Note we only need to wrap things the human user will see; we don't
  804: need to wrap the \texttt{tutorial} parameter to the
  805: \texttt{Apache::lonnet::restore} call, for instance, and in fact wierd
  806: things could happen if we did! Also note that resource names and such
  807: are already as internationalized as they are going to get, so we don't
  808: need to worry about them.
  809: 
  810: Since the internationalization system will return the value passed to
  811: \texttt{\&mt} by default if it can't find a translation, it's safe to
  812: internationalize code before translations exist, and in fact it's a
  813: necessary step.
  814: 
  815: Also note that punctuation should be wrapped in the \texttt{\&mt}
  816: calls, including things like trailing periods, since not all languages
  817: have the same punctuation standards as English.
  818: 
  819: This only covers simple internationalization. This can take you a long
  820: way, but if you encounter a more difficult problem, please send a note
  821: to the \texttt{lon-capa-dev@mail.lon-capa.org} mailing list.
  822: 
  823: \subsection{Complete Code Listing}
  824: 
  825: For your reference, I include the complete code listing for the tutorial
  826: here:
  827: 
  828: \begin{lyxcode}
  829: package~Apache::lontutorial;
  830: 
  831: ~
  832: 
  833: use~strict;
  834: 
  835: use~Apache::lonnavmaps;
  836: 
  837: use~Apache::Constants~qw(:common);
  838: 
  839: use~Apache::lonlocal;
  840: 
  841: ~
  842: 
  843: =pod
  844: 
  845: ~
  846: 
  847: =head1~NAME
  848: 
  849: ~
  850: 
  851: lontutorial~-~tutorial~code
  852: 
  853: ~
  854: 
  855: =head1~SYNOPSIS
  856: 
  857: ~
  858: 
  859: lontutorial~contains~the~code~from~the~tutorial,~and
  860: 
  861: does~tutorial~stuff.
  862: 
  863: ~
  864: 
  865: =cut
  866: 
  867: ~
  868: 
  869: sub~handler~\{
  870: 
  871: ~~~~my~\$r~=~shift;
  872: 
  873: 
  874: 
  875: ~~~~\#~Handle~header-only~request
  876: 
  877: ~~~~if~(\$r->header\_only)~\{
  878: 
  879: ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{
  880: 
  881: ~~~~~~~~~~~~\$r->content\_type('text/xml');
  882: 
  883: ~~~~~~~~\}~else~\{
  884: 
  885: ~~~~~~~~~~~~\$r->content\_type('text/html');
  886: 
  887: ~~~~~~~~\}
  888: 
  889: ~~~~~~~~\$r->send\_http\_header;
  890: 
  891: ~~~~~~~~return~OK;
  892: 
  893: ~~~~\}
  894: 
  895: ~~~~\#~Send~header,~don't~cache~this~page
  896: 
  897: ~~~~if~(\$env\{'browser.mathml'\})~\{
  898: 
  899: ~~~~~~~~\$r->content\_type('text/xml');
  900: 
  901: ~~~~\}~else~\{
  902: 
  903: ~~~~~~~~\$r->content\_type('text/html');
  904: 
  905: ~~~~\}
  906: 
  907: ~~~~\&Apache::loncommon::no\_cache(\$r);
  908: 
  909: ~~~~\$r->send\_http\_header;
  910: 
  911: 
  912: 
  913: ~~~~my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;~
  914: 
  915: ~~~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~name=\char`\"{}symb.'~.
  916: 
  917: ~~~~~~~~~~Apache::lonnet::escape(\$resource->symb())~.~'\char`\"{}~/></td>';
  918: 
  919: ~~~~~~~~~~~~~~~~~~~~~~~~~\};
  920: 
  921: 
  922: 
  923: ~~~~\$r->print('<html><head><title>Tutorial~Page</title></head>');
  924: 
  925: ~~~~\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
  926: 
  927: 
  928: 
  929: ~~~~if~(\$env\{'form.submission'\})~\{
  930: 
  931: ~~~~~~~~\$r->print('<p>'~.~\&mt('Form~submitted.')~.'</p>');
  932: 
  933: ~~~~\}~else~\{
  934: 
  935: ~~~~~~~~\$r->print('<p>'~.~\&mt('No~form~information~submitted.')~.~'</p>');
  936: 
  937: ~~~~\}
  938: 
  939: 
  940: 
  941: ~~~~my~\%countHash~=~Apache::lonnet::restore('tutorial');
  942: 
  943: ~~~~my~\%newHash~=~();
  944: 
  945: ~~~~foreach~(keys~\%env)~\{
  946: 
  947: ~~~~~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
  948: 
  949: ~~~~~~~~~~~~my~\$symb~=~substr(\$\_,~10);
  950: 
  951: ~~~~~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
  952: 
  953: ~~~~~~~~~~~~\$countHash\{\$symb\}++;
  954: 
  955: ~~~~~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
  956: 
  957: ~~~~~~~~\}
  958: 
  959: ~~~~\}
  960: 
  961: 
  962: 
  963: ~~~~my~\$countCode~=~sub~\{~my~\$resource~=~shift;
  964: 
  965: ~~~~~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
  966: 
  967: ~~~~~~~~\$countHash\{\$resource->symb()\}~.
  968: 
  969: ~~~~~~~~\char`\"{}</td>\char`\"{};
  970: 
  971: ~~~~~~\};
  972: 
  973: 
  974: 
  975: ~~~~\$r->print(\char`\"{}<form~method='post'><input~type='hidden'\char`\"{}~.
  976: 
  977: ~~~~~~~~~~~~~~\char`\"{}name='submission'~value='1'~/>\char`\"{});
  978: 
  979: ~~~~\$env\{'form.condition'\}~=~1;
  980: 
  981: ~~~~my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
  982: 
  983: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
  984: 
  985: ~~~~~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
  986: 
  987: ~~~~~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
  988: 
  989: ~~~~Apache::lonnavmaps::render(\$renderArgs);
  990: 
  991: 
  992: 
  993: ~~~~\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}'~.
  994: 
  995: ~~~~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
  996: 
  997: ~~~~\$r->print('</body></html>');
  998: 
  999: 
 1000: 
 1001: ~~~~Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
 1002: 
 1003: 
 1004: 
 1005: ~~~~return~OK;
 1006: 
 1007: \}
 1008: 
 1009: 
 1010: 
 1011: 1;
 1012: 
 1013: \end{lyxcode}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>