Skip to content

Commit e12b3fb

Browse files
authored
Merge pull request #220 from revisit-studies/custom-response
Document custom form elements for study configs
2 parents 4fba74d + 9789ae5 commit e12b3fb

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Docs Tech Stack
2+
- Docusaurus
3+
- Vite
4+
5+
Purpose
6+
This docs repo supports the ReVISit project. The docs are for users of our software, study designers. These study designers should be assumed to have some basic development knowledge (e.g. they know what JSON is and might have built a few websites, but they're not react experts).
7+
8+
The ReVISit project aims to create a robust platform for conducting and analyzing user studies focused on interactive data visualization tools. By leveraging modern web technologies and state management solutions, ReVISit provides researchers with the necessary tools to design, deploy, and evaluate visualization interfaces effectively.
9+
10+
The goal is to expand the scope of user studies to beyond the visualization community, making it easier for researchers from various fields to conduct studies on interactive data visualizations. This means we need state management that can handle complex interactions and data flows, as well as a user-friendly interface for both study designers and participants.
11+
12+
ReVISit Tech Stack
13+
- Yarn
14+
- React 18
15+
- TypeScript
16+
- Vite
17+
- Mantine UI
18+
- Firebase or Supabase as storage and database backends (.env selects the engine)
19+
20+
Verbiage
21+
- "Study Designer": The individual who creates and configures the user study.
22+
- "Participant": The user who takes part in the study.
23+
- "Analyst": The individual who reviews and analyzes the data collected from the study, both in the platform and externally.
24+
- "Study Config": The configuration file that defines the parameters and settings of a user study.
25+
26+
Study Configs
27+
The study configurations are defined in JSON files with schemas. These configs specify various aspects of the study, including the visualization tools to be used, the tasks participants need to complete, and the data sources involved. You can find the full definition of the study config typescript at https://raw.githubusercontent.com/revisit-studies/study/dev/src/parser/parser.ts. When adding new docs, always ready the current version of the schema and assume that you should read from the `dev` branch (replace dev in the string above if you're asked to look at a different branch).
28+
29+
How you should interact with this docs codebase
30+
You should read docs related to features that you're adding Fore example, if we're adding anew component, read the docs for the other component types. You can run git commands but don't run them unless asked to. Don't interact with GitHub issues or pull requests directly. You should only ever write user facing documentation, we don't host any development/architectural documents in this repo.
31+
32+
Typedoc
33+
Typedoc is built from the app for every release and included as reference material so you don't need to add that to this repo. Assume it will be handled, but state your assumption.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Custom Form Elements
2+
3+
Sometimes, the [form elements](./forms.md) ReVISit provides are not sufficient for your needs. In this case, you can create your own custom form elements. Your custom form elements can be coded in React and can use any React libraries you want. You can then include these custom form elements in your study config and they will be rendered in the study just like the built-in form elements.
4+
5+
This page will walk you through how to create and use custom form elements in your ReVISit study, and show you how to provide custom validation logic for your custom form elements.
6+
7+
## Creating Custom Form Elements
8+
9+
To create a custom form element, you need to define a React component that follows the structure defined by the `CustomResponseParams` type. This component receives the current response value, metadata about the response itself, optional custom parameters from the study config, the current validation error message if one exists, and a `field` helper for updating and blurring the value. You can then use this component in your study configuration to collect responses from participants. Here's a basic example of a custom form element that collects a color input from participants:
10+
11+
```tsx title="ColorPicker.tsx"
12+
import { CustomResponseParams } from '../../../store/types';
13+
14+
type ColorPickerValue = string;
15+
16+
export default function ColorPicker({
17+
response,
18+
value,
19+
error,
20+
disabled,
21+
field,
22+
}: CustomResponseParams<Record<string, unknown>, ColorPickerValue>) {
23+
const inputProps = field.getInputProps();
24+
25+
return (
26+
<>
27+
<p>{response.prompt}</p>
28+
<input
29+
type="color"
30+
{...inputProps}
31+
value={typeof value === 'string' ? value : '#000000'}
32+
disabled={disabled}
33+
onChange={(event) => field.setValue(event.currentTarget.value)}
34+
onBlur={() => field.onBlur()}
35+
/>
36+
{error && <p>{error}</p>}
37+
</>
38+
);
39+
}
40+
```
41+
42+
In this example, the `ColorPicker` component renders an HTML color input. The `getInputProps` function is useful for wiring a native input, while `setValue` updates the stored answer and `onBlur` marks the field as interacted with. For more custom interfaces, you can call `setValue` and `onBlur` directly without relying much on `getInputProps`.
43+
44+
## Using Custom Form Elements in Your Study Config
45+
46+
To use your custom form element in your study config, you need to define a response with `type` set to `custom` and provide the path to your custom form element component. Custom responses live inside a component's `response` array, just like built-in response types. Here's how you can include the `ColorPicker` custom form element in your study config:
47+
48+
```json
49+
{
50+
"type": "questionnaire",
51+
"response": [
52+
{
53+
"id": "favoriteColor",
54+
"prompt": "What is your favorite color?",
55+
"location": "belowStimulus",
56+
"type": "custom",
57+
"path": "my-study/assets/ColorPicker.tsx",
58+
"default": "#ff0000"
59+
}
60+
]
61+
}
62+
```
63+
64+
::note[Note]
65+
The path here is relative to the `src/public` directory of the ReVISit app, not the `public` directory used for most other study assets. You can place your custom form element component anywhere under `src/public`. We recommend that you follow the same folder structure that we suggest in the [react stimulus docs](./react-stimulus.md) for your custom form elements.
66+
:::
67+
68+
::note[JSON-serializable values]
69+
Custom response values must be JSON-serializable. This applies both to the values you store through `field.setValue(...)` and to any `default` value you define in the study config. Strings, numbers, booleans, `null`, arrays, and plain objects are supported.
70+
:::
71+
72+
## Custom Validation Logic for Custom Form Elements
73+
74+
Custom validation logic can be added to your custom form elements by defining a validation function that checks the response value and returns an error message if the value is invalid. The validation controls whether the participant can submit their response or not. The `validate` function receives the current value, the full set of response values for the component, and the current response config, so it can validate against sibling answers or `response.parameters`. Here's an example of how you can add custom validation logic to the `ColorPicker` component:
75+
76+
```tsx title="ColorPickerWithValidation.tsx"
77+
import { CustomResponseParams, CustomResponseValidate } from '../../../store/types';
78+
79+
type ColorPickerValue = string;
80+
81+
export default function ColorPicker({
82+
response,
83+
value,
84+
error,
85+
disabled,
86+
field,
87+
}: CustomResponseParams<Record<string, unknown>, ColorPickerValue>) {
88+
const inputProps = field.getInputProps();
89+
90+
return (
91+
<>
92+
<p>{response.prompt}</p>
93+
<input
94+
type="color"
95+
{...inputProps}
96+
value={typeof value === 'string' ? value : '#000000'}
97+
disabled={disabled}
98+
onChange={(event) => field.setValue(event.currentTarget.value)}
99+
onBlur={() => field.onBlur()}
100+
/>
101+
{error && <p>{error}</p>}
102+
</>
103+
);
104+
}
105+
106+
export const validate: CustomResponseValidate<ColorPickerValue> = (value, _values, response) => {
107+
if (typeof value !== 'string' || !value) {
108+
return `Please select a color for "${response.prompt}".`;
109+
}
110+
111+
return null;
112+
};
113+
```
114+
115+
In this example, we supplied a `validate` function that checks if a color has been selected. If no color is selected, it returns an error message prompting the participant to select a color. If a color is selected, it returns `null`, indicating that the response is valid. The function must be called `validate` and must be exported from the same file as the custom form element component.
116+
117+
When you include this custom form element in your study config, the validation logic will be automatically applied, and participants will not be able to submit their response until they have selected a color.
118+
119+
## Provenance tracking for custom form elements
120+
121+
Since custom form elements are hooked into the same response system as built-in form elements, their answers participate in the standard saving and provenance flow. To keep the stored answer in sync, update values through `field.setValue(...)`. Call `field.onBlur()` when the user finishes interacting with the element so validation and touched-state behavior work as expected.

0 commit comments

Comments
 (0)