Skip to content

Commit ccb1ced

Browse files
committed
Switch applets from storing state via RECORD_FORM_LABEL to using the PERSISTENCE_HASH.
The applet state is not a "kept extra answer" which is what the `RECORD_FORM_LABEL` approach is for. The applet state isn't an answer at all. It is just extra problem data that needs to persist. So exactly what the `PERSISTENCE_HASH` is for. In addition, webwork2 stores the kept extra answers in the `last_answer` column which is type `TEXT` and thus limited to 64KB. The data in the `PERSISTENCE_HASH` is stored in the `problem_data` column which is of type `MEDIUMTEXT` and so can hold up to 16MB of data. The applet state can become larger than 64KB as is evidenced by the issue posted in the forums at https://forums.openwebwork.org/mod/forum/discuss.php?d=8785. There is also a litle clean up of the `insertAll` method of the `AppletObjects.pl` macro. It can take advantage of the `tag` method.
1 parent bcebe4a commit ccb1ced

File tree

3 files changed

+27
-53
lines changed

3 files changed

+27
-53
lines changed

htdocs/js/AppletSupport/ww_applet_support.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ class ww_applet {
9595
if (typeof newState === 'undefined') newState = '<xml>restart_applet</xml>';
9696
const stateInput = ww_applet_list[this.appletName].stateInput;
9797
getQE(stateInput).value = newState;
98-
getQE(`previous_${stateInput}`).value = newState;
9998
}
10099

101100
// STATE:

lib/Applet.pm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ JavaScript "submitAction()" which then asks each of the applets on the page to p
270270
submit action which consists of
271271
272272
-- If the applet is to be reinitialized (appletName_state contains
273-
<xml>restart_applet</xml>) then the HTML elements appletName_state and
274-
previous_appletName_state are set to <xml>restart_applet</xml> to be interpreted by the
273+
<xml>restart_applet</xml>) then the HTML element appletName_state
274+
is set to <xml>restart_applet</xml> to be interpreted by the
275275
next setState command.
276276
-- Otherwise getState() from the applet and save it to the HTML input element
277277
appletName_state.

macros/graph/AppletObjects.pl

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -69,54 +69,23 @@ =head2 insertAll
6969

7070
# Inserts both header text and object text.
7171
sub insertAll {
72-
my $self = shift;
73-
my %options = @_;
74-
75-
my $includeAnswerBox = (defined($options{includeAnswerBox}) && $options{includeAnswerBox} == 1) ? 1 : 0;
76-
77-
my $reset_button = $options{reinitialize_button} || 0;
78-
79-
# Get data to be interpolated into the HTML code defined in this subroutine.
80-
# This consists of the name of the applet and the names of the routines to get and set State
81-
# of the applet (which is done every time the question page is refreshed and to get and set
82-
# Config which is the initial configuration the applet is placed in when the question is
83-
# first viewed. It is also the state which is returned to when the reset button is pressed.
72+
my ($self, %options) = @_;
8473

8574
# Prepare html code for storing state.
8675
my $appletName = $self->appletName;
8776
# The name of the hidden "answer" blank storing state.
8877
$self->{stateInput} = "$main::PG->{QUIZ_PREFIX}${appletName}_state";
89-
my $appletStateName = $self->{stateInput};
90-
91-
# Names of routines for this applet
92-
my $getState = $self->getStateAlias;
93-
my $setState = $self->setStateAlias;
94-
my $getConfig = $self->getConfigAlias;
95-
my $setConfig = $self->setConfigAlias;
9678

97-
my $base64_initialState = $self->base64_encode($self->initialState);
9879
# This insures that the state will be saved from one invocation to the next.
99-
# FIXME -- with PGcore the persistant data mechanism can be used instead
100-
main::RECORD_FORM_LABEL($appletStateName);
101-
my $answer_value = '';
102-
103-
# Implement the sticky answer mechanism for maintaining the applet state when the question
104-
# page is refreshed This is important for guest users for whom no permanent record of
105-
# answers is recorded.
106-
if (defined(${$main::inputs_ref}{$appletStateName}) && ${$main::inputs_ref}{$appletStateName} =~ /\S/) {
107-
$answer_value = ${$main::inputs_ref}{$appletStateName};
108-
} elsif (defined($main::rh_sticky_answers->{$appletStateName})) {
109-
$answer_value = shift(@{ $main::rh_sticky_answers->{$appletStateName} });
80+
my $answer_value = ${$main::inputs_ref}{ $self->{stateInput} } // '';
81+
if ($answer_value !~ /\S/ && defined(my $persistent_data = main::persistent_data($self->{stateInput}))) {
82+
$answer_value = $persistent_data;
11083
}
11184
$answer_value =~ tr/\\$@`//d; # Make sure student answers cannot be interpolated by e.g. EV3
11285
$answer_value =~ s/\s+/ /g; # Remove excessive whitespace from student answer
11386

11487
# Regularize the applet's state which could be in either XML format or in XML format encoded by base64.
115-
# In rare cases it might be simple string. Protect against that by putting xml tags around the state.
116-
# The result:
117-
# $base_64_encoded_answer_value -- a base64 encoded xml string
118-
# $decoded_answer_value -- an xml string
119-
88+
# In rare cases it might be a simple string. Protect against that by putting xml tags around the state.
12089
my $base_64_encoded_answer_value;
12190
my $decoded_answer_value;
12291
if ($answer_value =~ /<\??xml/i) {
@@ -125,44 +94,50 @@ sub insertAll {
12594
} else {
12695
$decoded_answer_value = $self->base64_decode($answer_value);
12796
if ($decoded_answer_value =~ /<\??xml/i) {
128-
# Great, we've decoded the answer to obtain an xml string
12997
$base_64_encoded_answer_value = $answer_value;
13098
} else {
131-
#WTF?? apparently we don't have XML tags
13299
$answer_value = "<xml>$answer_value</xml>";
133100
$base_64_encoded_answer_value = $self->base64_encode($answer_value);
134101
$decoded_answer_value = $answer_value;
135102
}
136103
}
137104
$base_64_encoded_answer_value =~ s/\r|\n//g; # Get rid of line returns
138105

106+
main::persistent_data($self->{stateInput} => $base_64_encoded_answer_value);
107+
139108
# Construct the reset button string (this is blank if the button is not to be displayed).
140-
my $reset_button_str = $reset_button
141-
? qq!<button type='button' class='btn btn-primary applet-reset-btn' data-applet-name="$appletName">
142-
Return this question to its initial state</button><br/>!
109+
my $reset_button_str = $options{reinitialize_button}
110+
? main::tag(
111+
'button',
112+
type => 'button',
113+
class => 'btn btn-primary applet-reset-btn mt-3',
114+
'data-applet-name' => $appletName,
115+
'Return this question to its initial state'
116+
)
143117
: '';
144118

145-
# Combine the state_input_button and the reset button into one string.
146-
my $state_storage_html_code = qq!<input type="hidden" name="previous_$appletStateName"
147-
id="previous_$appletStateName" value = "$base_64_encoded_answer_value">!
148-
. qq!<input type="hidden" name="$appletStateName" id="$appletStateName" value="$base_64_encoded_answer_value">!
149-
. $reset_button_str;
119+
# Construct the state storage hidden input.
120+
my $state_storage_html_code = main::tag(
121+
'input',
122+
type => 'hidden',
123+
name => $self->{stateInput},
124+
id => $self->{stateInput},
125+
value => $base_64_encoded_answer_value
126+
);
150127

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

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

162137
# Return HTML or TeX strings to be included in the body of the page
163138
return main::MODES(
164139
TeX => ' {\bf ' . $self->{type} . ' applet } ',
165-
HTML => $self->insertObject . $main::BR . $state_storage_html_code . $answerBox_code,
140+
HTML => $self->insertObject . $state_storage_html_code . $reset_button_str . $answerBox_code,
166141
PTX => ' applet '
167142
);
168143
}

0 commit comments

Comments
 (0)