Draupnir is a dynamic form generator for React applications, designed to streamline the process of creating forms based on JSON Schema.
MIT License
Draupnir is a dynamic form generator for React applications, designed to streamline the process of creating forms based on JSON Schema. Unlike some existing libraries, Draupnir prioritizes flexibility and customization, empowering developers to tailor the form renderer to their specific needs.
Table of Contents
NPM :
npm install draupnir
Yarn :
yarn add draupnir
Using Draupnir is straightforward. Simply provide your JSON Schema to the form generator, along with any customizations for the form renderer.
// Define your JSON Schema
const schema = {
// Your Schema here
};
// Initialize Widgets Object. This object contains the renderes.
const widgets = {
base: {
string: StringWidget,
// If you want use your own custom form components add this as needed
},
nonreactive: {},
custom: {},
};
const handleOnSubmit = async (val: any) => {
console.log(val);
// your onsubmit logic here
};
// Render the form
const App = () => (
<DraupnirRoot widgets={widgets}>
<DraupnirInstanceProvider
schema={schema}
mode={'onChange'} // Controls when validations should run, onChange, onBlur, all, onSubmit
>
// you can have children. This children will be outside the <form> element context
<DraupnirForm schema={schema} onSubmit={handleOnSubmit}>
<>
// you can extend the form with form action button here. this is inside <form> element context
<button type="submit">Sumit</button>
<button type="button">Cancel</button>
</>
</DraupnirForm>
<FormSection /> // some other extra contents that needs to access formstate.
</DraupnirInstanceProvider>
// you can add any number of instances inside draupnir root and they all their respective formstates. If you want different widgets then wrap it under new DraupnirRoot providing the desired custom widgets.
</DraupnirRoot>
);
export default App;
const FormSection = () => {
const { formState } = useDraupnirInstanceContext();
useEffect(() => {
const subscription = formProps.watch((value: Record<string, any>) => {
//do your thing with values
//contains form values such as values, errors, isdirty, formstate, fieldstate etc
});
return () => subscription.unsubscribe();
}, [formProps.watch]);
return <div>Form Section that contains input elements!</div>;
};
const schema = {
title: "Sample",
version: "0.1",
conditions: [
{
type: "if",
id: 'age',
match: 18,
operator: "lesser_or_equal",
then: ['hasVoterId']
},
{
type: "select_injection",
id: "gender",
match: "male",
mode: "replace",
then: "weight",
enum: ['less than 90', '90 - 100', '100 -120', '120 - 130'],
}
],
properties: {
title: {
id: "title",
type: "string",
widget: "heading",
hlevel: "h2",
label: "Personal Details Form"
},
fullname: {
id: "fullname",
type: "string",
label: "Full Name",
maximum: 20,
minimum: 5,
view: {
lg: 12,
xl: 4,
xxl: 4,
wide: 4
}
},
age: {
id: "age",
type: "number",
maximum: 50,
minimum: 0
},
hasVoterId: {
id: "hasVoterId",
type: 'boolean',
label: "Do you have Voter ID (Provided by your Respected Governtment)",
helperText: "If you have applied and waiting for issue, then please choose 'yes'."
},
gender: {
id: "gender",
type: "string",
widget: 'select',
enum: ['male', 'female', "others"],
default: "male"
},
weight: {
id: "weight",
type: "string",
widget: 'select',
enum: ['Lesser than 50', '55 - 60', "60 - 80"],
},
height: {
id: "height",
type: "string",
widget: 'select',
label: "Height (CM)",
enum: ['lesser than 150', '150 - 170', "Greater than 170"],
default: "lesser than 150"
},
isAgree: {
id: "isAgree",
type: "boolean",
required: true
},
separator: {
id: "separator",
type: "string",
widget: "separator"
}
},
} satisfies TSchema;
Link to Types : https://github.com/hashedalgorithm/draupnir/blob/main/src/types/schema.ts
type TSchema = {
title: string;
version?: string;
readOnly?: boolean;
properties: TProperties;
conditions: TCondition[];
meta?: Record<string, string>;
catchAll?: boolean;
};
TProperties
.TCondition[]
.These keys provide essential information and configuration options for defining a schema within Draupnir, ensuring flexibility and customization in form generation.
The TProperty
type represents a property/form field within a schema. Each property can have various configuration options that can alter behaviour, validations of that respective field:
...
properties: {
address.streetno: {
id: "address.streetno"
}
}
{
address: {
streetno: 123;
}
}
Note If the minimum property is not specified and the required property is set to true, the field will automatically be validated with a minimum length of 1. This behavior applies exclusively to fields of string or string array types.
In other words, when a string or string array is marked as required, the system will enforce that the field must contain at least one character or one element, respectively, unless a custom minimum value is explicitly provided.
Screen Breakpoints
sm: { max: '600px' },
md: { min: '601px', max: '767px' },
lg: { min: '768px', max: '991px' },
xl: { min: '992px', max: '1199px' },
xxl: { min: '1200px', max: '1500px' },
wide: { min: '1500px' },
It includes optional sizes for different breakpoints: 'sm', 'md', 'lg', 'xl', 'xxl', and 'wide'. The whole container is a grid of 12 columns and you can configure how much cols each field occupies here.
Example:
{
id: "fullname",
type: "string",
label: "Full Name",
view: {
lg: 12,
xl: 4,
xxl: 4,
wide: 4
}
}
Default field span
Breakpoint | Default Span size |
---|---|
sm | 12 |
md | 12 |
lg | 12 |
xl | 6 |
xxl | 6 |
wide | 6 |
type TEnums = Array<TEnum | string>;
type TEnum = {
label: string;
value: string;
disabled?: boolean;
};
{
enum: ['option1', 'option2', 'option3', ... n number of options that to be rendered.] // for just want to get the job done.
...other properties
}
//For more control
{
enum: [
{ lable: "Option 1", value: "option1" },
{ lable: "Option 2", value: "option2" },
{ lable: "Option 3", value: "option2", disabled: true },
...n number of options that to be rendered.
]
}
Note: It is Recommended to maintain uniformity by using either string or TEnum for your enum elements when there is conditions involved like select_injection.
Refer the example in the start of documentation for more context. The example showcases the usage of the enums as string[] uniformly throughtout the conditions and properties section.
'h1'
, 'h2'
, 'h3'
, 'h4'
, 'h5'
, 'h6'
. {
meta: {
userId: 'USR-123',
accountId: 'ACC-456'
},
...other properties
}
These keys provide comprehensive options for configuring fields within a schema, ensuring flexibility and customization in form generation.
To achieve the desired behavior for form fields, it's essential to use the appropriate types with specific widgets as per the guidelines.By adhering to these recommendations and using the appropriate widget types for each property type, you can ensure that the form behaves as expected, providing a smooth and intuitive user experience.
☑️ - This denotes this combition is supported ❌ - This reults in not-supported/inconsistent behaviour 🙅♂️ - This denotes the widget is still under work in progress 😏 - This will work. but not recommended.
Widget Type | Renderer |
---|---|
NA | Input ☑️ |
select | Select/Dropdown ☑️ |
checkbox | Checkbox ❌ |
checkbox-group | Checkbox Group |
radio | Radio Group ☑️ |
Input with Email Validation ☑️ | |
url | Input with URL Validation ☑️ |
datepicker | Datepicker 🙅♂️ |
text | TextArea ☑️ |
separator | Separator/Divider 😏 |
heading | Heading 😏 |
Widget Type | Renderer |
---|---|
NA | Number Input ☑️ |
select | Select/Dropdown ❌ |
checkbox | Checbox ❌ |
checkbox-group | Checkbox Group ❌ |
radio | Radio Group ❌ |
Input with Email Validation ❌ | |
url | Input with URL Validation ❌ |
datepicker | Datepicker ❌ |
text | TextArea ❌ |
separator | Separator/Divider 😏 |
heading | Heading 😏 |
Widget Type | Renderer |
---|---|
NA | Switch ☑️ |
select | Select/Dropdown ❌ |
checkbox | Checkbox ☑️ |
checkbox-group | Checkbox Group ❌ |
radio | Radio Group ❌ |
Input with Email Validation ❌ | |
url | Input with URL Validation ❌ |
datepicker | Datepicker ❌ |
text | TextArea ❌ |
separator | Separator/Divider 😏 |
heading | Heading 😏 |
Widget Type | Renderer |
---|---|
NA | React Fragment |
select | Select/Dropdown ❌ |
checkbox | Checkbox ❌ |
checkbox-group | Checkbox Group ☑️ |
radio | Radio Group ❌ |
Input with Email Validation ❌ | |
url | Input with URL Validation ❌ |
datepicker | Datepicker ❌ |
text | TextArea ❌ |
separator | Separator/Divider 😏 |
heading | Heading 😏 |
Widget Type | Renderer |
---|---|
NA | React Fragment |
select | Select/Dropdown ❌ |
checkbox | Checkbox ❌ |
checkbox-group | Checkbox Group ❌ |
radio | Radio Group ❌ |
Input with Email Validation ❌ | |
url | Input with URL Validation ❌ |
datepicker | Datepicker ❌ |
text | TextArea ❌ |
separator | Separator/Divider ☑️ |
heading | Heading ☑️ |
The TCondition
type represents a condition to be applied to the field:
type TCondition =
| {
type: 'if';
id: string;
match: string | number | boolean;
operator: TConditionOperator;
then: string[];
}
| {
type: 'ifelse';
id: string;
match: string | number | boolean;
operator: TConditionOperator;
then: string[];
else: string[];
}
| {
type: 'select_injection';
mode: 'replace' | 'add' | 'remove';
id: string;
match: string;
then: string;
enum: TEnums;
};
TConditionOperator
.These keys provide a flexible mechanism for defining conditional logic within schemas, enabling dynamic behavior based on specified conditions.
If you encounter any bugs or have suggestions for improvements, please open an issue.
Contributions are welcome! Please refer to the contribution guidelines before getting started.
This project is licensed under the MIT License - see the LICENSE file for details.