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