Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions htdocs/js/AppletSupport/ww_applet_support.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ class ww_applet {
if (typeof newState === 'undefined') newState = '<xml>restart_applet</xml>';
const stateInput = ww_applet_list[this.appletName].stateInput;
getQE(stateInput).value = newState;
getQE(`previous_${stateInput}`).value = newState;
}

// STATE:
Expand Down Expand Up @@ -275,12 +274,14 @@ class ww_applet {
if (form.submitHandlerInitialized) return;
form.submitHandlerInitialized = true;

// Connect the submit action handler to the form.
form.addEventListener('submit', () => {
for (const appletName in ww_applet_list) {
ww_applet_list[appletName].submitAction();
}
});
// Connect the submit action handler to the form submit buttons.
for (const button of form.querySelectorAll('input[type="submit"]')) {
button.addEventListener('click', () => {
for (const appletName in ww_applet_list) {
ww_applet_list[appletName].submitAction();
}
});
}
};

// Initialize applet support and the applets.
Expand Down
4 changes: 2 additions & 2 deletions lib/Applet.pm
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ JavaScript "submitAction()" which then asks each of the applets on the page to p
submit action which consists of

-- If the applet is to be reinitialized (appletName_state contains
<xml>restart_applet</xml>) then the HTML elements appletName_state and
previous_appletName_state are set to <xml>restart_applet</xml> to be interpreted by the
<xml>restart_applet</xml>) then the HTML element appletName_state
is set to <xml>restart_applet</xml> to be interpreted by the
next setState command.
-- Otherwise getState() from the applet and save it to the HTML input element
appletName_state.
Expand Down
75 changes: 25 additions & 50 deletions macros/graph/AppletObjects.pl
Original file line number Diff line number Diff line change
Expand Up @@ -69,54 +69,23 @@ =head2 insertAll

# Inserts both header text and object text.
sub insertAll {
my $self = shift;
my %options = @_;

my $includeAnswerBox = (defined($options{includeAnswerBox}) && $options{includeAnswerBox} == 1) ? 1 : 0;

my $reset_button = $options{reinitialize_button} || 0;

# Get data to be interpolated into the HTML code defined in this subroutine.
# This consists of the name of the applet and the names of the routines to get and set State
# of the applet (which is done every time the question page is refreshed and to get and set
# Config which is the initial configuration the applet is placed in when the question is
# first viewed. It is also the state which is returned to when the reset button is pressed.
my ($self, %options) = @_;

# Prepare html code for storing state.
my $appletName = $self->appletName;
# The name of the hidden "answer" blank storing state.
$self->{stateInput} = "$main::PG->{QUIZ_PREFIX}${appletName}_state";
my $appletStateName = $self->{stateInput};

# Names of routines for this applet
my $getState = $self->getStateAlias;
my $setState = $self->setStateAlias;
my $getConfig = $self->getConfigAlias;
my $setConfig = $self->setConfigAlias;

my $base64_initialState = $self->base64_encode($self->initialState);
# This insures that the state will be saved from one invocation to the next.
# FIXME -- with PGcore the persistant data mechanism can be used instead
main::RECORD_FORM_LABEL($appletStateName);
my $answer_value = '';

# Implement the sticky answer mechanism for maintaining the applet state when the question
# page is refreshed This is important for guest users for whom no permanent record of
# answers is recorded.
if (defined(${$main::inputs_ref}{$appletStateName}) && ${$main::inputs_ref}{$appletStateName} =~ /\S/) {
$answer_value = ${$main::inputs_ref}{$appletStateName};
} elsif (defined($main::rh_sticky_answers->{$appletStateName})) {
$answer_value = shift(@{ $main::rh_sticky_answers->{$appletStateName} });
my $answer_value = ${$main::inputs_ref}{ $self->{stateInput} } // '';
if ($answer_value !~ /\S/ && defined(my $persistent_data = main::persistent_data($self->{stateInput}))) {
$answer_value = $persistent_data;
}
$answer_value =~ tr/\\$@`//d; # Make sure student answers cannot be interpolated by e.g. EV3
$answer_value =~ s/\s+/ /g; # Remove excessive whitespace from student answer

# Regularize the applet's state which could be in either XML format or in XML format encoded by base64.
# In rare cases it might be simple string. Protect against that by putting xml tags around the state.
# The result:
# $base_64_encoded_answer_value -- a base64 encoded xml string
# $decoded_answer_value -- an xml string

# In rare cases it might be a simple string. Protect against that by putting xml tags around the state.
my $base_64_encoded_answer_value;
my $decoded_answer_value;
if ($answer_value =~ /<\??xml/i) {
Expand All @@ -125,44 +94,50 @@ sub insertAll {
} else {
$decoded_answer_value = $self->base64_decode($answer_value);
if ($decoded_answer_value =~ /<\??xml/i) {
# Great, we've decoded the answer to obtain an xml string
$base_64_encoded_answer_value = $answer_value;
} else {
#WTF?? apparently we don't have XML tags
$answer_value = "<xml>$answer_value</xml>";
$base_64_encoded_answer_value = $self->base64_encode($answer_value);
$decoded_answer_value = $answer_value;
}
}
$base_64_encoded_answer_value =~ s/\r|\n//g; # Get rid of line returns

main::persistent_data($self->{stateInput} => $base_64_encoded_answer_value);

# Construct the reset button string (this is blank if the button is not to be displayed).
my $reset_button_str = $reset_button
? qq!<button type='button' class='btn btn-primary applet-reset-btn' data-applet-name="$appletName">
Return this question to its initial state</button><br/>!
my $reset_button_str = $options{reinitialize_button}
? main::tag(
'button',
type => 'button',
class => 'btn btn-primary applet-reset-btn mt-3',
'data-applet-name' => $appletName,
'Return this question to its initial state'
)
: '';

# Combine the state_input_button and the reset button into one string.
my $state_storage_html_code = qq!<input type="hidden" name="previous_$appletStateName"
id="previous_$appletStateName" value = "$base_64_encoded_answer_value">!
. qq!<input type="hidden" name="$appletStateName" id="$appletStateName" value="$base_64_encoded_answer_value">!
. $reset_button_str;
# Construct the state storage hidden input.
my $state_storage_html_code = main::tag(
'input',
type => 'hidden',
name => $self->{stateInput},
id => $self->{stateInput},
value => $base_64_encoded_answer_value
);

# Construct the answerBox (if it is requested). This is a default input box for interacting
# with the applet. It is separate from maintaining state but it often contains similar
# data. Additional answer boxes or buttons can be defined but they must be explicitly
# connected to the applet with additional JavaScript commands.
my $answerBox_code = $includeAnswerBox
? $answerBox_code = main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50)
: '';
my $answerBox_code = $options{includeAnswerBox} ? main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) : '';

# Insert header material
main::HEADER_TEXT($self->insertHeader());

# Return HTML or TeX strings to be included in the body of the page
return main::MODES(
TeX => ' {\bf ' . $self->{type} . ' applet } ',
HTML => $self->insertObject . $main::BR . $state_storage_html_code . $answerBox_code,
HTML => $self->insertObject . $state_storage_html_code . $reset_button_str . $answerBox_code,
PTX => ' applet '
);
}
Expand Down