oTree Forum >

Time tracker

#1 by Moritz_otree

For all who have trouble to track the time a participant spent on the page, here is a code snippet of a project I did where a simple task has to be executed and the time is tracked and saved in the variable time_spent_1. The time stops if the participant clicks on the next button. On the Results_1 page you can see how long the player spent to click the next button. Furthermore, it is checked whether the answer is correct or not and a payoff is given correspondingly. I hope this helps a lot of people because as a beginner it was hard to get this solution from the internet. Nevertheless, feel free to ask me if something is unclear or isn't working. By the way, the task is to count the P's and the correct answeer is 45.

from otree.api import *

doc = """
Your app description

class C(BaseConstants):
    NAME_IN_URL = 'newapp'
    NUM_ROUNDS = 1

class Subsession(BaseSubsession):

class Group(BaseGroup):

class Player(BasePlayer):
    answer_1 = models.IntegerField(label="Answer:")
    time_spent_1 = models.FloatField()


class pq_page_1(Page):
    form_model = 'player'
    form_fields = ['answer_1', 'time_spent_1']

    def before_next_page(player: Player, timeout_happened):
        if player.answer_1 == 45:
            player.payoff = player.payoff + 10
            player.payoff = player.payoff + 0

class Results_1(Page):

page_sequence = [pq_page_1, Results_1]



{{ block title }}
{{ endblock }}
{{ block content }}

<input type="hidden" name="time_spent_1" id="time_spent_1"/>

var startTime;
window.onload = function() {
    startTime = Date.now();
function getTime(){
    var time_spent_1 = Date.now() - startTime;
    document.getElementById('time_spent_1').value = time_spent_1/1000;



    {{ formfield 'answer_1' }}
{{ formfield_errors 'time_spent_1' }}

    <button class="otree-btn-next btn btn-primary" onclick="getTime()">Next</button>

{{ endblock }}


{{ block title }}
    Results 1
{{ endblock }}
{{ block content }}

 <p>You spent {{ player.time_spent_1 }} seconds on the previous page.</p>

{{ if player.answer_1 == 45}}
        This answer was correct. You get 10€.
{{ else }}
        This answer was wrong. You get 0€.
{{ endif }}

<p>In total your payoff is {{ player.payoff }}.</p>

    {{ next_button }}

{{ endblock }}

#2 by BonnEconLab

Thanks, Moritz_otree, for your suggestion how to record the time that participants spent on a particular page via JavaScript.

I would like to point out one potential drawback of that method: Whenever a participant refreshes the respective page, the JavaScript code is executed for another time. Hence, the time recorded by Moritz_otree’s code reflects only the time since the latest refresh and not necessarily the entire time that a participant has spent on the respective page.

A way to circumvent this issue is by recording the time server-side. That is, in your __init__.py, include

import time

and define variables in the Player class for trial onset, trial offset, and trial duration:

class Player(BasePlayer):

    trial_onset = models.FloatField()
    trial_offset = models.FloatField()
    trial_duration = models.FloatField()

Of course, if your app includes multiple pages for which you would like to record how much time participants spent on them, you will need multiple such variables.

In vars_for_template of the respective page(s), include code to record the trial onset, such as:

    def vars_for_template(player):
        player.trial_onset = time.time()

In before_next_page of the respective page(s), include code to record the trial offset and calculate the duration as the difference between the trial offset and the trial onset:

    def before_next_page(player, timeout_happened):
        player.trial_offset = time.time()
        player.trial_duration = player.trial_offset - player.trial_onset

The drawback of server-side recording of the trial duration is that it is subject to any delays caused by (potentially slow) data transmission between the participant’s device and the oTree server, which can entail imprecise measurement. (This will probably not be an issue when using oTree for a lab experiment but may be an issue when conducting an online experiment.)

The ideal way might, thus, be combining JavaScript and server-side recording via liveSend():

In JavaScript, use functions like those proposed by Moritz_otree:

    window.onload = function() {
        startTime = Date.now();
        liveSend({'page_loaded': startTime});
    function getTime() {
        endTime = Date.now();
        liveSend({'form_submitted': endTime});

In the __init__.py, use something like the following:

class Player(BasePlayer):

    survey_onset = models.StringField(initial='')
    survey_offset = models.StringField(initial='')

class Demographics_live(Page):

    def live_method(player, data):
        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']) + ", "

This would record all page refreshes and all clicks on the next button with high precision — because JavaScript is executed on the participant’s device — and send them to the oTree server. This way you can keep track of whether the page was refreshed and whether the next button was clicked on multiple times (which can happen if some required input field has not been filled in yet).

#3 by Moritz_otree

Thank you very much for the suggested improvements! They are very helpful to solve these potential problems which might occur. I will definitely look into the liveSend()-method.

Write a reply

Set forum username