File:  [LON-CAPA] / loncom / html / adm / help / tex / Developer_Tutorial.tex
Revision 1.3: download - view: text, annotated - select for diffs
Mon Sep 22 19:07:59 2003 UTC (20 years, 11 months ago) by bowersj2
Branches: MAIN
CVS tags: version_1_1_X, version_1_1_3, version_1_1_2, version_1_1_1, version_1_1_0, version_1_0_99_3, version_1_0_99_2, version_1_0_99_1, version_1_0_99, HEAD
Internationalize the Developer Tutorial, since this seems like one of
those baseline things we'd like all new developers to know.

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

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