diff --git a/package-lock.json b/package-lock.json
index 64c0e0c..be02afb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,10 +13,7 @@
"src/@episerver/forms-sdk",
"samples/sample-react-app",
"samples/managementsite"
- ],
- "dependencies": {
- "react-router": "^6.21.0"
- }
+ ]
},
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
@@ -3461,14 +3458,6 @@
}
}
},
- "node_modules/@remix-run/router": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz",
- "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz",
@@ -15711,20 +15700,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-router": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz",
- "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==",
- "dependencies": {
- "@remix-run/router": "1.14.0"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8"
- }
- },
"node_modules/react-router-dom": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
@@ -19681,9 +19656,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
- "@episerver/forms-sdk": "file:../forms-sdk",
- "@types/react-router-dom": "^5.3.3",
- "react-router-dom": "^5.3.4"
+ "@episerver/forms-sdk": "file:../forms-sdk"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.4",
@@ -22617,10 +22590,8 @@
"@rollup/plugin-typescript": "^11.1.3",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
- "@types/react-router-dom": "^5.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-router-dom": "5.3.4",
"rollup": "^3.29.1",
"rollup-plugin-dts": "^6.0.2",
"rollup-plugin-postcss": "^4.0.2",
@@ -24394,11 +24365,6 @@
"source-map": "^0.7.3"
}
},
- "@remix-run/router": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz",
- "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A=="
- },
"@rollup/plugin-commonjs": {
"version": "25.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.4.tgz",
@@ -33076,14 +33042,6 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
},
- "react-router": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz",
- "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==",
- "requires": {
- "@remix-run/router": "1.14.0"
- }
- },
"react-router-dom": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz",
diff --git a/samples/ManagementSite/Alloy.ManagementSite.csproj b/samples/ManagementSite/Alloy.ManagementSite.csproj
index 13825f6..dffa3ef 100644
--- a/samples/ManagementSite/Alloy.ManagementSite.csproj
+++ b/samples/ManagementSite/Alloy.ManagementSite.csproj
@@ -19,9 +19,9 @@
-
+
-
+
diff --git a/src/@episerver/forms-react/src/components/FormBody.tsx b/src/@episerver/forms-react/src/components/FormBody.tsx
index 9f10fd6..8c5740d 100644
--- a/src/@episerver/forms-react/src/components/FormBody.tsx
+++ b/src/@episerver/forms-react/src/components/FormBody.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useRef } from "react";
import { useForms, useFormsDispatch } from "../context/store";
-import { FormContainer, FormSubmitter, IdentityInfo, SubmitButtonType, equals, isInArray, isNull, isNullOrEmpty, FormSubmitModel, FormSubmitResult, SubmitButton } from "@episerver/forms-sdk";
+import { FormContainer, FormSubmitter, IdentityInfo, SubmitButtonType, equals, isInArray, isNull, isNullOrEmpty, FormSubmitModel, FormSubmitResult, SubmitButton, ElementValidationResult, FormValidationResult } from "@episerver/forms-sdk";
import { RenderElementInStep } from "./RenderElementInStep";
import { DispatchFunctions } from "../context/dispatchFunctions";
import { FormStepNavigation } from "./FormStepNavigation";
@@ -15,13 +15,11 @@ export const FormBody = (props: FormBodyProps) => {
const formContext = useForms();
const form = formContext?.formContainer ?? {} as FormContainer;
const formSubmitter = new FormSubmitter(formContext?.formContainer ?? {} as FormContainer, props.baseUrl);
- const dispatch = useFormsDispatch();
- const dispatchFunctions = new DispatchFunctions(dispatch);
+ const dispatchFunctions = new DispatchFunctions();
const formTitleId = `${form.key}_label`;
const statusMessage = useRef("");
const statusDisplay = useRef("hide");
- const stepLocalizations = useRef>(form.steps?.filter(s => !isNull(s.formStep.localizations))[0]?.formStep.localizations);
//TODO: these variables should be get from api or sdk
const validateFail = useRef(false),
@@ -49,6 +47,21 @@ export const FormBody = (props: FormBodyProps) => {
const validationCssClass = validateFail.current ? "ValidationFail" : "ValidationSuccess";
+ const isInCurrentStep = (elementKey: string): boolean => {
+ let currentStep = form.steps[currentStepIndex];
+ if(currentStep){
+ return currentStep.elements.some(e => equals(e.key, elementKey));
+ }
+ return true;
+ }
+
+ const getFirstInvalidElement = (formValidationResults: FormValidationResult[]): string => {
+ return formValidationResults.filter(fv =>
+ fv.results.some(r => !r.valid) &&
+ form.steps[currentStepIndex]?.elements?.some(e => equals(e.key, fv.elementKey))
+ )[0]?.elementKey;
+ }
+
const handleSubmit = (e: any) => {
e.preventDefault();
@@ -58,35 +71,31 @@ export const FormBody = (props: FormBodyProps) => {
//Find submit button, if found then check property 'finalizeForm' of submit button. Otherwise, button Next/Previous was clicked.
let buttonId = e.nativeEvent.submitter.id;
- let submitButton = form.formElements.filter(fe => fe.key === buttonId)[0] as SubmitButton;
+ let submitButton = form.formElements.find(fe => fe.key === buttonId) as SubmitButton;
if (!isNull(submitButton)) {
//when submitting by SubmitButton, isProgressiveSubmit default is true
isProgressiveSubmit.current = true;
}
- //remove submissions of inactive elements and submissions with undefined value
+ //filter submissions by active elements and current step
let formSubmissions = (formContext?.formSubmissions ?? [])
- //only post value of active elements
- .filter(fs => !isInArray(fs.elementKey, formContext?.dependencyInactiveElements ?? []) && !isNull(fs.value));
+ .filter(fs => !isInArray(fs.elementKey, formContext?.dependencyInactiveElements ?? []) && isInCurrentStep(fs.elementKey));
//validate all submission data before submit
let formValidationResults = formSubmitter.doValidate(formSubmissions);
dispatchFunctions.updateAllValidation(formValidationResults);
//set focus on the 1st invalid element of current step
- let invalid = formValidationResults.filter(fv =>
- fv.results.some(r => !r.valid) &&
- form.steps[currentStepIndex]?.elements?.some(e => equals(e.key, fv.elementKey))
- )[0]?.elementKey;
+ let invalid = getFirstInvalidElement(formValidationResults);
if(!isNullOrEmpty(invalid)){
dispatchFunctions.updateFocusOn(invalid);
- isFormFinalized.current = false;
return;
}
+ let isLastStep = formContext?.currentStepIndex === form.steps.length - 1;
let model: FormSubmitModel = {
formKey: form.key,
locale: form.locale,
- isFinalized: submitButton?.properties?.finalizeForm || formContext?.currentStepIndex === form.steps.length - 1,
+ isFinalized: submitButton?.properties?.finalizeForm || isLastStep,
partialSubmissionKey: formContext?.submissionKey ?? "",
hostedPageUrl: window.location.pathname,
submissionData: formSubmissions,
@@ -95,6 +104,7 @@ export const FormBody = (props: FormBodyProps) => {
dispatchFunctions.updateIsSubmitting(true);
formSubmitter.doSubmit(model).then((response: FormSubmitResult)=>{
+ //get error or success message
if(response.success){
message.current = response.messages.map(m => m.message).join("
");
}
@@ -103,9 +113,27 @@ export const FormBody = (props: FormBodyProps) => {
//ignore validation message
message.current = response.messages.filter(m => isNullOrEmpty(m.identifier)).map(m => m.message).join("
");
}
+ //update validation message
+ if(response.validationFail){
+ let formValidationResults = formContext?.formValidationResults?.map(fr => {
+ let errorMessages = response.messages.filter(m => equals(m.identifier, fr.elementKey));
+ return errorMessages.length === 0 ? fr : {
+ ...fr,
+ results: fr.results.map(er => !errorMessages.some(em => equals(em.section, er.type)) ? er : {
+ ...er,
+ valid: false
+ })
+ } as FormValidationResult
+ }) ?? [];
+
+ dispatchFunctions.updateAllValidation(formValidationResults);
+
+ //set focus on the 1st invalid element of current step
+ dispatchFunctions.updateFocusOn(getFirstInvalidElement(formValidationResults));
+ }
validateFail.current = response.validationFail;
isSuccess.current = response.success;
- isFormFinalized.current = formContext?.currentStepIndex === form.steps.length - 1 && response.success;
+ isFormFinalized.current = isLastStep && response.success;
dispatchFunctions.updateSubmissionKey(response.submissionKey);
dispatchFunctions.updateIsSubmitting(false);
});
@@ -172,9 +200,7 @@ export const FormBody = (props: FormBodyProps) => {
{/* render step navigation*/}
diff --git a/src/@episerver/forms-react/src/components/FormStepNavigation.tsx b/src/@episerver/forms-react/src/components/FormStepNavigation.tsx
index 1f4bcb0..5b69c41 100644
--- a/src/@episerver/forms-react/src/components/FormStepNavigation.tsx
+++ b/src/@episerver/forms-react/src/components/FormStepNavigation.tsx
@@ -1,24 +1,22 @@
-import React from "react";
+import React, { useRef } from "react";
import { useForms, useFormsDispatch } from "../context/store";
import { FormCache, FormConstants, FormContainer, FormStep, StepDependCondition, SubmitButtonType, isNull } from "@episerver/forms-sdk";
import { DispatchFunctions } from "../context/dispatchFunctions";
import { useHistory } from "react-router-dom";
interface FormStepNavigationProps {
- stepLocalizations: React.MutableRefObject>
- form: FormContainer
- isFormFinalized: React.MutableRefObject
+ isFormFinalized: boolean
history?: any
}
export const FormStepNavigation = (props: FormStepNavigationProps) => {
- const formContext = useForms()
- const formCache = new FormCache()
- const dispatch = useFormsDispatch()
- const history = props.history
- const depend = new StepDependCondition(props.form, formContext?.dependencyInactiveElements ?? [])
- const { stepLocalizations, form, isFormFinalized } = props;
- const dispatchFuncs = new DispatchFunctions(dispatch);
+ const formContext = useForms();
+ const formCache = new FormCache();
+ const form = formContext?.formContainer ?? {} as FormContainer;
+ const depend = new StepDependCondition(form, formContext?.dependencyInactiveElements ?? []);
+ const { isFormFinalized, history } = props;
+ const dispatchFuncs = new DispatchFunctions();
+ const stepLocalizations = useRef>(form.steps?.filter(s => !isNull(s.formStep.localizations))[0]?.formStep.localizations);
const submittable = true
const stepCount = form.steps.length;
@@ -29,7 +27,7 @@ export const FormStepNavigation = (props: FormStepNavigationProps) => {
const nextButtonDisableState = (currentStepIndex == stepCount - 1) || !submittable;
const progressWidth = (100 * currentDisplayStepIndex / stepCount) + "%";
- const isShowStepNavigation = stepCount > 1 && currentStepIndex > -1 && currentStepIndex < stepCount && !isFormFinalized.current;
+ const isShowStepNavigation = stepCount > 1 && currentStepIndex > -1 && currentStepIndex < stepCount && !isFormFinalized;
const handlePrevStep = (event: React.MouseEvent) => {
event.preventDefault()
@@ -44,7 +42,7 @@ export const FormStepNavigation = (props: FormStepNavigationProps) => {
const goToStep = (stepIndex: number) => {
var step = form.steps[stepIndex].formStep as FormStep
- formCache.set(FormConstants.FormCurrentStep + props.form.key, stepIndex)
+ formCache.set(FormConstants.FormCurrentStep + form.key, stepIndex)
dispatchFuncs.updateCurrentStepIndex(stepIndex)
if (!isNull(step) && !isNull(step.properties.attachedContentLink)) {
diff --git a/src/@episerver/forms-react/src/context/dispatchFunctions.ts b/src/@episerver/forms-react/src/context/dispatchFunctions.ts
index 26f8bf6..a778193 100644
--- a/src/@episerver/forms-react/src/context/dispatchFunctions.ts
+++ b/src/@episerver/forms-react/src/context/dispatchFunctions.ts
@@ -1,10 +1,11 @@
import { FormContainer, ElementValidationResult, FormValidationResult, initFormState, IdentityInfo } from "@episerver/forms-sdk";
import { ActionType } from "./reducer";
+import { useFormsDispatch } from "./store";
export class DispatchFunctions {
readonly _dispatch: any
- constructor(dispatch: any){
- this._dispatch = dispatch;
+ constructor(){
+ this._dispatch = useFormsDispatch();
}
updateValidation = (elementKey: string, validationResults: ElementValidationResult[]) => {
diff --git a/src/@episerver/forms-react/src/hooks/useElement.ts b/src/@episerver/forms-react/src/hooks/useElement.ts
index 3b93425..87e9666 100644
--- a/src/@episerver/forms-react/src/hooks/useElement.ts
+++ b/src/@episerver/forms-react/src/hooks/useElement.ts
@@ -35,13 +35,12 @@ export interface ElementContext {
export const useElement = (element: FormElementBase) => {
const formContext = useForms();
- const dispatch = useFormsDispatch();
const extraAttr = useRef({});
const formValidation = new FormValidator(element);
const formCondition = new FormDependConditions(element)
const defaultValue = getDefaultValue(element);
const isVisible = useRef(true);
- const dispatchFuncs = new DispatchFunctions(dispatch);
+ const dispatchFuncs = new DispatchFunctions();
const elementRef = useRef(null);
//build element state
diff --git a/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts b/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts
index 967c487..732a99f 100644
--- a/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts
+++ b/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts
@@ -1,6 +1,6 @@
import { FormStorage } from "../form-storage";
import { FormValidator } from "../form-validator";
-import { equals } from "../helpers";
+import { equals, isNull } from "../helpers";
import { FormConstants, FormContainer, FormSubmission, FormValidationResult } from "../models";
import { ApiConstant } from "../form-loader/apiConstant";
@@ -104,6 +104,11 @@ export class FormSubmitter {
model.submissionData.forEach(data => {
let ovalue = data.value;
let key = `${FormConstants.FormFieldPrefix}${data.elementKey}`;
+
+ if(isNull(ovalue)) {
+ return;
+ }
+
// checking file upload elements, item must be File if any,
// for using Object.getPrototypeOf(variable) variable must be object type
if(Object.getPrototypeOf(ovalue) === FileList.prototype && (ovalue as FileList).length > 0) {
diff --git a/src/@episerver/forms-sdk/src/helpers/initFormState.ts b/src/@episerver/forms-sdk/src/helpers/initFormState.ts
index 0338cbd..538d82a 100644
--- a/src/@episerver/forms-sdk/src/helpers/initFormState.ts
+++ b/src/@episerver/forms-sdk/src/helpers/initFormState.ts
@@ -2,7 +2,7 @@ import { FormCache } from "../form-cache";
import { FormStorage } from "../form-storage";
import { ElementValidationResult, FormConstants, FormContainer, FormState, FormSubmission, FormValidationResult, StepDependencies, ValidatableElementBaseProperties } from "../models";
import { getDefaultValue } from "./elementHelper";
-import { isNull } from "./utils";
+import { equals, isNull } from "./utils";
/**
* Function to initialize FormState object
@@ -39,9 +39,12 @@ export function initFormState(formContainer: FormContainer): FormState {
stepDependencies = stepDependencies.concat({ elementKey: s.formStep.key, isSatisfied: false });
});
- //binding the elements with stored input value between Next/Prev navigation
- if (formData.length > 0) {
- formSubmissions = formData;
+ //binding the elements with saved data between Next/Prev navigation
+ if(formData.length > 0){
+ formSubmissions = formSubmissions.map(s => {
+ let savedData = formData.find(d => equals(d.elementKey, s.elementKey));
+ return savedData ? savedData : s;
+ });
}
return {