Adding timeout to survey-text type trials and force trial stop

788 views
Skip to first unread message

Daniel Soto

unread,
Aug 18, 2016, 3:23:37 PM8/18/16
to jsPsych
Hi Josh,

I have to add yet another experiment to my battery: Four groups of short, quick questions are presented to the user, one by another, using survey-text trials.
The user has 1 minute to answer each group, so if the time reaches 1 minute, the trial must stop immediately and go to the next group.

The structure is something like this:

var group1question1={ //plugin code goes here };
var group1question2={ //plugin code goes here };
var group1question3={ //plugin code goes here };

var group2question1={ //plugin code goes here };
var group2question2={ //plugin code goes here };
var group2question3={ //plugin code goes here };

var group3question1={ //plugin code goes here };
var group3question2={ //plugin code goes here };
var group3question3={ //plugin code goes here };

var group4question1={ //plugin code goes here };
var group4question2={ //plugin code goes here };
var group4question3={ //plugin code goes here };



From previous posts on this group I've read about the Chunks, and used them to loop those groups until the timer reaches the minute. Although it actually works, I can't force the trial to stop and end itself. In other words, if the user is at question 2 of the third group and he/she reaches the minute provided for that group, he/she will still be able to answer that trial, and only afterwards he/she will be forced to go to the next group. But in my case, I need to stop it immediately.

I've read that for other plugins something like that can be done, but I can't find anything for survey-text type trials.

Any pointers on this?

Thanks!

Daniel

PS: Let me know if you need to see the full code.

Josh de Leeuw

unread,
Aug 18, 2016, 4:55:55 PM8/18/16
to Daniel Soto, jsPsych
This is an interesting problem. In general the library is written so as not to meddle with the behavior of plugins while they are running. This helps ensure compatibility.

So here's what I would suggest: Add a timing_response option to the survey-text plugin. Model it after the option in single-stim or any of the other plugins that allow this. Then you can dynamically set this option when the trial starts to be 60 seconds minus the time taken on the previous items in the group.

I would be happy to integrate such a change to the plugin into the main library.

Josh

--
You received this message because you are subscribed to the Google Groups "jsPsych" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jspsych+u...@googlegroups.com.
To post to this group, send email to jsp...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jspsych/d3e927bb-acea-4e0c-a764-ed52963b209c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Daniel Soto

unread,
Aug 18, 2016, 10:01:47 PM8/18/16
to jsPsych, daniel.in...@gmail.com
And it is done.

I did as you suggested so here I share a modified version of the survey-text plugin, including the timing_response option. I would suggest to review it first, in case that something is missing. The main thing I notice is that the response field isn't created in the CSV when a timeout happens for a certain trial, which may be a desirable behavior since that field won't have anything after all, but I'll leave it to your judgement.

/**
 * jspsych-survey-text
 * a jspsych plugin for free response survey questions
 *
 * Josh de Leeuw
 *
 * documentation: docs.jspsych.org
 *
 */



var focused_box
jsPsych
.plugins['survey-text'] = (function() {


 
var plugin = {};


  plugin
.trial = function(display_element, trial) {


    trial
.preamble = typeof trial.preamble == 'undefined' ? "" : trial.preamble;
   
if (typeof trial.rows == 'undefined') {
      trial
.rows = [];
     
for (var i = 0; i < trial.questions.length; i++) {
        trial
.rows.push(1);
     
}
   
}
   
if (typeof trial.columns == 'undefined') {
      trial
.columns = [];
     
for (var i = 0; i < trial.questions.length; i++) {
        trial
.columns.push(40);
     
}
   
}
   
   
// Default value for time limit option
    trial
.timing_response = trial.timing_response || -1;
   
// Time handlers
   
var setTimeoutHandlers = [];
   
   
// if any trial variables are functions
   
// this evaluates the function and replaces
   
// it with the output of the function
    trial
= jsPsych.pluginAPI.evaluateFunctionParameters(trial);


   
// show preamble text
    display_element
.append($('<div>', {
     
"id": 'jspsych-survey-text-preamble',
     
"class": 'jspsych-survey-text-preamble'
   
}));


    $
('#jspsych-survey-text-preamble').html(trial.preamble);


   
// add questions
   
for (var i = 0; i < trial.questions.length; i++) {
     
// create div
      display_element
.append($('<div>', {
       
"id": 'jspsych-survey-text-' + i,
       
"class": 'jspsych-survey-text-question'
     
}));


     
// add question text
      $
("#jspsych-survey-text-" + i).append('<p class="jspsych-survey-text">' + trial.questions[i] + '</p>');


     
// add text box
     
//$("#jspsych-survey-text-" + i).append('<textarea name="#jspsych-survey-text-response-' + i + '" cols="' + trial.columns[i] + '" rows="' + trial.rows[i] + '"></textarea>'); //original
      focused_box
= $("#jspsych-survey-text-" + i).append('<textarea name="#jspsych-survey-text-response-' + i + '" cols="' + trial.columns[i] + '" rows="' + trial.rows[i] + '"></textarea>');
     
   
}


   
// add submit button
    display_element
.append($('<button>', {
     
'id': 'jspsych-survey-text-next',
     
'class': 'jspsych-btn jspsych-survey-text'
   
}));
    $
("#jspsych-survey-text-next").html('Submit Answers');
    $
("#jspsych-survey-text-next").click(function() {
       
// measure response time
     
var endTime = (new Date()).getTime();
     
var response_time = endTime - startTime;


     
// create object to hold responses
     
var question_data = {};
     
     
var obje;
      $
("div.jspsych-survey-text-question").each(function(index) {
       
var id = "Q" + index;
       
var val = $(this).children('textarea').val();
        console
.log('val is:   '+val);
       
var obje = {};
        obje
[id] = val;
        console
.log('obje is:   '+val);
        $
.extend(question_data, obje);
     
});
     
var trialdata = {
       
"rt": response_time,
       
"responses": JSON.stringify(question_data)
     
};


      display_element
.html('');


     
// next trial
      jsPsych
.finishTrial(trialdata);    
   
});
   
   
var end_trial = function() {
     
var endTime = (new Date()).getTime();
     
var response_time = endTime - startTime;


     
// kill any remaining setTimeout handlers
     
for (var i = 0; i < setTimeoutHandlers.length; i++) {
        clearTimeout
(setTimeoutHandlers[i]);
     
}


     
// kill keyboard listeners
     
if (typeof keyboardListener !== 'undefined') {
        jsPsych
.pluginAPI.cancelKeyboardResponse(keyboardListener);
     
}


     
// gather the data to store for the trial
     
var trial_data = {
       
"rt": response_time
     
};


     
//jsPsych.data.write(trial_data);


     
// clear the display
      display_element
.html('');


     
// move on to the next trial
      jsPsych
.finishTrial(trial_data);
   
};


   
var startTime = (new Date()).getTime();
   
   
if (trial.timing_response > 0) {
     
var t2 = setTimeout(function() {
        end_trial
();
     
}, trial.timing_response);
      setTimeoutHandlers
.push(t2);
   
}
   
 
};


 
return plugin;
})();


Ironically, I haven't obtained a good result when dynamically setting the time limit for consecutive trials. Can you lend me a hand with a short example, please?

Cheers!

Daniel

Josh de Leeuw

unread,
Aug 19, 2016, 3:55:17 PM8/19/16
to Daniel Soto, jsPsych
Here's an untested example of what I had in mind.

var time_left = 60000;

trial = {
  type: 'survey-text',
  questions: ['Type something here'],
  timing_response: function() {
    return time_left;
  }
  on_finish: function(data) {
    time_left = time_left - data.rt;
  },
  timing_post_trial: 0
}

timeline.push(trial, trial, trial, trial, trial);

That should give you a maximum of 60s to answer all 5 trials.

Daniel Soto

unread,
Aug 19, 2016, 6:41:46 PM8/19/16
to jsPsych, daniel.in...@gmail.com
I see where your idea is aiming to. I've been trying something similar yesterday.

Thanks for the pointers. I'm noticing some weird behavior on some trials when the time_left variable reaches zero, but I still have the whole weekend to make it work, so I'll post here when I get a final solution.

Cheers!

Josh de Leeuw

unread,
Aug 19, 2016, 8:40:29 PM8/19/16
to Daniel Soto, jsPsych
Yep, you will probably need to put each trial inside a timeline with a conditional_function that skips the trial if time_left = 0.

Rachit Dubey

unread,
Nov 22, 2016, 3:12:46 PM11/22/16
to jsPsych, daniel.in...@gmail.com
Hi,

Did you figure out a solution to this? Based on your solution, I am having a weird bug with setting the time limit for consecutive trials. 

Daniel Soto

unread,
Nov 30, 2016, 5:18:13 PM11/30/16
to jsPsych, daniel.in...@gmail.com
Hi Rachit.

I had a solution for this, but now I went to check it out to provide you the code and now it isn't working anymore and I don't know why.

So far I've found 2 possible bugs for this:

1.- In some cases the pointer to the current trial is lost when you get to the final trials of the set (which is bad if you have other kind of trials after the ones with timeouts) and,
2.- The original method to clear the timeout handlers just kill all of them, so that causes the trials to loose their handlers if any of them reaches zero (I've been trying to modify this but without any results).

I'll keep trying and see if I can figure out something.
Reply all
Reply to author
Forward
0 new messages