<SingleStepForm />{
componentName: 'ContentMain',
content: [{
uid: '',
componentName: 'Single Step Form',
dataSource: '',
fields: {
ModelJson: {
value: '[ { "title":"Form Name", "fields":{ "Name":{ "label":"Form Name", "type":"input", "value":"Form Name", "name":"Name" }, "Id":{ "label":"Id", "type":"hidden", "value":"", "name":"Id" } }, "fresh":true }, { "title":"Text Input", "fields":{ "FormId":{ "label":"FormId", "type":"hidden", "value":"stepOne", "name":"FormId" }, "Name":{ "label":"Name", "type":"hidden", "value":"textinput", "name":"Name" }, "Id":{ "label":"Id", "type":"input", "value":"fixtureName", "name":"Id" }, "Label":{ "label":"Label", "type":"input", "value":"Fixture Selection", "name":"Label" }, "BackEndLabel":{ "label":"BackEnd Label", "type":"input", "value":"Fixture Selection", "name":"BackEndLabel" }, "Value":{ "label":"Default Value", "type":"input", "value":"", "name":"Value" }, "DefaultValueSource":{ "label":"Default Value Source", "type":"select", "value":[ { "value":"None", "selected":false, "label":"None" }, { "value":"cookie", "selected":true, "label":"Cookie" } ], "name":"DefaultValueSource" }, "DefaultValueKey":{ "label":"Default Value Source Key", "type":"input", "value":"fixtureName", "name":"DefaultValueKey"}]
}
}
}]
}- `createdFields` is memoized to cache the original value and keep it from re-rendering each cycle
- The `false` parameter signifies that its not to return a multidimensional array, just a single array
5. `createFormInit()` is a 'factory' for both `<SingleStepForm />` and `<MultiStepForm />`. We will focus on what happens if this is called from `<SingleStepForm />`.
```typescript
const createFormInit: {
(arr: Array<ParsedFormModel>, multi: true): CFReturnType[][];
(arr: Array<ParsedFormModel>, multi: false): CFReturnType[];
} = (arr: Array<ParsedFormModel>, multi: boolean): any => {
return multi ? multiStepFormFields(arr) : parseFields(arr);
};const parseFields = (arr: Array<ParsedFormModel>) => {
const items = arr.reduce((acc: Array<CFReturnType | null>, curr) => {
const formField = createForm(curr);
return [...acc, formField];
}, []);
// filter out any null values due to early returns from hidden fields
return items.filter(Boolean) as Array<CFReturnType>;
};- `dataMap`
- `dataMap` is an object with `inputMap` values as the key and related props as the value. All of these will contain a 'file' value. This is the name of the React component that will be dynamically imported, and some of them will contain a 'props' value. Props is an object that contains more details for that particular input type such as type, icon, masking function etc..
```javascript
```
const dataMap: CFMappingType["dataMap"] = {
// ...
input: {
file: "Input",
props: { type: "text" },
},
phone: {
file: "Input",
props: {
type: "tel",
icon: "phone",
mask: masks.tel,
},
},
radio: {
file: "RadioGroup",
},
// ...
};2. Inside `createForm()` it will take the incoming field name and use it to index `inputMap`. At this point one of three things will happen:
- It will find a match and return that input type
- It will return a null value (these are currently hidden fields or unimportant fields)
- It will return undefined\
\
If it returns a type we will continue, if it returns a null we return a null out of createForm(), if its undefined we then assume and assign this field with a type 'input'
3. Now that we have the field type (the `inputType` value), we use this to index `dataMap` and return the file and or props from that
- We take this file name and dynamically import that component (ie: 'Input', 'Heading', 'RadioGroup', 'Tabs', etc..)
```javascript4. We then now build our return object which gets added to the array in `parseFields()` and gets sent back to `<SingleStepForm />`
```javascript
return {
Component,
data: getData(fields, title),
file,
formName: fields?.FormId?.value,
id: uid(32),
props: {
columns: getColumnWidth(fields.ColumnWidth?.value),
...props,
},
validations: getValidations(file, fields, props?.type),
};const getData: GetDataProps = ({ fields, title }) => ({
customValidationErrorMsg:
fields?.CustomValidationErrorMsg?.value || "field is required",
items: parseItems(fields?.InputItems?.value),
label: fields?.Label?.value || "",
maxLength: parseInt(fields?.MaximumLength?.value) || 524288,
minLength: parseInt(fields?.MinimumLength?.value) || 0,
name: fields?.Name?.value,
placeholder: fields?.PlaceholderText?.value,
required: fields?.Required?.value || false,
tabs: fields?.Tabs?.value.split("\n") || [],
title,
toolTipText: fields?.TooltipText?.value,
});const getValidations: GetValidationProps = (file, fields, regex = "") => {
const skipValidation = ["Heading", "Recaptcha", "Tabs"];
let pattern;
if (file && skipValidation.includes(file)) return null;
// Validations will usually come through as a string value from sitecore
// but can also come through as an array of objects, these have the type 'select'
// We first need to parse through this array and grab the value of the selected validation pattern
if (fields?.ValidationPattern?.type === "select") {
pattern = getSelectedValue(fields.ValidationPattern.value);
} else {
pattern = fields?.ValidationPattern?.value;
}
return {
shouldConfirm: fields?.AppearsOnFormConfirmation?.value,
validationPattern:
// 1. by regex in mapping props (phone, ssn)
// 2. by named regex pattern coming from Sitecore
regexMap[regex] || regexMap[pattern],
};
}; return (
<div className="relative">
<InputText {...propData} />
<div className="hidden lg:block absolute top-0 right-0 mt-4 lg:-mr-48">
<Tooltip error={Boolean(errors[name])} message={toolTipText} />
</div>
</div>
);
};
```[[form stepper element], [...first section fields], [...second section fields] ...etc]const formModel: Array<ParsedFormModel> = JSON.parse(modelJson.value);
const createdFields = useMemo(() => createFormInit(formModel, true), []);
const createdFieldsWithoutTabs = [...createdFields.slice(1), []];
const createdFieldsOnlyTabFields = [
...createdFields[0][0].data.tabs,
"Confirmation",
];
// ... return
<FormComponent onSubmit={handleFormSubmit}>
<FormStepper activeIndex={activeIndex} content={createdFieldsOnlyTabFields} />
{createdFieldsWithoutTabs.map((innerArray, index) =>
innerArray.map(
({ Component, data, id, props, validations, ...rest }: CFReturnType) => {
return (
<FieldWrapper
className={index === activeIndex ? "block" : "hidden"}
columns={props?.columns}
key={id}
>
<Component
register={register({
pattern: regexPattern(validations),
required: isRequired(data),
validate: {
match: (value: string) =>
matchingEmails(
name.toLowerCase(),
value,
getValues(["email", "emailconf"])
),
},
})}
{...{ data, errors, name, props, ...rest }}
/>
</FieldWrapper>
);
}
)
)}
{isLastStep && <ConfirmationStep data={confirmedValues} {...{ fields }} />}
<ButtonWrapper />
</FormComponent>;[
{
"title": "Radio List",
"fields": {
"FormId": {
"label": "FormId",
"type": "hidden",
"value": "stepOne",
"name": "FormId"
},
"Name": {
"label": "Name",
"type": "input",
"value": "Are you a customer requesting a single light for your yard or property?",
"name": "Name"
},
"Label": {
"label": "Label",
"type": "input",
"value": "Are you a customer requesting a single light for your yard or property?",
"name": "Label"
},
"BackEndLabel": {
"label": "BackEnd Label",
"type": "input",
"value": "Are you a customer requesting a single light for your yard or property?",
"name": "BackEndLabel"
},
"GroupName": {
"label": "Group Name",
"type": "input",
"value": "GroupName",
"name": "GroupName"
},
"Value": {
"label": "Default Value",
"type": "input",
"value": "",
"name": "Value"
},
"Id": {
"label": "Id",
"type": "input",
"value": "radiolist",
"name": "Id"
},
"TooltipText": {
"label": "Tooltip Text",
"type": "input",
"value": "",
"name": "TooltipText"
},
"CustomValidationErrorMsg": {
"label": "Custom Validation Err. Msg.",
"type": "input",
"value": "Please pick an option.",
"name": "CustomValidationErrorMsg"
},
"AppearsOnFormConfirmation": {
"label": "Appear on Confirmation",
"type": "checkbox",
"value": true,
"name": "AppearsOnFormConfirmation"
},
"Required": {
"label": "Required",
"type": "checkbox",
"value": true,
"name": "Required"
},
"InputItems": {
"label": "Items",
"type": "radiolist",
"value": "[{\"text\":\"Yes\",\"value\":\"Yes\"},{\"text\":\"No\",\"value\":\"No\"}]",
"name": "InputItems"
}
},
"fresh": true
},
{
"title": "Section Header",
"fields": {
"FormId": {
"label": "FormId",
"type": "hidden",
"value": "stepOne",
"name": "FormId"
},
"Name": {
"label": "Name",
"type": "hidden",
"value": "sectionhdr",
"name": "Name"
},
"Label": {
"label": "Label",
"type": "input",
"value": "Name & Address",
"name": "Label"
},
"BackEndLabel": {
"label": "BackEnd Label",
"type": "input",
"value": "Name & Address",
"name": "BackEndLabel"
},
"Id": {
"label": "Id",
"type": "hidden",
"value": "sectionhdr",
"name": "Id"
}
},
"fresh": true
},
{
"title": "First Name",
"fields": {
"FormId": {
"label": "FormId",
"type": "hidden",
"value": "stepOne",
"name": "FormId"
},
"Name": {
"label": "Name",
"type": "hidden",
"value": "FirstName",
"name": "Name"
},
"Id": {
"label": "Id",
"type": "hidden",
"value": "FirstName",
"name": "Id"
},
"Label": {
"label": "Label",
"type": "hidden",
"value": "First Name",
"name": "Label"
},
"BackEndLabel": {
"label": "BackEnd Label",
"type": "hidden",
"value": "First Name",
"name": "BackEndLabel"
},
"Value": {
"label": "Default Value",
"type": "hidden",
"value": "",
"name": "Value"
},
"ValidationPattern": {
"label": "Validation Rule",
"type": "hidden",
"value": "lettersWhiteSpace",
"name": "ValidationPattern"
},
"TooltipText": {
"label": "Tooltip Text",
"type": "input",
"value": "",
"name": "TooltipText"
},
"CustomValidationErrorMsg": {
"label": "Custom Validation Err. Msg.",
"type": "hidden",
"value": "Please enter a first name.",
"name": "CustomValidationErrorMsg"
},
"AppearsOnFormConfirmation": {
"label": "Appear on Confirmation",
"type": "checkbox",
"value": false,
"name": "AppearsOnFormConfirmation"
},
"Required": {
"label": "Required",
"type": "checkbox",
"value": true,
"name": "Required"
},
"MinimumLength": {
"label": "Min. Length",
"type": "hidden",
"value": "",
"name": "MinimumLength"
},
"MaximumLength": {
"label": "Max. Length",
"type": "hidden",
"value": "40",
"name": "MaximumLength"
},
"Predefined": {
"label": "Predefined",
"type": "hidden",
"value": "true",
"name": "Predefined"
},
"ColumnWidth": {
"label": "Column Width",
"type": "select",
"value": [
{
"value": "2",
"selected": false,
"label": "2"
},
{
"value": "3",
"selected": true,
"label": "3"
},
{
"value": "4",
"selected": false,
"label": "4"
},
{
"value": "5",
"selected": false,
"label": "5"
},
{
"value": "6",
"selected": false,
"label": "6"
}
],
"name": "ColumnWidth"
}
},
"fresh": true
}
]<FormComponent onSubmit={handleSubmit(onSubmit)}>
{createdFields.map(
({
Component,
data,
id,
props,
validations,
...rest
}: CFReturnType) => {
return (
<FieldWrapper columns={props?.columns} key={id}>
<Component
register={register({
pattern: regexPattern(validations),
required: isRequired(data),
validate: {
match: (value: string) =>
matchingEmails(
name.toLowerCase(),
value,
getValues(["email", "emailconf"])
),
},
})}
{...{ data, errors, name, props, ...rest }}
/>
</FieldWrapper>
);
}
)}
<Button type="submit" variant="primary">
submit
</Button>
</FormComponent>