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

Addition of a self-paced reading plugin #145

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7841109
Setup inital spr template
vzhang03 Oct 7, 2024
ea27f01
Inital working version of SPR complete
vzhang03 Oct 7, 2024
4d4e9f3
Working version of SPR with multiple words
vzhang03 Oct 7, 2024
6c169d4
Commited working fix to us styling using span, need to finalize update
vzhang03 Oct 7, 2024
c3f5323
All modes working, need to work on error handling and making code mor…
vzhang03 Oct 13, 2024
e866583
Successfully added JsPsych integrated keyboard listener
vzhang03 Oct 13, 2024
b302f12
Successfully refactored mode 1 and 2 to be simpler and more efficient
vzhang03 Oct 13, 2024
2c321e4
Completed mode 3, moving onto ending the trial and data handling
vzhang03 Oct 13, 2024
af9dcd2
working data collection and end trial, updated examples to showfinal …
vzhang03 Oct 13, 2024
ebd4b0d
Documentation updates and fixing the modes refactoring code
vzhang03 Oct 18, 2024
51c1a67
Updated generateBlanks to account for chunks including multiple words
vzhang03 Oct 18, 2024
e1c16fc
Finished splitting parameters, finalizing documentation
vzhang03 Oct 18, 2024
4c57593
Tested styling to be workign
vzhang03 Oct 18, 2024
c44db43
Finished up documentation for the SPR md file
vzhang03 Oct 18, 2024
273ed1a
Finishing up documentation within the trial parameters
vzhang03 Oct 18, 2024
30769b4
Pushing changeset
vzhang03 Oct 18, 2024
7e7303b
Added initial data model, missing docuemntation
vzhang03 Oct 21, 2024
66a5ab4
Initial delimiter model
vzhang03 Oct 21, 2024
fb8d507
fix desc and update to v8
jadeddelta Oct 23, 2024
39c28de
flesh out docs and add choices param
jadeddelta Oct 26, 2024
23e839b
remove delimiter param, added logic to handle it instead
jadeddelta Oct 26, 2024
bcfa327
Finished up majority of data saving - issues with extra and keypress …
vzhang03 Nov 1, 2024
ca8830e
Key press is working, time elapsed since last is working, only now is…
vzhang03 Nov 2, 2024
57bb8f2
document data types, fix stimulus data issue
jadeddelta Nov 3, 2024
241aac9
Fixed mode three, removed unneccessary comments and slight logic changes
vzhang03 Nov 4, 2024
644436f
cleanup code, add some error handling, revise docs
jadeddelta Feb 1, 2025
88cd637
code rewrite, consolidate text input and remove `line_size`
jadeddelta Feb 2, 2025
617aa02
cleanup docs, handle edge cases in mode 2 and 3
jadeddelta Feb 2, 2025
44373fc
fix sentence presentation to match size of words
jadeddelta Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
cleanup code, add some error handling, revise docs
  • Loading branch information
jadeddelta committed Feb 1, 2025
commit 644436faa8fe57b1eeb1c538c839ae355db234ee
2 changes: 1 addition & 1 deletion packages/plugin-spr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

This is a package built to enable self-paced reading trials. It supports different reading modes including single-word mask and multi-word (hide/show). This package allows you to also customize the format of the text displayed by using CSS.
This is a plugin built to conduct self-paced reading trials. It supports different reading modes including single-word mask and multi-word (hide/show). This package allows you to also customize the format of the text displayed by using CSS, and how many words display per key press.

## Loading

Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-spr/docs/spr.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ In addition to the [parameters available in all plugins](https://jspsych.org/lat
| ------------------- | ---------------- | ------------------ | ---------------------------------------- |
| unstructured_reading_string | String | "" | This is a representation of the input string that can be passed in as a full string and will be split using the splitting parameters of "chunk_size" and "line_size". |
| structured_reading_string | List | [] | This is the explicit declaration for what to display on the string. This should usually be a list of list of strings (list of lines, each line is a list of chunks). When using mode 3 the input can also be a list of strings with each string representing a chunk. |
| Mode | Number | 1 | Indicates the mode of text displaying used by the SPR plugin. Mode 1 is a masked presentation where clicking spacebar hides the previous shown words, mode 2 reveals one chunk at time but the chunks but previous ones remain visible. Mode 3 is when one word is displayed with no mask. |
| mode | Number | 1 | Indicates the mode of text displaying used by the SPR plugin. Mode 1 is a masked presentation where clicking spacebar hides the previous shown words, mode 2 reveals one chunk at time but the chunks but previous ones remain visible. Mode 3 is when one word is displayed with no mask. |
| chunk_size | String | int | Indicates the number of split words in the input string to be included within each chunk. |
| line_size | String | int | Indicates the number of chunks to be included within each line. |

Expand Down Expand Up @@ -86,7 +86,7 @@ import Spr from '@jspsych-contrib/plugin-spr';
```javascript
const trial = {
type: jsPsychSpr,
structured_reading_string: [["first and second", "second and fourth", "third"], ["fith", "sixth", "seventh"], ["eighth", "ninth", "tenth"]],
structured_reading_string: [["first and second", "second and fourth", "third"], ["fifth", "sixth", "seventh"], ["eighth", "ninth", "tenth"]],
mode: 2
};
```
Expand Down
49 changes: 40 additions & 9 deletions packages/plugin-spr/examples/mode1.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,50 @@
<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

const trial = {
type: jsPsychSpr,
structured_reading_string: [["first", "second", "third"], ["fith", "sixth", "seventh"], ["eighth", "ninth", "tenth"]],
mode: 1
const unstructured_stimulus = [
{ stimulus: "The quick brown fox jumps over the lazy dog." },
{ stimulus: "Portez ce vieux whisky au juge blond qui fume." },
{ stimulus: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich." }
]

const procedure_with_unstructured = {
timeline: [
{
type: jsPsychSpr,
unstructured_reading_string: jsPsych.timelineVariable('stimulus'),
mode: 1,
line_size: Number.MAX_VALUE,
}
],
timeline_variables: unstructured_stimulus
};

// TODO: when removing line_size, flatten the array
const structured_stimulus = [
{ struct_stim: [["This fox is crafty.", "The dog is lazy.", "Can", "they", "be", "friends?"]] },
{ struct_stim: [["Ce juge est blond.", "Il fume.", "Il", "est", "un", "peu", "bizarre."]] },
{ struct_stim: [["Der Boxkämpfer ist stark.", "Er", "ist", "schnell."]] }
]

const procedure_with_structured = {
timeline: [
{
type: jsPsychSpr,
structured_reading_string: jsPsych.timelineVariable('struct_stim'),
mode: 1,
line_size: Number.MAX_VALUE,
}
],
timeline_variables: structured_stimulus
};

jsPsych.run([trial])
jsPsych.run([procedure_with_unstructured, procedure_with_structured]);
</script>

</html>
51 changes: 40 additions & 11 deletions packages/plugin-spr/examples/mode2.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,51 @@

<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

const unstructured_stimulus = [
{ stimulus: "The quick brown fox jumps over the lazy dog." },
{ stimulus: "Portez ce vieux whisky au juge blond qui fume." },
{ stimulus: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich." }
]

const trial = {
type: jsPsychSpr,
structured_reading_string: [["first and second", "second and fourth", "third"], ["fith", "sixth", "seventh"], ["eighth", "ninth", "tenth"]],
mode: 2,
choices: "ALL_KEYS"
const procedure_with_unstructured = {
timeline: [
{
type: jsPsychSpr,
unstructured_reading_string: jsPsych.timelineVariable('stimulus'),
mode: 2,
line_size: Number.MAX_VALUE,
}
],
timeline_variables: unstructured_stimulus
};

jsPsych.run([trial])
// TODO: when removing line_size, flatten the array
const structured_stimulus = [
{ struct_stim: [["This fox is crafty.", "The dog is lazy.", "Can", "they", "be", "friends?"]] },
{ struct_stim: [["Ce juge est blond.", "Il fume.", "Il", "est", "un", "peu", "bizarre."]] },
{ struct_stim: [["Der Boxkämpfer ist stark.", "Er", "ist", "schnell."]] }
]

const procedure_with_structured = {
timeline: [
{
type: jsPsychSpr,
structured_reading_string: jsPsych.timelineVariable('struct_stim'),
mode: 2,
line_size: Number.MAX_VALUE,
}
],
timeline_variables: structured_stimulus
};

jsPsych.run([procedure_with_unstructured, procedure_with_structured]);
</script>

</html>
11 changes: 5 additions & 6 deletions packages/plugin-spr/examples/mode3.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

const trial = {
type: jsPsychSpr,
// structured_reading_string: [["first", "second", "third"], ["fith", "sixth", "seventh"], ["eighth", "ninth", "tenth"]],
structured_reading_string: ["first", "second", "third", "fith", "sixth", "seventh", "eighth", "ninth", "tenth"],
unstructured_reading_string: "The quick brown fox jumps over the lazy dog.",
mode: 3
};

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-spr/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@jspsych-contrib/plugin-spr",
"version": "0.0.1",
"description": "This is a package built to enable self-paced reading trials.",
"description": "Self-paced reading trials using the DOM.",
"type": "module",
"main": "dist/index.cjs",
"exports": {
Expand Down
91 changes: 47 additions & 44 deletions packages/plugin-spr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,38 +115,38 @@ type Info = typeof info;
*
* This is a package built to enable self-paced reading trials.
*
* @author Victor Zhang
* @author Victor Zhang, jadeddelta
* @see {@link https://github.com/jspsych/jspsych-contrib/packages/plugin-spr/README.md}}
*/
class SprPlugin implements JsPsychPlugin<Info> {
static info = info;
private index: number = 0;
private inner_index: number = -1; // mode 1-2: initialized so that not shown if has an inner_index
private current_display_string: string[] = []; // mode 1-2: use this to save iterations
private structured_reading_string: string[] | string[][] = [];
private innerIndex: number = -1; // mode 1-2: initialized so that not shown if has an innerIndex
private currentDisplayString: string[] = []; // mode 1-2: use this to save iterations
private readingString: string[] | string[][] = [];
private mode: 1 | 2 | 3;
private results = [];
private startTime;
private startTime: number;

constructor(private jsPsych: JsPsych) {}

trial(display_element: HTMLElement, trial: TrialType<Info>) {
this.initializeVariables(trial);
// setup html logic
var html = `<p></p>`;
var html = `<p id="jspsych-spr-content"></p>`;
display_element.innerHTML = html;

if (this.mode === 3) {
const blank = this.generateBlank(this.structured_reading_string[this.index]);
document.querySelector("p")!.innerHTML = blank;
const blank = this.generateBlank(this.readingString[this.index]);
document.querySelector("#jspsych-spr-content").innerHTML = blank;
this.addDataPoint(blank, this.index);
this.index = -1; // this initializes mode in way that allows to start at 0, might not be best way to do it
} else document.querySelector("p")!.innerHTML = this.updateDisplayString(); // update this, passing null for TS
} else document.querySelector("#jspsych-spr-content").innerHTML = this.updateDisplayString(); // update this, passing null for TS
}

private endTrial() {
var trial_data = {
stimulus: this.structured_reading_string.flat(),
stimulus: this.readingString.flat(),
mode: this.mode,
results: this.results,
};
Expand All @@ -157,13 +157,18 @@ class SprPlugin implements JsPsychPlugin<Info> {

private initializeVariables(trial: TrialType<Info>) {
if (trial.mode === 1 || trial.mode === 2 || trial.mode === 3) this.mode = trial.mode;
else throw console.error("Mode declared incorrectly, must be between 1 and 3.");
else throw new Error("Mode declared incorrectly, must be between 1 and 3.");

// creates inital reading string -> TODO: should instead use mode to determine
// todo: use mode to determine (not sure what this means @/victor)
if (trial.structured_reading_string.length > 0) {
this.structured_reading_string = trial.structured_reading_string;
if (trial.unstructured_reading_string.length > 0)
console.warn(
"Both structured and unstructured reading strings are defined. Using structured reading string."
);

this.readingString = trial.structured_reading_string;
} else {
this.structured_reading_string = this.createReadingString(
this.readingString = this.createReadingString(
trial.unstructured_reading_string,
trial.chunk_size,
trial.line_size
Expand Down Expand Up @@ -221,15 +226,15 @@ class SprPlugin implements JsPsychPlugin<Info> {

// handles logic on whether to display blank or show text using boolean/index
if (this.mode === 1 || this.mode === 2) {
const curr_length = this.structured_reading_string[this.index].length;
this.inner_index++;
const curr_length = this.readingString[this.index].length;
this.innerIndex++;

if (this.inner_index >= curr_length) {
if (this.innerIndex >= curr_length) {
// resets the index and moves onto the next
this.inner_index = -1; // ensures will be empty
this.innerIndex = -1; // ensures will be empty
this.index++;

if (this.index >= this.structured_reading_string.length) {
if (this.index >= this.readingString.length) {
this.endTrial();
return;
}
Expand All @@ -240,7 +245,7 @@ class SprPlugin implements JsPsychPlugin<Info> {
// might want to include incrementation here for consistency
this.index++;

if (this.index >= this.structured_reading_string.length) {
if (this.index >= this.readingString.length) {
this.endTrial();
return;
}
Expand All @@ -249,18 +254,16 @@ class SprPlugin implements JsPsychPlugin<Info> {
}
// need to handle a keyboard press element where records how long until press a key

// Add any action you want to happen on spacebar press
// e.g., changing the displayed text, ending the trial, etc.
document.querySelector("p")!.innerHTML = newHtml;
document.querySelector("#jspsych-spr-content").innerHTML = newHtml;
}

// This helper method assists with mode 1 and 2 to keep efficency when updating indicies and the scren
private updateDisplayString(info: any = {}): string {
if (this.mode === 1 || this.mode === 2) {
if (this.inner_index === -1) {
if (this.innerIndex === -1) {
// need to update new display string
const new_display_string: string[] = [];
const curr_segment = this.structured_reading_string[this.index];
const curr_segment = this.readingString[this.index];

for (var i = 0; i < curr_segment.length; i++) {
new_display_string.push(
Expand All @@ -270,50 +273,50 @@ class SprPlugin implements JsPsychPlugin<Info> {
);
}

this.current_display_string = new_display_string;
this.addDataPoint(this.current_display_string.join(" "), this.index, info.key);
this.currentDisplayString = new_display_string;
this.addDataPoint(this.currentDisplayString.join(" "), this.index, info.key);
} else {
if (this.mode === 1 && this.inner_index > 0) {
this.current_display_string[this.inner_index - 1] =
if (this.mode === 1 && this.innerIndex > 0) {
this.currentDisplayString[this.innerIndex - 1] =
"<span class='text-after-current-region'>" +
this.generateBlank(this.structured_reading_string[this.index][this.inner_index - 1]) +
this.generateBlank(this.readingString[this.index][this.innerIndex - 1]) +
"</span>";
} else if (this.mode === 2 && this.inner_index > 0) {
} else if (this.mode === 2 && this.innerIndex > 0) {
// changes classifier
this.current_display_string[this.inner_index - 1] =
this.currentDisplayString[this.innerIndex - 1] =
"<span class='text-after-current-region'>" +
this.structured_reading_string[this.index][this.inner_index - 1] +
this.readingString[this.index][this.innerIndex - 1] +
"</span>";
}

// shows next display
this.current_display_string[this.inner_index] =
this.currentDisplayString[this.innerIndex] =
"<span class='text-current-region'>" +
this.structured_reading_string[this.index][this.inner_index] +
this.readingString[this.index][this.innerIndex] +
"</span>";

this.addDataPoint(this.current_display_string.join(" "), this.index, info.key);
this.addDataPoint(this.currentDisplayString.join(" "), this.index, info.key);
}
} else if (this.mode == 3) {
var newHtml = "";
var stimulus = "";

// accounts for bad user input (not necessary) and could move it up to input
if (typeof this.structured_reading_string[this.index] === "string")
newHtml = this.structured_reading_string[this.index] as string;
if (typeof this.readingString[this.index] === "string")
stimulus = this.readingString[this.index] as string;
else {
for (const c of this.structured_reading_string[this.index]) {
newHtml += c + " ";
for (const c of this.readingString[this.index]) {
stimulus += c + " ";
}
}

newHtml = "<p class='text-current-region'>" + newHtml + "</p>";
var newHtml = "<p class='text-current-region'>" + stimulus + "</p>";

this.addDataPoint(newHtml, this.index, info.key);
this.addDataPoint(stimulus, this.index, info.key);
return newHtml;
}

var displayString = "";
for (const s of this.current_display_string) {
for (const s of this.currentDisplayString) {
displayString += s + " "; // include another element
}

Expand Down