diff --git a/docs/developers/plugin-development.md b/docs/developers/plugin-development.md
index 8e113ddf..4bcefa76 100644
--- a/docs/developers/plugin-development.md
+++ b/docs/developers/plugin-development.md
@@ -4,9 +4,11 @@
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()` that accepts an instance of jsPsych.
-* A `trial()` method 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]() in certain cirumstances. The `trial()` method should invoke `jsPsych.finishTrial()` to [end the trial and save data]() at the appropriate moment.
-* A static `info` property on the class that contains an [object describing the plugin's parameters]().
+* [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 invoke `jsPsych.finishTrial()` to [end the trial and save data](#save-data) at the appropriate moment.
+* [A static `info` property](#static-info) on the class that contains an object describing the plugin's parameters.
+
+### 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/packages/plugin-template/index.js) and [TypeScript](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-template-ts/src/index.ts) are available in the [jspsych-contrib repository](https://github.com/jspsych/jspsych-contrib/).
@@ -33,20 +35,63 @@ The plugin's `trial()` method is responsible for running a single trial. When th
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]().
-* `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]().
+* `trial` is an object containing all of the parameters specified in the corresponding [TimelineNode](/overview/timeline).
+* `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 that contains all of the available parameters for the plugin. Each parameter name is a property, and the value is an object that includes a description of the parameter, the value's type (string, integer, etc.), and the default value. See some of the plugin files in the jsPsych plugins folder for examples.
+The plugin's `info` property is an object with a `name` and `parameters` property.
-jsPsych allows most [plugin parameters to be dynamic](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.
+```js
+const info = {
+ name: 'my-awesome-plugin',
+ parameters: { }
+}
+```
+
+The `parameters` property is an object containing all of the parameters for the plugin. Each parameter has a `type` and `default` property.
+
+```js
+const info = {
+ name: 'my-awesome-plugin',
+ parameters: {
+ image: {
+ type: jspsych.ParameterType.IMAGE,
+ default: undefined
+ },
+ image_duration: {
+ type: jspsych.ParameterType.INT,
+ default: 500
+ }
+ }
+}
+```
+
+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.
+
+The `info` object should be a `static` member of the class.
+
+```js
+const info = {
+ ...
+}
+
+class MyAwesomePlugin {
+ constructor(...)
+
+ trial(...)
+}
+
+MyAwesomePlugin.info = info;
+```
## 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.
+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
@@ -68,15 +113,85 @@ display_element.innerHTML = '';
### Waiting for specified durations
+If you need to delay code execution for a fixed amount of time, we recommend using jsPsych's wrapper of the `setTimeout()` function, `jsPsych.pluginAPI.setTimeout()`. In `7.0` the only advantage of using this method is that it registers the timeout handler so that it can be easily cleared at the end of the trial using `jsPsych.pluginAPI.clearAllTimeouts()`. In future versions we may replace the implementation of `jsPsych.pluginAPI.setTimeout()` with improved timing functionality based on `requestAnimationFrame`.
+```js
+trial(display_element, trial){
+ // show image
+ display_element.innerHTML = `
`;
+
+ // hide image after trial.image_duration milliseconds
+ this.jsPsych.pluginAPI.setTimeout(()=>{
+ display_element.innerHTML = '';
+ }, trial.image_duration);
+}
+```
### Responding to keyboard events
-### Asynchronous Loading
+While the plugin framework allows you to set up any events that you would like to, including normal handling of `keyup` or `keydown` events, the `jsPsych.pluginAPI` module contains the [`getKeyboardResponse` function](/reference/jspsych-pluginAPI/#jspsychpluginapigetkeyboardresponse), which implements some additional helpful functionality for key responses in an experiment.
-### Writing data
+Here's a basic example. See the [`getKeyboardResponse` docs](/reference/jspsych-pluginAPI/#jspsychpluginapigetkeyboardresponse) for additional examples.
-To write data to [jsPsych's data collection](/reference/jspsych-data/#datacollection) pass an object of data as the parameter to `jsPsych.finishTrial()`:
+```js
+trial(display_element, trial){
+ // show image
+ display_element.innerHTML = `
`;
+
+ const after_key_response = (info) => {
+ // hide the image
+ display_element.innerHTML = '';
+
+ // record the response time as data
+ let data = {
+ rt: info.rt
+ }
+
+ // end the trial
+ this.jsPsych.finishTrial(data);
+ }
+
+ // set up a keyboard event to respond only to the spacebar
+ this.jsPsych.pluginAPI.getKeyboardResponse({
+ callback_function: after_key_response,
+ valid_responses: [' '],
+ persist: false
+ });
+}
+```
+
+### Asynchronous loading
+
+One of the [trial events](/overview/events) is `on_load`, which is normally triggered automatically when the `.trial()` method returns. In most cases, this return happens after the plugin has done its initial setup of the DOM (e.g., rendering an image, setting up event listeners and timers, etc.). However, in some cases a plugin may implement an asynchronous operation that needs to complete before the initial loading of the plugin is considered done. An example of this is the `audio-keyboard-response` plugin, in which the check to see if the audio file is loaded is asynchronous and the `.trial()` method returns before the audio file has been initialized and the display updated.
+
+If you would like to manually trigger the `on_load` event for a plugin, the `.trial()` method accepts an optional third parameter that is a callback function to invoke when loading is complete.
+
+In order to tell jsPsych to *not* invoke the regular callback when the `.trial()` method returns, you need to explicitly return a `Promise`. As of version `7.0` this Promise only serves as a flag to tell jsPsych that the `on_load` event should not be triggered. In future versions we may make the `Promise` functional so that the `trial` operation can be an `async` function.
+
+Here's a sketch of how the `on_load` event can be utilized in a plugin. Note that this example is only a sketch and leaves out all the stuff that happens between loading and finishing the trial. See the source for the `audio-keyboard-response` plugin for a complete exampe.
+
+```js
+trial(display_element, trial, on_load){
+ let trial_complete;
+
+ do_something_asynchronous().then(()=>{
+ on_load();
+ });
+
+ const end_trial = () => {
+ this.jsPsych.finishTrial({...})
+ trial_complete(); // not strictly necessary, but doesn't hurt.
+ }
+
+ return new Promise((resolve)=>{
+ trial_complete = resolve;
+ })
+}
+```
+
+### Save data
+
+To write data to [jsPsych's data collection](/reference/jspsych-data/#datacollection) pass an object of data as the parameter to `jsPsych.finishTrial()`.
```javascript
constructor(jsPsych){
@@ -84,7 +199,7 @@ constructor(jsPsych){
}
trial(display_element, trial){
- var data = {
+ let data = {
correct: true,
rt: 350
}
@@ -95,78 +210,10 @@ trial(display_element, trial){
The data recorded will be that `correct` is `true` and that `rt` is `350`. [Additional data for the trial](/overview/plugins/#data-collected-by-all-plugins) will also be collected automatically.
+## Advice for writing plugins
+
+If you are developing a plugin with the aim of including it in the main jsPsych repository we encourage you to follow the [contribution guidelines](/developers/contributing/#contributing-to-the-codebase).
+
+We also recommend that you make your plugin *as general as possible*. Consider using parameters to give the user of the plugin as many options for customization as possible. For example, if you have any text that displays in the plugin including things like button labels, implement the text as a parameter. This allows users running experiments in other languages to replace text values as needed.
-
-
-
-- Writing a plugin with TS
-- Writing a plugin with JS
-
-
-
-
-
-## Writing new plugins
-
-New plugins are welcome additions to the library. Plugins can be distributed independently of the main library or added to the GitHub repository via a pull request, following the process described above. If you want to add your plugin to the main library then there are a few guidelines to follow.
-
-#### Make the plugin as general as possible
-
-Plugins are most useful when they are flexible. Avoid fixing the value of parameters that could be variables. This is especially important for any text that displays on the screen in order to facilitate use in multiple languages.
-
-#### Use the jsPsych.pluginAPI module when appropriate
-
-The [pluginAPI module](/reference/jspsych-pluginAPI.md) contains functions relevant to plugin development. Avoid duplicating the functions defined within the library in your plugin, and instead use the pluginAPI whenever possible. If you have a suggestion for improving pluginAPI methods, then go ahead and submit a pull request to modify it directly.
-
-#### Document your plugin
-
-When submitting a pull request to add your plugin, make sure to include a documentation page in the same style as the other docs pages. Documentation files exist in the `docs` directory.
-
-#### Include an example file
-
-Write a short example HTML file to include in the `examples` directory. This should demonstrate the basic use cases of the plugin as clearly as possible.
-
-#### Include a testing file
-
-Automated code testing for jsPsych is implemented with [Jest](https://facebook.github.io/jest/). To run the tests, install Node and npm. Run `npm install` in the root jsPsych directory. Then run `npm test`. Plugins should have a testing file that validates the behavior of all the plugin parameters. See the `/tests/plugins` directory for examples.
-
-You can add new kinds of tasks to jsPsych by creating new plugins, or modifying existing plugins. A task can be virtually any kind of activity. If it can be implemented in JavaScript, then it almost certainly can be turned into a jsPsych plugin.
-
-### What's in a plugin file?
-
-Plugin files follow a specific template. Adherence to the template is what allows jsPsych to run a plugin without knowing anything about what the plugin is doing. What makes plugins so flexible is that the template imposes very few requirements on the code. Here's what an empty plugin template looks like:
-
-```js
-jsPsych.plugins['plugin-name'] = (function(){
-
- var plugin = {};
-
- plugin.info = {
- name: 'plugin-name',
- parameters: {
- }
- }
-
- plugin.trial = function(display_element, trial){
- jsPsych.finishTrial();
- }
-
- return plugin;
-
-})();
-```
-
-This plugin will work! It defines a plugin called 'plugin-name', and it does absolutely nothing. However, it won't break the experiment, and jsPsych will understand that this is a valid plugin.
-
-Let's examine it in more detail.
-
-The overall structure of the plugin is defined using a module JavaScript design pattern. This pattern uses a technique called an anonymous closure. This is why the first line has `(function(){` and the last line is `})();`. The details aren't important, but if you want to learn more about it, [this is a nice overview](http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html). The reason this pattern is useful is because it allows for persistent state and private scope. In other words, the plugin is isolated and can't be altered by other plugins.
-
-The module, created by the `(function(){` `})();` expressions, contains an object called `plugin`. The `plugin` object has two properties: `info` and `trial`. The `plugin` object is returned at the end of the module, which is what assigns the defined properties of `plugin` to `jsPsych['plugin-name']`.
-
-
-
-### The plugin template
-
-An empty plugin template is included in the `plugins/template` folder.