[system] / trunk / pg / lib / Applet.pm Repository:
ViewVC logotype

Annotation of /trunk/pg/lib/Applet.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5624 - (view) (download) (as text)

1 : gage 5574 ################################################################################
2 :     # WeBWorK Online Homework Delivery System
3 :     # Copyright 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
4 : gage 5624 # $CVSHeader: pg/lib/Applet.pm,v 1.6 2008/03/25 21:59:06 gage Exp $
5 : gage 5574 #
6 :     # This program is free software; you can redistribute it and/or modify it under
7 :     # the terms of either: (a) the GNU General Public License as published by the
8 :     # Free Software Foundation; either version 2, or (at your option) any later
9 :     # version, or (b) the "Artistic License" which comes with this package.
10 :     #
11 :     # This program is distributed in the hope that it will be useful, but WITHOUT
12 :     # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 :     # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
14 :     # Artistic License for more details.
15 :     ################################################################################
16 :    
17 :     =head1 NAME
18 :    
19 :     Applet.pl - Provides code for inserting FlashApplets and JavaApplets into webwork problems
20 :    
21 :     =head1 SYNPOSIS
22 :    
23 :     ###################################
24 :     # Create link to applet
25 :     ###################################
26 :     my $appletName = "LineThruPointsWW";
27 : gage 5594 $applet = new FlashApplet(
28 :     # can be replaced by $applet =FlashApplet() when using AppletObjects.pl
29 : gage 5574 codebase => findAppletCodebase("$appletName.swf"),
30 :     appletName => $appletName,
31 :     appletId => $appletName,
32 :     submitActionAlias => 'checkAnswer',
33 :     );
34 :    
35 :     ###################################
36 :     # Configure applet
37 :     ###################################
38 :    
39 :     #xml data to set up the problem-rac
40 : gage 5619 $applet->state(qq{<XML>
41 : gage 5574 <point xval='$xval_1' yval='$yval_1' />
42 :     <point xval='$xval_2' yval='$yval_2' />
43 :     </XML>});
44 :    
45 :    
46 :     ###################################
47 :     # insert applet header material
48 :     ###################################
49 :     HEADER_TEXT($applet->insertHeader );
50 :    
51 :     ###################################
52 :     # Text section
53 :     #
54 :    
55 :     ###################################
56 :     #insert applet into body
57 :     ###################################
58 :     TEXT( MODES(TeX=>'object code', HTML=>$applet->insertObject));
59 :    
60 :    
61 :     =head1 DESCRIPTION
62 :    
63 :     This file provides an object to store in one place
64 :     all of the information needed to call an applet.
65 :    
66 :     The object FlashApplet has defaults for inserting flash applets.
67 :    
68 :     =over
69 :    
70 :     =item *
71 :    
72 :     =item *
73 :    
74 :     =back
75 :    
76 :     (not yet completed)
77 :    
78 :     The module JavaApplet has defaults for inserting java applets.
79 :    
80 : gage 5619 The module Applet stores common code for the two types of applet.
81 : gage 5574
82 :     =head1 USAGE
83 :    
84 : gage 5619 These modules are activate by listing it in the modules section of global.conf and rebooting the server.
85 :     The companion file to this one is macros/AppletObjects.pl
86 : gage 5574
87 : gage 5619 qw(Applet FlashApplet JavaApplet)
88 :    
89 : gage 5574 =cut
90 :    
91 :    
92 :    
93 :     package Applet;
94 :    
95 : gage 5624 use URI::Escape;
96 : gage 5574
97 :    
98 :    
99 :     use MIME::Base64 qw( encode_base64 decode_base64);
100 :    
101 : gage 5594
102 :     =head2 Default javaScript functions placed in header
103 :    
104 :     These functions are automatically defined for use for
105 :     any javaScript placed in the text of a PG question.
106 :    
107 : gage 5619 getApplet(appletName) -- finds the applet path in the DOM
108 : gage 5594
109 : gage 5619 submitAction() -- calls the submit action of the applets
110 :    
111 : gage 5594
112 : gage 5619 initializeAction() -- calls the initialize action of the applets
113 : gage 5594
114 :     getQE(name) -- gets an HTML element of the question by name
115 :     or by id. Be sure to keep all names and ids
116 :     unique within a given PG question.
117 :    
118 :     getQuestionElement(name) -- long form of getQE(name)
119 :    
120 :     listQuestionElements() -- for discovering the names of inputs in the
121 :     PG question. An alert dialog will list all
122 :     of the elements.
123 :     Usage: Place this at the END of the question,
124 :     just before END_DOCUMENT():
125 :    
126 :     TEXT(qq!<script> listQuestionElements() </script>!);
127 :     ENDDOCUMENT();
128 :    
129 : gage 5619 list of accessor methods format: current_value = $self->method(new_value or empty)
130 : gage 5594
131 : gage 5619 appletId for simplicity and reliability appletId and appletName are always the same
132 :     appletName
133 :    
134 :     archive the name of the .jar file containing the applet code
135 :     code the name of the applet code in the .jar archive
136 :     codebase a prefix url used to find the archive and the applet itself
137 :    
138 :     height rectangle alloted in the html page for displaying the applet
139 :     width
140 :    
141 :     params an anonymous array containing name/value pairs
142 :     to configure the applet [name =>'value, ...]
143 :    
144 :     header stores the text to be added to the header section of the html page
145 :     object stores the text which places the applet on the html page
146 :    
147 :     debug in debug mode several alerts mark progress through the procedure of calling the applet
148 :    
149 :     config configuration are those customizable attributes of the applet which don't
150 :     change as it is used. When stored in hidden answer fields
151 :     it is usually stored in base64 encoded format.
152 :     base64_config base64 encode version of the contents of config
153 :    
154 :     configAlias (default: config ) names the applet command called with the contents of $self->config
155 :     to configure the applet. The parameters are passed to the applet in plain text using <xml>
156 :     The outer tags must be <xml> ..... </xml>
157 :     state state consists of those customizable attributes of the applet which change
158 :     as the applet is used. It is stored by the calling .pg question so that
159 :     when revisiting the question the applet
160 :     will be restored to the same state it was left in when the question was last
161 :     viewed.
162 :    
163 :     getStateAlias (default: getState) alias for command called to read the current state of the applet.
164 :     The state is passed in plain text xml format with outer tags: <xml>....</xml>
165 :     setStateAlias (default: setState) alias for the command called to reset the state of the applet.
166 :     The state is passed in plain text in xml format with outer tags: <xml>....</xml>
167 : gage 5594
168 : gage 5619 base64_state returns the base64 encoded version of the state stored in the applet object.
169 :    
170 :     initializeActionAlias -- (default: initializeAction) the name of the javaScript subroutine called to initialize the applet (some overlap with config/ and setState
171 :     submitActionAlias -- (default: submitAction)the name of the javaScript subroutine called when the submit button of the
172 :     .pg question is pressed.
173 : gage 5594
174 : gage 5619 returnFieldName
175 :    
176 :    
177 : gage 5578
178 : gage 5594
179 : gage 5574
180 : gage 5583 =cut
181 :    
182 : gage 5574
183 :    
184 :    
185 :     sub new {
186 :     my $class = shift;
187 :     my $self = {
188 :     appletName =>'',
189 : gage 5619 code=>'',
190 : gage 5574 codebase=>'',
191 : gage 5619 # appletId =>'', #always use identical applet Id's and applet Names
192 : gage 5574 params =>undef,
193 : gage 5619 width => 550,
194 :     height => 400,
195 :     base64_state => '',
196 :     base64_config => '',
197 :     getStateAlias => 'getXML',
198 :     setStateAlias => 'setState',
199 :     configAlias => 'config',
200 :     initializeActionAlias => 'setXML',
201 :     submitActionAlias => 'getXML',
202 :     returnFieldName => 'receivedField',
203 :     headerText => DEFAULT_HEADER_TEXT(),
204 :     objectText => '',
205 :     debug => 0,
206 : gage 5574 @_,
207 :     };
208 :     bless $self, $class;
209 :     return $self;
210 :     }
211 :    
212 :     sub header {
213 :     my $self = shift;
214 :     if ($_[0] eq "reset") { # $applet->header('reset'); erases default header text.
215 :     $self->{headerText}='';
216 :     } else {
217 :     $self->{headerText} .= join("",@_); # $applet->header(new_text); concatenates new_text to existing header.
218 :     }
219 :     $self->{headerText};
220 :     }
221 :     sub object {
222 :     my $self = shift;
223 :     if ($_[0] eq "reset") {
224 :     $self->{objectText}='';
225 :     } else {
226 :     $self->{objectText} .= join("",@_);
227 :     }
228 :     $self->{objectText};
229 :     }
230 :     sub params {
231 :     my $self = shift;
232 :     if (ref($_[0]) =~/HASH/) {
233 :     $self->{params} = shift;
234 : gage 5619 } elsif ( !defined($_[0]) or $_[0] =~ '') {
235 : gage 5574 # do nothing (read)
236 :     } else {
237 :     warn "You must enter a reference to a hash for the parameter list";
238 :     }
239 :     $self->{params};
240 :     }
241 :    
242 :     sub initializeActionAlias {
243 :     my $self = shift;
244 :     $self->{initializeActionAlias} = shift ||$self->{initializeActionAlias}; # replace the current contents if non-empty
245 :     $self->{initializeActionAlias};
246 :     }
247 :    
248 :     sub submitActionAlias {
249 :     my $self = shift;
250 :     $self->{submitActionAlias} = shift ||$self->{submitActionAlias}; # replace the current contents if non-empty
251 :     $self->{submitActionAlias};
252 :     }
253 : gage 5619 sub getStateAlias {
254 :     my $self = shift;
255 :     $self->{getStateAlias} = shift ||$self->{getStateAlias}; # replace the current contents if non-empty
256 :     $self->{getStateAlias};
257 :     }
258 :    
259 :     sub setStateAlias {
260 :     my $self = shift;
261 :     $self->{setStateAlias} = shift ||$self->{setStateAlias}; # replace the current contents if non-empty
262 :     $self->{setStateAlias};
263 :     }
264 :     sub configAlias {
265 :     my $self = shift;
266 :     $self->{configAlias} = shift ||$self->{configAlias}; # replace the current contents if non-empty
267 :     $self->{configAlias};
268 :     }
269 : gage 5574 sub returnFieldName {
270 :     my $self = shift;
271 :     $self->{returnFieldName} = shift ||$self->{returnFieldName}; # replace the current contents if non-empty
272 :     $self->{returnFieldName};
273 :     }
274 :     sub codebase {
275 :     my $self = shift;
276 :     $self->{codebase} = shift ||$self->{codebase}; # replace the current codebase if non-empty
277 :     $self->{codebase};
278 :     }
279 : gage 5619 sub code {
280 :     my $self = shift;
281 :     $self->{code} = shift ||$self->{code}; # replace the current code if non-empty
282 :     $self->{code};
283 :     }
284 :     sub height {
285 :     my $self = shift;
286 :     $self->{height} = shift ||$self->{height}; # replace the current height if non-empty
287 :     $self->{height};
288 :     }
289 :     sub width {
290 :     my $self = shift;
291 :     $self->{width} = shift ||$self->{width}; # replace the current width if non-empty
292 :     $self->{width};
293 :     }
294 :     sub archive {
295 :     my $self = shift;
296 :     $self->{archive} = shift ||$self->{archive}; # replace the current archive if non-empty
297 :     $self->{archive};
298 :     }
299 : gage 5574 sub appletName {
300 :     my $self = shift;
301 :     $self->{appletName} = shift ||$self->{appletName}; # replace the current appletName if non-empty
302 :     $self->{appletName};
303 :     }
304 : gage 5619 sub debug {
305 : gage 5574 my $self = shift;
306 : gage 5619 my $new_flag = shift;
307 :     $self->{debug} = $new_flag if defined($new_flag);
308 :     $self->{debug};
309 : gage 5574 }
310 : gage 5619 sub appletId {
311 :     appletName(@_);
312 :     }
313 :     sub state {
314 : gage 5574 my $self = shift;
315 :     my $str = shift;
316 : gage 5619 $self->{base64_state} = encode_base64($str) ||$self->{base64_state}; # replace the current string if non-empty
317 :     $self->{base64_state} =~ s/\n//g;
318 :     decode_base64($self->{base64_state});
319 : gage 5574 }
320 :    
321 : gage 5619 sub base64_state{
322 : gage 5574 my $self = shift;
323 : gage 5619 $self->{base64_state} = shift ||$self->{base64_state}; # replace the current string if non-empty
324 :     $self->{base64_state};
325 : gage 5574 }
326 : gage 5619 sub config {
327 :     my $self = shift;
328 :     my $str = shift;
329 :     $self->{base64_config} = encode_base64($str) || $self->{base64_config}; # replace the current string if non-empty
330 :     $self->{base64_config} =~ s/\n//g;
331 :     decode_base64($self->{base64_config});
332 :     }
333 :     sub base64_config {
334 :     my $self = shift;
335 :     $self->{base64_config} = shift ||$self->{base64_config}; # replace the current string if non-empty
336 :     $self->{base64_config} =$self->{base64_config};
337 :     $self->{base64_config};
338 :     }
339 : gage 5574 #FIXME
340 :     # need to be able to adjust header material
341 :    
342 :     sub insertHeader {
343 :     my $self = shift;
344 : gage 5622 my $codebase = $self->codebase;
345 :     my $appletId = $self->appletId;
346 :     my $appletName = $self->appletName;
347 : gage 5619 my $base64_initialState = $self->base64_state;
348 : gage 5622 my $initializeAction = $self->initializeActionAlias;
349 :     my $submitAction = $self->submitActionAlias;
350 : gage 5619 my $setState = $self->setStateAlias;
351 :     my $getState = $self->getStateAlias;
352 :     my $config = $self->configAlias;
353 :     my $base64_config = $self->base64_config;
354 :     my $debugMode = ($self->debug) ? "1": "0";
355 : gage 5622 my $returnFieldName = $self->{returnFieldName};
356 :     # my $encodeStateQ = ($self->debug)?'' : "state = Base64.encode(state);"; # in debug mode base64 encoding is not used.
357 :     # my $decodeStateQ = "if (!state.match(/<XML>*/i) ) {state = Base64.decode(state)}"; # decode if <XML> is not present
358 :     my $headerText = $self->header();
359 :    
360 : gage 5574 $headerText =~ s/(\$\w+)/$1/gee; # interpolate variables p17 of Cookbook
361 :    
362 :     return $headerText;
363 :    
364 :    
365 :     }
366 :    
367 :     sub insertObject {
368 : gage 5619 my $self = shift;
369 :     my $code = $self->{code};
370 :     my $codebase = $self->{codebase};
371 :     my $appletId = $self->{appletName};
372 : gage 5574 my $appletName = $self->{appletName};
373 : gage 5619 my $archive = $self->{archive};
374 :     my $width = $self->{width};
375 :     my $height = $self->{height};
376 : gage 5624 my $javaParameters = '';
377 :     my $flashParameters = '';
378 : gage 5619 my %param_hash = %{$self->params()};
379 :     foreach my $key (keys %param_hash) {
380 : gage 5624 $javaParameters .= qq!<param name ="$key" value = "$param_hash{$key}">\n!;
381 :     $flashParameters .= uri_escape($key).'='.uri_escape($param_hash{$key}).'&';
382 : gage 5619 }
383 : gage 5624 $flashParameters =~ s/\&$//; # trim last &
384 : gage 5619
385 : gage 5624
386 : gage 5574 $objectText = $self->{objectText};
387 :     $objectText =~ s/(\$\w+)/$1/gee;
388 :     return $objectText;
389 :     }
390 :     sub initialize {
391 :     my $self = shift;
392 :     return q{
393 :     <script>
394 : gage 5619 initializeAction();
395 : gage 5574 // this should really be done in the <body> tag
396 :     </script>
397 :     };
398 :    
399 :     }
400 :    
401 : gage 5619
402 :     use constant DEFAULT_HEADER_TEXT =><<'END_HEADER_SCRIPT';
403 :    
404 :     <script language="JavaScript">
405 :     var debug = $debugMode;
406 :     //
407 :     //CONFIGURATIONS
408 :     //
409 :     // configurations are "permanent"
410 :     applet_config_list["$appletName"] = function() {
411 :     if (debug) { alert("configure $appletName . $config ( $base64_config )");}
412 :     try {
413 :     if (debug || !( typeof(getApplet("$appletName").$config) == "undefined" ) ) {
414 :    
415 :     getApplet("$appletName").$config(Base64.decode("$base64_config"));
416 :     }
417 :     } catch(e) {
418 :     alert("error executing configuration command $config for $appletName: " + e );
419 :     }
420 :     }
421 :     //
422 :     //STATE
423 :     //
424 :     // state can vary as the applet is manipulated.
425 :     applet_setState_list["$appletName"] = function(state) {
426 : gage 5622 state = state || getQE("$appletName"+"_state").value
427 :     if (state.match("\S") ) { // if state is not all white space
428 :     if ( base64Q(state) ) {
429 :     state=Base64.decode(state);
430 :     }
431 :     if (debug) { alert("set state for $appletName to " + state);}
432 :     try {
433 :     if (debug || !( typeof(getApplet("$appletName").$setState) =="undefined" ) ) {
434 :     getApplet("$appletName").$setState( state );
435 :     }
436 :     } catch(e) {
437 :     alert("Error in setting state of $appletName using command $setState : " + e );
438 :     }
439 :     }
440 : gage 5619 };
441 :     applet_getState_list["$appletName"] = function () {
442 :     if (debug) { alert("getState for applet $appletName");}
443 :     try {
444 :     var applet = getApplet("$appletName");
445 :     var state;
446 :     if (!( typeof(getApplet("$appletName").$getState) =="undefined" ) ) {
447 :     state = applet.$getState(); // get state in xml format
448 :     }
449 :     if (!debug) {state = Base64.encode(state) }; // replace state by encoded version
450 :     getQE("$appletName"+"_state").value = state; //place in state htmlItem (debug: textarea, otherwise hidden)
451 :     } catch (e) {
452 :     alert("Error in getting state for $appletName " + e );
453 :     }
454 :     };
455 :     //
456 :     //INITIALIZE
457 :     //
458 :     applet_initializeAction_list["$appletName"] = function () {
459 :     applet_setState_list["$appletName"]();
460 :     };
461 :    
462 :     applet_submitAction_list["$appletName"] = function () {
463 :     applet_getState_list["$appletName"]();
464 :     getQE("$returnFieldName").value = getApplet("$appletName").sendData();
465 :     };
466 :     </script>
467 :    
468 :     END_HEADER_SCRIPT
469 :    
470 :     package FlashApplet;
471 :     @ISA = qw(Applet);
472 :    
473 :    
474 :    
475 :     =pod
476 :    
477 :     The secret to making this applet work with IE in addition to normal browsers
478 :     is the addition of the C(<form></form>) construct just before the object.
479 :    
480 :     For some reason IE has trouble locating a flash object which is contained
481 :     within a form. Adding this second blank form with the larger problemMainForm
482 :     seems to solve the problem.
483 :    
484 :     This follows method2 of the advice given in url(http://kb.adobe.com/selfservice/viewContent.do?externalId=kb400730&sliceId=2)
485 :     Method1 and methods involving SWFObject(Geoff Stearns) and SWFFormFix (Steve Kamerman) have yet to be fully investigated:
486 :     http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix_source.js
487 :     http://www.teratechnologies.net/stevekamerman/index.php?m=01&y=07&entry=entry070101-033933
488 :    
489 :     use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
490 :     <form></form>
491 :     <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
492 :     id="$appletName" width="500" height="375"
493 :     codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab">
494 :     <param name="movie" value="$codebase/$appletName.swf" />
495 :     <param name="quality" value="high" />
496 :     <param name="bgcolor" value="#869ca7" />
497 :     <param name="allowScriptAccess" value="sameDomain" />
498 :     <embed src="$codebase/$appletName.swf" quality="high" bgcolor="#869ca7"
499 :     width="$width" height="$height" name="$appletName" align="middle" id="$appletName"
500 :     play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
501 :     type="application/x-shockwave-flash"
502 :     pluginspage="http://www.macromedia.com/go/getflashplayer">
503 :     </embed>
504 :    
505 :     </object>
506 :     END_OBJECT_TEXT
507 :    
508 :    
509 :     =cut
510 :    
511 :     use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
512 :     <form></form>
513 :     <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
514 :     id="$appletName" width="500" height="375"
515 :     codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab">
516 :     <param name="movie" value="$codebase/$appletName.swf" />
517 :     <param name="quality" value="high" />
518 :     <param name="bgcolor" value="#869ca7" />
519 :     <param name="allowScriptAccess" value="sameDomain" />
520 : gage 5624 <param name="FlashVars" value="$flashParameters"/>
521 : gage 5619 <embed src="$codebase/$appletName.swf" quality="high" bgcolor="#869ca7"
522 :     width="$width" height="$height" name="$appletName" align="middle" id="$appletName"
523 :     play="true" loop="false" quality="high" allowScriptAccess="sameDomain"
524 :     type="application/x-shockwave-flash"
525 : gage 5624 pluginspage="http://www.macromedia.com/go/getflashplayer"
526 :     FlashVars="$flashParameters">
527 : gage 5619 </embed>
528 :    
529 :     </object>
530 :     END_OBJECT_TEXT
531 :    
532 :     sub new {
533 :     my $class = shift;
534 :     $class -> SUPER::new( objectText => DEFAULT_OBJECT_TEXT(),
535 :     @_
536 :     );
537 :    
538 :     }
539 :    
540 :    
541 :     package JavaApplet;
542 :     @ISA = qw(Applet);
543 :    
544 :    
545 :    
546 :     =pod
547 :    
548 :     The secret to making this applet work with IE in addition to normal browsers
549 :     is the addition of the C(<form></form>) construct just before the object.
550 :    
551 :     For some reason IE has trouble locating a flash object which is contained
552 :     within a form. Adding this second blank form with the larger problemMainForm
553 :     seems to solve the problem.
554 :    
555 :     This follows method2 of the advice given in url(http://kb.adobe.com/selfservice/viewContent.do?externalId=kb400730&sliceId=2)
556 :     Method1 and methods involving SWFObject(Geoff Stearns) and SWFFormFix (Steve Kamerman) have yet to be fully investigated:
557 :     http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix_source.js
558 :     http://www.teratechnologies.net/stevekamerman/index.php?m=01&y=07&entry=entry070101-033933
559 :    
560 :     use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
561 :     <form></form>
562 :     <applet
563 :     code = "$code"
564 :     codebase = "$codebase"
565 :     archive = "$archive"
566 :     name = "$appletName"
567 :     id = "$appletName"
568 :     width = "$width"
569 :     height = "$height"
570 :     MAYSCRIPT
571 :     >
572 : gage 5624 $javaParameters
573 : gage 5619 </applet>
574 :     END_OBJECT_TEXT
575 :    
576 :     =cut
577 :    
578 :     use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
579 :     <form></form>
580 :     <applet
581 :     code = "$code"
582 :     codebase = "$codebase"
583 :     archive = "$archive"
584 :     name = "$appletName"
585 :     id = "$appletName"
586 :     width = "$width"
587 :     height = "$height"
588 :     MAYSCRIPT
589 :     >
590 : gage 5624 $javaParameters
591 : gage 5619 </applet>
592 :     END_OBJECT_TEXT
593 :    
594 :     sub new {
595 :     my $class = shift;
596 :     $class -> SUPER::new( objectText => DEFAULT_OBJECT_TEXT(),
597 :     @_
598 :     );
599 :    
600 :     }
601 :    
602 :    
603 :    
604 : gage 5574 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9