#1 by WD95
Hello everyone, I'm currently working on a feature where the submit button's enablement state is controlled based on the user's input. Here's the scenario: participants answer a question, and if they get it wrong, the submit button becomes disabled for 10 seconds as a cooldown period. Originally, I planned to use JavaScript to manage the button's disabled state according to the correctness of the user's answer. However, I hit a snag: the validation to determine the answer's correctness happens post-form submission, which has thrown a wrench in my approach. I'm reaching out to see if anyone has a tried-and-true method or any alternative technical solutions to effectively disable the submit button following an incorrect answer. Any insights or suggestions on this would be greatly appreciated! Below is the code snippet I'm working with: document.addEventListener('DOMContentLoaded', function() { var form = document.querySelector('form'); form.addEventListener('submit', function(e) { if (!{{ player.is_answer_correct }}) { e.preventDefault(); // Prevent form submission disableSubmitButtonForSeconds(10); // Disable button } }); // Function to disable submit button for a given time function disableSubmitButtonForSeconds(seconds) { var submitButton = document.querySelector("button[type='submit']"); submitButton.disabled = true; // Disable button immediately setTimeout(function() { // Re-enable button after specified seconds submitButton.disabled = false; }, seconds * 1000); // Convert seconds to milliseconds } }); Thank you in advance for your help!
#2 by BonnEconLab
You can, of course, do the validation in JavaScript. To do so, write a function in JavaScript that compares the contents of the input field — document.getElementById("id-of-the-input-field").value — to the correct value. Then, have this function triggered by a button that does NOT submit the form. For instance, <button type="button" onclick="functionThatChecksForCorrectness()">Validate input</button> Alternatively, you can have this be triggered whenever a participant makes an input. <input type="text" oninput="functionThatChecksForCorrectness()"> The drawback is that the correct answer will be included in your JavaScript code, so tech-savvy participants could find out the correct answer by inspecting your JavaScript code. That is not too likely, though, I think. The more severe drawback is that participants can easily circumvent your JavaScript-based timeout by simply refreshing the page. So, I would recommend having the sever do the validation. For this, you will need to use a “live page,” see https://otree.readthedocs.io/en/latest/live.html. That is, you send the input to the server for validation before the participants hit the “Next” button, and only if the server confirms that the input is valid, the “Next” button will be shown (with a delay that you can also set server-side, so it can’t be influenced by participants refreshing the page). Here is some code that I once wrote for a test page. On that test page, there are two input fields, a text input field for “age,” and three radio buttons for “gender.” The purpose of the JavaScript code is to display the “Next” button only if all input fields are filled. However, it also live-sends the “age” input to the sever every time the “age” input is changed. (This does not make much substantial sense, it is for demonstration purposes only.) Moreover, it live-sends the (client-side) time at which the page was loaded, and allows live-sending the time at which the submit button was pressed. In Demographics_live.html, I included: {{ block scripts }} <script> function showNextButton() { if (document.getElementById('id_age').value != "" & ( (document.getElementById('id_gender-0').checked == true) | (document.getElementById('id_gender-1').checked == true) | (document.getElementById('id_gender-2').checked == true) ) ) { document.getElementById('next-button').style.visibility = "visible"; } else { document.getElementById('next-button').style.visibility = "hidden"; } liveSend({"player_age": document.getElementById('id_age').value}); }; document.getElementById('id_age').oninput = function() { showNextButton(); }; document.getElementById('id_gender-0').oninput = function() { showNextButton(); }; document.getElementById('id_gender-1').oninput = function() { showNextButton(); }; document.getElementById('id_gender-2').oninput = function() { showNextButton(); }; window.onload = function() { startTime = Date.now(); liveSend({'page_loaded': startTime}); }; function getTime() { endTime = Date.now(); liveSend({'form_submitted': endTime}); }; </script> {{ endblock }} In the __init__.py file, I included: class Demographics_live(Page): form_model = 'player' form_fields = ['age', 'gender'] @staticmethod def live_method(player, data): if "player_age" in data: player.age = int(data['player_age']) if "page_loaded" in data: player.survey_onset = player.field_maybe_none('survey_onset') + str(data['page_loaded']) + ", " if "form_submitted" in data: player.survey_offset = player.field_maybe_none('survey_offset') + str(data['form_submitted']) + ", " By including a function liveRecv(data) in your JavaScript, you can receive messages from the server, say, the server-side confirmation that a participant’s input was correct. See https://otree.readthedocs.io/en/latest/live.html#sending-data-to-the-page.
#3 by BonnEconLab
Here is some code that should achieve in principle what you desire: ############################## MyPage.html ############################## {{ block title }} Input that is checked {{ endblock }} {{ block content }} {{ formfields }} <p id="validate_button_container"> <button type="button" onclick="sendInput()" class="btn btn-warning">Validate input</button> </p> <div id="next_button_container" style="visibility: hidden;"> {{ next_button }} </div> <p id="checking_if_correct" class="text-secondary" style="visibility: hidden;"> Checking if input is correct. </p> {{ endblock }} {{ block scripts }} <script> function liveRecv(data) { alert("The input is " + data + "."); if (data == "correct") { document.getElementById("next_button_container").style.visibility = "visible"; document.getElementById("checking_if_correct").innerHTML = "Input is correct. You may continue."; } else { document.getElementById("validate_button_container").style.visibility = "visible"; document.getElementById("checking_if_correct").style.visibility = "hidden"; }; }; function sendInput() { document.getElementById("validate_button_container").style.visibility = "hidden"; document.getElementById("checking_if_correct").style.visibility = "visible"; liveSend({"some_input": document.getElementById('id_some_input').value}); }; </script> {{ endblock }} ############################## __init__.py ############################## from otree.api import * from time import sleep doc = """ An app to demonstrate live send and receive """ class C(BaseConstants): NAME_IN_URL = 'zzz_live_send_receive' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 correct_answer = "BonnEconLab" class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): some_input = models.StringField() correct_answer = "BonnEconLab" def some_input_error_message(player, value): if value != C.correct_answer: return 'This is not the correct answer.' # PAGES class MyPage(Page): form_model = "player" form_fields = ["some_input"] @staticmethod def live_method(player, data): if "some_input" in data: player.some_input = str(data["some_input"]) if player.some_input == C.correct_answer: return {player.id_in_group: 'correct'} else: sleep(10) return {player.id_in_group: 'incorrect'} class Results(Page): pass page_sequence = [ MyPage, ]
#4 by WD95
Hello, First and foremost, I want to express my deep gratitude for the valuable advice you provided earlier. Thanks to your assistance, it workes now, which has been a tremendous help to me. Building on this. I have further explored the application of the 'live_method' function and attempted to use it to record the time intervals between each click of the "Validate input" button. Unfortunately, I encountered some difficulties in the implementation process, and it seems that this part of the code has not been successful in capturing the time intervals between submissions. Below is the snippet of code I attempted to implement. I am unsure where the problem lies, and I would be immensely grateful if you could provide your help and advice once again. Your opinions are incredibly valuable to me, and I look forward to possibly receiving your guidance again. ############################## __init__.py ############################## class Player(BasePlayer): count_entered = models.IntegerField(min=0, max=100, label="Input") num_submit = models.IntegerField(initial=0) #time time_on_page = models.LongStringField() class MyPage(Page): form_model = "player" form_fields = ["some_input"] @staticmethod def live_method(player, data): if "some_input" in data: player.some_input = str(data["some_input"]) if player.some_input == C.correct_answer: return {player.id_in_group: 'correct'} else: sleep(10) return {player.id_in_group: 'incorrect'} if 'interval' in data: current_intervals = player.time_on_page new_interval = data['interval'] if current_intervals: updated_intervals = f"{current_intervals},{new_interval}" else: updated_intervals = str(new_interval) player.time_on_page = updated_intervals class Results(Page): pass page_sequence = [ MyPage, ] ############################## MyPage.html ############################## {{ block title }} Input that is checked {{ endblock }} {{ block content }} {{ formfields }} <p id="validate_button_container"> <button type="button" onclick="sendInput()" class="btn btn-warning">Validate input</button> </p> <div id="next_button_container" style="visibility: hidden;"> {{ next_button }} </div> <p id="checking_if_correct" class="text-secondary" style="visibility: hidden;"> Checking if input is correct. </p> {{ endblock }} {{ block scripts }} <script> var lastSubmitTime = Date.now(); function liveRecv(data) { alert("The input is " + data +"."); if (data == "correct") { document.getElementById("next_button_container").style.visibility = "visible"; document.getElementById("checking_if_correct").innerHTML = "回答正确,请点击继续按钮。"; } else { document.getElementById("validate_button_container").style.visibility = "visible"; document.getElementById("checking_if_correct").style.visibility = "hidden"; }; }; function sendInput() { var currentTime = Date.now() var interval = currentTime - lastSubmitTime lastSubmitTime = currentTime document.getElementById("validate_button_container").style.visibility = "hidden"; document.getElementById("checking_if_correct").style.visibility = "visible"; liveSend({ "count_entered": document.getElementById('id_count_entered').value, "interval": interval }); }; </script> {{ endblock }} Thank you once again for your previous help and support.
#5 by BonnEconLab
* CAUTION: I JUST REALIZED THAT PYTHON’S sleep(X) COMMAND ALSO BLOCKS ALL OTHER PARTICIPANTS FROM ADVANCING FOR x SECONDS. EVEN WORSE, THE DELAYS FROM ALL INCORRECTLY ANSWERING PARTICIPANTS ADD UP. * Hence, you will have to find a solution so that the delay is only applied to the respective participant! Apart from that, there are two errors in your Python code: 1. player.time_on_page needs to be assigned an initial value. 2. In the live_method, the “if 'interval' in data:” part needs to be moved in front of the “if "some_input" in data:” part because otherwise, the return is triggered before the “if 'interval' in data:” part is reached at all. Hence, class Player(BasePlayer): some_input = models.StringField() time_on_page = models.LongStringField(initial="") class MyPage(Page): form_model = "player" form_fields = ["some_input"] @staticmethod def live_method(player, data): if 'interval' in data: current_intervals = player.time_on_page new_interval = data['interval'] if current_intervals: updated_intervals = f"{current_intervals},{new_interval}" else: updated_intervals = str(new_interval) player.time_on_page = updated_intervals if "some_input" in data: player.some_input = str(data["some_input"]) if player.some_input == C.correct_answer: return {player.id_in_group: 'correct'} else: sleep(10) return {player.id_in_group: 'incorrect'}
#6 by BonnEconLab
I was unable to come up with a server-side solution for the delay that does *not* affect all participants simultaneously. Hence, I would suggest using JavaScript and counting on participants rather spending time on figuring out the correct answer to your question than hacking your JavaScript code: ############################## MyPage.html ############################## {{ block content }} {{ formfields }} <p id="validate_button_container"> <button id="validate_button" type="button" onclick="sendInput()" class="btn btn-warning" disabled>Validate input</button> </p> <div id="next_button_container" style="visibility: hidden;"> {{ next_button }} </div> {{ endblock }} {{ block scripts }} <script> setTimeout(() => { document.getElementById("validate_button").disabled = false; }, 10000) var lastSubmitTime = Date.now(); function liveRecv(data) { alert("The input is " + data + "."); if (data == "correct") { document.getElementById("validate_button_container").style.visibility = "hidden"; document.getElementById("next_button_container").style.visibility = "visible"; } else { document.getElementById("validate_button").disabled = true; setTimeout(() => { document.getElementById("validate_button").disabled = false; }, 10000) }; }; function sendInput() { var currentTime = Date.now() var interval = currentTime - lastSubmitTime lastSubmitTime = currentTime liveSend({ "some_input": document.getElementById('id_some_input').value, "interval": interval, }); }; </script> {{ endblock }} ############################## __init__.py ############################## I also suggest to record the intervals between submissions server-side. This is because whenever a participant refreshes the page, the JavaScript-recorded intervals do not make sense anymore. More specifically, I suggest recording each time point at which the page was loaded/refreshed and at which a validation request was received. The difference between the time points yields the interval in seconds. class MyPage(Page): form_model = "player" form_fields = ["some_input"] @staticmethod def vars_for_template(player): current_intervals = player.time_on_page new_interval = str(time.time()) if current_intervals: updated_intervals = f"{current_intervals},{new_interval}" else: updated_intervals = str(new_interval) player.time_on_page = updated_intervals @staticmethod def live_method(player, data): if "some_input" in data: current_intervals = player.time_on_page new_interval = str(time.time()) updated_intervals = f"{current_intervals},{new_interval}" player.time_on_page = updated_intervals player.some_input = str(data["some_input"]) if player.some_input == C.correct_answer: return {player.id_in_group: 'correct'} else: #time.sleep(10) # BEWARE! This blocks Python completely, also for all other participants. # Unfortunately, so does the following: #time_first_reply = time.time() #time_last_reply = time.time() #while time_last_reply - time_first_reply < 10: # time_last_reply = time.time() return {player.id_in_group: 'incorrect'}
#7 by WD95
Hello! I would like to take this opportunity to express my deepest gratitude to you. The advice you provided was not only invaluable but also proved to be the key to solving my problem. By following your suggestion to use JavaScript, I was able to successfully resolve the issue that had been troubling me. More importantly, your response taught me how to effectively implement interaction between the front end and the back end. This learning will be of great benefit and significance to my future project development. Please allow me to once again express my heartfelt thanks for your generous assistance.