\section{Adding a Handler to LON-CAPA}
In this section, we will add a brand new {}``handler'' to LON-CAPA.
A \textbf{handler\index{handler}} is code that gets loaded by the
Apache web server that tells the web server how to handle a certain
kind of request. For a complete discussion of this process, you will
need to read the Apache documentation; we especially recommend the
O'Reilly book {}``Writing Apache Modules with Perl and C''. In this
chapter we will add a {}``Response'' handler; this is the most important
handler in the system because it is directly responsible for what
the user sees on the screen when they end up using the handler.
As a side-effect of installing a new handler from scratch, we will
also show how to modify the files LON-CAPA uses and run LON-CAPA with
the modified files.
After completing this tutorial you should be able to more easily understand
the structure of the vast majority of LON-CAPA code.
If you are brave and are not using Red Hat to run LON-CAPA, the \texttt{/etc/httpd}
and \texttt{/var/log/httpd} directories may not exist, as your distribution
may prefer to refer to them as \texttt{/etc/apache} and \texttt{/etc/log/apache},
such as Gentoo does. In that case, I recommend symlinking \texttt{/etc/httpd}
to \texttt{/etc/apache} (LON-CAPA depends on \texttt{/etc/httpd} existing),
and either make the appropriate substitutions in the following directions
for the log files, or make a symlink there, too. (\texttt{ln -s /etc/apache
/etc/httpd})
\subsection{Pre-requisites}
\begin{itemize}
\item LON-CAPA must be installed and running.
\item \textbf{Perl}: You're going to need to know some or you will not understand
the code. You don't need to be intimately familiar with the many hundreds
of libraries Perl has, though, which is much harder than learning
the language.
\item Knowledge of the Apache server is not required for this tutorial,
but {}``real work'' may require more information then is given here.
\end{itemize}
\subsection{Adding a New Handler}
You need to create a \emph{new Perl module} to serve as the handler.
A {}``Perl module''\index{Perl module} is a file that has a name
ending in {}``.pm''. For the sake of concreteness, I will refer
to this file as \texttt{lontutorial.pm}%
\footnote{Currently, almost all LON-CAPA Perl modules are prefixed with {}``lon''.
This is a weak convention used inconsistently, and theoretically it
will be dropped from the project someday. %
}.
Create this new file where ever you'd like for now.
\subsubsection{Apache Handler Shell}
Put the following code into your Perl module:
\begin{lyxcode}
package~Apache::lontutorial;
~
use~strict;
use~Apache::Constants~qw(:common);
use~Apache::loncommon;
~
=pod
~
=head1~NAME
~
lontutorial~-~tutorial~code
~
=head1~SYNOPSIS
~
lontutorial~contains~the~code~from~the~tutorial,~and
does~tutorial~stuff.
~
=cut
~
sub~handler~\{
~~~~my~\$r~=~shift;
~
~~~~\$r->print('Hello~world!');
~~~~return~OK;
\}
~
1;
\end{lyxcode}
A line-by-line breakdown:
\begin{itemize}
\item \texttt{\textbf{package Apache::lontutorial;}}: This puts everything
in the file in the \texttt{Apache::lontutorial} namespace. Note that
the \texttt{lontutorial} part should match your filename before the
\texttt{.pm}. (The file may contain other packages; see \texttt{lonnavmaps.pm}
or \texttt{lonhelper.pm} for an example. But this package should show
up in the file or Apache may have trouble finding your file.)
\item \texttt{\textbf{use strict;}}: Turns on strict variable checking,
which is a Very Good Idea. If you intend to submit the code back to
the main LON-CAPA project, please make sure to use this.
\item \texttt{\textbf{use Apache::Constants qw(:common);}}: This loads certain
constants used to communicate with Apache into the current namespace.
\item \texttt{\textbf{use Apache::loncommon;}}: This loads some commonly-used
functions we will use later. See \texttt{man Apache::loncommon} for
more information.
\item \texttt{\textbf{=pod}} through \texttt{\textbf{=cut}}: This contains
inline documentation in the POD format. Please see \texttt{man perlpod}
for more information. I encourage you to document your code. Note
many current LON-CAPA modules do not contain good examples. Please
consult \texttt{perldoc lonhelp.pm} in the \texttt{loncom/interface}
directory of the CVS repository, or see the \texttt{lonhelp.pm} information
later in the developer manual, for style considerations. The documentation
so produced can be easily added to this Developer's Manual if it would
be useful to others, so you're accomplishing the dual purpose of documenting
in the file and the manual at the same time.
\item \texttt{\textbf{sub handler \{}}: Apache will call the \texttt{handler}
function in the namespace it is given for the handler in the \texttt{loncapa\_apache.conf}
file; see below. Thus, we need to supply it, or Apache will fail and
complain about a missing subroutine.
\item \texttt{\textbf{my \$r = shift;}}: The first and only parameter to
the handler function is the Apache Response object. We will partially
cover this object in this tutorial, for more information consult outside
documentation.
\item \texttt{\textbf{\$r->print('Hello world!');}}: \texttt{\$r->print}
is how we sent text to the user. This is the \emph{only} method to
send text to the user's web browser, and in the end, all output ends
up going through this function.
\item \texttt{\textbf{return OK;}}: {}``OK'' is a constant we imported
in our namespace in the use \texttt{Apache::Constants} line. It tells
the server the page was successfully displayed, as opposed to a 404
or other error.
\item \texttt{\textbf{1;}}: This tells Perl the module was successfully
parsed, and is a required part of Perl modules.
\end{itemize}
\subsubsection{Telling Apache About Your Handler}
In order for Apache to use your file, you need to tell Apache where
to find it, and what URL(s) to use for it. Add the following to your
\texttt{/etc/httpd/conf/loncapa\_apache.conf}, after the \texttt{PerlTransHandler}
declaration:
\begin{lyxcode}
<Location~/adm/tutorial>
PerlAccessHandler~~~~~~~Apache::lonacc
SetHandler~~~~~~~~~~~~~~perl-script
PerlHandler~~~~~~~~~~~~~Apache::lontutorial
ErrorDocument~~~~~~500~~/adm/errorhandler
</Location>
\end{lyxcode}
This will tell Apache to call Apache::lontutorial::handler when someone
goes to /adm/tutorial to see what to do.
Note: The \texttt{Apache::} is not technically necessary in mod\_perl,
it's just that LON-CAPA uses it. We may remove it from the system
someday. In the meantime, it would be consistent with the rest of
the system to continue use it. (If you don't understand what this
is saying, don't worry about it.)
\subsubsection{Putting The File Where Apache Can Find It}
Now you need to put that file where Apache can find it.
Apache will look for it in \texttt{/home/httpd/lib/perl/Apache}/,
so copy it there.
\begin{lyxcode}
cp~lontutorial~/home/httpd/lib/perl/Apache/
\end{lyxcode}
\subsubsection{Running Apache}
You can now (re-)start the Apache server.
\begin{lyxcode}
/etc/init.d/httpd~restart
\end{lyxcode}
Also, if you are logged into LON-CAPA, log out (exit) before continuing.
(Note re-starting the server does \emph{not} automatically log you
out, which is really helpful while debugging so you don't have to
continuously log back in.)
If everything went well, you can now go to your server and go to \texttt{http://your.server/adm/tutorial},
with \texttt{your.server} replaced by the domain name of your LON-CAPA
server, of course. (I will use {}``your.server'' that way for the
rest of this tutorial.)
And you should see\ldots{} {}``Bad Request''? What happened?
You don't know it yet, but we're making a handler for functionality
that will require the user to be logged in for it to work, because
it is going to depend on information about the user. Right now, nobody
is logged in. The \texttt{PerlAccessHandler\index{PerlAccessHandler}}
line in what we put in \texttt{loncapa\_apache.conf} tells Apache
to use {}``Apache::lonacc::handler'' as an \textbf{access handler}\index{access handler}.
An \textbf{access handler} determines whether the user has permission
to perform the current action. lonacc is a handler written for LON-CAPA
that among other things, ensures the user is logged in. This handler
told Apache that this is a {}``Bad Request'', which is why you got
the {}``Bad Request'' screen. Your handler code was actually \textbf{never
even executed}; Apache aborted the request before getting to your
code.
Log into LON-CAPA, and choose a role with a course. (You may need
to create a course; please see the Course Management manual for more
information.) Any role with a course, Student, Instructor, Course
Coordinator or otherwise is fine. We won't be making any changes to
the course data so it doesn't matter if it's a {}``real'' course.
Once you've gotten to the first page of the course (normal entry),
change your URL to read \texttt{http://your.server/adm/tutorial}.
You should now see {}``Hello world!'' on the screen.
\paragraph{Apache can't find \texttt{/adm/tutorial}:}
If Apache is claiming it can't find the file named {}``/adm/tutorial'',
something is wrong with your \texttt{loncapa\_apache.conf} file. Please
double check the entry to make sure it is in the right place, that
it is typed correctly, and that all the lines are present. If it is
still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
list, including a copy of your \texttt{loncapa\_apache.conf} file
in the email; either something is wrong that is beyond the scope of
this document(other system configuration issues) or you really can't
find the error.
\paragraph{{}``Something went wrong, please help us find out what''}
If you get an icon of the LON-CAPA logo with a cracked screen and
the preceding message, something is wrong with your Perl code. This
is the standard screen that asks the user to send us more information
about what happened, so we can try to diagnose the problem. Since
you're a developer, you can ignore that screen and actually collect
the information and act on it directly.
The Perl error landed in \texttt{/var/log/httpd/error\_log}, in the
last few lines. Take a look at what was in there, and try to fix it.
\begin{lyxcode}
tail~/var/log/httpd/error\_log
\end{lyxcode}
Since I can't guess what error you encountered, I'm afraid I can't
help you out here. But I do know the code I wrote above worked, so
as a last resort try to figure out what is different. If you are absolutely
stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
list, including a copy of your \texttt{lontutorial.pm} file in the
email; either something is wrong that is beyond the scope of this
document (other system configuration issues) or you really can't find
the error.
Actually, if you have the time, I recommend adding a syntax error
to your \texttt{lontutorial.pm}, even if it worked the first time,
so you can see what happens. This will not be the last time there's
an error in your code\ldots{}
\subsubsection{Modifying The Module}
When Apache is started, it reads all the Perl modules in LON-CAPA
into memory, and after that, does not use the \texttt{.pm} files in
\texttt{/home/httpd/lib/perl/Apache}%
\footnote{Slight exaggeration here: Actually it only loads the ones mentioned
in startup.pl, and since your module isn't in that file, it won't
be loaded at startup. But this really doesn't matter much.%
}. To get Apache to use your newly-edited files, you need to copy them
to \texttt{/home/httpd/lib/perl/Apache} and restart the Apache server.
It is generally not a good idea to directly modify the files in \texttt{/home/httpd/lib/perl/Apache}
while developing, because of the difficulty you will have later propagating
changes made there back to more useful places, such as in the LON-CAPA
CVS repository. To alleviate the annoyance of constantly copying the
file to that directory and restarting the server, compose a single
command line that does all of it at once. I often use something like
the following:
\begin{lyxcode}
pushd~.;~cd~YOUR\_DIRECTORY;~cp~lontutorial.pm
~/home/httpd/lib/perl/Apache;~/etc/init.d/httpd~restart;
~popd
\end{lyxcode}
where YOUR\_DIRECTORY is replaced by the directory containing the
\texttt{lontutorial.pm} file, and that is all one line.
The {}``pushd'' and {}``popd'' commands are built-in to the Bash
shell; other shells may or may not contain such commands.
Once you've created this command line, you can just use the Up arrow
in your shell to get it back. You can add whatever you want to it
as needed; that's why I wrap it in the {}``pushd'' and {}``popd''
commands, so you can {}``cd'' without affecting your current directory.
If you had a syntax error in your Perl module, fix it now. If you
didn't, I'd recommend make a small change to the printed string or
something and making sure you have figured out how to change the code
in the Apache server correctly before continuing.
Once again, notice that even after re-starting the server, you are
still logged in with the same settings you were using before the server
re-start.
\subsection{Making The Handler Do Something}
\subsubsection{A Bit More Boilerplate}
At this point, your handler isn't doing much. If you look carefully,
you'll notice that the {}``Hello World'' on the screen is in a mono-spaced
font, not the proportional font used by default in HTML. That's because
\emph{all} you sent was {}``Hello World!''; the browser looks at
the header of the HTTP response to see what kind of response it is
getting back, but you didn't even \emph{send} a header. Technically,
that's not a legal HTTP response, but the browser was accommodating
and displayed what you got back anyhow as text. (Consult documentation
on the HTTP protocol for more information about this; generally you
will not need to worry about this in LON-CAPA beyond the boilerplate
presented here.)
To send back a proper HTTP response, add this code after the \texttt{my
\$r = shift;}:
\begin{lyxcode}
~~~~\#~Handle~header-only~request
~~~~if~(\$r->header\_only)~\{
~~~~~~~~if~(\$ENV\{'browser.mathml'\})~\{
~~~~~~~~~~~~\$r->content\_type('text/xml');
~~~~~~~~\}~else~\{
~~~~~~~~~~~~\$r->content\_type('text/html');
~~~~~~~~\}
~~~~~~~~\$r->send\_http\_header;
~~~~~~~~return~OK;
~~~~\}
~~~~\#~Send~header,~don't~cache~this~page
~~~~if~(\$ENV\{'browser.mathml'\})~\{
~~~~~~~~\$r->content\_type('text/xml');
~~~~\}~else~\{
~~~~~~~~\$r->content\_type('text/html');
~~~~\}
~~~~\&Apache::loncommon::no\_cache(\$r);
~~~~\$r->send\_http\_header;
\end{lyxcode}
This is boilerplate that appears at the top of many LON-CAPA handlers.
It handles the case where the user is requesting that only headers
be sent; unusual, but worth handling. It sets the content type appropriately,
and tells the client not to cache this page, which is appropriate
since it will be dynamic.
For efficiency, when you use \texttt{\$r->print()} to send something
to the user, Apache assumes you know what you are doing and will immediately%
\footnote{Actually, if you send a lot of little things, Apache is smart enough
to bundle them together into one big network packet for efficiency.
If you absolutely want Apache to send everything it has to the user
\emph{right now}, use \texttt{\$r->rflush()}\ldots{} but sparingly,
as repeated use can cause a \emph{huge} slowdown.%
} send what you printed across the network. Since the HTTP header is
supposed to precede all content, it is important that you send the
header before you send any other content across the network, so we
ask the Apache server to go ahead and send the header with \texttt{\$r->send\_http\_header()}.
Since the \texttt{Apache::loncommon::no\_cache()} call affects the
header, it must be called before we send it out, because once the
header is sent there's no getting it back.
All code that follows this code will send the output to the user directly.
Finally, let's add two more lines to make it fit into the system better.
Wrap the following three lines around the {}``Hello World'' line,
as shown:
\begin{lyxcode}
\$r->print('<html><head><title>Tutorial~Page</title></head>');
\$r->print(\&Apache::loncommon::bodytag('Tutorial~Handler','',''));
\$r->print('Hello~world!');
\$r->print('</body></html>');
\end{lyxcode}
Now use your command line to copy \texttt{lontutorial.pm} and restart
the server. Go to \texttt{/adm/tutorial}, and you'll see a nice looking
page saying {}``Hello world!''.
Congratulations, you now have a handler ready to do some real work
with!
\subsubsection{Rending Navmaps}
To start with, let's go ahead and display the navmap for the current
course.
Under \texttt{use strict;}, add this line:
\begin{lyxcode}
use~Apache::lonnavmaps;
\end{lyxcode}
Remove the {}``Hello world!'' line and replace it with this:
\begin{lyxcode}
\$ENV\{'form.condition'\}~=~1;
my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource{]},
~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
Apache::lonnavmaps::render(\$renderArgs);
\end{lyxcode}
Line by line:
\begin{itemize}
\item \texttt{\textbf{use Apache::lonnavmaps;}}: \texttt{Apache::lonnavmaps}
contains routines that help render navmaps. For more information,
see later in this manual or type \texttt{man Apache::lonnavmaps}.
This ensures these routines are loaded into memory.
\item \texttt{\textbf{\$ENV\{'form.condition'\} = 1;}}: This is an an argument
being passed to the Apache::lonnavmaps::render routine in a rather
unorthodox way. This will cause the navmap to render all of the resources,
by default, except for what we explicitly exclude. Since we're not
going to exclude anything (which we would do with \texttt{\$ENV\{'form.filter'\}}),
all resources will be shown.
\item \texttt{\textbf{my \$renderArgs\ldots{}\$r\};}}: Since the \texttt{render}
routine takes a \emph{lot} of arguments, the \texttt{render} routine
takes in a hash reference instead of a traditional list of arguments.
For full information about what that function takes, consult the documentation.
\texttt{'cols'} will tell the render function what to render in the
navmap; {}``Apache::lonnavmaps::resource'' is a constant that
indicates the standard listing of the resource with a link
to the resource, which is the first column of the \textbf{NAV} display
you are used to. \texttt{'showParts' => 0} tells the render function
not to show individual parts, which will not be useful to us. \texttt{'r'
=> \$r} passes the Apache response object to the \texttt{render} function,
which it uses to provide periodic output to the user by using the
\texttt{rflush} method on the object.
\item \texttt{\textbf{Apache::lonnavmaps::render(\$renderArgs);}}: Performs
the actual rendering of the navmaps. Since we passed it \texttt{\$r},
it will take care of displaying the navmaps for us. If we didn't pass
it \texttt{\$r}, it would return a string containing the rendered
HTML, which we would be responsible for \texttt{\$r->print}ing.
\end{itemize}
Save this and use your command line to re-load the server. Make sure
you've selected a specific course role before trying to visit \texttt{/adm/tutorial}.
You may need to create a course with a few resources in it first,
depending on your setup.
\subsubsection{Goal}
In order to exercise storing data on the LON-CAPA network, we're going
to do something a little silly, but pedagogically useful. Let's add
two columns to the navmap, one which will provide a checkbox which
we can click, and another which will show how many times we've selected
that particular resource and submitted a form. While this is somewhat
silly, the general principle of doing something for some selection
of resources is a fairly common operation in LON-CAPA, as is storing
or loading data from the network.
In doing this, we'll learn:
\begin{itemize}
\item How to store data on the LON-CAPA network correctly.
\item How to process incoming form input in LON-CAPA.
\item How to work with and identify resources in LON-CAPA.
\end{itemize}
\subsection{Adding A Form For Input}
The first thing we need to do to accomplish this is to add an HTML
form to the screen so we have something to submit. Just above the
\texttt{\$ENV\{'form.condition'\}} line, add the following:
\begin{lyxcode}
\$r->print(\char`\"{}<form~method='post'><input~type='hidden'~name='submission'~value='1'~/>\char`\"{});
\end{lyxcode}
This will print out a form element, and a hidden form element named
{}``submission'' with a value of {}``1''. Strictly speaking, this
is not necessary, but this serves as a {}``sentinel'', which makes
it easy to check later whether or not we need to process input with
a single \texttt{if} statement.
Just before \texttt{\$r->print({}``</body></html>'');}, add this:
\begin{lyxcode}
\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}Increment\char`\"{}~/></form>');
\end{lyxcode}
If you now use your command line to re-load the server, and you examine
the source code of the page on /adm/tutorial, you should now see a
form, with a hidden value and a submit button, though pressing the
submit button won't do much.
\subsubsection{Seeing The Incoming Data}
LON-CAPA automatically processing incoming POST data and exposes it
to you in the \texttt{\%ENV} hash. The data will appear in \texttt{\$ENV\{'form.\$varname'\}},
where \texttt{\$varname} is the variable name of the HTML form element.
In this case, since we have an element named {}``submission'', a
{}``1'' will appear in \texttt{\$ENV\{'form.submission'\}} when
we hit the {}``Increment'' button. To see this, add the following
after the \texttt{bodytag} call:
\begin{lyxcode}
if~(\$ENV\{'form.submission'\})~\{
~~~~\$r->print('<p>Form~submitted.</p>');
\}~else~\{
~~~~\$r->print('<p>No~form~information~submitted.</p>');
\}
\end{lyxcode}
Reload the tutorial code into the server. Now, when you type in \texttt{/adm/tutorial},
{}``No form information submitted.'' will appear on the screen.
When you hit {}``Increment'', {}``Form submitted.'' will appear.
Note this only applies to POST'ed data. If you use GET, the data will
appear on the query string. For your code, this will show up in \texttt{\$ENV\{QUERY\_STRING\}}.
If you want to invoke LON-CAPA's processing on that string, so you
see the variables in \texttt{\%ENV}, use
\texttt{Apache::loncommon::get\_unprocessed\_cgi(\$ENV\{QUERY\_STRING\});}.
This is particularly useful for cases where input may be coming in via
either POST or GET.
\subsubsection{Adding Checkboxes for Input}
Now we want to add a checkbox column into the navmap rendering so
we have a place to input our selections. In order to do that, we need
to provide the rendering code with a subroutine that will take an
\texttt{Apache::lonnavmaps::resource} object and return the string
we want to print, including the \texttt{td} and
\texttt{/td} tags. (For more information,
consult the \texttt{Apache::lonnavmaps::render} documentation.) Add
the following after the second and last \texttt{send\_http\_header}
line:
\begin{lyxcode}
my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;
~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~'~.
~~~~'name=\char`\"{}symb.'~.
~~~~Apache::lonnet::escape(\$resource->symb())~.~
~~~~'\char`\"{}~/></td>';
\};
\end{lyxcode}
A \textbf{symb} is a string which uniquely identifies a resource within
a course, since the same resource can show up in multiple sequences,
or multiple times in the same sequence. Thus, that makes a good ID
for the checkboxes. Given an \texttt{Apache::lonnavmaps::resource}
object, the {}``symb'' method will return the symb. \texttt{Apache::lonnet::escape}
is a function that does URL-escaping on the resulting string, so it's
safe to pass into an HTML attribute. (XML-type escaping would work
too.)
Go ahead and load this into the server, so you can see the HTML output
by viewing the source.
\subsubsection{Getting The Checked Resources}
Let's make sure we can retrieve the checked resources before continuing.
Add the following code before the \texttt{\$r->print({}``<form}\ldots{}
code:
\begin{lyxcode}
foreach~(keys~\%ENV)~\{
~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
~~~~~~~~my~\$symb~=~substr(\$\_,~10);
~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
~~~~~~~~\$r->print(\char`\"{}\$symb<br~/>\char`\"{});
~~~~\}
\}
\end{lyxcode}
That will print each symb that we select the checkbox for on the screen.
You may remove this later if you find it unsightly.
Re-load the code into the server and make sure it's still working.
Try checking a few boxes and hitting {}``Increment'' to make sure
the symbs appear at the top of the screen.
\subsection{Storing Data on the LON-CAPA Network}
Let's suppose you want to record how often you submit a given checkbox.
You can't write to the current server's hard drive, because there's
no guarantee that next time you log on, that you'll get the same server.
(Of course, if you're on a single development system, you can know
that, but if you want your code to be generally useful for LON-CAPA,
you can't depend on being on the same system.)
Let's go ahead and retrieve the hash we're going to store. If it doesn't
already exist, it will end up getting created implicitly. Before the
\texttt{foreach (keys \%ENV)}, add
\begin{lyxcode}
my~\%countHash~=~Apache::lonnet::restore('tutorial');
my~\%newHash~=~();
\end{lyxcode}
\texttt{\%countHash} will contain what we've stored. \texttt{\%newHash}
will be used to store new data into the stored hash; please see the
documentation on lonnet to see why this is necessary.
\texttt{'tutorial'} identifies the hash we wish to store; if we make
a later \texttt{restore} call with that parameter we'll get this hash
back. Normally you'll want to choose the name of the hash more carefully,
for instance including the course ID into it, unless you really mean
to do something for all courses. For simplicity, we are leaving that
out.
Replace the \texttt{foreach} loop that is printing out the symbs with
this:
\begin{lyxcode}
foreach~(keys~\%ENV)~\{
~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
~~~~~~~~my~\$symb~=~substr(\$\_,~10);
~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
~~~~~~~~\$countHash\{\$symb\}++;
~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
~~~~\}
\}
\end{lyxcode}
That will increment the count for each symb that we checked a box
for. We need to store the data the user changed, so just before \texttt{return
OK;}, add the following line of code:
\begin{lyxcode}
Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
\end{lyxcode}
\subsubsection{Displaying The Count}
Finally, we need a column that will display the count of times we've
checked the box. Right after the \texttt{foreach} loop we just modified,
add
\begin{lyxcode}
my~\$countCode~=~sub~\{~my~\$resource~=~shift;
~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
~~~~\$countHash\{\$resource->symb()\}~.
~~~~\char`\"{}</td>\char`\"{};
~~\};
\end{lyxcode}
Add \texttt{\$countCode} to the 'cols' list in the \texttt{\$renderArgs}:
\begin{lyxcode}
my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
\end{lyxcode}
Viola! That should do it.
\subsection{Internationalization}
In order for your handler to be able to submitted to LON-CAPA, we'd
really appreciate it if your handler was already
internationalized. ``Internationalization'' refers to the process of
adding hooks to code to make it easy to ``localize''. ``Localizing'' a
program means taking advantage of those hooks and create a file that
describes to the LON-CAPA web system how to display LON-CAPA in a
language other than English.
For more complete information about Internationalization and
Localization, please refer to the Internationalization chapter. For
now, suffice it to say we need to wrap the text strings we are using
in the program in calls to the hook subroutine, which will be named
\texttt{\&mt}.
First, at the top of your handler, near the other \texttt{use}
commands, add \texttt{use Apache::lonlocal;}.
Second, find where all the strings are in your program and wrap them
in calls to \texttt{\&mt}. I see the following:
\begin{enumerate}
\item In the \texttt{bodytag} call, the title:
\begin{lyxcode}
\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
\end{lyxcode}
\item In the two messages printed out showing the form submissions:
(Of course in this case these are really just debugging messages
we'd remove before actually using this handler. But let's localize
them for practice)
\begin{lyxcode}
if~(\$ENV{'form.submission'})~{
~~~~\$r->print('<p>'.\&mt('Form submitted').'</p>');
}~else~{
~~~~\$r->print('<p>'.\&mt('No form information submitted.').'</p>');
}
\end{lyxcode}
Note we do \emph{not} generally want to wrap HTML tags unless we are
absolutely forced to; those are constant across human languages and
would only burder the translators with stuff they should not need to
deal with.
\item The label of the button we've created:
\begin{lyxcode}
\$r->print('<input type=\char`\"{}submit\char`\"{} value=\char`\"{}'~.
~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
\end{lyxcode}
\end{enumerate}
Note we only need to wrap things the human user will see; we don't
need to wrap the \texttt{tutorial} parameter to the
\texttt{Apache::lonnet::restore} call, for instance, and in fact wierd
things could happen if we did! Also note that resource names and such
are already as internationalized as they are going to get, so we don't
need to worry about them.
Since the internationalization system will return the value passed to
\texttt{\&mt} by default if it can't find a translation, it's safe to
internationalize code before translations exist, and in fact it's a
necessary step.
Also note that punctuation should be wrapped in the \texttt{\&mt}
calls, including things like trailing periods, since not all languages
have the same punctuation standards as English.
This only covers simple internationalization. This can take you a long
way, but if you encounter a more difficult problem, please send a note
to the \texttt{lon-capa-dev@mail.lon-capa.org} mailing list.
\subsection{Complete Code Listing}
For your reference, I include the complete code listing for the tutorial
here:
\begin{lyxcode}
package~Apache::lontutorial;
~
use~strict;
use~Apache::lonnavmaps;
use~Apache::Constants~qw(:common);
use~Apache::lonlocal;
~
=pod
~
=head1~NAME
~
lontutorial~-~tutorial~code
~
=head1~SYNOPSIS
~
lontutorial~contains~the~code~from~the~tutorial,~and
does~tutorial~stuff.
~
=cut
~
sub~handler~\{
~~~~my~\$r~=~shift;
~~~~\#~Handle~header-only~request
~~~~if~(\$r->header\_only)~\{
~~~~~~~~if~(\$ENV\{'browser.mathml'\})~\{
~~~~~~~~~~~~\$r->content\_type('text/xml');
~~~~~~~~\}~else~\{
~~~~~~~~~~~~\$r->content\_type('text/html');
~~~~~~~~\}
~~~~~~~~\$r->send\_http\_header;
~~~~~~~~return~OK;
~~~~\}
~~~~\#~Send~header,~don't~cache~this~page
~~~~if~(\$ENV\{'browser.mathml'\})~\{
~~~~~~~~\$r->content\_type('text/xml');
~~~~\}~else~\{
~~~~~~~~\$r->content\_type('text/html');
~~~~\}
~~~~\&Apache::loncommon::no\_cache(\$r);
~~~~\$r->send\_http\_header;
~~~~my~\$checkboxCode~=~sub~\{~my~\$resource~=~shift;~
~~~~~~return~'<td~align=\char`\"{}center\char`\"{}><input~type=\char`\"{}checkbox\char`\"{}~name=\char`\"{}symb.'~.
~~~~~~~~~~Apache::lonnet::escape(\$resource->symb())~.~'\char`\"{}~/></td>';
~~~~~~~~~~~~~~~~~~~~~~~~~\};
~~~~\$r->print('<html><head><title>Tutorial~Page</title></head>');
~~~~\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
~~~~if~(\$ENV\{'form.submission'\})~\{
~~~~~~~~\$r->print('<p>'~.~\&mt('Form~submitted.')~.'</p>');
~~~~\}~else~\{
~~~~~~~~\$r->print('<p>'~.~\&mt('No~form~information~submitted.')~.~'</p>');
~~~~\}
~~~~my~\%countHash~=~Apache::lonnet::restore('tutorial');
~~~~my~\%newHash~=~();
~~~~foreach~(keys~\%ENV)~\{
~~~~~~~~if~(substr(\$\_,~0,~10)~eq~'form.symb.')~\{
~~~~~~~~~~~~my~\$symb~=~substr(\$\_,~10);
~~~~~~~~~~~~\$symb~=~Apache::lonnet::unescape(\$symb);
~~~~~~~~~~~~\$countHash\{\$symb\}++;
~~~~~~~~~~~~\$newHash\{\$symb\}~=~\$countHash\{\$symb\};
~~~~~~~~\}
~~~~\}
~~~~my~\$countCode~=~sub~\{~my~\$resource~=~shift;
~~~~~~return~'<td~align=\char`\"{}center\char`\"{}>'~.
~~~~~~~~\$countHash\{\$resource->symb()\}~.
~~~~~~~~\char`\"{}</td>\char`\"{};
~~~~~~\};
~~~~\$r->print(\char`\"{}<form~method='post'><input~type='hidden'\char`\"{}~.
~~~~~~~~~~~~~~\char`\"{}name='submission'~value='1'~/>\char`\"{});
~~~~\$ENV\{'form.condition'\}~=~1;
~~~~my~\$renderArgs~=~\{~'cols'~=>~{[}Apache::lonnavmaps::resource,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\$checkboxCode,~\$countCode{]},
~~~~~~~~~~~~~~~~~~~~~~~'showParts'~=>~0,
~~~~~~~~~~~~~~~~~~~~~~~'r'~=>~\$r~\};
~~~~Apache::lonnavmaps::render(\$renderArgs);
~~~~\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}'~.
~~~~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
~~~~\$r->print('</body></html>');
~~~~Apache::lonnet::store(\textbackslash{}\%newHash,~'tutorial');
~~~~return~OK;
\}
1;
\end{lyxcode}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>