diff --git a/samples/ManagementSite/Alloy.ManagementSite.csproj b/samples/ManagementSite/Alloy.ManagementSite.csproj index e5b90e1..52ff9ca 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 0cbf507..77feb39 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, useMemo, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { useForms } from "../context/store"; -import { FormContainer, FormSubmitter, IdentityInfo, equals, isInArray, isNull, isNullOrEmpty, FormSubmitModel, FormSubmitResult, SubmitButton, FormValidationResult, FormCache, FormConstants, ProblemDetail, StepDependCondition } from "@episerver/forms-sdk"; +import { FormContainer, FormSubmitter, IdentityInfo, isInArray, isNull, isNullOrEmpty, FormSubmitModel, FormSubmitResult, SubmitButton, FormCache, FormConstants, ProblemDetail, StepDependCondition } from "@episerver/forms-sdk"; import { RenderElementInStep } from "./RenderElementInStep"; import { DispatchFunctions } from "../context/dispatchFunctions"; import { FormStepNavigation } from "./FormStepNavigation"; @@ -38,8 +38,6 @@ export const FormBody = (props: FormBodyProps) => { isSuccess = useRef(false), submissionWarning = useRef(false), message = useRef(""), - isReadOnlyMode = false, - readOnlyModeMessage = "", submissionStorageKey = FormConstants.FormSubmissionId + form.key, isStepValidToDisplay = stepDependCondition.isStepValidToDisplay(currentStepIndex, currentPageUrl), isMalFormSteps = stepHelper.isMalFormSteps(); @@ -110,31 +108,7 @@ export const FormBody = (props: FormBodyProps) => { //submit data to API dispatchFunctions.updateIsSubmitting(true); formSubmitter.doSubmit(model).then((response: FormSubmitResult)=>{ - //get error or success message - if(!response.success) { - //ignore validation message - showError(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(stepHelper.getFirstInvalidElement(formValidationResults, currentStepIndex)); - } - - validateFail.current = response.validationFail; + //go here, response.success always is true isSuccess.current = response.success; isFormFinalized.current = isLastStep && response.success; dispatchFunctions.updateSubmissionKey(response.submissionKey); @@ -154,6 +128,16 @@ export const FormBody = (props: FormBodyProps) => { break; case 400: //validate fail + validateFail.current = false; + let formValidationResults = formContext?.formValidationResults?.map(fr => isNull(e.errors[fr.elementKey]) ? fr : { + ...fr, + result: { valid: false, message: e.errors[fr.elementKey].join("
") } + }) ?? []; + + dispatchFunctions.updateAllValidation(formValidationResults); + + //set focus on the 1st invalid element of current step + dispatchFunctions.updateFocusOn(stepHelper.getFirstInvalidElement(formValidationResults, currentStepIndex)); break; } @@ -178,6 +162,13 @@ export const FormBody = (props: FormBodyProps) => { isSuccess.current = false; },[currentStepIndex]); + //Run in-case change page by url. The currentStepIndex that get from cache is incorrect. + useEffect(()=>{ + if(!isStepValidToDisplay){ + dispatchFunctions.updateCurrentStepIndex(stepHelper.getCurrentStepIndex(currentPageUrl)); + } + },[]); + isMalFormSteps && showError("Improperly formed FormStep configuration. Some steps are attached to pages, while some steps are not attached, or attached to content with no public URL."); return ( @@ -199,13 +190,6 @@ export const FormBody = (props: FormBodyProps) => { {form.properties.description} } - {isReadOnlyMode && readOnlyModeMessage && -
- - {readOnlyModeMessage} - -
- } {/* area for showing Form's status or validation */}
diff --git a/src/@episerver/forms-react/src/components/elements/shared/ElementWrapper.tsx b/src/@episerver/forms-react/src/components/elements/shared/ElementWrapper.tsx index a379ba8..d4bdc18 100644 --- a/src/@episerver/forms-react/src/components/elements/shared/ElementWrapper.tsx +++ b/src/@episerver/forms-react/src/components/elements/shared/ElementWrapper.tsx @@ -1,15 +1,15 @@ -import { ElementValidationResult } from "@episerver/forms-sdk"; +import { FormValidationResult } from "@episerver/forms-sdk"; import React, { ReactNode } from "react"; export interface ElementWrapperProps{ className?: string isVisible: boolean, children: ReactNode, - validationResults?: ElementValidationResult[] + validationResults?: FormValidationResult } export default function ElementWrapper(props: ElementWrapperProps){ - const isFail = props.validationResults?.some(r => !r.valid); + const isFail = !props.validationResults?.result?.valid; return ( <> diff --git a/src/@episerver/forms-react/src/components/elements/shared/ValidationMessage.tsx b/src/@episerver/forms-react/src/components/elements/shared/ValidationMessage.tsx index e0e300e..a1efd9f 100644 --- a/src/@episerver/forms-react/src/components/elements/shared/ValidationMessage.tsx +++ b/src/@episerver/forms-react/src/components/elements/shared/ValidationMessage.tsx @@ -1,25 +1,23 @@ -import { ValidatableElementBase, ElementValidationResult, isNull } from "@episerver/forms-sdk"; +import { ValidatableElementBase, FormValidationResult } from "@episerver/forms-sdk"; import React from "react"; interface ValidationMessageProps { element: ValidatableElementBase, - validationResults: ElementValidationResult[] + validationResults: FormValidationResult } export const ValidationMessage = (props: ValidationMessageProps) => { - const { element, validationResults } = props; + const { validationResults } = props; return ( <> - {element.properties.validators?.map((v, i)=> { - let valid = isNull(validationResults) || validationResults.length === 0 || isNull(validationResults[i]?.valid) || validationResults[i].valid; - return ( - - {v.model.message} - - ); - })} + + {validationResults.result.message} + ); } \ No newline at end of file diff --git a/src/@episerver/forms-react/src/context/dispatchFunctions.ts b/src/@episerver/forms-react/src/context/dispatchFunctions.ts index a778193..480168e 100644 --- a/src/@episerver/forms-react/src/context/dispatchFunctions.ts +++ b/src/@episerver/forms-react/src/context/dispatchFunctions.ts @@ -8,11 +8,11 @@ export class DispatchFunctions { this._dispatch = useFormsDispatch(); } - updateValidation = (elementKey: string, validationResults: ElementValidationResult[]) => { + updateValidation = (elementKey: string, validationResult: ElementValidationResult) => { this._dispatch({ type: ActionType.UpdateValidation, elementKey: elementKey, - validationResults + validationResult }); } diff --git a/src/@episerver/forms-react/src/context/reducer.ts b/src/@episerver/forms-react/src/context/reducer.ts index e192fc6..96a1adb 100644 --- a/src/@episerver/forms-react/src/context/reducer.ts +++ b/src/@episerver/forms-react/src/context/reducer.ts @@ -33,7 +33,7 @@ export function formReducer(formState: FormState, action: any) { ...formState, formValidationResults: formState.formValidationResults.map(fv => equals(fv.elementKey, action.elementKey) ? { elementKey: action.elementKey, - results: action.validationResults + result: action.validationResult } as FormValidationResult : fv) } as FormState; } diff --git a/src/@episerver/forms-react/src/hooks/useElement.ts b/src/@episerver/forms-react/src/hooks/useElement.ts index 87e9666..cd81d49 100644 --- a/src/@episerver/forms-react/src/hooks/useElement.ts +++ b/src/@episerver/forms-react/src/hooks/useElement.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from "react"; -import { useForms, useFormsDispatch } from "../context/store"; +import { useForms } from "../context/store"; import { //models FormContainer, @@ -9,7 +9,6 @@ import { ValidatableElementBaseProperties, ValidatorType, SatisfiedActionType, - ElementValidationResult, //functions equals, getDefaultValue, @@ -20,13 +19,14 @@ import { FormValidator, FormSubmission, FormDependConditions, + FormValidationResult, } from "@episerver/forms-sdk"; import { DispatchFunctions } from "../context/dispatchFunctions"; export interface ElementContext { value: any, defaultValue: any, - validationResults: ElementValidationResult[], + validationResults: FormValidationResult, extraAttr: any, validatorClasses: string, isVisible: boolean, @@ -47,7 +47,7 @@ export const useElement = (element: FormElementBase) => { const value = (formContext?.formSubmissions ?? []) .filter(s => equals(s.elementKey, element.key))[0]?.value ?? defaultValue ?? ""; const validationResults = (formContext?.formValidationResults ?? []) - .filter(s => equals(s.elementKey, element.key))[0]?.results ?? []; + .filter(s => equals(s.elementKey, element.key))[0] ?? {elementKey: element.key, result: {valid: true}}; //build extra attributes for element const validatableProps = (element.properties as unknown) as ValidatableElementBaseProperties; @@ -143,8 +143,7 @@ export const useElement = (element: FormElementBase) => { if (/file/.test(type)) { submissionValue = files; - let validationResults = formValidation.validate(files) - dispatchFuncs.updateValidation(element.key, validationResults); + dispatchFuncs.updateValidation(element.key, formValidation.validate(files)); } //update form context @@ -152,11 +151,8 @@ export const useElement = (element: FormElementBase) => { } const handleBlur = (e: any) => { - //call validation from form-sdk - let validationResults = formValidation.validate(value); - //update form context - dispatchFuncs.updateValidation(element.key, validationResults); + dispatchFuncs.updateValidation(element.key, formValidation.validate(value)); } const handleReset = () => { diff --git a/src/@episerver/forms-sdk/src/form-step/stepHelper.ts b/src/@episerver/forms-sdk/src/form-step/stepHelper.ts index 9504539..3bb1193 100644 --- a/src/@episerver/forms-sdk/src/form-step/stepHelper.ts +++ b/src/@episerver/forms-sdk/src/form-step/stepHelper.ts @@ -81,8 +81,8 @@ export class StepHelper { */ getFirstInvalidElement (formValidationResults: FormValidationResult[], stepIndex: number): string { return formValidationResults.filter(fv => - fv.results.some(r => !r.valid) && - this._form.steps[stepIndex]?.elements?.some(e => equals(e.key, fv.elementKey)) + !fv.result.valid && + this.isInCurrentStep(fv.elementKey, stepIndex) )[0]?.elementKey; } diff --git a/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts b/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts index 6419d2a..88d1d06 100644 --- a/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts +++ b/src/@episerver/forms-sdk/src/form-submit/formSubmit.ts @@ -199,7 +199,7 @@ export class FormSubmitter { let value = formSubmissions.filter(fs => equals(fs.elementKey, e.key))[0]?.value; return { elementKey: e.key, - results: formValidator.validate(value) + result: formValidator.validate(value) } as FormValidationResult; }); } diff --git a/src/@episerver/forms-sdk/src/form-validator/formValidator.ts b/src/@episerver/forms-sdk/src/form-validator/formValidator.ts index 1e114b9..002130b 100644 --- a/src/@episerver/forms-sdk/src/form-validator/formValidator.ts +++ b/src/@episerver/forms-sdk/src/form-validator/formValidator.ts @@ -130,13 +130,14 @@ export class FormValidator { * @param value Value of field to validate * @returns An object that contains validate result */ - validate(value: any): ElementValidationResult[]{ + validate(value: any): ElementValidationResult{ + let result: ElementValidationResult = {valid: true, message: ""}; let validatorProps = this._element.properties as ValidatableElementBaseProperties; if(isNull(validatorProps?.validators)){ - return []; + return result; } - return validatorProps.validators.map((v) => { + validatorProps.validators.every((v) => { let valid = true; switch(v.type){ case ValidatorType.CaptchaValidator: @@ -164,10 +165,13 @@ export class FormValidator { break; } - return { - type: v.type, - valid - } as ElementValidationResult; - }) + if(!valid) { + result = {valid, message: v.model.message}; + return false; + } + return true; + }); + + return result; } } \ No newline at end of file diff --git a/src/@episerver/forms-sdk/src/helpers/initFormState.ts b/src/@episerver/forms-sdk/src/helpers/initFormState.ts index 728d901..0f86ecf 100644 --- a/src/@episerver/forms-sdk/src/helpers/initFormState.ts +++ b/src/@episerver/forms-sdk/src/helpers/initFormState.ts @@ -1,9 +1,9 @@ import { FormCache } from "../form-cache"; import { StepHelper } from "../form-step"; import { FormStorage } from "../form-storage"; -import { ElementValidationResult, FormConstants, FormContainer, FormState, FormSubmission, FormValidationResult, StepDependencies, ValidatableElementBaseProperties } from "../models"; +import { FormConstants, FormContainer, FormState, FormSubmission, FormValidationResult, StepDependencies } from "../models"; import { getDefaultValue } from "./elementHelper"; -import { equals, isNull, isNullOrEmpty } from "./utils"; +import { equals, isNullOrEmpty } from "./utils"; /** * Function to initialize FormState object @@ -26,17 +26,7 @@ export function initFormState(formContainer: FormContainer, currentPageUrl?: str formSubmissions = formSubmissions.concat({ elementKey: e.key, value: getDefaultValue(e) } as FormSubmission); //init form validation - let validatableProps = e.properties as ValidatableElementBaseProperties; - let elementValidationResults = [] as ElementValidationResult[]; - - //some elements don't have validator - if (!isNull(validatableProps.validators)) { - validatableProps.validators.forEach(v => { - elementValidationResults = elementValidationResults.concat({ type: v.type, valid: true }); //default valid = true to hide message - }); - } - - formValidationResults = formValidationResults.concat({ elementKey: e.key, results: elementValidationResults }); + formValidationResults = formValidationResults.concat({ elementKey: e.key, result: {valid: true, message: ""} }); }); stepDependencies = stepDependencies.concat({ elementKey: s.formStep.key, isSatisfied: false }); }); diff --git a/src/@episerver/forms-sdk/src/models/ProblemDetail.ts b/src/@episerver/forms-sdk/src/models/ProblemDetail.ts index b791729..6d44259 100644 --- a/src/@episerver/forms-sdk/src/models/ProblemDetail.ts +++ b/src/@episerver/forms-sdk/src/models/ProblemDetail.ts @@ -8,4 +8,5 @@ export interface ProblemDetail { detail: string; instance: string; traceId: string; + errors: Record; } \ No newline at end of file diff --git a/src/@episerver/forms-sdk/src/models/states/FormValidation.ts b/src/@episerver/forms-sdk/src/models/states/FormValidation.ts index 6383623..b9bad7d 100644 --- a/src/@episerver/forms-sdk/src/models/states/FormValidation.ts +++ b/src/@episerver/forms-sdk/src/models/states/FormValidation.ts @@ -3,14 +3,11 @@ */ export interface FormValidationResult { - elementKey: string - results: ElementValidationResult[] + elementKey: string; + result: ElementValidationResult; } -/** - * Represent type of object that is element validation result - */ export interface ElementValidationResult { - type: string - valid: boolean + valid: boolean; + message: string; } \ No newline at end of file