diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index 395969bf3f..e9d599d8c3 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -95,7 +95,6 @@ class ww_applet { if (typeof newState === 'undefined') newState = 'restart_applet'; const stateInput = ww_applet_list[this.appletName].stateInput; getQE(stateInput).value = newState; - getQE(`previous_${stateInput}`).value = newState; } // STATE: @@ -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. diff --git a/lib/Applet.pm b/lib/Applet.pm index 78f7e9789b..12b42a4678 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -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 - restart_applet) then the HTML elements appletName_state and - previous_appletName_state are set to restart_applet to be interpreted by the + restart_applet) then the HTML element appletName_state + is set to restart_applet to be interpreted by the next setState command. -- Otherwise getState() from the applet and save it to the HTML input element appletName_state. diff --git a/macros/graph/AppletObjects.pl b/macros/graph/AppletObjects.pl index e8ad53a762..d85047f76a 100644 --- a/macros/graph/AppletObjects.pl +++ b/macros/graph/AppletObjects.pl @@ -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) { @@ -125,10 +94,8 @@ 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 = "$answer_value"; $base_64_encoded_answer_value = $self->base64_encode($answer_value); $decoded_answer_value = $answer_value; @@ -136,25 +103,33 @@ sub insertAll { } $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!
! + 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!! - . qq!! - . $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()); @@ -162,7 +137,7 @@ sub insertAll { # 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 ' ); }