Metadata Questionnaire

  • 2nd Dec 2024
  • Updated on 14th May 2025
Experiment element only

This documentation describes only one part of an experiment. For other tasks, see the related pages.

Participant Metadata

The metadata questionnaire collects essential participant information before the experiment begins. Participants are required to fill out this form to ensure their data is recorded properly. It gathers demographic details such as participant ID, age, handedness, language background, and geographic origin through input fields, dropdowns, and radio buttons.

Overview

Participant information input form

This PCIbex script defines a structured approach to collecting essential participant metadata before an experiment begins. The collected data is stored in global variables and logged for later analysis while ensuring participant anonymity and proper validation. Metadata is recorded in three different ways, each corresponding to a separate trial sequence.

The standard metadata form (pcibex-metadata) requires participants to manually enter responses and ensures all fields are completed before proceeding.

The predefined HTML questionnaire (html-metadata) loads an external form (questions.html) for structured data collection, enforcing completion before moving forward. The incremental metadata collection (incremental-metadata) logs responses in real time as participants enter them, helping to minimize errors and ensure accuracy.

These three methods provide flexibility in data collection, combining structured input validation with real-time data logging.

Summary of differences:

  • html-metadata → Uses an external HTML file for structured questionnaires.
  • pcibex-metadata → Standard form, ensures all fields are completed before continuing.
  • incremental-metadata → Records responses as they are entered, reducing participant errors in real-time.

Dependencies

  1. Resources
    • questions.html: the file containing the questions that participants need to answer.
  2. Scripts
    • main.js
  3. Aesthetics
    • global_main.css
    • PennController.css
  4. Modules
    • PennController.js

Other modules and aesthetics may be necessary if your experiment uses other trials, e.g. a form for recording participant information.

HTML form

The following part of the code handles the metadata questionnaire. It creates a new trial with two main elements:

  1. An HTML page that displays the questionnaire for participants to complete.
  2. A button for participants to continue to the next screen, which only becomes active once all fields in the questionnaire are appropriately filled out.

The .print() method displays the questionnaire and the button. The .wait() method pauses the experiment until the participant has completed the questionnaire and pressed the continue button.

The newHtml("questionnaire", "questions.html") creates an HTML element that loads the content from an external file named questions.html, where the metadata questions are specified. The .log() method tracks participant responses for data collection. In this case, this is the participant's ID, age, handedness, and gender.

The following methods set up warning messages for incomplete responses:

  • .checkboxWarning(...): Displays a warning if a checkbox is not ticked.
  • .radioWarning(...): Displays a warning if no radio button option is selected
  • .inputWarning(...): Displays a warning if an input field is left empty.

The newButton("continue", "Continue") creates a button, which is centered on the screen.

The .wait(...) method pauses the experiment until the participant has filled out the questionnaire. It checks if all required fields are completed:

  • getHtml("questionnaire").test.complete(): This tests if all fields are filled correctly.
  • .failure(getHtml("questionnaire").warn()): If the test fails, the corresponding warning messages are displayed.
// Metadata Questionnaire
newTrial("metadata",
    newHtml("questionnaire", "questions.html")
        .log()
        .checkboxWarning("To continue, please, tick the field '%name%'")
        .radioWarning("To continue, please, choose an option in the field '%name%'")
        .inputWarning("To continue, please, fill in the field '%name%'")
        .print()
    ,
    newButton("continue", "Continue")
        .center()
        .print()
        .wait(getHtml("questionnaire").test.complete()
                  .failure(getHtml("questionnaire").warn()))
);

The content of the questions.html file can be adjusted to the requirements of the experimenter. The demo does not contain a checkbox, but it has provisions for a checkbox error. Note that if a question is obligatory, this needs to be specified within the <input> tag. The input id must correspond to the error class. Click on Details to see the contents of the html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Participant Information Form</title>
    <style>
        input[type="text"], input[type="number"] {
            width: 100%;
            padding: 8px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        .radio-group {
            margin-bottom: 20px;
        }
        .radio-group label {
            display: inline-block;
            margin-right: 15px;
        }
        button {
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>

    <h2>Participant Information Form</h2>

    <form>
        <label for="Participant ID">Participant ID:</label>
        <input type="text" id="Participant ID" name="Participant ID" class="obligatory" />

        <label for="Age">Age:</label>
        <input type="text" id="Age" name="Age" class="obligatory" />

        <div class="radio-group">
            <label>Handedness:</label>
            <label><input type="radio" name="Handedness" value="left" class="obligatory"/> Left-handed</label>
            <label><input type="radio" name="Handedness" value="right" /> Right-handed</label>
            <label><input type="radio" name="Handedness" value="ambidextrous" /> Ambidextrous</label>
        </div>

        <div class="radio-group">
            <label>Gender:</label>
            <label><input type="radio" name="Gender" value="male" class="obligatory"> Male</label>
            <label><input type="radio" name="Gender" value="female"> Female</label>
            <label><input type="radio" name="Gender" value="other"> Other</label>
        </div>
        </div>
        <label class="error" for="Participant ID"></label><br />
        <label class="error" for="Age"></label><br />
        <label class="error" for="Handedness"></label><br />
        <label class="error" for="Gender"></label><br />
    </form>

</body>
</html>

Trial "pcibex-metadata"

In this method, participants manually enter responses using text input fields, dropdown menus, and radio buttons. The input fields are displayed all at once. The validation procedure ensures that all required fields are completed before allowing participants to continue. Data is stored in global variables and logged for all trials.

The code consists of the following elements:

  • newText(): Displays an introductory message and questions
  • newTextInput("input_ID").log().print();: Input field which records participant ID
  • newTextInput("input_age").length(2).log().print();: Input field of 2 characters, recording age
  • newScale("input_german", "✔", "✖").radio().log().print();: A radio button selection for "Yes" (✔) or "No" (✖), logging the response.
  • newDropDown("input_land", "(please choose one)").add(...).log().print();: A dropdown menu listing German federal states and relevant neighboring regions (Austria, Switzerland).
  • newTextInput("input_comments").log().print();: A free-text input area, logging participant comments and feedback.
  • newKey("just for callback", "").callback(getText("errorSelect").remove());: Clears any previous error messages when the participant modifies their response.
  • newButton("next", "Continue")... .wait(...);: The Continue button is only enabled if all required fields are properly completed. If any required fields are missing, an error message appears in red. The requirements are:
    • ID is not empty
    • Age input is a valid number
    • A federal state is selected
    • The language question is answered.

Responses are stored in global variables for later reference:

  • getVar("ID")getTextInput("input_ID")
  • getVar("GERMAN")getScale("input_german")
  • getVar("LAND")getDropDown("input_land")
  • getVar("AGE")getTextInput("input_age")
  • getVar("COMMENTS")getTextInput("input_comments")
// Metadata recorded from PCIbex components
newTrial("pcibex-metadata",
    defaultText
        .cssContainer({"margin-top":"1em", "margin-bottom":"1em"})
        .print()
    ,
    newText("participant_info_header", "<h2>We need the following information to evaluate the results.</h2><p>This information will be treated strictly anonymously and it will not be possible to identify you at a later date. Questions marked with * are obligatory.</p></div>")
    ,
    
    // Participant ID (6-place)
    newText("participantID", "Prolific-ID*")
        .print()
    ,
    newTextInput("input_ID")
        .log()
        .print()
    ,
    // Age
    newText("Age*")
        .print()
    ,
    newTextInput("input_age")
        .length(2)
        .log()
        .print()
    ,

    // German native speaker question
    newText("Is German your native language?*")
        .print()
    ,
    newScale("input_german",   "", "")
        .radio()
        .log()
        .labelsPosition("right")
        .print()
    ,
    // Federal state of origin
    newText("In which federal state is your German dialect mainly spoken?*")
        .print()
    ,
    newDropDown("input_land", "(please choose one)")
        .add("Baden-Württemberg", "Bayern", "Berlin", "Brandenburg", "Bremen", "Hamburg", "Hessen", "Mecklenburg-Vorpommern", "Niedersachsen", "Nordrhein-Westfalen", "Rheinland-Pfalz", "Saarland", "Sachsen", "Sachsen-Anhalt", "Schleswig-Holstein", "Thüringen", "not Germany, but Austria", "not Germany, but Switzerland", "none of these")
        .log()
        .print()
    ,
    // Feedback
    newText("Comments, suggestions, criticism and requests")
        .print()
    ,
    newTextInput("input_comments")
        .log()
        .print()
    ,    
    // Clear error messages if the participant changes the input
    newKey("just for callback", "") 
        .callback( getText("errorSelect").remove() )
   ,
    // Continue. Only validate a click when ID and age information is input properly
    newButton("next", "Continue")
        .cssContainer({"margin-top":"1em", "margin-bottom":"1em"})
        .print()
        // Check whether all questions have been answered
        .wait(
             newFunction('dummy', ()=>true).test.is(true)
            // ID
            .and( getTextInput("input_ID").testNot.text(""))
            .and( getTextInput("input_age").test.text(/^\d+$/))
            .and( getDropDown("input_land").test.selected())
            .and( getScale("input_german").test.selected())
                .failure( 
                    newText('errorSelect', "Please answer all questions.")
                        .color("Crimson").print()
                         )
            )
    ,
    // Store the texts from inputs into the Var elements
    getVar("ID")     .set( getTextInput("input_ID") ),
    getVar("GERMAN") .set( getScale("input_german") ),
    getVar("LAND")   .set( getDropDown("input_land") ),
    getVar("AGE")    .set( getTextInput("input_age") ),
    getVar("COMMENTS")     .set( getTextInput("input_comments") )
);

Trial "incremental-metadata"

Data is recorded incrementally, meaning each response is stored as soon as it is entered. Each field must be confirmed (e.g., pressing enter after entering ID/age) and is validated at the spot. Data is assigned to global variables in real time. The Continue button appears and the participant can only continue once all information has been filled. This ensures that participants do not skip any questions.

The code is similar to the pcibex-metadata trial. However, participants press Enter to confirm inputs. This is handled by the .wait() function, which pauses the trial until a specific condition is met.

There is also a final validation procedure witin the "Continue" button.

// Metadata recorded incrementally

// Questions appear as soon as information is input
newTrial("incremental-metadata",
    defaultText
        .cssContainer({"margin-top":"1em", "margin-bottom":"1em"})
        .print()
    ,
    newText("participant_info_header", "<h2>We need the following information to evaluate the results.</h2><p>The data will be treated anonymously and it will not be possible to identify you at a later date.</p>")
    ,
    // Participant ID (6-place)
    newText("participantID", "<b>6-digit participant ID.</b><br>(please confirm your entry by pressing enter)")
    ,
    newTextInput("input_ID")
        .length(6)
        .log()
        .print()
        .wait()
    ,

    // Federal state of origin
    newText("<b>In which federal state is your German dialect mainly spoken?</b>")
    ,
    newDropDown("land", "(please select one)")
        .add("Baden-Württemberg", "Bayern", "Berlin", "Brandenburg", "Bremen", "Hamburg", "Hessen", "Mecklenburg-Vorpommern", "Niedersachsen", "Nordrhein-Westfalen", "Rheinland-Pfalz", "Saarland", "Sachsen", "Sachsen-Anhalt", "Schleswig-Holstein", "Thüringen", "nicht Deutschland, sondern Österreich", "nicht Deutschland, sondern Schweiz", "keines davon")
        .log()
        .print()
        .wait()
    ,

    // Age
    newText("<b>Age in years</b><br>(please confirm your entry by pressing enter)")
    ,
    newTextInput("input_age")
        .length(2)
        .log()
        .print()
        .wait()
    ,
    
    // Handedness
    newText("<b>Handedness</b>")
    ,
    newScale("input_hand",   "right", "left", "both")
        .radio()
        .log()
        .labelsPosition("right")
        .print()
        .wait()
    ,
    // Clear error messages if the participant changes the input
    newKey("just for callback", "") 
        .callback( getText("errorage").remove() , getText("errorID").remove() )
    ,
    // Formatting text for error messages
    defaultText.color("Crimson").print()
    ,
    // Continue. Only validate a click when ID and age information is input properly
    newButton("continue", "Continue")
        .cssContainer({"margin-top":"1em", "margin-bottom":"1em"})
        .print()
        // Check for participant ID and age input
        .wait(
             newFunction('dummy', ()=>true).test.is(true)
            // ID
            .and( getTextInput("input_ID").testNot.text("")
                .failure( newText('errorID', "Please enter your participant ID.") )
            // Age
            ).and( getTextInput("input_age").test.text(/^\d+$/)
                .failure( newText('errorage', "Please enter your age."), 
                          getTextInput("input_age").text("")))  
        )
    ,
    // Store the texts from inputs into the Var elements
    getVar("ID")     .set( getTextInput("input_ID") ),
    getVar("LAND")   .set( getDropDown("land") ),
    getVar("AGE")    .set( getTextInput("input_age") ),
    getVar("HAND")   .set( getScale("input_hand") )
);

Running Code

To run the code, clone/download the content of the GitHub repository and copy it into a new empty project in your PCIbex farm. Alternatively, click on the demo link and copy the template.