{"id":20200299,"url":"https://github.com/anthonydibi/jspsychexample","last_synced_at":"2026-05-31T01:31:21.796Z","repository":{"id":160214144,"uuid":"604792315","full_name":"anthonydibi/JsPsychExample","owner":"anthonydibi","description":null,"archived":false,"fork":false,"pushed_at":"2023-05-11T00:02:43.000Z","size":1612,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-03T08:45:23.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anthonydibi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-21T19:55:42.000Z","updated_at":"2023-05-08T22:03:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"bb292b90-3c41-42c8-9c67-3a45e640850f","html_url":"https://github.com/anthonydibi/JsPsychExample","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/anthonydibi/JsPsychExample","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonydibi%2FJsPsychExample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonydibi%2FJsPsychExample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonydibi%2FJsPsychExample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonydibi%2FJsPsychExample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anthonydibi","download_url":"https://codeload.github.com/anthonydibi/JsPsychExample/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anthonydibi%2FJsPsychExample/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33716338,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T04:42:57.145Z","updated_at":"2026-05-31T01:31:21.779Z","avatar_url":"https://github.com/anthonydibi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UPMC Survey Task\n\n## Introduction\nThis is a survey task built using JsPsych. It is bootstrapped using `create-react-app`, although it would function identically without being built in React - I had just bootstrapped\nit that way in case I needed to build an interface for the survey. However, JsPsych handles all of the user input and UI.\n\n## Project Structure\n\nMost files of interest are found in `src`. `App.tsx` is the root of the app, and renders the `Experiment` component (although, this component does not actually *render* anything itself, it just calls JsPsych to render things). Experiment is where the timeline for the experiment is built. Also, an attempt is made to post a record to the REDCap API. Logic for the different sections of the experiment live in different files - for example, `Pretask.ts` holds the logic for generating the pretask stimuli.\n\n## Running locally\nPrerequisites\n- [Install `git`](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)\n- [Install `npm` and `nodejs`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)\n \nTo run this project, clone the repository using `git clone \u003curl\u003e`, where `\u003curl\u003e` is this repo's url. Then, run `npm install` in the project directory to install dependencies, and then `npm run start` to run a server for the survey on `localhost`. `npm` should automatically open the locally hosted survey in your browser.\n\n## JsPsych\n[JsPsych](https://www.jspsych.org/7.3/) is a behavior experiment building framework for the browser. It provides an interface for displaying stimuli to users and collecting their reactions to those stimuli. Their documentation is great, so anything you want to learn about it can be found at the above link.\n\nTo use JsPsych, it must be installed using `npm` and then imported. We can then initialize the JsPsych environment:\n```js\nconst jsPsych: JsPsych = initJsPsych({ on_finish: function(): void {\n    jsPsych.data.displayData();\n}});\n```\n\nhere, `on_finish` is a function that will be run when the survey is over. In this case, we display the data for testing.\n\nThe main concept to understand with JsPsych is that JsPsych experiments are run from a timeline. The timeline is a list:\n```js\nconst timeline: Array\u003cobject\u003e = [];\n```\n\nand then stimuli can be appended to this list, and when the experiment is run, they will be displayed to the user in order:\n\n```js\nconst welcome = {\n    type: jsPsychHtmlButtonResponse,\n    stimulus: `Welcome to the experiment. Press \"continue\" to advance. subject_id: ${subject_id}, study_id: ${study_id}, session_id: ${session_id}`,\n    choices: ['Continue'],\n}\ntimeline.push(welcome);\n\nconst instructions = {\n    type: jsPsychHtmlButtonResponse,\n    stimulus: 'You have three choices for rating each face: positive, indicating that the person seems happy, pleased, or delighted; neutral, indicating a lack of positive or negative emotional expression; or negative, indicating that the person seems sad, afraid, angry, or disgusted. You will see faces only for a brief moment, so please respond as fast as possible. Select the most accurate description for the emotional expression on each face. If the face contains a positive emotional expression, press the \"positive\" button. If the face contains a neutral emotional expression, press the \"neutral\" button. If the face contains a negative emotional expression, press the \"negative\" button. Press \"continue\" to advance.',\n    choices: ['Continue'],\n}\ntimeline.push(instructions);\n```\n\nhere, `welcome` and `instructions` are two stimuli. There are a few ways to represent a stimulus, but in this case we provide `type`, which describes what is shown by that stimulus and how a response is collected - for example, `jsPsychHtmlButtonResponse` is the default export of `@jspsych/plugin-html-button-response`, which provides a built-in solution for showing HTML to the user and then collecting a response by using buttons. `stimulus` is the HTML shown to the user, and `choices` corresponds to what buttons will be displayed for the user to choose from.\n\nThen, we tell JsPsych to run the timeline:\n\n```js\njsPsych.run(timeline);\n```\n\nand then JsPsych starts showing the user all of the stimuli in the timeline.\n\n## Styling\nIf you want to change the appearance of the JsPsych HTML elements, it is quite easy to do so. You can follow the instructions [here](https://www.jspsych.org/7.0/overview/style/) - simply add new CSS rules to `jspsych.css`. For example, to change the appearance of a JsPsych button, you could add a rule like this to `jspsych.css`: \n```css\n.jspsych-btn {\n  background-color: blue;\n}\n```\nThe JsPsych documentation also gives good advice on how to use developer tools to find class names for JsPsych elements you want to apply styles to:\n![image](https://user-images.githubusercontent.com/57297382/236931387-a341870f-8bea-48e7-8720-fbc45deca38c.png)\n\n## Deployment\nThis survey has components deployed to various places. The front end for the survey is deployed to Netlify under my personal Github account - if you would like to deploy this to a UPMC account, it is quite easy as Netlify is very user-friendly - just follow the instructions [here](https://www.netlify.com/blog/2016/07/22/deploy-react-apps-in-less-than-30-seconds/). Images for the survey are retrieved from an S3 bucket, under Linghai Wang's AWS account. The URLs are retrieved from a Lambda function that is exposed through an API gateway under this same account. This API relies on the images being kept in a folder related to their task - for example, pre-task images can be stored under a folder named `pre-task`, and URLs for these images can be fetched like so:\n```js\nconst imageUrlResponse = await fetch(\"https://7qfbe3atn3.execute-api.us-east-1.amazonaws.com/default/get_pretask_images?task=pre-task\",\n    {method: \"GET\", mode: \"cors\"});\nconst imageUrls: Array\u003cstring\u003e = await imageUrlResponse.json();\n```\n\nThe lambda function uses the AWS SDK to retrieve image links from the bucket. The actual image data is not sent from the lambda, it just sends the urls so that the images can be loaded as needed on the client side:\n\n```js\nconst aws = require('aws-sdk');\n\nconst s3 = new aws.S3({ apiVersion: '2006-03-01', signatureVersion: 'v4' });\n\n\nexports.handler = async (event, context) =\u003e {\n    const task = event[\"queryStringParameters\"][\"task\"];\n    const objectUrls = [];\n    var params = {\n      Bucket: 'ibeltimages',\n      Prefix: task\n    };\n    let s3Objects;\n    try {\n       s3Objects = await s3.listObjectsV2(params).promise();\n    } catch (e) {\n       return e;\n    }\n    s3Objects.Contents.map((obj) =\u003e {\n        objectUrls.push(`https://ibeltimages.s3.amazonaws.com/${obj.Key}`)\n    })\n    return {\n        \"isBase64Encoded\": false,\n        \"statusCode\": 200,\n        headers: {\n          \"Access-Control-Allow-Origin\": \"*\", \n          \"Access-Control-Allow-Credentials\": true,\n        },\n        \"body\": JSON.stringify(objectUrls),\n    };\n};\n```\nThis lambda function takes in a query string parameter `task`, which must correspond to a folder name in the S3 bucket. It then retrieves all objects from that folder, and gets their object URLs. These URLs are returned in the response body to be used in the survey.\n\nthen `imageUrls` will contain all of the image URLs under that folder.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonydibi%2Fjspsychexample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanthonydibi%2Fjspsychexample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanthonydibi%2Fjspsychexample/lists"}