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

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

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