# Plugin development ## Requirements for a plugin As of version 7.0, plugins are [JavaScript Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes). A plugin must implement: * [A `constructor()`](#constructor) that accepts an instance of jsPsych. * [A `trial()` method](#trial) that accepts an `HTMLElement` as its first argument and an `object` of trial parameters as its second argument. There is an optional third argument to [handle the `on_load` event](#asynchronous-loading) in certain cirumstances. The `trial()` method should *either* invoke `jsPsych.finishTrial()` or should be an `async` function that returns a data object to [end the trial and save data](#save-data). * [A static `info` property](#static-info) on the class that contains an object describing the plugin's parameters, data generated, and version. ### Templates Plugins can be written in either plain JavaScript or in TypeScript. Template files for both [JavaScript](https://github.com/jspsych/jspsych-contrib/blob/main/templates/plugin-template-js/src/index.js) and [TypeScript](https://github.com/jspsych/jspsych-contrib/blob/main/templates/plugin-template-ts/src/index.ts) are available in the [jspsych-contrib repository](https://github.com/jspsych/jspsych-contrib/). ## Plugin components ### constructor() The plugin's `constructor()` will be passed a reference to the instance of the `JsPsych` class that is running the experiment. The constructor should store this reference so that the plugin can access functionality from the core library and its modules. ```js constructor(jsPsych){ this.jsPsych = jsPsych; } ``` ### trial() The plugin's `trial()` method is responsible for running a single trial. When the jsPsych timeline reaches a trial using the plugin it will invoke the `trial()` method for the plugin. There are three parameters that are passed into the trial method. * `display_element` is the DOM element where jsPsych content is being rendered. This parameter will be an `HTMLElement`, and you can use it to modify the portion of the document that jsPsych controls. * `trial` is an object containing all of the parameters specified in the corresponding [TimelineNode](../overview/timeline.md). * `on_load` is an optional parameter that contains a callback function to invoke when `trial()` has completed its initial loading. See [handling the on_load event](#asynchronous-loading). The only requirement for the `trial` method is that it calls `jsPsych.finishTrial()` when it is done. This is how jsPsych knows to advance to the next trial in the experiment (or end the experiment if it is the last trial). The plugin can do whatever it needs to do before that point. ### static info The plugin's `info` property is an object with a `name`, `version`, `parameters`, and `data` property. ```js const info = { name: 'my-awesome-plugin', version: version, parameters: { }, data: { } } ``` The `version` field describes the version of the plugin. The version will be automatically included in the data generated by the plugin. In most cases, the version should imported from the `package.json` file by including an import statement at the top of the file. This allows the `version` field be automatically updated. ```javascript import { version } from '../package.json'; const info = { ... version: version; ... } ``` ??? info "Automatic versioning with custom build environments" If you are using a custom build environment that imports its own `tsconfig.json` file that does not extend jsPsych's, and you want to use this automatic versioning syntax, you must add `"resolveJsonModule": true` to the config's `compilerOptions` object. If you are not using a build environment that supports `import` and `package.json` (such as writing a plain JS file), you can manually enter the `version` as a string. ```javascript const info = { ... version: "1.0.0"; ... } ``` The `parameters` property is an object containing all of the parameters for the plugin. Each parameter has a `type` and `default` property. The `data` field describes the types of data generated by the plugin. Each parameter has a `type` property. ```js const info = { name: 'my-awesome-plugin', version: version, parameters: { image: { type: ParameterType.IMAGE, default: undefined }, image_duration: { type: ParameterType.INT, default: 500 } }, data: { response: { type: ParameterType.STRING, }, }, } ``` If the `default` value is `undefined` then a user must specify a value for this parameter when creating a trial using the plugin on the timeline. If they do not, then an error will be generated and shown in the console. If a `default` value is specified in `info` then that value will be used by the plugin unless the user overrides it by specifying that property. jsPsych allows most [plugin parameters to be dynamic](../overview/dynamic-parameters.md), which means that the parameter value can be a function that will be evaluated right before the trial starts. However, if you want your plugin to have a parameter that is a function that _shouldn't_ be evaluated before the trial starts, then you should make sure that the parameter type is `'FUNCTION'`. This tells jsPsych not to evaluate the function as it normally does for dynamic parameters. See the `canvas-*` plugins for examples. We strongly encourage using [JSDoc comments](https://jsdoc.app/about-getting-started) to document the parameters and data generated by the plugin, as shown below. We use these comments to automatically generate documentation for the plugins and to generate default descriptions of variables for experiment metadata. ```js const info = { name: 'my-awesome-plugin', version: version, parameters: { /** The path to the image file to display. */ image: { type: ParameterType.IMAGE, default: undefined }, /** The duration to display the image in milliseconds. */ image_duration: { type: ParameterType.INT, default: 500 } }, data: { /** The text of the response generated by the participant. */ response: { type: ParameterType.STRING, }, }, } ``` The `info` object must be a `static` member of the class, as shown below. ```js const info = { name: 'my-awesome-plugin', version: version, parameters: { /** The path to the image file to display. */ image: { type: ParameterType.IMAGE, default: undefined }, /** The duration to display the image in milliseconds. */ image_duration: { type: ParameterType.INT, default: 500 } }, data: { /** The text of the response generated by the participant. */ response: { type: ParameterType.STRING, }, }, } class MyAwesomePlugin { constructor(...) trial(...) } MyAwesomePlugin.info = info; ``` #### Parameter Types jsPsych currently has support for the following parameter types: | Type Name | Description | Example | | --------- | ----------- | ------- | | BOOL | A simple truth value. | `true` or `false` | | STRING | A set of characters. | "Continue" | | INT | A value that supports whole numbers. | 12 | | FLOAT | A value that supports decimal numbers. | 5.55 | | FUNCTION | A Javascript function, tends to process multiple objects in an array from other parameters. | `function(tries) { return "
You have " + tries + " tries left." }` | | KEY | A single key, with support for function keys like arrows and spacebars. | `"j"`, `"n"`, `"ArrowLeft"` | | KEYS | Either an array of keys, or the string `"ALL_KEYS"` or `"NO_KEYS"`, indicating their respective inclusion/exclusion criterea. | `["f", "j"]` | | SELECT | A list of strings that a developer can choose between as a parameter. | `["cm", "px", "em"]` | | HTML_STRING | A string with HTML markup. | `"
This is the prompt.
"` | | IMAGE | A string that contains the path to an image file. | `"my_image.jpg"` | | AUDIO | A string that contains the path to an audio file. | `"my_sound.mp3"` | | VIDEO | A string that contains the path to a video file. | `"my_video.mp4"` | | OBJECT | A general JSON object (key-value pairs). | `{ rt: 350, response: "hello!", correct: true }` | | COMPLEX | A JSON object that one can specify nested parameters for. | `{ rt: 350, response: "hello!", correct: true }` | | TIMELINE | A jsPsych timeline object with trials. | `[{ type: jsPsychKeyboardResponse, stimulus: 'my_image.jpg }]` | Within each parameter, you may also specify if it is an array of the specific type. For example, a parameter that requires a list of button labels would be described as: ```js const info = { // ... parameters: { /** The labels to be displayed on each button. */ labels: { type: ParameterType.STRING, array: true, default: ["Pause", "Play", "Continue"] } }, // ... } ``` Specific parameter types also have their own special markup. For `ParameterType.SELECT`, you specify the options one can choose with an `options` field, and then the `default` field must be within that field. ```js const info = { // ... parameters: { /** The units of measure used to display the length and width of the stimulus. */ units: { type: ParameterType.SELECT, options: ["em", "px", "vh", "vw"], default: "px" } }, // ... } ``` For `ParameterType.COMPLEX`, we may specify the underlying fields in the object with the `nested` field. This acts in the same way as us defining parameters regularly, only we are now just delineating the fields within the object itself. ```js const info = { // ... parameters: { /** Where to display the location of the stimuli. */ locations: { type: ParameterType.COMPLEX, array: true, default: undefined, nested: { /** The x-coordinate of the stimulus, in the units from the `units` field. */ x: { type: ParameterType.INT }, /** The y-coordinate of the stimulus. */ y: { type: ParameterType.INT } } } }, // ... } ``` For more complicated scenarios, typically when handling data generated from an arbitrary function or user input, where we have a general idea of what data type it could produce, we may also specify multiple types of data. As an example, if we know we'll get either some number (integer or float) or a string from a field, we can specify it as such: ```js const info = { // ... data: { /** The response given by the user. */ response: { type: ParameterType.INT | ParameterType.FLOAT | ParameterType.STRING } } } ``` ## Plugin functionality Inside the `.trial()` method you can do pretty much anything that you want, including modifying the DOM, setting up event listeners, and making asynchronous requests. In this section we'll highlight a few common things that you might want to do as examples. ### Changing the content of the display There are a few ways to change the content of the display. The `display_element` parameter of the trial method contains the `HTMLElement` for displaying jsPsych content, so you can use various JavaScript methods for interaction with the display element. A common one is to change the `innerHTML`. Here's an example of using `innerHTML` to display an image specified in the `trial` parameters. ```javascript trial(display_element, trial){ let html_content = `