Annotation of loncom/interface/lonhelper.pm, revision 1.8
1.1 bowersj2 1: # The LearningOnline Network with CAPA
2: # .helper XML handler to implement the LON-CAPA helper
3: #
1.6 bowersj2 4: # $Id: lonhelper.pm,v 1.5 2003/04/10 18:02:09 bowersj2 Exp $
1.1 bowersj2 5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28: # (Page Handler
29: #
30: # (.helper handler
31: #
32:
1.3 bowersj2 33: =pod
34:
35: =head1 lonhelper - HTML Helper framework for LON-CAPA
36:
37: Helpers, often known as "wizards", are well-established UI widgets that users
38: feel comfortable with. It can take a complicated multidimensional problem the
39: user has and turn it into a series of bite-sized one-dimensional questions.
40:
41: For developers, helpers provide an easy way to bundle little bits of functionality
42: for the user, without having to write the tedious state-maintenence code.
43:
44: Helpers are defined as XML documents, placed in the /home/httpd/html/adm/helpers
45: directory and having the .helper file extension. For examples, see that directory.
46:
47: All classes are in the Apache::lonhelper namespace.
48:
49: =head2 lonhelper XML file format
50:
51: A helper consists of a top-level <helper> tag which contains a series of states.
52: Each state contains one or more state elements, which are what the user sees, like
53: messages, resource selections, or date queries.
54:
55: The helper tag is required to have one attribute, "title", which is the name
56: of the helper itself, such as "Parameter helper".
57:
58: =head2 State tags
59:
60: State tags are required to have an attribute "name", which is the symbolic
1.7 bowersj2 61: name of the state and will not be directly seen by the user. The helper is
62: required to have one state named "START", which is the state the helper
1.5 bowersj2 63: will start with. By convention, this state should clearly describe what
1.3 bowersj2 64: the helper will do for the user, and may also include the first information
65: entry the user needs to do for the helper.
66:
67: State tags are also required to have an attribute "title", which is the
68: human name of the state, and will be displayed as the header on top of
69: the screen for the user.
70:
71: =head2 Example Helper Skeleton
72:
73: An example of the tags so far:
74:
75: <helper title="Example Helper">
76: <state name="START" title="Demonstrating the Example Helper">
77: <!-- notice this is the START state the wizard requires -->
78: </state>
79: <state name="GET_NAME" title="Enter Student Name">
80: </state>
81: </helper>
82:
83: Of course this does nothing. In order for the wizard to do something, it is
84: necessary to put actual elements into the wizard. Documentation for each
85: of these elements follows.
86:
87: =cut
88:
1.1 bowersj2 89: package Apache::lonhelper;
1.2 bowersj2 90: use Apache::Constants qw(:common);
91: use Apache::File;
1.3 bowersj2 92: use Apache::lonxml;
1.2 bowersj2 93:
1.7 bowersj2 94: # Register all the tags with the helper, so the helper can
95: # push and pop them
96:
97: my @helperTags;
98:
99: sub register {
100: my ($namespace, @tags) = @_;
101:
102: for my $tag (@tags) {
103: push @helperTags, [$namespace, $tag];
104: }
105: }
106:
1.2 bowersj2 107: BEGIN {
1.7 bowersj2 108: Apache::lonxml::register('Apache::lonhelper',
109: ('helper'));
110: register('Apache::lonhelper', ('state'));
1.2 bowersj2 111: }
112:
1.7 bowersj2 113: # Since all helpers are only three levels deep (helper tag, state tag,
1.3 bowersj2 114: # substate type), it's easier and more readble to explicitly track
115: # those three things directly, rather then futz with the tag stack
116: # every time.
117: my $helper;
118: my $state;
119: my $substate;
1.4 bowersj2 120: # To collect parameters, the contents of the subtags are collected
121: # into this paramHash, then passed to the element object when the
122: # end of the element tag is located.
123: my $paramHash;
1.2 bowersj2 124:
125: sub handler {
1.3 bowersj2 126: my $r = shift;
1.2 bowersj2 127: $ENV{'request.uri'} = $r->uri();
128: my $filename = '/home/httpd/html' . $r->uri();
129: my $fh = Apache::File->new($filename);
130: my $file;
1.3 bowersj2 131: read $fh, $file, 100000000;
132:
133: Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
134:
135: # Send header, don't cache this page
136: if ($r->header_only) {
137: if ($ENV{'browser.mathml'}) {
138: $r->content_type('text/xml');
139: } else {
140: $r->content_type('text/html');
141: }
142: $r->send_http_header;
143: return OK;
144: }
145: if ($ENV{'browser.mathml'}) {
146: $r->content_type('text/xml');
147: } else {
148: $r->content_type('text/html');
149: }
150: $r->send_http_header;
151: $r->rflush();
1.2 bowersj2 152:
1.3 bowersj2 153: # Discard result, we just want the objects that get created by the
154: # xml parsing
155: &Apache::lonxml::xmlparse($r, 'helper', $file);
1.2 bowersj2 156:
1.3 bowersj2 157: $r->print($helper->display());
1.7 bowersj2 158: return OK;
1.2 bowersj2 159: }
160:
161: sub start_helper {
162: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
163:
164: if ($target ne 'helper') {
165: return '';
166: }
1.7 bowersj2 167:
168: for my $tagList (@helperTags) {
169: Apache::lonxml::register($tagList->[0], $tagList->[1]);
170: }
1.2 bowersj2 171:
1.3 bowersj2 172: $helper = Apache::lonhelper::helper->new($token->[2]{'title'});
1.4 bowersj2 173: return '';
1.2 bowersj2 174: }
175:
176: sub end_helper {
177: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
178:
1.3 bowersj2 179: if ($target ne 'helper') {
180: return '';
181: }
1.7 bowersj2 182:
183: for my $tagList (@helperTags) {
184: Apache::lonxml::deregister($tagList->[0], $tagList->[1]);
185: }
186:
1.4 bowersj2 187: return '';
1.2 bowersj2 188: }
1.1 bowersj2 189:
1.3 bowersj2 190: sub start_state {
191: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
192:
193: if ($target ne 'helper') {
194: return '';
195: }
196:
197: $state = Apache::lonhelper::state->new($token->[2]{'name'},
198: $token->[2]{'title'});
199: return '';
200: }
201:
202: # don't need this, so ignore it
203: sub end_state {
204: return '';
205: }
206:
1.1 bowersj2 207: 1;
208:
1.3 bowersj2 209: package Apache::lonhelper::helper;
210:
211: use Digest::MD5 qw(md5_hex);
212: use HTML::Entities;
213: use Apache::loncommon;
214: use Apache::File;
215:
216: sub new {
217: my $proto = shift;
218: my $class = ref($proto) || $proto;
219: my $self = {};
220:
221: $self->{TITLE} = shift;
222:
223: # If there is a state from the previous form, use that. If there is no
224: # state, use the start state parameter.
225: if (defined $ENV{"form.CURRENT_STATE"})
226: {
227: $self->{STATE} = $ENV{"form.CURRENT_STATE"};
228: }
229: else
230: {
231: $self->{STATE} = "START";
232: }
233:
234: $self->{TOKEN} = $ENV{'form.TOKEN'};
235: # If a token was passed, we load that in. Otherwise, we need to create a
236: # new storage file
237: # Tried to use standard Tie'd hashes, but you can't seem to take a
238: # reference to a tied hash and write to it. I'd call that a wart.
239: if ($self->{TOKEN}) {
240: # Validate the token before trusting it
241: if ($self->{TOKEN} !~ /^[a-f0-9]{32}$/) {
242: # Not legit. Return nothing and let all hell break loose.
243: # User shouldn't be doing that!
244: return undef;
245: }
246:
247: # Get the hash.
248: $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN}); # Note the token is not the literal file
249:
250: my $file = Apache::File->new($self->{FILENAME});
251: my $contents = <$file>;
1.5 bowersj2 252:
253: # Now load in the contents
254: for my $value (split (/&/, $contents)) {
255: my ($name, $value) = split(/=/, $value);
256: $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
257: $self->{VARS}->{$name} = $value;
258: }
259:
1.3 bowersj2 260: $file->close();
261: } else {
262: # Only valid if we're just starting.
263: if ($self->{STATE} ne 'START') {
264: return undef;
265: }
266: # Must create the storage
267: $self->{TOKEN} = md5_hex($ENV{'user.name'} . $ENV{'user.domain'} .
268: time() . rand());
269: $self->{FILENAME} = $Apache::lonnet::tmpdir . md5_hex($self->{TOKEN});
270: }
271:
272: # OK, we now have our persistent storage.
273:
274: if (defined $ENV{"form.RETURN_PAGE"})
275: {
276: $self->{RETURN_PAGE} = $ENV{"form.RETURN_PAGE"};
277: }
278: else
279: {
280: $self->{RETURN_PAGE} = $ENV{REFERER};
281: }
282:
283: $self->{STATES} = {};
284: $self->{DONE} = 0;
285:
286: bless($self, $class);
287: return $self;
288: }
289:
290: # Private function; returns a string to construct the hidden fields
291: # necessary to have the helper track state.
292: sub _saveVars {
293: my $self = shift;
294: my $result = "";
295: $result .= '<input type="hidden" name="CURRENT_STATE" value="' .
296: HTML::Entities::encode($self->{STATE}) . "\" />\n";
297: $result .= '<input type="hidden" name="TOKEN" value="' .
298: $self->{TOKEN} . "\" />\n";
299: $result .= '<input type="hidden" name="RETURN_PAGE" value="' .
300: HTML::Entities::encode($self->{RETURN_PAGE}) . "\" />\n";
301:
302: return $result;
303: }
304:
305: # Private function: Create the querystring-like representation of the stored
306: # data to write to disk.
307: sub _varsInFile {
308: my $self = shift;
309: my @vars = ();
310: for my $key (keys %{$self->{VARS}}) {
311: push @vars, &Apache::lonnet::escape($key) . '=' .
312: &Apache::lonnet::escape($self->{VARS}->{$key});
313: }
314: return join ('&', @vars);
315: }
316:
1.5 bowersj2 317: # Use this to declare variables.
318: # FIXME: Document this
319: sub declareVar {
320: my $self = shift;
321: my $var = shift;
322:
323: if (!defined($self->{VARS}->{$var})) {
324: $self->{VARS}->{$var} = '';
325: }
326:
327: my $envname = 'form.' . $var . '.forminput';
328: if (defined($ENV{$envname})) {
329: $self->{VARS}->{$var} = $ENV{$envname};
330: }
331: }
332:
1.3 bowersj2 333: sub changeState {
334: my $self = shift;
335: $self->{STATE} = shift;
336: }
337:
338: sub registerState {
339: my $self = shift;
340: my $state = shift;
341:
342: my $stateName = $state->name();
343: $self->{STATES}{$stateName} = $state;
344: }
345:
346: # Done in four phases
347: # 1: Do the post processing for the previous state.
348: # 2: Do the preprocessing for the current state.
349: # 3: Check to see if state changed, if so, postprocess current and move to next.
350: # Repeat until state stays stable.
351: # 4: Render the current state to the screen as an HTML page.
352: sub display {
353: my $self = shift;
354:
355: my $result = "";
356:
357: # Phase 1: Post processing for state of previous screen (which is actually
358: # the "current state" in terms of the helper variables), if it wasn't the
359: # beginning state.
360: if ($self->{STATE} ne "START" || $ENV{"form.SUBMIT"} eq "Next ->") {
361: my $prevState = $self->{STATES}{$self->{STATE}};
362: $prevState->postprocess();
363: }
364:
365: # Note, to handle errors in a state's input that a user must correct,
366: # do not transition in the postprocess, and force the user to correct
367: # the error.
368:
369: # Phase 2: Preprocess current state
370: my $startState = $self->{STATE};
371: my $state = $self->{STATES}{$startState};
372:
373: # Error checking; it is intended that the developer will have
374: # checked all paths and the user can't see this!
375: if (!defined($state)) {
376: $result .="Error! The state ". $startState ." is not defined.";
377: return $result;
378: }
379: $state->preprocess();
380:
381: # Phase 3: While the current state is different from the previous state,
382: # keep processing.
383: while ( $startState ne $self->{STATE} )
384: {
385: $startState = $self->{STATE};
386: $state = $self->{STATES}{$startState};
387: $state->preprocess();
388: }
389:
390: # Phase 4: Display.
391: my $stateTitle = $state->title();
392: my $bodytag = &Apache::loncommon::bodytag("$self->{TITLE}",'','');
393:
394: $result .= <<HEADER;
395: <html>
396: <head>
397: <title>LON-CAPA Helper: $self->{TITLE}</title>
398: </head>
399: $bodytag
400: HEADER
401: if (!$state->overrideForm()) { $result.="<form name='wizform' method='GET'>"; }
402: $result .= <<HEADER;
403: <table border="0"><tr><td>
404: <h2><i>$stateTitle</i></h2>
405: HEADER
406:
407: if (!$state->overrideForm()) {
408: $result .= $self->_saveVars();
409: }
410: $result .= $state->render() . "<p> </p>";
411:
412: if (!$state->overrideForm()) {
413: $result .= '<center>';
414: if ($self->{STATE} ne $self->{START_STATE}) {
415: #$result .= '<input name="SUBMIT" type="submit" value="<- Previous" /> ';
416: }
417: if ($self->{DONE}) {
418: my $returnPage = $self->{RETURN_PAGE};
419: $result .= "<a href=\"$returnPage\">End Helper</a>";
420: }
421: else {
422: $result .= '<input name="back" type="button" ';
423: $result .= 'value="<- Previous" onclick="history.go(-1)" /> ';
424: $result .= '<input name="SUBMIT" type="submit" value="Next ->" />';
425: }
426: $result .= "</center>\n";
427: }
428:
1.5 bowersj2 429: foreach my $key (keys %{$self->{VARS}}) {
430: $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
431: }
432:
1.3 bowersj2 433: $result .= <<FOOTER;
434: </td>
435: </tr>
436: </table>
437: </form>
438: </body>
439: </html>
440: FOOTER
441:
442: # Handle writing out the vars to the file
443: my $file = Apache::File->new('>'.$self->{FILENAME});
444: print $file $self->_varsInFile();
445:
446: return $result;
447: }
448:
449: 1;
450:
451: package Apache::lonhelper::state;
452:
453: # States bundle things together and are responsible for compositing the
1.4 bowersj2 454: # various elements together. It is not generally necessary for users to
455: # use the state object directly, so it is not perldoc'ed.
456:
457: # Basically, all the states do is pass calls to the elements and aggregate
458: # the results.
1.3 bowersj2 459:
460: sub new {
461: my $proto = shift;
462: my $class = ref($proto) || $proto;
463: my $self = {};
464:
465: $self->{NAME} = shift;
466: $self->{TITLE} = shift;
467: $self->{ELEMENTS} = [];
468:
469: bless($self, $class);
470:
471: $helper->registerState($self);
472:
473: return $self;
474: }
475:
476: sub name {
477: my $self = shift;
478: return $self->{NAME};
479: }
480:
481: sub title {
482: my $self = shift;
483: return $self->{TITLE};
484: }
485:
1.4 bowersj2 486: sub preprocess {
487: my $self = shift;
488: for my $element (@{$self->{ELEMENTS}}) {
489: $element->preprocess();
490: }
491: }
492:
1.6 bowersj2 493: # FIXME: Document that all postprocesses must return a true value or
494: # the state transition will be overridden
1.4 bowersj2 495: sub postprocess {
496: my $self = shift;
1.6 bowersj2 497:
498: # Save the state so we can roll it back if we need to.
499: my $originalState = $helper->{STATE};
500: my $everythingSuccessful = 1;
501:
1.4 bowersj2 502: for my $element (@{$self->{ELEMENTS}}) {
1.6 bowersj2 503: my $result = $element->postprocess();
504: if (!$result) { $everythingSuccessful = 0; }
505: }
506:
507: # If not all the postprocesses were successful, override
508: # any state transitions that may have occurred. It is the
509: # responsibility of the states to make sure they have
510: # error handling in that case.
511: if (!$everythingSuccessful) {
512: $helper->{STATE} = $originalState;
1.4 bowersj2 513: }
514: }
515:
516: sub overrideForm {
517: return 0;
518: }
519:
520: sub addElement {
521: my $self = shift;
522: my $element = shift;
523:
524: push @{$self->{ELEMENTS}}, $element;
525: }
526:
527: sub render {
528: my $self = shift;
529: my @results = ();
530:
531: for my $element (@{$self->{ELEMENTS}}) {
532: push @results, $element->render();
533: }
534: return join("\n", @results);
535: }
536:
537: 1;
538:
539: package Apache::lonhelper::element;
540: # Support code for elements
541:
542: =pod
543:
544: =head2 Element Base Class
545:
546: The Apache::lonhelper::element base class provides support methods for
547: the elements to use, such as a multiple value processer.
548:
549: B<Methods>:
550:
551: =over 4
552:
553: =item * process_multiple_choices(formName, varName): Process the form
554: element named "formName" and place the selected items into the helper
555: variable named varName. This is for things like checkboxes or
556: multiple-selection listboxes where the user can select more then
557: one entry. The selected entries are delimited by triple pipes in
1.5 bowersj2 558: the helper variables, like this:
559:
560: CHOICE_1|||CHOICE_2|||CHOICE_3
1.4 bowersj2 561:
562: =back
563:
564: =cut
565:
1.5 bowersj2 566: BEGIN {
1.7 bowersj2 567: &Apache::lonhelper::register('Apache::lonhelper::element',
568: ('nextstate'));
1.5 bowersj2 569: }
570:
1.4 bowersj2 571: # Because we use the param hash, this is often a sufficent
572: # constructor
573: sub new {
574: my $proto = shift;
575: my $class = ref($proto) || $proto;
576: my $self = $paramHash;
577: bless($self, $class);
578:
579: $self->{PARAMS} = $paramHash;
580: $self->{STATE} = $state;
581: $state->addElement($self);
582:
583: # Ensure param hash is not reused
584: $paramHash = {};
585:
586: return $self;
587: }
588:
1.5 bowersj2 589: sub start_nextstate {
590: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
591:
592: if ($target ne 'helper') {
593: return '';
594: }
595:
596: $paramHash->{NEXTSTATE} = &Apache::lonxml::get_all_text('/nextstate',
597: $parser);
598: return '';
599: }
600:
601: sub end_nextstate { return ''; }
602:
1.4 bowersj2 603: sub preprocess {
604: return 1;
605: }
606:
607: sub postprocess {
608: return 1;
609: }
610:
611: sub render {
612: return '';
613: }
614:
1.3 bowersj2 615: sub process_multiple_choices {
616: my $self = shift;
617: my $formname = shift;
618: my $var = shift;
619:
620: my $formvalue = $ENV{'form.' . $formname};
621: if ($formvalue) {
1.5 bowersj2 622: # Must extract values from querystring directly, as there
1.3 bowersj2 623: # may be more then one.
624: my @values;
1.5 bowersj2 625: for my $formparam (split (/&/, $ENV{QUERY_STRING})) {
1.3 bowersj2 626: my ($name, $value) = split(/=/, $formparam);
627: if ($name ne $formname) {
628: next;
629: }
630: $value =~ tr/+/ /;
631: $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
632: push @values, $value;
633: }
1.5 bowersj2 634: $helper->{VARS}->{$var} = join('|||', @values);
1.3 bowersj2 635: }
636:
637: return;
638: }
639:
1.4 bowersj2 640: 1;
641:
642: package Apache::lonhelper::message;
643:
644: =pod
645:
646: =head2 Element: message
647:
648: Message elements display the contents of their <message_text> tags, and
1.5 bowersj2 649: transition directly to the state in the <nextstate> tag. Example:
1.4 bowersj2 650:
651: <message>
1.5 bowersj2 652: <nextstate>GET_NAME</nextstate>
1.4 bowersj2 653: <message_text>This is the <b>message</b> the user will see,
654: <i>HTML allowed</i>.</message_text>
655: </message>
656:
1.5 bowersj2 657: This will display the HTML message and transition to the <nextstate> if
1.7 bowersj2 658: given. The HTML will be directly inserted into the helper, so if you don't
1.4 bowersj2 659: want text to run together, you'll need to manually wrap the <message_text>
660: in <p> tags, or whatever is appropriate for your HTML.
661:
1.5 bowersj2 662: Message tags do not add in whitespace, so if you want it, you'll need to add
663: it into states. This is done so you can inline some elements, such as
664: the <date> element, right between two messages, giving the appearence that
665: the <date> element appears inline. (Note the elements can not be embedded
666: within each other.)
667:
1.4 bowersj2 668: This is also a good template for creating your own new states, as it has
669: very little code beyond the state template.
670:
671: =cut
672:
673: no strict;
674: @ISA = ("Apache::lonhelper::element");
675: use strict;
676:
677: BEGIN {
1.7 bowersj2 678: &Apache::lonhelper::register('Apache::lonhelper::message',
1.5 bowersj2 679: ('message', 'message_text'));
1.3 bowersj2 680: }
681:
1.5 bowersj2 682: sub new {
683: my $ref = Apache::lonhelper::element->new();
684: bless($ref);
685: }
1.4 bowersj2 686:
687: # CONSTRUCTION: Construct the message element from the XML
688: sub start_message {
689: return '';
1.3 bowersj2 690: }
691:
1.4 bowersj2 692: sub end_message {
693: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
694:
695: if ($target ne 'helper') {
696: return '';
697: }
698: Apache::lonhelper::message->new();
699: return '';
1.3 bowersj2 700: }
701:
1.5 bowersj2 702: sub start_message_text {
1.4 bowersj2 703: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
704:
705: if ($target ne 'helper') {
706: return '';
707: }
1.5 bowersj2 708:
709: $paramHash->{MESSAGE_TEXT} = &Apache::lonxml::get_all_text('/message_text',
710: $parser);
711: }
1.3 bowersj2 712:
1.5 bowersj2 713: sub end_message_text { return 1; }
714:
715: sub render {
716: my $self = shift;
717:
718: return $self->{MESSAGE_TEXT};
719: }
720: # If a NEXTSTATE was given, switch to it
721: sub postprocess {
722: my $self = shift;
723: if (defined($self->{NEXTSTATE})) {
724: $helper->changeState($self->{NEXTSTATE});
725: }
1.6 bowersj2 726:
727: return 1;
1.5 bowersj2 728: }
729: 1;
730:
731: package Apache::lonhelper::choices;
732:
733: =pod
734:
735: =head2 Element: choices
736:
737: Choice states provide a single choice to the user as a text selection box.
738: A "choice" is two pieces of text, one which will be displayed to the user
739: (the "human" value), and one which will be passed back to the program
740: (the "computer" value). For instance, a human may choose from a list of
741: resources on disk by title, while your program wants the file name.
742:
743: <choices> takes an attribute "variable" to control which helper variable
744: the result is stored in.
745:
746: <choices> takes an attribute "multichoice" which, if set to a true
747: value, will allow the user to select multiple choices.
748:
749: B<SUB-TAGS>
750:
751: <choices> can have the following subtags:
752:
753: =over 4
754:
755: =item * <nextstate>state_name</nextstate>: If given, this will cause the
756: choice element to transition to the given state after executing. If
757: this is used, do not pass nextstates to the <choice> tag.
758:
759: =item * <choice />: If the choices are static,
760: this element will allow you to specify them. Each choice
761: contains attribute, "computer", as described above. The
762: content of the tag will be used as the human label.
763: For example,
764: <choice computer='234-12-7312'>Bobby McDormik</choice>.
765:
766: <choice> may optionally contain a 'nextstate' attribute, which
767: will be the state transisitoned to if the choice is made, if
768: the choice is not multichoice.
769:
770: =back
771:
772: To create the choices programmatically, either wrap the choices in
773: <condition> tags (prefered), or use an <exec> block inside the <choice>
774: tag. Store the choices in $state->{CHOICES}, which is a list of list
775: references, where each list has three strings. The first is the human
776: name, the second is the computer name. and the third is the option
777: next state. For example:
778:
779: <exec>
780: for (my $i = 65; $i < 65 + 26; $i++) {
781: push @{$state->{CHOICES}}, [chr($i), $i, 'next'];
782: }
783: </exec>
784:
785: This will allow the user to select from the letters A-Z (in ASCII), while
786: passing the ASCII value back into the helper variables, and the state
787: will in all cases transition to 'next'.
788:
789: You can mix and match methods of creating choices, as long as you always
790: "push" onto the choice list, rather then wiping it out. (You can even
791: remove choices programmatically, but that would probably be bad form.)
792:
793: =cut
794:
795: no strict;
796: @ISA = ("Apache::lonhelper::element");
797: use strict;
798:
799: BEGIN {
1.7 bowersj2 800: &Apache::lonhelper::register('Apache::lonhelper::choices',
1.5 bowersj2 801: ('choice', 'choices'));
802: }
803:
804: sub new {
805: my $ref = Apache::lonhelper::element->new();
806: bless($ref);
807: }
808:
809: # CONSTRUCTION: Construct the message element from the XML
810: sub start_choices {
811: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
812:
813: if ($target ne 'helper') {
814: return '';
815: }
816:
817: # Need to initialize the choices list, so everything can assume it exists
818: $paramHash->{'variable'} = $token->[2]{'variable'};
819: $helper->declareVar($paramHash->{'variable'});
820: $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
821: $paramHash->{CHOICES} = [];
822: return '';
823: }
824:
825: sub end_choices {
826: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
827:
828: if ($target ne 'helper') {
829: return '';
830: }
831: Apache::lonhelper::choices->new();
832: return '';
833: }
834:
835: sub start_choice {
836: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
837:
838: if ($target ne 'helper') {
839: return '';
840: }
841:
842: my $computer = $token->[2]{'computer'};
843: my $human = &Apache::lonxml::get_all_text('/choice',
844: $parser);
845: my $nextstate = $token->[2]{'nextstate'};
846: push @{$paramHash->{CHOICES}}, [$human, $computer, $nextstate];
847: return '';
848: }
849:
850: sub end_choice {
851: return '';
852: }
853:
854: sub render {
855: # START HERE: Replace this with correct choices code.
856: my $self = shift;
857: my $var = $self->{'variable'};
858: my $buttons = '';
859: my $result = '';
860:
861: if ($self->{'multichoice'}) {
1.6 bowersj2 862: $result .= <<SCRIPT;
1.5 bowersj2 863: <script>
864: function checkall(value) {
865: for (i=0; i<document.forms.wizform.elements.length; i++) {
866: document.forms.wizform.elements[i].checked=value;
867: }
868: }
869: </script>
870: SCRIPT
871: $buttons = <<BUTTONS;
872: <br />
873: <input type="button" onclick="checkall(true)" value="Select All" />
874: <input type="button" onclick="checkall(false)" value="Unselect All" />
1.6 bowersj2 875: <br />
1.5 bowersj2 876: BUTTONS
877: }
878:
879: if (defined $self->{ERROR_MSG}) {
1.6 bowersj2 880: $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br />';
1.5 bowersj2 881: }
882:
883: $result .= $buttons;
1.6 bowersj2 884:
1.5 bowersj2 885: $result .= "<table>\n\n";
886:
887: my $type = "radio";
888: if ($self->{'multichoice'}) { $type = 'checkbox'; }
889: my $checked = 0;
890: foreach my $choice (@{$self->{CHOICES}}) {
891: $result .= "<tr>\n<td width='20'> </td>\n";
892: $result .= "<td valign='top'><input type='$type' name='$var.forminput'"
893: . "' value='" .
894: HTML::Entities::encode($choice->[1])
895: . "'";
896: if (!$self->{'multichoice'} && !$checked) {
897: $result .= " checked ";
898: $checked = 1;
899: }
900: $result .= "/></td><td> " . $choice->[0] . "</td></tr>\n";
901: }
902: $result .= "</table>\n\n\n";
903: $result .= $buttons;
904:
905: return $result;
906: }
907:
908: # If a NEXTSTATE was given or a nextstate for this choice was
909: # given, switch to it
910: sub postprocess {
911: my $self = shift;
912: my $chosenValue = $ENV{'form.' . $self->{'variable'} . '.forminput'};
913:
1.6 bowersj2 914: if (!$chosenValue) {
915: $self->{ERROR_MSG} = "You must choose one or more choices to" .
916: " continue.";
917: return 0;
918: }
919:
920: if ($self->{'multichoice'}) {
921: $self->process_multiple_choices($self->{'variable'}.'.forminput',
922: $self->{'variable'});
923: }
924:
1.5 bowersj2 925: if (defined($self->{NEXTSTATE})) {
926: $helper->changeState($self->{NEXTSTATE});
927: }
928:
929: foreach my $choice (@{$self->{CHOICES}}) {
930: if ($choice->[1] eq $chosenValue) {
931: if (defined($choice->[2])) {
932: $helper->changeState($choice->[2]);
933: }
934: }
935: }
1.6 bowersj2 936: return 1;
1.5 bowersj2 937: }
938: 1;
939:
940: package Apache::lonhelper::date;
941:
942: =pod
943:
944: =head2 Element: date
945:
946: Date elements allow the selection of a date with a drop down list.
947:
948: Date elements can take two attributes:
949:
950: =over 4
951:
952: =item * B<variable>: The name of the variable to store the chosen
953: date in. Required.
954:
955: =item * B<hoursminutes>: If a true value, the date will show hours
956: and minutes, as well as month/day/year. If false or missing,
957: the date will only show the month, day, and year.
958:
959: =back
960:
961: Date elements contain only an option <nextstate> tag to determine
962: the next state.
963:
964: Example:
965:
966: <date variable="DUE_DATE" hoursminutes="1">
967: <nextstate>choose_why</nextstate>
968: </date>
969:
970: =cut
971:
972: no strict;
973: @ISA = ("Apache::lonhelper::element");
974: use strict;
975:
976: use Time::localtime;
977:
978: BEGIN {
1.7 bowersj2 979: &Apache::lonhelper::register('Apache::lonhelper::date',
1.5 bowersj2 980: ('date'));
981: }
982:
983: # Don't need to override the "new" from element
984: sub new {
985: my $ref = Apache::lonhelper::element->new();
986: bless($ref);
987: }
988:
989: my @months = ("January", "February", "March", "April", "May", "June", "July",
990: "August", "September", "October", "November", "December");
991:
992: # CONSTRUCTION: Construct the message element from the XML
993: sub start_date {
994: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
995:
996: if ($target ne 'helper') {
997: return '';
998: }
999:
1000: $paramHash->{'variable'} = $token->[2]{'variable'};
1001: $helper->declareVar($paramHash->{'variable'});
1002: $paramHash->{'hoursminutes'} = $token->[2]{'hoursminutes'};
1003: }
1004:
1005: sub end_date {
1006: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1007:
1008: if ($target ne 'helper') {
1009: return '';
1010: }
1011: Apache::lonhelper::date->new();
1012: return '';
1013: }
1014:
1015: sub render {
1016: my $self = shift;
1017: my $result = "";
1018: my $var = $self->{'variable'};
1019:
1020: my $date;
1021:
1022: # Default date: The current hour.
1023: $date = localtime();
1024: $date->min(0);
1025:
1026: if (defined $self->{ERROR_MSG}) {
1027: $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
1028: }
1029:
1030: # Month
1031: my $i;
1032: $result .= "<select name='${var}month'>\n";
1033: for ($i = 0; $i < 12; $i++) {
1034: if ($i == $date->mon) {
1035: $result .= "<option value='$i' selected>";
1036: } else {
1037: $result .= "<option value='$i'>";
1038: }
1039: $result .= $months[$i] . "</option>\n";
1040: }
1041: $result .= "</select>\n";
1042:
1043: # Day
1044: $result .= "<select name='${var}day'>\n";
1045: for ($i = 1; $i < 32; $i++) {
1046: if ($i == $date->mday) {
1047: $result .= '<option selected>';
1048: } else {
1049: $result .= '<option>';
1050: }
1051: $result .= "$i</option>\n";
1052: }
1053: $result .= "</select>,\n";
1054:
1055: # Year
1056: $result .= "<select name='${var}year'>\n";
1057: for ($i = 2000; $i < 2030; $i++) { # update this after 64-bit dates
1058: if ($date->year + 1900 == $i) {
1059: $result .= "<option selected>";
1060: } else {
1061: $result .= "<option>";
1062: }
1063: $result .= "$i</option>\n";
1064: }
1065: $result .= "</select>,\n";
1066:
1067: # Display Hours and Minutes if they are called for
1068: if ($self->{'hoursminutes'}) {
1069: # Build hour
1070: $result .= "<select name='${var}hour'>\n";
1071: $result .= "<option " . ($date->hour == 0 ? 'selected ':'') .
1072: " value='0'>midnight</option>\n";
1073: for ($i = 1; $i < 12; $i++) {
1074: if ($date->hour == $i) {
1075: $result .= "<option selected value='$i'>$i a.m.</option>\n";
1076: } else {
1077: $result .= "<option value='$i'>$i a.m</option>\n";
1078: }
1079: }
1080: $result .= "<option " . ($date->hour == 12 ? 'selected ':'') .
1081: " value='12'>noon</option>\n";
1082: for ($i = 13; $i < 24; $i++) {
1083: my $printedHour = $i - 12;
1084: if ($date->hour == $i) {
1085: $result .= "<option selected value='$i'>$printedHour p.m.</option>\n";
1086: } else {
1087: $result .= "<option value='$i'>$printedHour p.m.</option>\n";
1088: }
1089: }
1090:
1091: $result .= "</select> :\n";
1092:
1093: $result .= "<select name='${var}minute'>\n";
1094: for ($i = 0; $i < 60; $i++) {
1095: my $printedMinute = $i;
1096: if ($i < 10) {
1097: $printedMinute = "0" . $printedMinute;
1098: }
1099: if ($date->min == $i) {
1100: $result .= "<option selected>";
1101: } else {
1102: $result .= "<option>";
1103: }
1104: $result .= "$printedMinute</option>\n";
1105: }
1106: $result .= "</select>\n";
1107: }
1108:
1109: return $result;
1110:
1111: }
1112: # If a NEXTSTATE was given, switch to it
1113: sub postprocess {
1114: my $self = shift;
1115: my $var = $self->{'variable'};
1116: my $month = $ENV{'form.' . $var . 'month'};
1117: my $day = $ENV{'form.' . $var . 'day'};
1118: my $year = $ENV{'form.' . $var . 'year'};
1119: my $min = 0;
1120: my $hour = 0;
1121: if ($self->{'hoursminutes'}) {
1122: $min = $ENV{'form.' . $var . 'minute'};
1123: $hour = $ENV{'form.' . $var . 'hour'};
1124: }
1125:
1126: my $chosenDate = Time::Local::timelocal(0, $min, $hour, $day, $month, $year);
1127: # Check to make sure that the date was not automatically co-erced into a
1128: # valid date, as we want to flag that as an error
1129: # This happens for "Feb. 31", for instance, which is coerced to March 2 or
1130: # 3, depending on if it's a leapyear
1131: my $checkDate = localtime($chosenDate);
1132:
1133: if ($checkDate->mon != $month || $checkDate->mday != $day ||
1134: $checkDate->year + 1900 != $year) {
1135: $self->{ERROR_MSG} = "Can't use " . $months[$month] . " $day, $year as a "
1136: . "date because it doesn't exist. Please enter a valid date.";
1.6 bowersj2 1137: return 0;
1.5 bowersj2 1138: }
1139:
1140: $helper->{VARS}->{$var} = $chosenDate;
1141:
1142: if (defined($self->{NEXTSTATE})) {
1143: $helper->changeState($self->{NEXTSTATE});
1144: }
1.6 bowersj2 1145:
1146: return 1;
1.5 bowersj2 1147: }
1148: 1;
1149:
1150: package Apache::lonhelper::resource;
1151:
1152: =pod
1153:
1154: =head2 Element: resource
1155:
1156: <resource> elements allow the user to select one or multiple resources
1157: from the current course. You can filter out which resources they can view,
1158: and filter out which resources they can select. The course will always
1159: be displayed fully expanded, because of the difficulty of maintaining
1160: selections across folder openings and closings. If this is fixed, then
1161: the user can manipulate the folders.
1162:
1163: <resource> takes the standard variable attribute to control what helper
1164: variable stores the results. It also takes a "multichoice" attribute,
1165: which controls whether the user can select more then one resource.
1166:
1167: B<SUB-TAGS>
1168:
1169: =over 4
1170:
1171: =item * <filterfunc>: If you want to filter what resources are displayed
1172: to the user, use a filter func. The <filterfunc> tag should contain
1173: Perl code that when wrapped with "sub { my $res = shift; " and "}" is
1174: a function that returns true if the resource should be displayed,
1175: and false if it should be skipped. $res is a resource object.
1176: (See Apache::lonnavmaps documentation for information about the
1177: resource object.)
1178:
1179: =item * <choicefunc>: Same as <filterfunc>, except that controls whether
1180: the given resource can be chosen. (It is almost always a good idea to
1181: show the user the folders, for instance, but you do not always want to
1182: let the user select them.)
1183:
1184: =item * <nextstate>: Standard nextstate behavior.
1185:
1186: =item * <valuefunc>: This function controls what is returned by the resource
1187: when the user selects it. Like filterfunc and choicefunc, it should be
1188: a function fragment that when wrapped by "sub { my $res = shift; " and
1189: "}" returns a string representing what you want to have as the value. By
1190: default, the value will be the resource ID of the object ($res->{ID}).
1191:
1192: =back
1193:
1194: =cut
1195:
1196: no strict;
1197: @ISA = ("Apache::lonhelper::element");
1198: use strict;
1199:
1200: BEGIN {
1.7 bowersj2 1201: &Apache::lonhelper::register('Apache::lonhelper::resource',
1.5 bowersj2 1202: ('resource', 'filterfunc',
1203: 'choicefunc', 'valuefunc'));
1204: }
1205:
1206: sub new {
1207: my $ref = Apache::lonhelper::element->new();
1208: bless($ref);
1209: }
1210:
1211: # CONSTRUCTION: Construct the message element from the XML
1212: sub start_resource {
1213: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1214:
1215: if ($target ne 'helper') {
1216: return '';
1217: }
1218:
1219: $paramHash->{'variable'} = $token->[2]{'variable'};
1220: $helper->declareVar($paramHash->{'variable'});
1221: return '';
1222: }
1223:
1224: sub end_resource {
1225: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1226:
1227: if ($target ne 'helper') {
1228: return '';
1229: }
1230: if (!defined($paramHash->{FILTER_FUNC})) {
1231: $paramHash->{FILTER_FUNC} = sub {return 1;};
1232: }
1233: if (!defined($paramHash->{CHOICE_FUNC})) {
1234: $paramHash->{CHOICE_FUNC} = sub {return 1;};
1235: }
1236: if (!defined($paramHash->{VALUE_FUNC})) {
1237: $paramHash->{VALUE_FUNC} = sub {my $res = shift; return $res->{ID}; };
1238: }
1239: Apache::lonhelper::resource->new();
1.4 bowersj2 1240: return '';
1241: }
1242:
1.5 bowersj2 1243: sub start_filterfunc {
1244: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1245:
1246: if ($target ne 'helper') {
1247: return '';
1248: }
1249:
1250: my $contents = Apache::lonxml::get_all_text('/filterfunc',
1251: $parser);
1252: $contents = 'sub { my $res = shift; ' . $contents . '}';
1253: $paramHash->{FILTER_FUNC} = eval $contents;
1254: }
1255:
1256: sub end_filterfunc { return ''; }
1257:
1258: sub start_choicefunc {
1259: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1260:
1261: if ($target ne 'helper') {
1262: return '';
1263: }
1264:
1265: my $contents = Apache::lonxml::get_all_text('/choicefunc',
1266: $parser);
1267: $contents = 'sub { my $res = shift; ' . $contents . '}';
1268: $paramHash->{CHOICE_FUNC} = eval $contents;
1269: }
1270:
1271: sub end_choicefunc { return ''; }
1272:
1273: sub start_valuefunc {
1274: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1275:
1276: if ($target ne 'helper') {
1277: return '';
1278: }
1279:
1280: my $contents = Apache::lonxml::get_all_text('/valuefunc',
1281: $parser);
1282: $contents = 'sub { my $res = shift; ' . $contents . '}';
1283: $paramHash->{VALUE_FUNC} = eval $contents;
1284: }
1285:
1286: sub end_valuefunc { return ''; }
1287:
1288: # A note, in case I don't get to this before I leave.
1289: # If someone complains about the "Back" button returning them
1290: # to the previous folder state, instead of returning them to
1291: # the previous helper state, the *correct* answer is for the helper
1292: # to keep track of how many times the user has manipulated the folders,
1293: # and feed that to the history.go() call in the helper rendering routines.
1294: # If done correctly, the helper itself can keep track of how many times
1295: # it renders the same states, so it doesn't go in just this state, and
1296: # you can lean on the browser back button to make sure it all chains
1297: # correctly.
1298: # Right now, though, I'm just forcing all folders open.
1299:
1300: sub render {
1301: my $self = shift;
1302: my $result = "";
1303: my $var = $self->{'variable'};
1304: my $curVal = $helper->{VARS}->{$var};
1305:
1306: if (defined $self->{ERROR_MSG}) {
1307: $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
1308: }
1309:
1310: my $filterFunc = $self->{FILTER_FUNC};
1311: my $choiceFunc = $self->{CHOICE_FUNC};
1312: my $valueFunc = $self->{VALUE_FUNC};
1313:
1314: # Create the composite function that renders the column on the nav map
1315: # have to admit any language that lets me do this can't be all bad
1316: # - Jeremy (Pythonista) ;-)
1317: my $checked = 0;
1318: my $renderColFunc = sub {
1319: my ($resource, $part, $params) = @_;
1320:
1321: if (!&$choiceFunc($resource)) {
1322: return '<td> </td>';
1323: } else {
1324: my $col = "<td><input type='radio' name='${var}.forminput' ";
1325: if (!$checked) {
1326: $col .= "checked ";
1327: $checked = 1;
1328: }
1329: $col .= "value='" .
1330: HTML::Entities::encode(&$valueFunc($resource))
1331: . "' /></td>";
1332: return $col;
1333: }
1334: };
1335:
1336: $ENV{'form.condition'} = 1;
1337: $result .=
1338: &Apache::lonnavmaps::render( { 'cols' => [$renderColFunc,
1339: Apache::lonnavmaps::resource()],
1340: 'showParts' => 0,
1341: 'url' => $helper->{URL},
1342: 'filterFunc' => $filterFunc,
1343: 'resource_no_folder_link' => 1 }
1344: );
1345:
1346: return $result;
1347: }
1348:
1349: sub postprocess {
1350: my $self = shift;
1351: if (defined($self->{NEXTSTATE})) {
1352: $helper->changeState($self->{NEXTSTATE});
1353: }
1.6 bowersj2 1354:
1355: return 1;
1.5 bowersj2 1356: }
1357:
1358: 1;
1359:
1360: package Apache::lonhelper::student;
1361:
1362: =pod
1363:
1364: =head2 Element: student
1365:
1366: Student elements display a choice of students enrolled in the current
1367: course. Currently it is primitive; this is expected to evolve later.
1368:
1369: Student elements take two attributes: "variable", which means what
1370: it usually does, and "multichoice", which if true allows the user
1371: to select multiple students.
1372:
1373: =cut
1374:
1375: no strict;
1376: @ISA = ("Apache::lonhelper::element");
1377: use strict;
1378:
1379:
1380:
1381: BEGIN {
1.7 bowersj2 1382: &Apache::lonhelper::register('Apache::lonhelper::student',
1.5 bowersj2 1383: ('student'));
1384: }
1385:
1386: sub new {
1387: my $ref = Apache::lonhelper::element->new();
1388: bless($ref);
1389: }
1.4 bowersj2 1390:
1.5 bowersj2 1391: sub start_student {
1.4 bowersj2 1392: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1393:
1394: if ($target ne 'helper') {
1395: return '';
1396: }
1397:
1.5 bowersj2 1398: $paramHash->{'variable'} = $token->[2]{'variable'};
1399: $helper->declareVar($paramHash->{'variable'});
1400: $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
1401: }
1402:
1403: sub end_student {
1404: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1405:
1406: if ($target ne 'helper') {
1407: return '';
1408: }
1409: Apache::lonhelper::student->new();
1.3 bowersj2 1410: }
1.5 bowersj2 1411:
1412: sub render {
1413: my $self = shift;
1414: my $result = '';
1415: my $buttons = '';
1416:
1417: if ($self->{'multichoice'}) {
1418: $result = <<SCRIPT;
1419: <script>
1420: function checkall(value) {
1421: for (i=0; i<document.forms.wizform.elements.length; i++) {
1422: document.forms.wizform.elements[i].checked=value;
1423: }
1424: }
1425: </script>
1426: SCRIPT
1427: $buttons = <<BUTTONS;
1428: <br />
1429: <input type="button" onclick="checkall(true)" value="Select All" />
1430: <input type="button" onclick="checkall(false)" value="Unselect All" />
1431: <br />
1432: BUTTONS
1433: }
1434:
1435: if (defined $self->{ERROR_MSG}) {
1436: $result .= '<font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
1437: }
1438:
1439: # Load up the students
1440: my $choices = &Apache::loncoursedata::get_classlist();
1441:
1442: my @keys = keys %{$choices};
1443:
1444: # Constants
1445: my $section = Apache::loncoursedata::CL_SECTION();
1446: my $fullname = Apache::loncoursedata::CL_FULLNAME();
1447:
1448: # Sort by: Section, name
1449: @keys = sort {
1450: if ($choices->{$a}->[$section] ne $choices->{$b}->[$section]) {
1451: return $choices->{$a}->[$section] cmp $choices->{$b}->[$section];
1452: }
1453: return $choices->{$a}->[$fullname] cmp $choices->{$b}->[$fullname];
1454: } @keys;
1455:
1456: my $type = 'radio';
1457: if ($self->{'multichoice'}) { $type = 'checkbox'; }
1458: $result .= "<table cellspacing='2' cellpadding='2' border='0'>\n";
1459: $result .= "<tr><td></td><td align='center'><b>Student Name</b></td>".
1460: "<td align='center'><b>Section</b></td></tr>";
1461:
1462: my $checked = 0;
1463: foreach (@keys) {
1464: $result .= "<tr><td><input type='$type' name='" .
1465: $self->{'variable'} . '.forminput' . "'";
1466:
1467: if (!$self->{'multichoice'} && !$checked) {
1468: $result .= " checked ";
1469: $checked = 1;
1470: }
1471: $result .=
1472: " value='" . HTML::Entities::encode($_)
1473: . "' /></td><td>"
1474: . HTML::Entities::encode($choices->{$_}->[$fullname])
1475: . "</td><td align='center'>"
1476: . HTML::Entities::encode($choices->{$_}->[$section])
1477: . "</td></tr>\n";
1478: }
1479:
1480: $result .= "</table>\n\n";
1481: $result .= $buttons;
1.4 bowersj2 1482:
1.5 bowersj2 1483: return $result;
1484: }
1485:
1.6 bowersj2 1486: sub postprocess {
1487: my $self = shift;
1488:
1489: my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
1490: if (!$result) {
1491: $self->{ERROR_MSG} = 'You must choose at least one student '.
1492: 'to continue.';
1493: return 0;
1494: }
1495:
1496: if ($self->{'multichoice'}) {
1497: $self->process_multiple_choices($self->{'variable'}.'.forminput',
1498: $self->{'variable'});
1499: }
1500: if (defined($self->{NEXTSTATE})) {
1501: $helper->changeState($self->{NEXTSTATE});
1502: }
1503:
1504: return 1;
1505: }
1506:
1.5 bowersj2 1507: 1;
1508:
1509: package Apache::lonhelper::files;
1510:
1511: =pod
1512:
1513: =head2 Element: files
1514:
1515: files allows the users to choose files from a given directory on the
1516: server. It is always multichoice and stores the result as a triple-pipe
1517: delimited entry in the helper variables.
1518:
1519: Since it is extremely unlikely that you can actually code a constant
1520: representing the directory you wish to allow the user to search, <files>
1521: takes a subroutine that returns the name of the directory you wish to
1522: have the user browse.
1523:
1524: files accepts the attribute "variable" to control where the files chosen
1525: are put. It accepts the attribute "multichoice" as the other attribute,
1526: defaulting to false, which if true will allow the user to select more
1527: then one choice.
1528:
1529: <files> accepts three subtags. One is the "nextstate" sub-tag that works
1530: as it does with the other tags. Another is a <filechoice> sub tag that
1531: is Perl code that, when surrounded by "sub {" and "}" will return a
1532: string representing what directory on the server to allow the user to
1533: choose files from. Finally, the <filefilter> subtag should contain Perl
1534: code that when surrounded by "sub { my $filename = shift; " and "}",
1535: returns a true value if the user can pick that file, or false otherwise.
1536: The filename passed to the function will be just the name of the file,
1537: with no path info.
1538:
1539: =cut
1540:
1541: no strict;
1542: @ISA = ("Apache::lonhelper::element");
1543: use strict;
1544:
1545: BEGIN {
1.7 bowersj2 1546: &Apache::lonhelper::register('Apache::lonhelper::files',
1547: ('files', 'filechoice', 'filefilter'));
1.5 bowersj2 1548: }
1549:
1550: sub new {
1551: my $ref = Apache::lonhelper::element->new();
1552: bless($ref);
1553: }
1554:
1555: sub start_files {
1556: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1557:
1558: if ($target ne 'helper') {
1559: return '';
1560: }
1561: $paramHash->{'variable'} = $token->[2]{'variable'};
1562: $helper->declareVar($paramHash->{'variable'});
1563: $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
1564: }
1565:
1566: sub end_files {
1567: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1568:
1569: if ($target ne 'helper') {
1570: return '';
1571: }
1572: if (!defined($paramHash->{FILTER_FUNC})) {
1573: $paramHash->{FILTER_FUNC} = sub { return 1; };
1574: }
1575: Apache::lonhelper::files->new();
1576: }
1577:
1578: sub start_filechoice {
1579: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1580:
1581: if ($target ne 'helper') {
1582: return '';
1583: }
1584: $paramHash->{'filechoice'} = Apache::lonxml::get_all_text('/filechoice',
1585: $parser);
1586: }
1587:
1588: sub end_filechoice { return ''; }
1589:
1590: sub start_filefilter {
1591: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
1592:
1593: if ($target ne 'helper') {
1594: return '';
1595: }
1596:
1597: my $contents = Apache::lonxml::get_all_text('/filefilter',
1598: $parser);
1599: $contents = 'sub { my $filename = shift; ' . $contents . '}';
1600: $paramHash->{FILTER_FUNC} = eval $contents;
1601: }
1602:
1603: sub end_filefilter { return ''; }
1.3 bowersj2 1604:
1605: sub render {
1606: my $self = shift;
1.5 bowersj2 1607: my $result = '';
1608: my $var = $self->{'variable'};
1609:
1610: my $subdirFunc = eval('sub {' . $self->{'filechoice'} . '}');
1611: my $subdir = &$subdirFunc();
1612:
1613: my $filterFunc = $self->{FILTER_FUNC};
1614: my $buttons = '';
1615:
1616: if ($self->{'multichoice'}) {
1617: $result = <<SCRIPT;
1618: <script>
1619: function checkall(value) {
1620: for (i=0; i<document.forms.wizform.elements.length; i++) {
1621: ele = document.forms.wizform.elements[i];
1622: if (ele.type == "checkbox") {
1623: document.forms.wizform.elements[i].checked=value;
1624: }
1625: }
1626: }
1627: </script>
1628: SCRIPT
1629: my $buttons = <<BUTTONS;
1630: <br />
1631: <input type="button" onclick="checkall(true)" value="Select All" />
1632: <input type="button" onclick="checkall(false)" value="Unselect All" />
1633: <br />
1634: BUTTONS
1635: }
1636:
1637: # Get the list of files in this directory.
1638: my @fileList;
1639:
1640: # If the subdirectory is in local CSTR space
1641: if ($subdir =~ m|/home/([^/]+)/public_html|) {
1642: my $user = $1;
1643: my $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
1644: @fileList = &Apache::lonnet::dirlist($subdir, $domain, $user, '');
1645: } else {
1646: # local library server resource space
1647: @fileList = &Apache::lonnet::dirlist($subdir, $ENV{'user.domain'}, $ENV{'user.name'}, '');
1648: }
1.3 bowersj2 1649:
1.5 bowersj2 1650: $result .= $buttons;
1651:
1.6 bowersj2 1652: if (defined $self->{ERROR_MSG}) {
1653: $result .= '<br /><font color="#FF0000">' . $self->{ERROR_MSG} . '</font><br /><br />';
1654: }
1655:
1.5 bowersj2 1656: $result .= '<table border="0" cellpadding="1" cellspacing="1">';
1657:
1658: # Keeps track if there are no choices, prints appropriate error
1659: # if there are none.
1660: my $choices = 0;
1661: my $type = 'radio';
1662: if ($self->{'multichoice'}) {
1663: $type = 'checkbox';
1664: }
1665: # Print each legitimate file choice.
1666: for my $file (@fileList) {
1667: $file = (split(/&/, $file))[0];
1668: if ($file eq '.' || $file eq '..') {
1669: next;
1670: }
1671: my $fileName = $subdir .'/'. $file;
1672: if (&$filterFunc($file)) {
1673: $result .= '<tr><td align="right">' .
1674: "<input type='$type' name='" . $var
1675: . ".forminput' value='" . HTML::Entities::encode($fileName) .
1676: "'";
1677: if (!$self->{'multichoice'} && $choices == 0) {
1678: $result .= ' checked';
1679: }
1680: $result .= "/></td><td>" . $file . "</td></tr>\n";
1681: $choices++;
1682: }
1683: }
1684:
1685: $result .= "</table>\n";
1686:
1687: if (!$choices) {
1688: $result .= '<font color="#FF0000">There are no files available to select in this directory. Please go back and select another option.</font><br /><br />';
1689: }
1690:
1691: $result .= $buttons;
1692:
1693: return $result;
1.4 bowersj2 1694: }
1.5 bowersj2 1695:
1.4 bowersj2 1696: sub postprocess {
1697: my $self = shift;
1.6 bowersj2 1698: my $result = $ENV{'form.' . $self->{'variable'} . '.forminput'};
1699: if (!$result) {
1700: $self->{ERROR_MSG} = 'You must choose at least one file '.
1701: 'to continue.';
1702: return 0;
1703: }
1704:
1.5 bowersj2 1705: if ($self->{'multichoice'}) {
1706: $self->process_multiple_choices($self->{'variable'}.'.forminput',
1707: $self->{'variable'});
1708: }
1709: if (defined($self->{NEXTSTATE})) {
1710: $helper->changeState($self->{NEXTSTATE});
1.3 bowersj2 1711: }
1.6 bowersj2 1712:
1713: return 1;
1.3 bowersj2 1714: }
1.8 ! bowersj2 1715:
! 1716: 1;
! 1717:
! 1718: package Apache::lonhelper::general;
! 1719:
! 1720: =pod
! 1721:
! 1722: =head2 General-purpose tag: <exec>
! 1723:
! 1724: The contents of the exec tag are executed as Perl code, not inside a
! 1725: safe space, so the full range of $ENV and such is available. The code
! 1726: will be executed as a subroutine wrapped with the following code:
! 1727:
! 1728: "sub { my $helper = shift; my $state = shift;" and
! 1729:
! 1730: "}"
! 1731:
! 1732: The return value is ignored.
! 1733:
! 1734: $helper is the helper object. Feel free to add methods to the helper
! 1735: object to support whatever manipulation you may need to do (for instance,
! 1736: overriding the form location if the state is the final state; see
! 1737: lonparm.helper for an example).
! 1738:
! 1739: $state is the $paramHash that has currently been generated and may
! 1740: be manipulated by the code in exec. Note that the $state is not yet
! 1741: an actual state B<object>, it is just a hash, so do not expect to
! 1742: be able to call methods on it.
! 1743:
! 1744: =cut
! 1745:
! 1746: BEGIN {
! 1747: &Apache::lonhelper::register('Apache::lonhelper::general',
! 1748: 'exec', 'condition', 'clause');
! 1749: }
! 1750:
! 1751: sub start_exec {
! 1752: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
! 1753:
! 1754: if ($target ne 'helper') {
! 1755: return '';
! 1756: }
! 1757:
! 1758: my $code = &Apache::lonxml::get_all_text('/exec', $parser);
! 1759:
! 1760: $code = eval ('sub { my $helper = shift; my $state = shift; ' .
! 1761: $code . "}");
! 1762: &$code($helper, $paramHash);
! 1763: }
! 1764:
! 1765: sub end_exec { return ''; }
! 1766:
! 1767: =pod
! 1768:
! 1769: =head2 General-purpose tag: <condition>
! 1770:
! 1771: The <condition> tag allows you to mask out parts of the helper code
! 1772: depending on some programatically determined condition. The condition
! 1773: tag contains a tag <clause> which contains perl code that when wrapped
! 1774: with "sub { my $helper = shift; my $state = shift; " and "}", returns
! 1775: a true value if the XML in the condition should be evaluated as a normal
! 1776: part of the helper, or false if it should be completely discarded.
! 1777:
! 1778: The <clause> tag must be the first sub-tag of the <condition> tag or
! 1779: it will not work as expected.
! 1780:
! 1781: =cut
! 1782:
! 1783: # The condition tag just functions as a marker, it doesn't have
! 1784: # to "do" anything. Technically it doesn't even have to be registered
! 1785: # with the lonxml code, but I leave this here to be explicit about it.
! 1786: sub start_condition { return ''; }
! 1787: sub end_condition { return ''; }
! 1788:
! 1789: sub start_clause {
! 1790: my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
! 1791:
! 1792: if ($target ne 'helper') {
! 1793: return '';
! 1794: }
! 1795:
! 1796: my $clause = Apache::lonxml::get_all_text('/clause', $parser);
! 1797: $clause = eval('sub { my $helper = shift; my $state = shift; '
! 1798: . $clause . '}');
! 1799: if (!&$clause($helper, $paramHash)) {
! 1800: # Discard all text until the /condition.
! 1801: &Apache::lonxml::get_all_text('/condition', $parser);
! 1802: }
! 1803: }
! 1804:
! 1805: sub end_clause { return ''; }
1.5 bowersj2 1806:
1.4 bowersj2 1807: 1;
1.3 bowersj2 1808:
1.1 bowersj2 1809: __END__
1.3 bowersj2 1810:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>