psychopy.org | Reference | Downloads | Github

Custom web component for online experiments: Forms, surveys, questionnaires, and other web-based content

While Pavlovia/PsychoJS is seeing increased interest recently, some of PsychoPy’s components have not yet been implemented online. Additionally, there may always be an interest in custom components for online experiments even after all PsychoPy features are ported.

Of particular interest are PsychoPy’s form components, as the majority of IRB-approved online experiments require consent forms, demographic surveys, and feedback questionnaires, for example. However, forms functionality and rating scales are not currently available online.

Available options to consider:

  1. Make do with existing features: For example, use the Keyboard component for consent, the Slider component for demographics, multiple text inputs for surveys, custom coding for free-text inputs, etc. It is also possible to use the info dialog for demographics, though not in experiments that require consent first.
  2. Use something like Qualtrics instead: For example, it is possible to have a workflow such as Qualtrics -> Pavlovia -> Qualtrics if you want to have Qualtrics (or similar tool) handle consent and/or debrief, while Pavlovia handles only the core experiment component.
  3. Build a custom web component.

For the experiment I worked on, I decided to build a custom web-based component using good-old HTML/CSS and some Javascript (jQuery). If you would like to do the same for your experiment, then follow the steps below.

Note: I would not recommend this approach for anyone not familiar with web development. When PsychoJS catches up to PsychoPy, I would expect the built-in functionality to be far superior in terms of ease of use, cross-platform compatibility, and integration. Experienced web-developers know what it’s like to build that from scratch.

  1. Find Find and sync+fork (download) my sample project Custom Web Component from Pavlovia to PsychoPy, or download the experiment files directly from GitLab.
  2. Open the experiment (web-component.psyexp), and select any routine (they are all the same).
  3. Copy the routine (Experiment > Copy Routine) and paste it into your own experiment (Experiment > Paste Routine).
  4. Click the _code component in the routine, select the Begin Routine tab, and change the file name in the first line of code to your own html file.
  5. Create your own html file as desired, and place it in the html directory of your project.

Run the experiment using local debug mode to see how it works. The web component routine automatically includes the html file as an iframe, and usually requires no additional configuration.

The provided code automatically adjusts the iframe width and height to match its contents, centers it in the screen, and adds a vertical scrollbar if needed. It also automatically detects and handles a form within the file. If you require something more sophisticated (eg, multiple forms, or multi-step forms), then you will need to modify this code, but otherwise, it should just work. Feel free to use any of the provided sample html files as starting points.

One advantage of this approach is the ability to insert any custom web-based content (not just forms) anywhere in the experiment (eg, Consent -> Experiment Part 1 -> Distraction Survey -> Experiment Part 2 -> Debrief). The main advantage however, is the ability to include any custom web-based content not currently implemented in PsychoJS.

6 Likes

This sounds awesome. I can’t wait to try it out (though I’ll probably still use Qualtrics for PI sheets since I’m using it for balanced allocation to conditions.

This is great to show people how to add their own custom forms/JS

We do actually expect to have basic forms working online in the next release (2020.2) in July.

Although the class called RatingScale isn’t available that doesn’t mean you can’t provide rating scales. Slider achieves the same and more functoinality and that does work online. RatingScale is not supported specifically because it’s less flexible than Slider.

I’ve been playing about with this but I haven’t figured out how to pass the form variables back to PsychoPy.

My form (from https://pavlovia.org/Wake/brookes-template-2020) looks like this:

<form name="form1" method="post" action="">
  <h2 class="style1">Embedded Form </h2>
  <p class="style1">Which of the following demos would you like to see?<br>
Unfortunately this functionality doesn't yet work.</p>
  <table border="0">
    <tr>
      <td class="style1">Random Seed demo</td>
      <td class="style1"><input name="showSeed" type="checkbox" id="showSeed" value="1"></td>
    </tr>
    <tr>
      <td class="style1">Embedded YouTube video </td>
      <td class="style1"><input name="showVideo" type="checkbox" id="showVideo" value="1"></td>
    </tr>
    <tr>
      <td class="style1">Slider</td>
      <td class="style1"><input name="showSlider" type="checkbox" id="showSlider" value="1"></td>
    </tr>
    <tr>
      <td class="style1">Loop which repeats incorrect trials </td>
      <td class="style1"><input name="showLoop" type="checkbox" id="showLoop" value="1"></td>
    </tr>
    <tr>
      <td class="style1">Age text entry </td>
      <td class="style1"><input name="showAge" type="checkbox" id="showAge" value="1"></td>
    </tr>
  </table>
  <p>
    <span class="style1">
    <input type="submit" name="Submit" value="Submit">
  </span> </p>
</form></center>

I’m assuming that there should be a way since you can record the values to the data file. Do I need to address the trial handler in some way?

My closest attempt so far is

showSlider = psychoJS.experiment._trialsData.map((trial) => trial['showSlider']);

This returns an empty array, even if I put an nRep=1 loop around the Routine.

@wakecarter Placing the following in the End Routine tab gives me non-empty results:
console.log ( 'DEBUG:FRM', psychoJS.experiment._currentTrialData );

I have also added a params object to the code (download the latest version for that) - eg:
showSlider = psychoJS.experiment._currentTrialData['showSlider'];
or
showSlider = params['showSlider'];
But just note that params is reset in each Routine, while _currentTrialData would be reset at each trial loop.

1 Like

Thank you. That worked and I’ve also managed to work out how to send a variable to the form from PsychoPy using code from https://html-online.com/articles/get-url-parameters-javascript/

This is brilliant, thanks for sharing it !!! :smiley:

Very nice @thomas_pronk - I like the way you attached everything to the window object to make it available to iframe code as parent.

This version is good for programmers who want to handle the finish event manually. Since you have essentially created a library, you could also pull it out into a separate .js file and just import it in PsychoJS (eg, using $.getScript).

Automated testing for PsychoJS sounds like a very interesting project - I look forward to hearing more about it!

Then I’m afraid I got carried away too much, since my original aim was building something very accessible :slight_smile:

$.getScript. I like the idea of wrapping up more code in a separate library, but getScript could be tricky. I read in the jQuery docs than I can attach a callback for when the request is completed, but it doesn’t guarantee that the script is already executed at that point. I’ll go explore options to download a JS file in advance, similar to other PsychoJS resources.

Automated Testing. Sure! I’ll give an update when I got something nice. I’ll start a new thread on it to keep things organized. As a teaser: I’m collecting experiments that serve as simple feature demos over here. These are already useful for manual e2e testing, and with some tweaks could be automated too.

I was made aware of this PsychoJS feature; it seems a nice way to get resources: https://pavlovia.org/docs/experiments/resources

psychoJS.downloadResources([
  // relative path to index.html
  { name: 'trialTypes_A.xls', path: 'trialTypes_A.xls' },
  // absolute path:
  { name: 'trialTypes_B.xls', path: 'http://a.website.org/a.path/trialTypes_B.xls' }
]);

For my eye tracking demo I implemented “download JS library” in a very PsychoJS-conformant way. To check whether the library is actually initialized (which is a problem both in the jQuery approach and the PsychoJS approach), I check each frame whether the expected global (window.webgazer) is exposed. Once it is, the library is ready, and I finish the routine.

I’ve been trying to use this for a scrollable debrief all evening. It seems to be fine on my laptop but I can’t get the debrief to only cover the top 75% (and therefore not the continue button) on my iPhone.

I have the following code:

Begin Experiment

// Exposes PsychoJS's addData for use in HTML pages
window.addData = function(key, value) {
    psychoJS.experiment.addData(key, value);
}

// Adds an iframe on top of the PsychoJS canvas. Use src to specify an HTML page
window.startHTML = (src) => {
  $('body').append('<iframe id="iframe" src="' + src +'" style="width: 100%; height: 75%; position:absolute;z-index:1;top:0;left:0;border:0; overflow:auto;"></iframe>');    
  window.finishedHTML = false;
};

// Removes the iframe again
window.finishHTML = () => {
  $('#iframe').remove();
  window.finishedHTML = true;
};

// Note that you can use window.finishRoutine to check if the HTML page has completed

Begin Routine

 window.startHTML('debrief.html');

HTML file

<html>
  <head>
    <meta charset="utf-8">    
  </head>
  <body style='width: 90%; height: 75%; background-color: grey; color: white; font-family:arial; font-size:160%; margin: 5px 10px'>
<h1>Experiment Debriefing</h1>
<h3>Thank you for taking part in this research!</h3>
<p style='color: ff9999'><i>Please read this page carefully to learn important information about your experience in this study. After this debriefing, you may choose to have the data collected from you removed from this research study by simply closing the browser so that the data will not be saved. Or, if you are still happy for your contribution, please continue and then wait for the message "Thank you for your patience" to save your data.</i></p> 

<p>Some more stuff I've deleted here.</p>
  </body>
</html>

The debrief scrolls on the mobile but since the continue button is invisible then it gets randomly hit at some point during the scrolling (which defeats the object of the debrief).

Any ideas? I’ve tried changing top:0; to top:250; to try to give space at the top of the page for the button but it didn’t seem to move the iFrame.

I also tried changing the z-index to -1 just in case I could have the button in front of the html, but that seemed to make it invisible.

UPDATE: I implemented a workaround by using a continue button within the iframe instead of using the mouse button I’ve created for other pages.

1 Like

Happy to read you got it fixed. If you got any other issues, I’d love to take a shot, though I’m a bit slow with replying this & next week (doing some vacation)

Hi @arnon_weinberg,

NB - This a repost of my original reply. I changed the naming scheme of my experiments; this one is now prefixed “demo” instead of “e2e”.

I found your web-component via the Crib Sheet . Firstly, thanks for building and sharing this component! Secondly, I used it as the basis for my own version, where I tried to streamline things a bit and add some functionality.

Best, Thomas