Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloze improvements #2846

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spotty-mails-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jspsych/plugin-cloze": minor
---

adds support for multiple correct answers and case sensitivity
3 changes: 2 additions & 1 deletion docs/plugins/cloze.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ In addition to the [parameters available in all plugins](../overview/plugins.md#

| Parameter | Type | Default Value | Description |
| ------------- | -------- | ------------------ | ---------------------------------------- |
| text | string | *undefined* | The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. % correct solution %). |
| text | string | *undefined* | The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. % correct solution %). To input multiple correct answers, add a / between each answer (i.e. %correct/alsocorrect%). |
| button_text | string | OK | Text of the button participants have to press for finishing the cloze test. |
| check_answers | boolean | false | Boolean value indicating if the answers given by participants should be compared against a correct solution given in the text (between % signs) after the button was clicked. If ```true```, answers are checked and in case of differences, the ```mistake_fn``` is called. In this case, the trial does not automatically finish. If ```false```, no checks are performed and the trial automatically ends when clicking the button. |
| allow_blanks | boolean | true | Boolean value indicating if the answers given by participants should be checked for completion after the button was clicked. If ```true```, answers are not checked for completion and blank answers are allowed. The trial will then automatically finish upon the clicking the button. If ```false```, answers are checked for completion, and in case there are some fields with missing answers, the ```mistake_fn``` is called. In this case, the trial does not automatically finish. |
| case_sensitivity | boolean | true | Boolean value indicating if the answers given by participants should also be checked to have the right case along with correctness. If set to ```false```, case is disregarded and participants may type in whatever case they please. |
| mistake_fn | function | ```function(){}``` | Function called if ```check_answers``` is set to ```true``` and there is a difference between the participant's answers and the correct solution provided in the text, or if ```allow_blanks``` is set to ```false``` and there is at least one field with a blank answer. |

## Data Generated
Expand Down
8 changes: 8 additions & 0 deletions examples/jspsych-cloze.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
text: 'The %% is the largest terrestrial mammal. It lives in both %% and %%.'
});

// an example that allows the user to input a solution that doesn't require case sensitivity, and allows multiple responses
timeline.push({
type: jsPsychCloze,
text: 'The %CASE/door/EyE% is closed.',
check_answers: true,
case_sensitivity: false,
})

// another example with checking if all the blanks are filled in
timeline.push({
type: jsPsychCloze,
Expand Down
15 changes: 15 additions & 0 deletions packages/plugin-cloze/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ describe("cloze", () => {
await expectFinished();
});

test("ends trial on button click when answers are checked and correct without case sensitivity", async () => {
const { expectFinished } = await startTimeline([
{
type: cloze,
text: "This is a %cloze% text.",
check_answers: true,
case_sensitivity: false,
},
]);

getInputElementById("input0").value = "CLOZE";
clickTarget(document.querySelector("#finish_cloze_button"));
await expectFinished();
});

test("ends trial on button click when all answers are checked for completion and are complete", async () => {
const { expectFinished } = await startTimeline([
{
Expand Down
45 changes: 30 additions & 15 deletions packages/plugin-cloze/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
const info = <const>{
name: "cloze",
parameters: {
/** The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. %solution%). */
/** The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. %solution%). For multiple answers, type them with a slash (i.e. %1/2/3%). */
text: {
type: ParameterType.HTML_STRING,
pretty_name: "Cloze text",
Expand All @@ -27,6 +27,12 @@ const info = <const>{
pretty_name: "Allow blanks",
default: true,
},
/** Boolean value indicating if the solutions checker must be case sensitive. */
case_sensitivity: {
type: ParameterType.BOOL,
pretty_name: "Case sensitivity",
default: true,
},
/** Function called if either the check_answers is set to TRUE or the allow_blanks is set to FALSE and there is a discrepancy between the set answers and the answers provide or if all input fields aren't filled out, respectively. */
mistake_fn: {
type: ParameterType.FUNCTION,
Expand Down Expand Up @@ -55,7 +61,7 @@ class ClozePlugin implements JsPsychPlugin<Info> {
var html = '<div class="cloze">';
// odd elements are text, even elements are the blanks
var elements = trial.text.split("%");
const solutions = this.getSolutions(trial.text);
const solutions = this.getSolutions(trial.text, trial.case_sensitivity);

let solution_counter = 0;
for (var i = 0; i < elements.length; i++) {
Expand All @@ -78,10 +84,12 @@ class ClozePlugin implements JsPsychPlugin<Info> {

for (var i = 0; i < solutions.length; i++) {
var field = document.getElementById("input" + i) as HTMLInputElement;
answers.push(field.value.trim());
answers.push(
trial.case_sensitivity ? field.value.trim() : field.value.toLowerCase().trim()
);

if (trial.check_answers) {
if (answers[i] !== solutions[i]) {
if (!solutions[i].includes(answers[i])) {
field.style.color = "red";
answers_correct = false;
} else {
Expand Down Expand Up @@ -112,15 +120,18 @@ class ClozePlugin implements JsPsychPlugin<Info> {
trial.button_text +
"</button>";
display_element.querySelector("#finish_cloze_button").addEventListener("click", check);

(display_element.querySelector("#input0") as HTMLElement).focus();
}

private getSolutions(text: string) {
const solutions = [];
private getSolutions(text: string, case_sensitive: boolean) {
const solutions: String[][] = [];
const elements = text.split("%");
for (let i = 0; i < elements.length; i++) {
if (i % 2 == 1) {
solutions.push(elements[i].trim());
}

for (let i = 1; i < elements.length; i += 2) {
solutions.push(
case_sensitive ? elements[i].trim().split("/") : elements[i].toLowerCase().trim().split("/")
);
}

return solutions;
Expand All @@ -142,13 +153,13 @@ class ClozePlugin implements JsPsychPlugin<Info> {
}

private create_simulation_data(trial: TrialType<Info>, simulation_options) {
const solutions = this.getSolutions(trial.text);
const solutions = this.getSolutions(trial.text, trial.case_sensitivity);
const responses = [];
for (const word of solutions) {
if (word == "") {
for (const wordList of solutions) {
if (wordList.includes("")) {
responses.push(this.jsPsych.randomization.randomWords({ exactly: 1 }));
} else {
responses.push(word);
responses.push(wordList);
}
}

Expand All @@ -166,6 +177,8 @@ class ClozePlugin implements JsPsychPlugin<Info> {
private simulate_data_only(trial: TrialType<Info>, simulation_options) {
const data = this.create_simulation_data(trial, simulation_options);

data.response = data.response[0];

this.jsPsych.finishTrial(data);
}

Expand All @@ -180,7 +193,9 @@ class ClozePlugin implements JsPsychPlugin<Info> {
const inputs = display_element.querySelectorAll('input[type="text"]');
let rt = this.jsPsych.randomization.sampleExGaussian(750, 200, 0.01, true);
for (let i = 0; i < data.response.length; i++) {
this.jsPsych.pluginAPI.fillTextInput(inputs[i] as HTMLInputElement, data.response[i], rt);
let res = data.response[i][Math.floor(Math.random() * data.response[i].length)];

this.jsPsych.pluginAPI.fillTextInput(inputs[i] as HTMLInputElement, res, rt);
rt += this.jsPsych.randomization.sampleExGaussian(750, 200, 0.01, true);
}
this.jsPsych.pluginAPI.clickTarget(display_element.querySelector("#finish_cloze_button"), rt);
Expand Down