################################################################################
# WeBWorK Online Homework Delivery System
# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/
# $CVSHeader: pg/lib/Applet.pm,v 1.15 2009/02/07 22:28:34 gage Exp $
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of either: (a) the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version, or (b) the "Artistic License" which comes with this package.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the
# Artistic License for more details.
################################################################################
=head1 NAME
Applet.pl - Provides code for inserting FlashApplets and JavaApplets into webwork problems
=head1 SYNPOSIS
###################################
# Create link to applet
###################################
my $appletName = "LineThruPointsWW";
$applet = new FlashApplet(
# can be replaced by $applet =FlashApplet() when using AppletObjects.pl
codebase => findAppletCodebase("$appletName.swf"),
appletName => $appletName,
appletId => $appletName,
submitActionAlias => 'checkAnswer',
);
###################################
# Configure applet
###################################
#xml data to set up the problem-rac
$applet->config(qq{
});
###################################
# insert applet header material
###################################
HEADER_TEXT($applet->insertHeader );
###################################
# Text section
#
###################################
#insert applet into body
###################################
TEXT( MODES(TeX=>'object code', HTML=>$applet->insertObject));
=head1 DESCRIPTION
This file provides an object to store in one place
all of the information needed to call an applet.
The object FlashApplet has defaults for inserting flash applets.
=over
=item *
=item *
=back
(not yet completed)
The module JavaApplet has defaults for inserting java applets.
The module Applet stores common code for the two types of applet.
=head1 USAGE
These modules are activate by listing it in the modules section of global.conf and rebooting the server.
The companion file to this one is macros/AppletObjects.pl
qw(Applet FlashApplet JavaApplet)
=cut
package Applet;
use URI::Escape;
use MIME::Base64 qw( encode_base64 decode_base64);
=head2 Default javaScript functions placed in header
=pod
These functions are automatically defined for use for
any javaScript placed in the text of a PG question.
getApplet(appletName) -- finds the applet path in the DOM
submitAction() -- calls the submit action of the applets
initializeWWquestion() -- calls the initialize action of the applets
getQE(name) -- gets an HTML element of the question by name
or by id. Be sure to keep all names and ids
unique within a given PG question.
getQuestionElement(name) -- long form of getQE(name)
listQuestionElements() -- for discovering the names of inputs in the
PG question. An alert dialog will list all
of the elements.
Usage: Place this at the END of the question, just before END_DOCUMENT():
TEXT(qq!!);
ENDDOCUMENT();
to obtain a list of all of the HTML elements in the question
List of accessor methods made available by the FlashApplet class:
Usage: $current_value = $applet->method(new_value or empty)
These can also be set when creating the class -- for exampe:
$applet = new FlashApplet(
# can be replaced by $applet =FlashApplet() when using AppletObjects.pl
codebase => findAppletCodebase("$appletName.swf"),
appletName => $appletName,
appletId => $appletName,
submitActionAlias => 'checkAnswer',
);
appletId for simplicity and reliability appletId and appletName are always the same
appletName
archive the name of the .jar file containing the applet code
code the name of the applet code in the .jar archive
codebase a prefix url used to find the archive and the applet itself
height rectangle alloted in the html page for displaying the applet
params an anonymous array containing name/value pairs
to configure the applet [name =>'value, ...]
header stores the text to be added to the header section of the html page
object stores the text which places the applet on the html page
debug in debug mode several alerts mark progress through the procedure of calling the applet
config configuration are those customizable attributes of the applet which don't
change as it is used. When stored in hidden answer fields
it is usually stored in base64 encoded format.
base64_config base64 encode version of the contents of config
configAlias (default: config ) names the applet command called with the contents of $self->config
to configure the applet. The parameters are passed to the applet in plain text using
The outer tags must be .....
state state consists of those customizable attributes of the applet which change
as the applet is used. It is stored by the calling .pg question so that
when revisiting the question the applet
will be restored to the same state it was left in when the question was last
viewed.
getStateAlias (default: getState) alias for command called to read the current state of the applet.
The state is passed in plain text xml format with outer tags: ....
setStateAlias (default: setState) alias for the command called to reset the state of the applet.
The state is passed in plain text in xml format with outer tags: ....
base64_state returns the base64 encoded version of the state stored in the applet object.
initializeActionAlias -- (default: initializeAction) the name of the javaScript subroutine called to initialize the applet (some overlap with config/ and setState
submitActionAlias -- (default: submitAction)the name of the javaScript subroutine called when the submit button of the
.pg question is pressed.
answerBox -- name of answer box to return answer to: default defaultAnswerBox
getAnswer -- (formerly sendData) get student answer from applet and place in answerBox
returnFieldName -- (deprecated) synonmym for answerBox
=cut
sub new {
my $class = shift;
my $self = {
appletName =>'',
code=>'',
codebase=>'',
# appletId =>'', #always use identical applet Id's and applet Names
params =>undef,
width => 550,
height => 400,
bgcolor => "#869ca7",
base64_state => undef, # this is an state to use for initializing the first occurence of the question.
base64_config => undef, # this is the initial (and final?) configuration
getStateAlias => 'getXML',
setStateAlias => 'setXML',
configAlias => 'config',
initializeActionAlias => 'setXML',
submitActionAlias => 'getXML',
submitActionScript => '', # script executed on submitting the WW question
answerBox => 'answerBox',
headerText => DEFAULT_HEADER_TEXT(),
objectText => '',
debug => 0,
@_,
};
bless $self, $class;
$self->state('');
$self->config('');
return $self;
}
sub header {
my $self = shift;
if ($_[0] eq "reset") { # $applet->header('reset'); erases default header text.
$self->{headerText}='';
} else {
$self->{headerText} .= join("",@_); # $applet->header(new_text); concatenates new_text to existing header.
}
$self->{headerText};
}
sub object {
my $self = shift;
if ($_[0] eq "reset") {
$self->{objectText}='';
} else {
$self->{objectText} .= join("",@_);
}
$self->{objectText};
}
sub params {
my $self = shift;
if (ref($_[0]) =~/HASH/) {
$self->{params} = shift;
} elsif ( !defined($_[0]) or $_[0] =~ '') {
# do nothing (read)
} else {
warn "You must enter a reference to a hash for the parameter list";
}
$self->{params};
}
sub initializeActionAlias {
my $self = shift;
$self->{initializeActionAlias} = shift ||$self->{initializeActionAlias}; # replace the current contents if non-empty
$self->{initializeActionAlias};
}
sub submitActionAlias {
my $self = shift;
$self->{submitActionAlias} = shift ||$self->{submitActionAlias}; # replace the current contents if non-empty
$self->{submitActionAlias};
}
sub submitActionScript {
my $self = shift;
$self->{submitActionScript} = shift ||$self->{submitActionScript}; # replace the current contents if non-empty
$self->{submitActionScript};
}
sub getStateAlias {
my $self = shift;
$self->{getStateAlias} = shift ||$self->{getStateAlias}; # replace the current contents if non-empty
$self->{getStateAlias};
}
sub setStateAlias {
my $self = shift;
$self->{setStateAlias} = shift ||$self->{setStateAlias}; # replace the current contents if non-empty
$self->{setStateAlias};
}
sub configAlias {
my $self = shift;
$self->{configAlias} = shift ||$self->{configAlias}; # replace the current contents if non-empty
$self->{configAlias};
}
sub returnFieldName {
my $self = shift;
$self->{answerBox} = shift ||$self->{answerBox}; # replace the current contents if non-empty
$self->{answerBox};
}
sub answerBox {
my $self = shift;
$self->{answerBox} = shift ||$self->{answerBox}; # replace the current contents if non-empty
$self->{answerBox};
}
sub codebase {
my $self = shift;
$self->{codebase} = shift ||$self->{codebase}; # replace the current codebase if non-empty
$self->{codebase};
}
sub code {
my $self = shift;
$self->{code} = shift ||$self->{code}; # replace the current code if non-empty
$self->{code};
}
sub height {
my $self = shift;
$self->{height} = shift ||$self->{height}; # replace the current height if non-empty
$self->{height};
}
sub width {
my $self = shift;
$self->{width} = shift ||$self->{width}; # replace the current width if non-empty
$self->{width};
}
sub bgcolor {
my $self = shift;
$self->{bgcolor} = shift ||$self->{bgcolor}; # replace the current background color if non-empty
$self->{bgcolor};
}
sub archive {
my $self = shift;
$self->{archive} = shift ||$self->{archive}; # replace the current archive if non-empty
$self->{archive};
}
sub appletName {
my $self = shift;
$self->{appletName} = shift ||$self->{appletName}; # replace the current appletName if non-empty
$self->{appletName};
}
sub debug {
my $self = shift;
my $new_flag = shift;
$self->{debug} = $new_flag if defined($new_flag);
$self->{debug};
}
sub appletId {
appletName(@_);
}
sub state {
my $self = shift;
my $str = shift;
$self->{base64_state} = encode_base64($str) ||$self->{base64_state}; # replace the current string if non-empty
$self->{base64_state} =~ s/\n//g;
decode_base64($self->{base64_state});
}
sub base64_state{
my $self = shift;
$self->{base64_state} = shift ||$self->{base64_state}; # replace the current string if non-empty
$self->{base64_state};
}
sub config {
my $self = shift;
my $str = shift;
$self->{base64_config} = encode_base64($str) || $self->{base64_config}; # replace the current string if non-empty
$self->{base64_config} =~ s/\n//g;
decode_base64($self->{base64_config});
}
sub base64_config {
my $self = shift;
$self->{base64_config} = shift ||$self->{base64_config}; # replace the current string if non-empty
$self->{base64_config} =$self->{base64_config};
$self->{base64_config};
}
#FIXME
# need to be able to adjust header material
sub insertHeader {
my $self = shift;
my $codebase = $self->codebase;
my $appletId = $self->appletId;
my $appletName = $self->appletName;
my $base64_initialState = $self->base64_state;
my $initializeAction = $self->initializeActionAlias;
my $submitActionAlias = $self->submitActionAlias;
my $submitActionScript = $self->submitActionScript;
my $setStateAlias = $self->setStateAlias;
my $getStateAlias = $self->getStateAlias;
my $configAlias = $self->configAlias;
my $base64_config = $self->base64_config;
my $debugMode = ($self->debug) ? "1": "0";
my $returnFieldName = $self->{returnFieldName};
my $answerBox = $self->{answerBox};
my $headerText = $self->header();
$submitActionScript =~ s/"/\\"/g; # escape quotes for ActionScript
# other variables should not have quotes.
$submitActionScript =~ s/\n/ /g; # replace returns with spaces -- returns in the wrong spot can cause trouble with javaScript
$submitActionScript =~ s/\r/ /g; # replace returns with spaces -- returns can cause trouble
$headerText =~ s/(\$\w+)/$1/gee; # interpolate variables p17 of Cookbook
return $headerText;
}
sub insertObject {
my $self = shift;
my $code = $self->{code};
my $codebase = $self->{codebase};
my $appletId = $self->{appletName};
my $appletName = $self->{appletName};
my $archive = $self->{archive};
my $width = $self->{width};
my $height = $self->{height};
my $applet_bgcolor = $self->{bgcolor};
my $javaParameters = '';
my $flashParameters = '';
my %param_hash = %{$self->params()};
foreach my $key (keys %param_hash) {
$javaParameters .= qq!\n!;
$flashParameters .= uri_escape($key).'='.uri_escape($param_hash{$key}).'&';
}
$flashParameters =~ s/\&$//; # trim last &
$objectText = $self->{objectText};
$objectText =~ s/(\$\w+)/$1/gee;
return $objectText;
}
# sub initialize {
# my $self = shift;
# return q{
#
# };
#
# }
########################################################
# HEADER material for one flash or java applet
########################################################
use constant DEFAULT_HEADER_TEXT =><<'END_HEADER_SCRIPT';
END_HEADER_SCRIPT
package FlashApplet;
@ISA = qw(Applet);
=head2 Insertion HTML code for FlashApplet
=pod
The secret to making this applet work with IE in addition to normal browsers
is the addition of the C() construct just before the object.
For some reason IE has trouble locating a flash object which is contained
within a form. Adding this second blank form with the larger problemMainForm
seems to solve the problem.
This follows method2 of the advice given in url(http://kb.adobe.com/selfservice/viewContent.do?externalId=kb400730&sliceId=2)
Method1 and methods involving SWFObject(Geoff Stearns) and SWFFormFix (Steve Kamerman) have yet to be fully investigated:
http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix_source.js
http://www.teratechnologies.net/stevekamerman/index.php?m=01&y=07&entry=entry070101-033933
use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
END_OBJECT_TEXT
=cut
use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
END_OBJECT_TEXT
sub new {
my $class = shift;
$class -> SUPER::new( objectText => DEFAULT_OBJECT_TEXT(),
@_
);
}
package JavaApplet;
@ISA = qw(Applet);
=head2 Insertion HTML code for JavaApplet
=pod
The secret to making this applet work with IE in addition to normal browsers
is the addition of the C() construct just before the object.
For some reason IE has trouble locating a flash object which is contained
within a form. Adding this second blank form with the larger problemMainForm
seems to solve the problem.
This follows method2 of the advice given in url(http://kb.adobe.com/selfservice/viewContent.do?externalId=kb400730&sliceId=2)
Method1 and methods involving SWFObject(Geoff Stearns) and SWFFormFix (Steve Kamerman) have yet to be fully investigated:
http://devel.teratechnologies.net/swfformfix/swfobject_swfformfix_source.js
http://www.teratechnologies.net/stevekamerman/index.php?m=01&y=07&entry=entry070101-033933
use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
END_OBJECT_TEXT
=cut
use constant DEFAULT_OBJECT_TEXT =><<'END_OBJECT_TEXT';
END_OBJECT_TEXT
sub new {
my $class = shift;
$class -> SUPER::new( objectText => DEFAULT_OBJECT_TEXT(),
@_
);
}
1;