js-exform/README.md

166 lines
6.1 KiB
Markdown

## Introduction
This project is to develop a Vue 3 single page questionnaire application to be embedded in online psychology experiments. Demo pages are hosted to show how it looks like:
- [Standard](https://js-exform.pages.dev/demo)
- [Dark mode](https://js-exform.pages.dev/demo-dark)
- [Chinese version](https://js-exform.pages.dev/demo-cn)
This project is still under active development and could be very unstable (due to skill issues).
The following parts of the documentation are generated by `Claude-3.7-Sonnet`.
## Data Transport
The questionnaire component receives input via the `window.postMessage` API (see [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) for details). The parent window should post a message with the following structure:
```javascript
window.postMessage({
type: 'spec',
title: 'Your Questionnaire Title',
items: [...], // Array of question items
settings: {...} // Configuration options
}, '*');
```
**Security Note:** When using postMessage in production, always specify an exact target origin instead of '*' to prevent potential cross-site scripting vulnerabilities. The component uses the `settings.origin` property for sending responses back to ensure proper security.
## Input Schema
### Root Object
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| type | string | Yes | Must be 'spec' |
| title | string | Yes | Main title of the questionnaire |
| items | array | Yes | Array of question items |
| settings | object | No | Configuration options |
### Item Object
Each item in the `items` array represents a question with the following structure:
```javascript
{
type: 'text', // 'text', 'radio', 'checkbox', 'scale', 'display'
title: 'Question text',
desc: 'Optional description text with HTML support',
key: 'unique_identifier', // Optional, defaults to title
optTexts: ['Option 1', 'Option 2'], // For 'radio', 'checkbox', 'scale'
optValues: ['value1', 'value2'], // For 'radio', 'checkbox', 'scale'
required: true, // Whether answer is required
minOpts: 1, // For 'checkbox', minimum options to select
maxOpts: 3, // For 'checkbox', maximum options to select
allowBack: true // Override global back button setting for this item
}
```
#### Item Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| type | string | Yes | Question type: 'text', 'radio', 'checkbox', 'scale', or 'display' |
| title | string | Yes | Question text |
| desc | string | No | Additional descriptive text (supports HTML) |
| key | string | No | Unique identifier (defaults to title if not provided) |
| optTexts | array | Conditional | Array of option texts for 'radio', 'checkbox', 'scale' |
| optValues | array | Conditional | Array of option values for 'radio', 'checkbox', 'scale' |
| required | boolean | No | Whether an answer is required (default: false) |
| minOpts | number | No | Minimum options to select for 'checkbox' (default: 1) |
| maxOpts | number | No | Maximum options to select for 'checkbox' (default: total options) |
| allowBack | boolean | No | Override global back button setting for this specific item, useful for splitting the survey into sections |
### Settings Object
```javascript
{
allowBack: true, // Allow navigation to previous questions
allowAutoNext: true, // Auto-advance after selection for radio/scale
darkMode: false, // Visual theme
showItemIndex: true, // Show item index in the header
origin: '*', // Specify the origin to post messages
lang: 'en-US' // Language code
}
```
## Question Types
- **text**: Single-line text input
- **radio**: Single-choice selection with radio buttons
- **checkbox**: Multiple-choice selection with checkboxes
- **scale**: Slider scale with labeled endpoints
- **display**: Information display with no input required
## Response Format
When the questionnaire is completed, the component sends a message to the parent window:
```javascript
{
type: 'response',
started: 1646120000000, // Timestamp when questionnaire started
ended: 1646121000000, // Timestamp when questionnaire completed
results: [
{
title: 'Question text',
key: 'question_key',
type: 'question_type',
answer: 'user_response',
answerText: 'Text representation of response',
answerValue: 'Value representation of response',
refilled: false, // Whether the answer was changed
responseTime: 5000 // Time spent on question in milliseconds
},
// Additional question results...
]
}
```
## Standalone Examples
Please refer to source code of demo files.
## Usage in PsychoJS experiments
To embed questionnaires in a PsychoJS experiment managed by PsychoPy Builder, you are required to create a custom code component in the `Begin Routine` section, basically following the steps:
1. Create the `<iframe>` element
2. Load questionnaire specification file, either from a constant string or external file
3. Add styling to make it hover
4. Send data to start up when it is ready
5. Append the element to the main document
6. Handle response data and remove the element once finished
Here is an example:
```javascript
function embedQuestionnaire() {
const iframe = document.createElement('iframe');
const spec = JSON.parse(psychoJS.serverManager.getResource('spec.json.txt')); // Use preloaded resource here
const src = 'https://js-exform.pages.dev'; // Or replace it with your own build
iframe.id = 'iframe';
iframe.style = 'width: 100%; height: 100%; border: none; position: absolute; top: 0; left: 0;';
iframe.onload = () => { iframe.contentWindow.postMessage(spec, src); };
iframe.src = src;
document.body.append(iframe);
window.addEventListener('message', (event) => {
// Handle event.data to proceed
// ......
iframe.remove();
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', embedQuestionnaire);
} else {
embedQuestionnaire();
}
```
## For Developers
To modify or build the component, simply follow the standard routine:
```sh
npm install
npm run dev
npm run build
```