    Image credits:
    
    Dog1
    https://commons.wikimedia.org/wiki/File:Yellow_Labrador_Retriever_2.jpg
    SixtyWeb CC BY-SA 3.0
    
    Dog2
    https://commons.wikimedia.org/wiki/File:Flat_Coated_Retriever_-_black.jpg
    Gunnandreassen CC BY-SA 3.0
    
    Dog3
    https://commons.wikimedia.org/wiki/File:Great_Pyrenees_Mountain_Dog.jpg
    HeartSpoon CC BY-SA 3.0
    
    Cat1
    https://commons.wikimedia.org/wiki/File:Gillie_hunting_(2292639848).jpg
    Dwight Sipler from Stow, MA, USA CC BY 2.0
    
    Cat2
    https://commons.wikimedia.org/wiki/File:Gatos_cats_7_cropped.jpg
    Luis Miguel Bugallo Sánchez CC BY-SA 2.5 ES
    
    Cat3
    https://commons.wikimedia.org/wiki/File:Brown_and_white_tabby_cat_with_green_eyes-Hisashi-03.jpg
    Hisashi from Japan CC BY-SA 2.0
    
    Horse1
    https://commons.wikimedia.org/wiki/File:Kerry_Bog_Pony,_Bog_Village,_Kerry,_Ireland.jpg
    Jim Linwood CC BY 2.0
    
    Horse2
    https://commons.wikimedia.org/wiki/File:Dancingcolors.jpg
    Coreada CC BY-SA 3.0
    
    Horse3
    https://commons.wikimedia.org/wiki/File:Vesuvio_Bien_Vee_-_Paso_Fino_Bellas_Formas_National_Champion_3_Year_Old_Gelding_in_2001;_photo_taken_in_May_of_2006.JPG
    Soltera CC BY-SA 3.0
    
    <?php
    $json = file_get_contents('php://input');
    $obj = json_decode($json, true);
    $username = explode("/", dirname(__FILE__))[2];
    $server_data = '/home/'.$username.'/server_data';
    $path = $server_data."/".$obj["filename"];
    if (substr(realpath(dirname($path)), 0, strlen($server_data))!=$server_data) {
        error_log("attempt to write to bad path: ".$path);
    } else {
        file_put_contents($path, $obj["filedata"], FILE_APPEND);
    }
    ?>
    
    var jsPsych = initJsPsych({
        on_finish: function() {
            var experiment_data = jsPsych.data.get();
            save_data("test.csv", experiment_data.csv());
        }
    });
    
    var factors = {
        image: ['Dog1.jpg', 'Dog2.jpg', 'Dog3.jpg'],
        duration: [400, 800, 1200]
    };
    
    var factorial_values = jsPsych.randomization.factorial(factors);
    
    var trial = {
        type: jsPsychImageKeyboardResponse,
        prompt: '<p>Press a key!</p>',
        stimulus: jsPsych.timelineVariable('image'),
        trial_duration: jsPsych.timelineVariable('duration')
    };
    
    var trials_with_variables = {
        timeline: [trial],
        timeline_variables: factorial_values
    };
    
    function save_data(name, data_in){
        var url = 'save_data.php';
        var data_to_send = {filename: name, filedata: data_in};
        fetch(url, {
            method: 'POST',
            body: JSON.stringify(data_to_send),
            headers: new Headers({
                    'Content-Type': 'application/json'
            })
        });
    }
    
    jsPsych.run([trials_with_variables]);
    
    <html>
        <head>
            <title>datasaving</title>
            <script src="https://unpkg.com/jspsych@8.0.2"></script>
            <script src="https://unpkg.com/@jspsych/plugin-image-keyboard-response@2.0.0"></script>
            <script src="experiment.js"></script>
            <link href="https://unpkg.com/jspsych@8.0.2/css/jspsych.css" rel="stylesheet" type="text/css" />
            
            </head>
        <body></body>
    </html>
    
    for (var i=5; i>0; i--) {
        window.alert("The number is "+i);
    }
    
            jsPsych.data.displayData();
        }
    });
    
    var sentence = ["I", "wandered", "lonely", "as", "a", "cloud"];
    
    var trials = [];
    
    for (var word of sentence) {
      var trial = {
          type: jsPsychHtmlKeyboardResponse,
          prompt: '<p>Press a key!</p>',
          stimulus: word
      };
      trials.push(trial);
    }
    
    // add a list of all images, these will be loaded right at the start
    // to avoid delays
    jsPsych.run(trials);
    
    function greeting(person) {
        window.alert("hello "+person+"!");
    }
    
    var names = ['Hendrick', 'Arran', 'Kalvyn', 'Priyangi', 'Ted'];
    
    for (var name of names) {
        greeting(name);
    }
    
    <html>
        <head>
            <title>My experiment</title>
            <script src="https://unpkg.com/jspsych@7.3.0"></script>
            <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.1"></script>
            <script src="ex2_loop.js"></script>
            <link href="https://unpkg.com/jspsych@7.3.0/css/jspsych.css" rel="stylesheet" type="text/css" />
        </head>
    </html>
    
    <html>
        <head>
        </head>
        <body>
          <p>This doesn't get included in the course documents, it's just for testing!</p>
          <p>Running ex1 ....</p>
          <script src="ex1_loop.js"></script>
          <p>Running ex3 ....</p>
          <script src="ex3_loop.js"></script>
        </body>
    </html>
    
        }
    });
    
    };
    
    var get_id = {
        type: jsPsychSurveyText,
        questions: [{prompt: 'What is your ID?'}],
        on_finish: function(data) {
            var response = data.response.Q0;
            jsPsych.data.addProperties({ id: response });
        }
    };
    
    };
    
    };
    
            })
        });
    }
    
    jsPsych.run([get_id, trials_with_variables]);
    
    <html>
        <head>
            <title>survey_text</title>
            <script src="https://unpkg.com/@jspsych/plugin-survey-text@2.0.0"></script>
            
            </head>
    </html>
    
    var jsPsych = initJsPsych();
    
    var variables = [
        { image: "Dog1.jpg" },
        { image: "Dog2.jpg" }
    ];
    
        stimulus: jsPsych.timelineVariable("image")
    };
    
    var repeated_variables = jsPsych.randomization.repeat(variables, 5);
    
        timeline_variables: repeated_variables
    };
    
    <html>
        <head>
            <title>repetition</title>
            
            </head>
    </html>
    
    ];
    
    };
    
        timeline_variables: variables
    };
    
    <html>
        <head>
            <title>trials_with_variables</title>
            
            </head>
    </html>
    
          jsPsych.data.displayData();
        }
    });
    
    };
    
    };
    
    };
    
    <html>
        <head>
            <title>factorial</title>
            
            </head>
    </html>
    
            jsPsych.data.displayData('csv');
        }
    });
    
    var stroop_variables = [
      { colour: "blue", text: "blue", condition: "congruent" },
      { colour: "red", text: "red", condition: "congruent" },
      { colour: "yellow", text: "yellow", condition: "congruent" },
      { colour: "green", text: "green", condition: "congruent" },
      { colour: "blue", text: "yellow", condition: "incongruent" },
      { colour: "red", text: "green", condition: "incongruent" },
      { colour: "yellow", text: "blue", condition: "incongruent" },
      { colour: "green", text: "red", condition: "incongruent" }
    ];
    
    // these are in HTML, so <br> means "line break"
    var instructions = {
        type: jsPsychInstructions,
        pages: [
          "Welcome to the experiment.<br>Press Space to continue.",
          "In this experiment you will be presented with the words blue, red, yellow and green.<br>Press Space to continue.",
          "As soon as you see a new word, press its first letter.<br>For example, press the B key for blue.<br>Press Space to continue.",
          "Try to answer as quickly as you can!<br>Press Space to start the experiment.",
        ],
        key_forward: ' '
    }
    
    var fixation = {
        type: jsPsychHtmlKeyboardResponse,
        stimulus: '+',
        trial_duration: 500,
        response_ends_trial: false
    };
    
    // blank (ITI stands for "inter trial interval")
    var iti = {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: '',
      trial_duration: 250,
      response_ends_trial: false
    }
    
    var trials = [instructions];
    for (var values of stroop_variables) {
        // these two are always the same ....
        trials.push(iti);
        trials.push(fixation);
        // .... but the trial node will vary
        var trial = {
            type: jsPsychHtmlKeyboardResponse,
            stimulus: '<p style="color: '+values.colour+'">'+values.text+'</p>',
            // 'choices' restricts the available responses for the participant
            choices: ['r','g','b','y'],
            data: values
        };
        trials.push(trial);
    }
    
    <html>
        <head>
            <title>stroop_loop</title>
            <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@2.0.0"></script>
            <script src="https://unpkg.com/@jspsych/plugin-instructions@2.0.0"></script>
            <script src="stroop_loop.js"></script>
            
            <style>
              body {
                background-color: #777777;
              }
              .jspsych-display-element {
                 font-size: xx-large;
                 font-family: sans;
                 font-weight: bold
              }
            </style>
    
            </head>
    </html>
    
        // create a list of all images so we can tell jsPsych to
        // preload them for speed (see initJsPsych below)
        // (Note: jsPsych will usually work out what to preload for
        // itself, but I've left this example to show how to do it)
        var cats = ['Cat1.jpg', 'Cat2.jpg', 'Cat3.jpg'];
        var dogs = ['Dog1.jpg', 'Dog2.jpg', 'Dog3.jpg'];
        var all_images = cats.concat(dogs);
    
        var jsPsych = initJsPsych({
            on_finish: function() {
                jsPsych.data.displayData('csv');
            },
            preload_images: all_images
        });
    
        // this example uses fewer durations (just to make the number
        // of trials sensible).
        var factors = {
            image1: cats,
            image2: dogs,
            trial_duration: [750, 1500],
            fixation_duration: [250, 500]
        };
    
        var factorial_values = jsPsych.randomization.factorial(factors);
    
        var trials = [];
    
        for (values of factorial_values) {
            var fixation = {
                type: jsPsychHtmlKeyboardResponse,
                stimulus: '+',
                trial_duration: values.fixation_duration,
                response_ends_trial: false
            };
            trials.push(fixation);
            // note that when using this approach, the 'stimulus' field doesn't
            // need to be in a function
            var trial = {
                prompt: '<p>Press a key!</p>',
                stimulus: '<img src="'+values.image1+'"> <img src="'+values.image2+'">',
                trial_duration: values.duration,
                data: values
            };
            trials.push(trial);
                /* Note: above we just copy the variable 'values' directly into
                   the 'data' field, as it already has the information we need.
                   We could copy individual items like this:
                   data: {
                       fixation_duration: values.fixation_duration,
                       trial_duration: values.duration,
                       image1: values.image1,
                       image2: values.image2
                   }
                   but it would have exactly the same effect. */
        }
    
        jsPsych.run(trials);

    <html>
        <head>
            <title>multiple_images_loop</title>
            <script src="multiple_images_loop.js"></script>
            
            </head>
    </html>
    
        }
    });
    
        { colour: "blue", text: "blue", condition: "congruent" },
        { colour: "red", text: "red", condition: "congruent" },
        { colour: "yellow", text: "yellow", condition: "congruent" },
        { colour: "green", text: "green", condition: "congruent" },
        { colour: "blue", text: "yellow", condition: "incongruent" },
        { colour: "red", text: "green", condition: "incongruent" },
        { colour: "yellow", text: "blue", condition: "incongruent" },
        { colour: "green", text: "red", condition: "incongruent" }
    ];
    
        pages: [
        ],
    }
    
    };
    
    var iti = {
    }
    
        stimulus: function () {
            // note: the outer parentheses are only here so we can break the line
            return (
                 '<p style="color: '+jsPsych.timelineVariable("colour")+'">'
                 +jsPsych.timelineVariable("text")
                 +'</p>'
            );
        },
        // 'choices' restricts the available responses for the participant
        choices: ['r','g','b','y'],
        data: {
          colour: jsPsych.timelineVariable('colour'),
          text: jsPsych.timelineVariable('text'),
          condition: jsPsych.timelineVariable('condition')
        }
    };
    
        timeline: [iti, fixation, trial],
        timeline_variables: stroop_variables
    };
    
    jsPsych.run([instructions, trials_with_variables]);
    
    <html>
        <head>
            <title>stroop_timeline_variables</title>
            <script src="stroop_timeline_variables.js"></script>
            
            <style>
              body {
              }
              }
            </style>
    
            </head>
    </html>
    
      var jsPsych = initJsPsych({
          on_finish: function() {
          }
      });
    
      var factors = {
          image: ['Dog1.jpg', 'Dog2.jpg', 'Dog3.jpg'],
          duration: [400, 800, 1200],
          fixation_duration: [250, 500, 750]
      };
    
      var factorial_values = jsPsych.randomization.factorial(factors);
    
      var fixation = {
          stimulus: '+',
          trial_duration: jsPsych.timelineVariable('fixation_duration'),
          response_ends_trial: false
      };
    
          type: jsPsychImageKeyboardResponse,
          stimulus: jsPsych.timelineVariable('image'),
          trial_duration: jsPsych.timelineVariable('duration')
      };
    
      var trials_with_variables = {
          timeline: [fixation, trial],
          timeline_variables: factorial_values
      };
    
      var instructions = {
          stimulus: 'Welcome to the experiment!<br>When you see an image press any key you like.'
      }
    
      jsPsych.run([instructions, trials_with_variables]);
    
    <html>
        <head>
            <title>factorial_with_fixation</title>
            
            </head>
    </html>
    
    <!DOCTYPE html>
    <html>
        <head>
            <script src="https://unpkg.com/jspsych@7.3.3"></script>
            <script src="https://unpkg.com/@jspsych/plugin-image-keyboard-response@1.1.2"></script>
            <link href="https://unpkg.com/jspsych@7.3.3/css/jspsych.css" rel="stylesheet" type="text/css" />
        </head>
        <script>
        var jsPsych = initJsPsych();
    
            type: jsPsychImageKeyboardResponse,
            stimulus: 'nasa_proxima.png'
        };
    
        jsPsych.run([trial]);
        </script>
    </html>
    
        duration: [400, 800, 1200],
        fixation_duration: [500, 1000, 1500]
    };
    
    // adding a 'data' field gives us extra values which:
    // - are saved automatically if we save the whole dataset at the end
    // - are accessible in save_data_line if we save line-by-line
        trial_duration: jsPsych.timelineVariable('fixation_duration'),
        response_ends_trial: false,
        data: {
            type: 'fixation'
        }
    };
    
    // fixation_duration is put into the 'data' field for this node as this
    // is the one we report at the end -- fixation nodes are filtered out
        trial_duration: jsPsych.timelineVariable('duration'),
        data: {
            type: 'trial',
            trial_duration: jsPsych.timelineVariable('duration'),
            fixation_duration: jsPsych.timelineVariable('fixation_duration')
        },
        // this gives jsPsych a function to run every time this trial ends
        // the function is given the data from the trial (with response)
        on_finish: function (data) {
            save_data_line(data);
        }
    };
    
        timeline: [fixation, trial],
    };
    
        // this is exactly the same as in the previous example
        // (all it does is send data to the server!)
            })
        });
    }
    
    function save_data_line(data) {
        // choose the data we want to save
        var data_to_save = [
            data.type, data.stimulus, data.trial_duration, data.fixation_duration, data.rt
        ];
        // join these with commas and add a newline
        var line = data_to_save.join(',')+"\n";
        save_data("test.csv", line);
    }
    
    <html>
        <head>
            <title>linebyline</title>
            
            </head>
    </html>
    
    // example demonstrating server participant number allocator
    var participant_id;
    
            jsPsych.data.addProperties({"participant_id": participant_id});
            save_data(participant_id+"_data.csv", experiment_data.csv());
        }
    });
    
    var consent = {
        type: jsPsychHtmlButtonResponse,
        stimulus: (
            "This is where the experiment consent screen would go.<br>"
            +"Click 'OK' to continue, or close the tab if you do not wish to continue."
        ),
        choices: ["OK"]
    };
    
    var get_participant_id = {
        type: jsPsychCallFunction,
        async: true,
        func: function (done) {
            fetch("participant_id_allocator.php")
                .then(function (response) {return response.text();})
                .then(function (response_text) {return parseInt(response_text);})
                .then(function (result) {
                    participant_id = result;
                    // condition number is the remainder of participant_id divided by 3
                    condition_number = participant_id % 3;
                    // if remainder is 0 change to 3 (so the conditions will be 1,2,3)
                    if (condition_number == 0) {
                        condition_number = 3;
                    }
                    // record this in our results
                    jsPsych.data.addProperties({condition: condition_number});
                })            .then(done())
        }
    }
    
    };
    
    };
    
    };
    
            })
        });
    }
    
    jsPsych.run([consent, get_participant_id, trials_with_variables]);
    
    <html>
        <head>
            <title>allocator</title>
            <script src="https://unpkg.com/@jspsych/plugin-html-button-response@2.0.0"></script>
            <script src="https://unpkg.com/@jspsych/plugin-call-function@2.0.0"></script>
            <script src="allocator.js"></script>
            
            </head>
    </html>
    
    <?php
        $username = explode("/", dirname(__FILE__))[2];
        $server_data = '/home/'.$username.'/server_data';
        $partid_file = $server_data.'/participant_id.txt';
        if (!file_exists($partid_file)) {
            $partid = 0;
        } else {
            $partid = (int)file_get_contents($partid_file);
        }
        $partid += 1;
        if (substr(realpath(dirname($partid_file)), 0, strlen($server_data))!=$server_data) {
            error_log("attempt to write to bad path: ".$partid_file);
        } else {
            file_put_contents($partid_file, (string)$partid);
        }
        printf('%d', $partid); // send ID!
    ?>

        var initJsPsych({
            },
        });
    
            duration: [750, 1500],
        };
    
        var fixation = {
            stimulus: '+',
            trial_duration: jsPsych.timelineVariable('fixation_duration'),
            response_ends_trial: false
        };
    
        // add all of the relevant variables to the data field so they
        // will appear in the results
            prompt: '<p>Press a key!</p>',
            stimulus: function () {
                // note: the outer parentheses are only here so we can break the line
                return (
                   '<img src="'+jsPsych.timelineVariable("image1")+'">'
                   +'<img src="'+jsPsych.timelineVariable("image2")+'">'
                );
            },
            data: {
              fixation_duration: jsPsych.timelineVariable('fixation_duration'),
              trial_duration: jsPsych.timelineVariable('duration'),
              image1: jsPsych.timelineVariable('image1'),
              image2: jsPsych.timelineVariable('image2')
            }
        };
    
        var trials_with_variables = {
            timeline: [fixation, trial],
            timeline_variables: factorial_values
        };
    
        jsPsych.run([trials_with_variables]);
    
    <html>
        <head>
            <title>multiple_images</title>
            <script src="multiple_images.js"></script>
            
            </head>
    </html>
    
    <html>
        <head>
            <title>pptcondition</title>
            <script src="pptcondition.js"></script>
            
            </head>
    </html>
    
        }
    });
    
    // e.g. with three conditions
    var participant_id = jsPsych.data.getURLVariable('participant');
    var condition_number = participant_id % 3;
    if (condition_number == 0) {
        condition_number = 3;
    }
    jsPsych.data.addProperties({
        participant: participant_id,
        condition: condition_number
    });
    
    };
    
    };
    
    };
    
            })
        });
    }
    
    /*
        This is a simple example of using functions to
        build up trials.
       
        We'll use it to demo some debugging, and remind
        you how functions work!
    
        There are some deliberately introduced bugs here ....
    
    */
    
    function word_trial(word) {
        var trial = 
            stimulus: word
        };
    }
    
    // O wild West Wind, thou breath of Autumn's being
    var sentence = ["O","wild","West","Wind,","thou","breath","of","Autumn's","being"];
    
    for (var word in sentence) {
        var trial = wordtrial(word);
    }
    
    <html>
      <head>
        <title>Debugging demo experiment!</title>
        <script src="https://unpkg.com/jspsych@7.3.0"></script>
        <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.1"></script>
        <link href="https://unpkg.com/jspsych@7.3.0/css/jspsych.css" rel="stylesheet" type="text/css" />
        <script src="debugging_example.js"></script>
      </head>
      <body></body>
    </html>

        stimulus: 'nasa_proxima.png'
    };
    
    jsPsych.run([trial]);
    
    <html>
        <head>
            <title>s04</title>
            
            </head>
    </html>
    
        }
    });
    
    var trial1 = {
        stimulus: 'Cat1.jpg'
    };
    var trial2 = {
        stimulus: 'Cat2.jpg'
    };
    
    jsPsych.run([trial1, trial2]);

    <html>
        <head>
            <title>s04_multi_image</title>
            
            </head>
    </html>
    
    <html>
        <head>
            <title>stroop_functions</title>
            <script src="stroop_functions.js"></script>
            
            <style>
              body {
              }
              }
            </style>
    
            </head>
    </html>
    
        }
    });
    
    // the colours are also the words ....
    var colours = ['red', 'green', 'blue', 'yellow'];
    
    var n_trials = 30;
    
    // returns a JavaScript object with { text: ...., colour: .... }
    // using a random colour (text is the same as colour)
    function congruent() {
        // pick a colour ....
        // (when we're only picking one, with/without replacement doesn't matter)
        var colour_list = jsPsych.randomization.sampleWithReplacement(colours,1);
        // this returns a list with one item, so we select the first (only) item
        return { text: colour_list[0], colour: colour_list[0], condition: 'congruent' };
    }
    
    // using a random colour (text is different to colour)
    function incongruent() {
        // pick two colours without replacement (i.e. they will be different)
        var colour_list = jsPsych.randomization.sampleWithoutReplacement(colours,2);
        // this returns a list with two item, we select these out
        return { text: colour_list[0], colour: colour_list[1], condition: 'incongruent' };
    }
    
        pages: [
        ],
    }
    
    };
    
    var iti = {
    }
    
    // repeat this code n_trials times
    for (var i=0; i<n_trials; i++) {
        var values;
        // Math.random returns a random number between 0 and 1. Use this to decide
        // whether the current trial is congruent or incongruent.
        if (Math.random() < 0.5) {
            values = congruent();
        } else {
            values = incongruent();
        }
        };
    }