跳转至

开发插件

插件的要求

在7.0版本中,插件通过JavaScript类实现。插件中需要实现:

模板

可以使用JavaScript或TypeScript编写插件。JavaScriptTypeScript的模板文件可以在jspsych-contrib仓库中找到。

插件的构成

constructor()

插件的constructor()会接收一个JsPsych的实例,constructor函数需要将其保存下来用于访问。

constructor(jsPsych){
  this.jsPsych = jsPsych;
}

trial()

插件的trial()方法用于运行试次。当jsPsych的时间线运行到了一个试次的时候,就会触发该插件的trial()方法。

trial方法接受以下三个参数:

  • display_element是用于渲染jsPsych的实验内容的DOM元素。该参数需要是一个HTMLElement,我们可以用这个参数控制将jsPsych呈现在文档额哪个部分。
  • trial是相应的时间线节点中设定的参数。
  • on_load是一个可选参数,包含了一个回调函数,会在trial()完成加载后触发。详见处理on_load事件

对于trial方法的唯一要求是在试次完成后调用jsPsych.finishTrial(),这样jsPsych才能知道什么时候进入下一个试次(如果是最后一个试次,则结束实验)。在那之前,插件爱干啥干啥。

static info

info属性是一个对象,包含nameversionparametersdata属性。

const info = {
  name: 'my-awesome-plugin',
  version: version,
  parameters: { },
  data: { }
}

version属性标记了插件的版本,且会被保存在数据中。多数情况下,它应该从package.json文件引入,做法是在index.ts文件顶部添加import语句。这样,version可以自动更新。

import { version } from '../package.json';

const info = {
  ...
  version: version;
  ...
}

如果你没有使用支持importpackage.json的构建环境(比如直接编写JS文件),则可以手动写version

const info = {
  ...
  version: "1.0.0";
  ...
}

parameters参数包含了试次所需要的参数,每个参数都有typedefault属性。

data参数描述了插件产生的数据,每个参数都包含一个type属性。

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,
    },
  },
}

如果default值为undefined,则用户在使用这个插件的时候必须指定该参数的值,否则会在控制台报错。如果在info中指定了default值,则插件会在没有指定该参数的值的时候使用该默认值。

jsPsych多数情况下允许动态参数,即可以使用函数作为参数,这些函数会在试次开始前执行。但是,如果你希望插件的某一个参数是一个函数,且该函数 不需要 在试次开始前执行,就需要将参数的type设置为'FUNCTION',这样jsPsych就不会把它当作一个动态参数。参见canvas-*系列插件。

我们强烈推荐使用JSDoc comments为参数和产生的数据撰写文档,如下所示。我们会使用这些注释自动生成文档以及为实验元数据生成变量的默认描述。

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,
    },
  },
}

info对象必须是类的一个静态成员,如下所示。

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,
    },
  },
}

## 插件的功能

`.trial()`方法中我们可以做很多事情比如修改DOM元素创建事件监听器创建异步的请求等等在这一部分我们就来看一些常见的功能

### 改变呈现的内容

改变呈现内容的方法有多种trial方法的`display_element`参数包含了呈现实验内容的`HTMLElement`所以我们可以使用JavaScript中的方法和这个元素进行交互一种常见的方法是改变他的`innerHTML`下面的例子中使用`innerHTML`呈现`trial`参数中设定的图片

```javascript
trial(display_element, trial){
  let html_content = `<img src="${trial.image}"></img>`;

  display_element.innerHTML = html_content;
}

等待一段时间

如果我们需要代码延迟运行一段时间,可以使用jsPsych封装的setTimeout()函数——jsPsych.pluginAPI.setTimeout()。该方法产生的倒计时会在试次结束时自动停止,以防止插件之间互相干扰。

在未来版本中,我们可能会修改jsPsych.pluginAPI.setTimeout()的实现,包括基于requestAnimationFrame对计时功能进行改善。

trial(display_element, trial){
  // show image
  display_element.innerHTML = `<img src="${trial.image}"></img>`;

  // hide image after trial.image_duration milliseconds
  this.jsPsych.pluginAPI.setTimeout(()=>{
    display_element.innerHTML = '';
  }, trial.image_duration);
}

处理键盘事件

插件允许我们监听很多事件,包括keyupkeydownjsPsych.pluginAPI模块包含了getKeyboardResponse函数,该函数可以很方便地处理实验中的按键。

下面是一个简单的例子。更多示例详见getKeyboardResponse处文档

trial(display_element, trial){
  // show image
  display_element.innerHTML = `<img src="${trial.image}"></img>`;

  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
  });
}

异步加载

试次事件中包括了on_load,该事件通常在.trial()方法完成后自动触发。在大多数情况下,这一事件发生在DOM元素的初始化(例如,渲染图片,创建事件监听器和定时器,等等)之后。但是,在某些情况下,插件可能会执行一个异步操作,该操作需要在插件的初始加载完成之前执行。例如audio-keyboard-response插件中,检查音频文件是否加载完成是异步操作,.trial()方法在音频文件初始化、DOM元素更新前就完成执行了。

如果您想手动触发插件的on_load事件,可以使用.trial()方法可选的第三个传入参数,该参数是加载完成时调用的回调函数。

为了告诉jsPsych在.trial()方法完成时 使用常规的回调,我们需要显式返回一个 Promise对象。从 8.0 版本开始,我们推荐将trial函数修改为异步函数。

下面的示例中展示了如何在插件中使用on_load事件。请注意,此示例省略了加载和完成试次之间发生的所有事情。完整示例参见audio-keyboard-response插件的源代码。

async trial(display_element, trial, on_load){
  let trial_complete;

  await do_something_asynchronous()

  on_load();

  await do_the_rest_of_the_trial();

  return data_generated_by_the_trial;
}

保存数据

如果要将数据写入jsPsych的数据集,需要将数据对象传入jsPsych.finishTrial()

constructor(jsPsych){
  this.jsPsych = jsPsych;
}

trial(display_element, trial){
  let data = {
    correct: true,
    rt: 350
  }

  this.jsPsych.finishTrial(data);
}

8.0版本中,如果你的trial()是异步的,则也可以将数据对象作为返回值返回,这等价于调用jsPsych.finishTrial(data)

constructor(jsPsych){
  this.jsPsych = jsPsych;
}

async trial(display_element, trial){

  let data = {
    correct: true,
    rt: 350
  }

  return data;
}

记录的数据中,correcttruert350其他的一些数据也会由插件自动收集。

当试次结束时

当插件结束时,应该调用jsPsych.finishTrial(),或者如果trial()是异步的,可以返回一个数据对象,这样jsPsych才知道何时进入下一个试次(或者结束实验)。

8.0版本中,结束试次会自动清空屏幕内容并结束尚未结束的倒计时。

As of version 8.0, ending the trial will automatically clear the display element and automatically clear any timeouts that are still pending.

模拟模式

插件可以支持模拟模式

如果要插件支持模拟模式,插件需要有一个simulate()函数,该函数接受4个传入参数:

simulate(trial, simulation_mode, simulation_options, load_callback)

  • trial: 和传入插件的trial()方法的trial参数相同,包含了试次使用的参数。
  • simulation_mode: 字符串,可以是"data-only""visual",用来表示使用哪种模拟模式。插件是否支持"visual" mode. If "visual"模式是可选的。如果不支持"visual"模式,则插件会在被要求使用"visual"模式的时候自动使用"data-only"模式。
  • simulation_options: 该对象包含了模拟相关的参数。
  • load_callback: 该函数在模拟到on_load事件将要触发前调用。请在正确的时候调用这个回调,这样实验中的on_load事件才能正常运行。

一般来说,模拟模式的流程如下:

  1. 生成符合trial参数的数据。
  2. 将生成的数据和simulation_options中的数据合并。
  3. 验证最终的数据对象是否仍然符合trial参数。例如,检查反应时是否比试次的时长更长。
  4. data-only模式下,调用jsPsych.finishTrial()并传入生成的数据。
  5. visual模式下,调用trial()方法,并使用生成的数据触发相应的事件。Plugin API module提供了多种方法用来模拟诸如按键和点击鼠标这种事件。

我们计划在未来对模拟模式的开发进行更详细的讲解。不过目前,最好还是参考一下支持了模拟模式的插件的源代码,去看看上述流程是如何实现的。

关于编写插件的建议

如果你希望将开发的插件加入到jsPsych的主仓库中,请参照贡献代码指南

开发的插件最好能 适用于多种场景 。可以考虑通过参数方便用户进行自定义。例如,如果你的插件内会呈现文字,比如说按钮上的问题,可以将这些文字设置为参数。这样,适用其他语言的用户在适用插件的时候也可以对这些文字进行替换。

插件模板

插件模板可以在jspsych-contrib仓库找到。仓库中还提供了生成新插件的命令行工具,详见README文件。