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

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
        !           255: still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.msu.edu}
        !           256: list, including a copy of your \texttt{loncapa\_apache.conf} file
        !           257: in the email; either something is wrong that is beyond the scope of
        !           258: this document(other system configuration issues) or you really can't
        !           259: find the error.
        !           260: 
        !           261: 
        !           262: \paragraph{{}``Something went wrong, please help us find out what''}
        !           263: 
        !           264: If you get an icon of the LON-CAPA logo with a cracked screen and
        !           265: the preceding message, something is wrong with your Perl code. This
        !           266: is the standard screen that asks the user to send us more information
        !           267: about what happened, so we can try to diagnose the problem. Since
        !           268: you're a developer, you can ignore that screen and actually collect
        !           269: the information and act on it directly. 
        !           270: 
        !           271: The Perl error landed in \texttt{/var/log/httpd/error\_log}, in the
        !           272: last few lines. Take a look at what was in there, and try to fix it. 
        !           273: 
        !           274: \begin{lyxcode}
        !           275: tail~/var/log/httpd/error\_log
        !           276: \end{lyxcode}
        !           277: Since I can't guess what error you encountered, I'm afraid I can't
        !           278: help you out here. But I do know the code I wrote above worked, so
        !           279: as a last resort try to figure out what is different. If you are absolutely
        !           280: stumped, please ask for help on the \texttt{lon-capa-dev@mail.msu.edu}
        !           281: list, including a copy of your \texttt{lontutorial.pm} file in the
        !           282: email; either something is wrong that is beyond the scope of this
        !           283: document (other system configuration issues) or you really can't find
        !           284: the error.
        !           285: 
        !           286: Actually, if you have the time, I recommend adding a syntax error
        !           287: to your \texttt{lontutorial.pm}, even if it worked the first time,
        !           288: so you can see what happens. This will not be the last time there's
        !           289: an error in your code\ldots{}
        !           290: 
        !           291: 
        !           292: \subsubsection{Modifying The Module}
        !           293: 
        !           294: When Apache is started, it reads all the Perl modules in LON-CAPA
        !           295: into memory, and after that, does not use the \texttt{.pm} files in
        !           296: \texttt{/home/httpd/lib/perl/Apache}%
        !           297: \footnote{Slight exaggeration here: Actually it only loads the ones mentioned
        !           298: in startup.pl, and since your module isn't in that file, it won't
        !           299: be loaded at startup. But this really doesn't matter much.%
        !           300: }. To get Apache to use your newly-edited files, you need to copy them
        !           301: to \texttt{/home/httpd/lib/perl/Apache} and restart the Apache server.
        !           302: 
        !           303: It is generally not a good idea to directly modify the files in \texttt{/home/httpd/lib/perl/Apache}
        !           304: while developing, because of the difficulty you will have later propagating
        !           305: changes made there back to more useful places, such as in the LON-CAPA
        !           306: CVS repository. To alleviate the annoyance of constantly copying the
        !           307: file to that directory and restarting the server, compose a single
        !           308: command line that does all of it at once. I often use something like
        !           309: the following:
        !           310: 
        !           311: \begin{lyxcode}
        !           312: pushd~.;~cd~YOUR\_DIRECTORY;~cp~lontutorial.pm
        !           313: 
        !           314: ~/home/httpd/lib/perl/Apache;~/etc/init.d/httpd~restart;
        !           315: 
        !           316: ~popd
        !           317: \end{lyxcode}
        !           318: where YOUR\_DIRECTORY is replaced by the directory containing the
        !           319: \texttt{lontutorial.pm} file, and that is all one line.
        !           320: 
        !           321: The {}``pushd'' and {}``popd'' commands are built-in to the Bash
        !           322: shell; other shells may or may not contain such commands.
        !           323: 
        !           324: Once you've created this command line, you can just use the Up arrow
        !           325: in your shell to get it back. You can add whatever you want to it
        !           326: as needed; that's why I wrap it in the {}``pushd'' and {}``popd''
        !           327: commands, so you can {}``cd'' without affecting your current directory.
        !           328: 
        !           329: If you had a syntax error in your Perl module, fix it now. If you
        !           330: didn't, I'd recommend make a small change to the printed string or
        !           331: something and making sure you have figured out how to change the code
        !           332: in the Apache server correctly before continuing. 
        !           333: 
        !           334: Once again, notice that even after re-starting the server, you are
        !           335: still logged in with the same settings you were using before the server
        !           336: re-start.
        !           337: 
        !           338: 
        !           339: \subsection{Making The Handler Do Something}
        !           340: 
        !           341: 
        !           342: \subsubsection{A Bit More Boilerplate}
        !           343: 
        !           344: At this point, your handler isn't doing much. If you look carefully,
        !           345: you'll notice that the {}``Hello World'' on the screen is in a mono-spaced
        !           346: font, not the proportional font used by default in HTML. That's because
        !           347: \emph{all} you sent was {}``Hello World!''; the browser looks at
        !           348: the header of the HTTP response to see what kind of response it is
        !           349: getting back, but you didn't even \emph{send} a header. Technically,
        !           350: that's not a legal HTTP response, but the browser was accommodating
        !           351: and displayed what you got back anyhow as text. (Consult documentation
        !           352: on the HTTP protocol for more information about this; generally you
        !           353: will not need to worry about this in LON-CAPA beyond the boilerplate
        !           354: presented here.)
        !           355: 
        !           356: To send back a proper HTTP response, add this code after the \texttt{my
        !           357: \$r = shift;}:
        !           358: 
        !           359: \begin{lyxcode}
        !           360: ~~~~\#~Handle~header-only~request
        !           361: 
        !           362: ~~~~if~(\$r->header\_only)~\{
        !           363: 
        !           364: ~~~~~~~~if~(\$ENV\{'browser.mathml'\})~\{
        !           365: 
        !           366: ~~~~~~~~~~~~\$r->content\_type('text/xml');
        !           367: 
        !           368: ~~~~~~~~\}~else~\{
        !           369: 
        !           370: ~~~~~~~~~~~~\$r->content\_type('text/html');
        !           371: 
        !           372: ~~~~~~~~\}
        !           373: 
        !           374: ~~~~~~~~\$r->send\_http\_header;
        !           375: 
        !           376: ~~~~~~~~return~OK;
        !           377: 
        !           378: ~~~~\}
        !           379: 
        !           380: ~~~~\#~Send~header,~don't~cache~this~page
        !           381: 
        !           382: ~~~~if~(\$ENV\{'browser.mathml'\})~\{
        !           383: 
        !           384: ~~~~~~~~\$r->content\_type('text/xml');
        !           385: 
        !           386: ~~~~\}~else~\{
        !           387: 
        !           388: ~~~~~~~~\$r->content\_type('text/html');
        !           389: 
        !           390: ~~~~\}
        !           391: 
        !           392: ~~~~\&Apache::loncommon::no\_cache(\$r);
        !           393: 
        !           394: ~~~~\$r->send\_http\_header;
        !           395: \end{lyxcode}
        !           396: This is boilerplate that appears at the top of many LON-CAPA handlers.
        !           397: It handles the case where the user is requesting that only headers
        !           398: be sent; unusual, but worth handling. It sets the content type appropriately,
        !           399: and tells the client not to cache this page, which is appropriate
        !           400: since it will be dynamic.
        !           401: 
        !           402: For efficiency, when you use \texttt{\$r->print()} to send something
        !           403: to the user, Apache assumes you know what you are doing and will immediately%
        !           404: \footnote{Actually, if you send a lot of little things, Apache is smart enough
        !           405: to bundle them together into one big network packet for efficiency.
        !           406: If you absolutely want Apache to send everything it has to the user
        !           407: \emph{right now}, use \texttt{\$r->rflush()}\ldots{} but sparingly,
        !           408: as repeated use can cause a \emph{huge} slowdown.%
        !           409: } send what you printed across the network. Since the HTTP header is
        !           410: supposed to precede all content, it is important that you send the
        !           411: header before you send any other content across the network, so we
        !           412: ask the Apache server to go ahead and send the header with \texttt{\$r->send\_http\_header()}. 
        !           413: 
        !           414: Since the \texttt{Apache::loncommon::no\_cache()} call affects the
        !           415: header, it must be called before we send it out, because once the
        !           416: header is sent there's no getting it back.
        !           417: 
        !           418: All code that follows this code will send the output to the user directly.
        !           419: 
        !           420: Finally, let's add two more lines to make it fit into the system better.
        !           421: Wrap the following three lines around the {}``Hello World'' line,
        !           422: as shown:
        !           423: 
        !           424: \begin{lyxcode}
        !           425: \$r->print('<html><head><title>Tutorial~Page</title></head>');
        !           426: 
        !           427: \$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','',''));
        !           428: 
        !           429: \$r->print('Hello~world!');
        !           430: 
        !           431: \$r->print('</body></html>');
        !           432: \end{lyxcode}
        !           433: Now use your command line to copy \texttt{lontutorial.pm} and restart
        !           434: the server. Go to \texttt{/adm/tutorial}, and you'll see a nice looking
        !           435: page saying {}``Hello world!''.
        !           436: 
        !           437: Congratulations, you now have a handler ready to do some real work
        !           438: with!
        !           439: 
        !           440: 
        !           441: \subsubsection{Rending Navmaps}
        !           442: 
        !           443: To start with, let's go ahead and display the navmap for the current
        !           444: course. 
        !           445: 
        !           446: Under \texttt{use strict;}, add this line:
        !           447: 
        !           448: \begin{lyxcode}
        !           449: use~Apache::lonnavmaps;
        !           450: \end{lyxcode}
        !           451: Remove the {}``Hello world!'' line and replace it with this:
        !           452: 
        !           453: \begin{lyxcode}
        !           454: \$ENV\{'form.condition'\}~=~1;
        !           455: 
        !           456: my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource{]},
        !           457: 
        !           458: ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
        !           459: 
        !           460: ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
        !           461: 
        !           462: Apache::lonnavmaps::render(\$renderArgs);
        !           463: \end{lyxcode}
        !           464: Line by line:
        !           465: 
        !           466: \begin{itemize}
        !           467: \item \texttt{\textbf{use Apache::lonnavmaps;}}: \texttt{Apache::lonnavmaps}
        !           468: contains routines that help render navmaps. For more information,
        !           469: see later in this manual or type \texttt{man Apache::lonnavmaps}.
        !           470: This ensures these routines are loaded into memory.
        !           471: \item \texttt{\textbf{\$ENV\{'form.condition'\} = 1;}}: This is an an argument
        !           472: being passed to the Apache::lonnavmaps::render routine in a rather
        !           473: unorthodox way. This will cause the navmap to render all of the resources,
        !           474: by default, except for what we explicitly exclude. Since we're not
        !           475: going to exclude anything (which we would do with \texttt{\$ENV\{'form.filter'\}}),
        !           476: all resources will be shown.
        !           477: \item \texttt{\textbf{my \$renderArgs\ldots{}\$r\};}}: Since the \texttt{render}
        !           478: routine takes a \emph{lot} of arguments, the \texttt{render} routine
        !           479: takes in a hash reference instead of a traditional list of arguments.
        !           480: For full information about what that function takes, consult the documentation.
        !           481: \texttt{'cols'} will tell the render function what to render in the
        !           482: navmap; {}``0'' is the standard listing of the resource with a link
        !           483: to the resource. \texttt{'showParts' => 0} tells the render function
        !           484: not to show individual parts, which will not be useful to us. \texttt{'r'
        !           485: => \$r} passes the Apache response object to the \texttt{render} function,
        !           486: which it uses to provide periodic output to the user by using the
        !           487: \texttt{rflush} method on the object.
        !           488: \item \texttt{\textbf{Apache::lonnavmaps::render(\$renderArgs);}}: Performs
        !           489: the actual rendering of the navmaps. Since we passed it \texttt{\$r},
        !           490: it will take care of displaying the navmaps for us. If we didn't pass
        !           491: it \texttt{\$r}, it would return a string containing the rendered
        !           492: HTML, which we would be responsible for \texttt{\$r->print}ing.
        !           493: \end{itemize}
        !           494: Save this and use your command line to re-load the server. Make sure
        !           495: you've selected a specific course role before trying to visit \texttt{/adm/tutorial}.
        !           496: You may need to create a course with a few resources in it first,
        !           497: depending on your setup.
        !           498: 
        !           499: 
        !           500: \subsubsection{Goal}
        !           501: 
        !           502: In order to exercise storing data on the LON-CAPA network, we're going
        !           503: to do something a little silly, but pedagogically useful. Let's add
        !           504: two columns to the navmap, one which will provide a checkbox which
        !           505: we can click, and another which will show how many times we've selected
        !           506: that particular resource and submitted a form. While this is somewhat
        !           507: silly, the general principle of doing something for some selection
        !           508: of resources is a fairly common operation in LON-CAPA, as is storing
        !           509: or loading data from the network.
        !           510: 
        !           511: In doing this, we'll learn:
        !           512: 
        !           513: \begin{itemize}
        !           514: \item How to store data on the LON-CAPA network correctly.
        !           515: \item How to process incoming form input in LON-CAPA.
        !           516: \item How to work with and identify resources in LON-CAPA.
        !           517: \end{itemize}
        !           518: 
        !           519: \subsection{Adding A Form For Input}
        !           520: 
        !           521: The first thing we need to do to accomplish this is to add an HTML
        !           522: form to the screen so we have something to submit. Just above the
        !           523: \texttt{\$ENV\{'form.condition'\}} line, add the following:
        !           524: 
        !           525: \begin{lyxcode}
        !           526: \$r->print(\char`\"{}<form~method='post'><input~type='hidden'~name='submission'~value='1'~/>\char`\"{});
        !           527: \end{lyxcode}
        !           528: This will print out a form element, and a hidden form element named
        !           529: {}``submission'' with a value of {}``1''. Strictly speaking, this
        !           530: is not necessary, but this serves as a {}``sentinel'', which makes
        !           531: it easy to check later whether or not we need to process input with
        !           532: a single \texttt{if} statement.
        !           533: 
        !           534: Just before \texttt{\$r->print({}``</body></html>'');}, add this:
        !           535: 
        !           536: \begin{lyxcode}
        !           537: \$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}Increment\char`\"{}~/></form>');
        !           538: \end{lyxcode}
        !           539: If you now use your command line to re-load the server, and you examine
        !           540: the source code of the page on /adm/tutorial, you should now see a
        !           541: form, with a hidden value and a submit button, though pressing the
        !           542: submit button won't do much.
        !           543: 
        !           544: 
        !           545: \subsubsection{Seeing The Incoming Data}
        !           546: 
        !           547: LON-CAPA automatically processing incoming POST data and exposes it
        !           548: to you in the \texttt{\%ENV} hash. The data will appear in \texttt{\$ENV\{'form.\$varname'\}},
        !           549: where \texttt{\$varname} is the variable name of the HTML form element.
        !           550: In this case, since we have an element named {}``submission'', a
        !           551: {}``1'' will appear in \texttt{\$ENV\{'form.submission'\}} when
        !           552: we hit the {}``Increment'' button. To see this, add the following
        !           553: after the \texttt{bodytag} call:
        !           554: 
        !           555: \begin{lyxcode}
        !           556: if~(\$ENV\{'form.submission'\})~\{
        !           557: 
        !           558: ~~~~\$r->print('<p>Form~submitted.</p>');
        !           559: 
        !           560: \}~else~\{
        !           561: 
        !           562: ~~~~\$r->print('<p>No~form~information~submitted.</p>');
        !           563: 
        !           564: \}
        !           565: \end{lyxcode}
        !           566: Reload the tutorial code into the server. Now, when you type in \texttt{/adm/tutorial},
        !           567: {}``No form information submitted.'' will appear on the screen.
        !           568: When you hit {}``Increment'', {}``Form submitted.'' will appear.
        !           569: 
        !           570: Note this only applies to POST'ed data. If you use GET, the data will
        !           571: appear on the query string. For your code, this will show up in \texttt{\$ENV\{QUERY\_STRING\}}.
        !           572: If you want to invoke LON-CAPA's processing on that string, so you
        !           573: see the variables in \texttt{\%ENV}, use \texttt{Apache::loncommon::get\_unprocessed\_cgi(\$ENV\{QUERY\_STRING\});}.
        !           574: 
        !           575: 
        !           576: \subsubsection{Adding Checkboxes for Input}
        !           577: 
        !           578: Now we want to add a checkbox column into the navmap rendering so
        !           579: we have a place to input our selections. In order to do that, we need
        !           580: to provide the rendering code with a subroutine that will take an
        !           581: \texttt{Apache::lonnavmaps::resource} object and return the string
        !           582: we want to print, including the <td> and </td> tags. (For more information,
        !           583: consult the \texttt{Apache::lonnavmaps::render} documentation.) Add
        !           584: the following after the second and last \texttt{send\_http\_header}
        !           585: line:
        !           586: 
        !           587: \begin{lyxcode}
        !           588: my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;
        !           589: 
        !           590: ~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~'~.
        !           591: 
        !           592: ~~~~'name=\char`\"{}symb.'~.
        !           593: 
        !           594: ~~~~Apache::lonnet::escape(\$resource->symb())~.~
        !           595: 
        !           596: ~~~~'\char`\"{}~/></td>';
        !           597: 
        !           598: \};
        !           599: \end{lyxcode}
        !           600: A \textbf{symb} is a string which uniquely identifies a resource within
        !           601: a course, since the same resource can show up in multiple sequences,
        !           602: or multiple times in the same sequence. Thus, that makes a good ID
        !           603: for the checkboxes. Given an \texttt{Apache::lonnavmaps::resource}
        !           604: object, the {}``symb'' method will return the symb. \texttt{Apache::lonnet::escape}
        !           605: is a function that does URL-escaping on the resulting string, so it's
        !           606: safe to pass into an HTML attribute. (XML-type escaping would work
        !           607: too.)
        !           608: 
        !           609: Go ahead and load this into the server, so you can see the HTML output
        !           610: by viewing the source.
        !           611: 
        !           612: 
        !           613: \subsubsection{Getting The Checked Resources}
        !           614: 
        !           615: Let's make sure we can retrieve the checked resources before continuing.
        !           616: Add the following code before the \texttt{\$r->print({}``<form}\ldots{}
        !           617: code:
        !           618: 
        !           619: \begin{lyxcode}
        !           620: foreach~(keys~\%ENV)~\{
        !           621: 
        !           622: ~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
        !           623: 
        !           624: ~~~~~~~~my~\$symb~=~substr(\$\_,~10);
        !           625: 
        !           626: ~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
        !           627: 
        !           628: ~~~~~~~~\$r->print(\char`\"{}\$symb<br~/>\char`\"{});
        !           629: 
        !           630: ~~~~\}
        !           631: 
        !           632: \}
        !           633: \end{lyxcode}
        !           634: That will print each symb that we select the checkbox for on the screen.
        !           635: You may remove this later if you find it unsightly.
        !           636: 
        !           637: Re-load the code into the server and make sure it's still working.
        !           638: Try checking a few boxes and hitting {}``Increment'' to make sure
        !           639: the symbs appear at the top of the screen.
        !           640: 
        !           641: 
        !           642: \subsection{Storing Data on the LON-CAPA Network}
        !           643: 
        !           644: Let's suppose you want to record how often you submit a given checkbox.
        !           645: You can't write to the current server's hard drive, because there's
        !           646: no guarantee that next time you log on, that you'll get the same server.
        !           647: (Of course, if you're on a single development system, you can know
        !           648: that, but if you want your code to be generally useful for LON-CAPA,
        !           649: you can't depend on being on the same system.)
        !           650: 
        !           651: Let's go ahead and retrieve the hash we're going to store. If it doesn't
        !           652: already exist, it will end up getting created implicitly. Before the
        !           653: \texttt{foreach (keys \%ENV)}, add 
        !           654: 
        !           655: \begin{lyxcode}
        !           656: my~\%countHash~=~Apache::lonnet::restore('tutorial');
        !           657: 
        !           658: my~\%newHash~=~();
        !           659: \end{lyxcode}
        !           660: \texttt{\%countHash} will contain what we've stored. \texttt{\%newHash}
        !           661: will be used to store new data into the stored hash; please see the
        !           662: documentation on lonnet to see why this is necessary. 
        !           663: 
        !           664: \texttt{'tutorial'} identifies the hash we wish to store; if we make
        !           665: a later \texttt{restore} call with that parameter we'll get this hash
        !           666: back. Normally you'll want to choose the name of the hash more carefully,
        !           667: for instance including the course ID into it, unless you really mean
        !           668: to do something for all courses. For simplicity, we are leaving that
        !           669: out.
        !           670: 
        !           671: Replace the \texttt{foreach} loop that is printing out the symbs with
        !           672: this:
        !           673: 
        !           674: \begin{lyxcode}
        !           675: foreach~(keys~\%ENV)~\{
        !           676: 
        !           677: ~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
        !           678: 
        !           679: ~~~~~~~~my~\$symb~=~substr(\$\_,~10);
        !           680: 
        !           681: ~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
        !           682: 
        !           683: ~~~~~~~~\$countHash\{\$symb\}++;
        !           684: 
        !           685: ~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
        !           686: 
        !           687: ~~~~\}
        !           688: 
        !           689: \}
        !           690: \end{lyxcode}
        !           691: That will increment the count for each symb that we checked a box
        !           692: for. We need to store the data the user changed, so just before \texttt{return
        !           693: OK;}, add the following line of code:
        !           694: 
        !           695: \begin{lyxcode}
        !           696: Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
        !           697: \end{lyxcode}
        !           698: 
        !           699: \subsubsection{Displaying The Count}
        !           700: 
        !           701: Finally, we need a column that will display the count of times we've
        !           702: checked the box. Right after the \texttt{foreach} loop we just modified,
        !           703: add
        !           704: 
        !           705: \begin{lyxcode}
        !           706: my~\$countCode~=~sub~\{~my~\$resource~=~shift;
        !           707: 
        !           708: ~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
        !           709: 
        !           710: ~~~~\$countHash\{\$resource->symb()\}~.
        !           711: 
        !           712: ~~~~\char`\"{}</td>\char`\"{};
        !           713: 
        !           714: ~~\};
        !           715: \end{lyxcode}
        !           716: Add \texttt{\$countCode} to the 'cols' list in the \texttt{\$renderArgs}: 
        !           717: 
        !           718: \begin{lyxcode}
        !           719: my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
        !           720: 
        !           721: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
        !           722: 
        !           723: ~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
        !           724: 
        !           725: ~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
        !           726: \end{lyxcode}
        !           727: Viola! That should do it.
        !           728: 
        !           729: 
        !           730: \subsection{Complete Code Listing}
        !           731: 
        !           732: For your reference, I include the complete code listing for the tutorial
        !           733: here:
        !           734: 
        !           735: \begin{lyxcode}
        !           736: package~Apache::lontutorial;
        !           737: 
        !           738: ~
        !           739: 
        !           740: use~strict;
        !           741: 
        !           742: use~Apache::lonnavmaps;
        !           743: 
        !           744: use~Apache::Constants~qw(:common);
        !           745: 
        !           746: ~
        !           747: 
        !           748: =pod
        !           749: 
        !           750: ~
        !           751: 
        !           752: =head1~NAME
        !           753: 
        !           754: ~
        !           755: 
        !           756: lontutorial~-~tutorial~code
        !           757: 
        !           758: ~
        !           759: 
        !           760: =head1~SYNOPSIS
        !           761: 
        !           762: ~
        !           763: 
        !           764: lontutorial~contains~the~code~from~the~tutorial,~and
        !           765: 
        !           766: does~tutorial~stuff.
        !           767: 
        !           768: ~
        !           769: 
        !           770: =cut
        !           771: 
        !           772: ~
        !           773: 
        !           774: sub~handler~\{
        !           775: 
        !           776: ~~~~my~\$r~=~shift;
        !           777: 
        !           778: 
        !           779: 
        !           780: ~~~~\#~Handle~header-only~request
        !           781: 
        !           782: ~~~~if~(\$r->header\_only)~\{
        !           783: 
        !           784: ~~~~~~~~if~(\$ENV\{'browser.mathml'\})~\{
        !           785: 
        !           786: ~~~~~~~~~~~~\$r->content\_type('text/xml');
        !           787: 
        !           788: ~~~~~~~~\}~else~\{
        !           789: 
        !           790: ~~~~~~~~~~~~\$r->content\_type('text/html');
        !           791: 
        !           792: ~~~~~~~~\}
        !           793: 
        !           794: ~~~~~~~~\$r->send\_http\_header;
        !           795: 
        !           796: ~~~~~~~~return~OK;
        !           797: 
        !           798: ~~~~\}
        !           799: 
        !           800: ~~~~\#~Send~header,~don't~cache~this~page
        !           801: 
        !           802: ~~~~if~(\$ENV\{'browser.mathml'\})~\{
        !           803: 
        !           804: ~~~~~~~~\$r->content\_type('text/xml');
        !           805: 
        !           806: ~~~~\}~else~\{
        !           807: 
        !           808: ~~~~~~~~\$r->content\_type('text/html');
        !           809: 
        !           810: ~~~~\}
        !           811: 
        !           812: ~~~~\&Apache::loncommon::no\_cache(\$r);
        !           813: 
        !           814: ~~~~\$r->send\_http\_header;
        !           815: 
        !           816: 
        !           817: 
        !           818: ~~~~my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;~
        !           819: 
        !           820: ~~~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~name=\char`\"{}symb.'~.
        !           821: 
        !           822: ~~~~~~~~~~Apache::lonnet::escape(\$resource->symb())~.~'\char`\"{}~/></td>';
        !           823: 
        !           824: ~~~~~~~~~~~~~~~~~~~~~~~~~\};
        !           825: 
        !           826: 
        !           827: 
        !           828: ~~~~\$r->print('<html><head><title>Tutorial~Page</title></head>');
        !           829: 
        !           830: ~~~~\$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','',''));
        !           831: 
        !           832: 
        !           833: 
        !           834: ~~~~if~(\$ENV\{'form.submission'\})~\{
        !           835: 
        !           836: ~~~~~~~~\$r->print('<p>Form~submitted.</p>');
        !           837: 
        !           838: ~~~~\}~else~\{
        !           839: 
        !           840: ~~~~~~~~\$r->print('<p>No~form~information~submitted.</p>');
        !           841: 
        !           842: ~~~~\}
        !           843: 
        !           844: 
        !           845: 
        !           846: ~~~~my~\%countHash~=~Apache::lonnet::restore('tutorial');
        !           847: 
        !           848: ~~~~my~\%newHash~=~();
        !           849: 
        !           850: ~~~~foreach~(keys~\%ENV)~\{
        !           851: 
        !           852: ~~~~~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
        !           853: 
        !           854: ~~~~~~~~~~~~my~\$symb~=~substr(\$\_,~10);
        !           855: 
        !           856: ~~~~~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
        !           857: 
        !           858: ~~~~~~~~~~~~\$countHash\{\$symb\}++;
        !           859: 
        !           860: ~~~~~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
        !           861: 
        !           862: ~~~~~~~~\}
        !           863: 
        !           864: ~~~~\}
        !           865: 
        !           866: 
        !           867: 
        !           868: ~~~~my~\$countCode~=~sub~\{~my~\$resource~=~shift;
        !           869: 
        !           870: ~~~~~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
        !           871: 
        !           872: ~~~~~~~~\$countHash\{\$resource->symb()\}~.
        !           873: 
        !           874: ~~~~~~~~\char`\"{}</td>\char`\"{};
        !           875: 
        !           876: ~~~~~~\};
        !           877: 
        !           878: 
        !           879: 
        !           880: ~~~~\$r->print(\char`\"{}<form~method='post'><input~type='hidden'\char`\"{}~.
        !           881: 
        !           882: ~~~~~~~~~~~~~~\char`\"{}name='submission'~value='1'~/>\char`\"{});
        !           883: 
        !           884: ~~~~\$ENV\{'form.condition'\}~=~1;
        !           885: 
        !           886: ~~~~my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
        !           887: 
        !           888: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
        !           889: 
        !           890: ~~~~~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
        !           891: 
        !           892: ~~~~~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
        !           893: 
        !           894: ~~~~Apache::lonnavmaps::render(\$renderArgs);
        !           895: 
        !           896: 
        !           897: 
        !           898: ~~~~\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}Increment\char`\"{}~/></form>');
        !           899: 
        !           900: ~~~~\$r->print('</body></html>');
        !           901: 
        !           902: 
        !           903: 
        !           904: ~~~~Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
        !           905: 
        !           906: 
        !           907: 
        !           908: ~~~~return~OK;
        !           909: 
        !           910: \}
        !           911: 
        !           912: 
        !           913: 
        !           914: 1;
        !           915: 
        !           916: \end{lyxcode}

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