166 lines
6.1 KiB
Markdown
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
|
|
``` |