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