File:
[LON-CAPA] /
loncom /
html /
adm /
help /
tex /
Developer_Tutorial.tex
Revision
1.7:
download - view:
text,
annotated -
select for diffs
Fri Apr 25 16:02:41 2014 UTC (10 years, 3 months ago) by
bisitz
Branches:
MAIN
CVS tags:
version_2_12_X,
version_2_11_X,
version_2_11_5_msu,
version_2_11_5,
version_2_11_4_uiuc,
version_2_11_4_msu,
version_2_11_4,
version_2_11_3_uiuc,
version_2_11_3_msu,
version_2_11_3,
version_2_11_2_uiuc,
version_2_11_2_msu,
version_2_11_2_educog,
version_2_11_2,
version_2_11_1,
version_2_11_0_RC3,
version_2_11_0,
HEAD
Consistent wording "e-mail"
1: \label{Developer_Tutorial}
2:
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
38: of libraries Perl has, though, which is much harder than learning
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
257: still incorrect, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
258: list, including a copy of your \texttt{loncapa\_apache.conf} file
259: in the e-mail; either something is wrong that is beyond the scope of
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
282: stumped, please ask for help on the \texttt{lon-capa-dev@mail.lon-capa.org}
283: list, including a copy of your \texttt{lontutorial.pm} file in the
284: e-mail; either something is wrong that is beyond the scope of this
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:
366: ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{
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:
384: ~~~~if~(\$env\{'browser.mathml'\})~\{
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}
456: \$env\{'form.condition'\}~=~1;
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.
473: \item \texttt{\textbf{\$env\{'form.condition'\} = 1;}}: This is an an argument
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
477: going to exclude anything (which we would do with \texttt{\$env\{'form.filter'\}}),
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
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
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
527: \texttt{\$env\{'form.condition'\}} line, add the following:
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
552: to you in the \texttt{\%env} hash. The data will appear in \texttt{\$env\{'form.\$varname'\}},
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
555: {}``1'' will appear in \texttt{\$env\{'form.submission'\}} when
556: we hit the {}``Increment'' button. To see this, add the following
557: after the \texttt{bodytag} call:
558:
559: \begin{lyxcode}
560: if~(\$env\{'form.submission'\})~\{
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
575: appear on the query string. For your code, this will show up in \texttt{\$env\{QUERY\_STRING\}}.
576: If you want to invoke LON-CAPA's processing on that string, so you
577: see the variables in \texttt{\%env}, use
578: \texttt{Apache::loncommon::get\_unprocessed\_cgi(\$env\{QUERY\_STRING\});}.
579: This is particularly useful for cases where input may be coming in via
580: either POST or GET.
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
589: we want to print, including the \texttt{td} and
590: \texttt{/td} tags. (For more information,
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}
628: foreach~(keys~\%env)~\{
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
661: \texttt{foreach (keys \%env)}, add
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}
683: foreach~(keys~\%env)~\{
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:
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
746: language other than English.
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}
775: if~(\$env{'form.submission'})~{
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:
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:
839: use~Apache::lonlocal;
840:
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:
879: ~~~~~~~~if~(\$env\{'browser.mathml'\})~\{
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:
897: ~~~~if~(\$env\{'browser.mathml'\})~\{
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:
925: ~~~~\$r->print(\&Apache::loncommon::bodytag(\&mt('Tutorial~Handler'),'',''));
926:
927:
928:
929: ~~~~if~(\$env\{'form.submission'\})~\{
930:
931: ~~~~~~~~\$r->print('<p>'~.~\&mt('Form~submitted.')~.'</p>');
932:
933: ~~~~\}~else~\{
934:
935: ~~~~~~~~\$r->print('<p>'~.~\&mt('No~form~information~submitted.')~.~'</p>');
936:
937: ~~~~\}
938:
939:
940:
941: ~~~~my~\%countHash~=~Apache::lonnet::restore('tutorial');
942:
943: ~~~~my~\%newHash~=~();
944:
945: ~~~~foreach~(keys~\%env)~\{
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:
979: ~~~~\$env\{'form.condition'\}~=~1;
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:
993: ~~~~\$r->print('<input~type=\char`\"{}submit\char`\"{}~value=\char`\"{}'~.
994:
995: ~~~~~~~~\&mt('Increment')~.~'\char`\"{}~/></form>');
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>