Annotation of loncom/html/adm/help/tex/Developer_Tutorial.tex, revision 1.3

1.1       bowersj2    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
1.2       bowersj2  255: still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
1.1       bowersj2  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
1.2       bowersj2  280: stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
1.1       bowersj2  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
1.2       bowersj2  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
1.1       bowersj2  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
1.2       bowersj2  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.
1.1       bowersj2  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
1.2       bowersj2  587: we want to print, including the \texttt{td} and 
                    588: \texttt{/td} tags. (For more information,
1.1       bowersj2  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: 
1.3     ! bowersj2  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: 
1.1       bowersj2  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: 
1.3     ! bowersj2  837: use~Apache::lonlocal;
        !           838: 
1.1       bowersj2  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: 
1.3     ! bowersj2  923: ~~~~\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
1.1       bowersj2  924: 
                    925: 
                    926: 
                    927: ~~~~if~(\$ENV\{'form.submission'\})~\{
                    928: 
1.3     ! bowersj2  929: ~~~~~~~~\$r->print('<p>'~.~\&mt('Form~submitted.')~.'</p>');
1.1       bowersj2  930: 
                    931: ~~~~\}~else~\{
                    932: 
1.3     ! bowersj2  933: ~~~~~~~~\$r->print('<p>'~.~\&mt('No~form~information~submitted.')~.~'</p>');
1.1       bowersj2  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: 
1.3     ! bowersj2  991: ~~~~\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}'~.
        !           992: 
        !           993: ~~~~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
1.1       bowersj2  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>