Skip to content

Commit

Permalink
Merge pull request #53 from episerver/bugfix/AFORM-3781-Show-invalid-…
Browse files Browse the repository at this point in the history
…error-under-the-element-when-validate-fail-from-server

Show error from server and adjust some code
  • Loading branch information
hungoptimizely authored Dec 15, 2023
2 parents 686f224 + 81d43fb commit a2fcf77
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 86 deletions.
46 changes: 2 additions & 44 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions samples/ManagementSite/Alloy.ManagementSite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
<PackageReference Include="EPiServer.CMS.UI.VisitorGroups" Version="12.23.0" />
<PackageReference Include="EPiServer.CMS.UI.AspNetIdentity" Version="12.23.0" />
<PackageReference Include="EPiServer.ImageLibrary.ImageSharp" Version="2.0.1" />
<PackageReference Include="Optimizely.Headless.Form.Service" Version="0.1.0--ci-165" />
<PackageReference Include="Optimizely.Headless.Form.Service" Version="0.1.0--inte-173" />
<PackageReference Include="Optimizely.Cms.Content.EPiServer" Version="0.3.1" />
<PackageReference Include="EPiServer.Forms" Version="5.8.0-ci-021200" />
<PackageReference Include="EPiServer.Forms" Version="5.8.0-ci-021201" />
<PackageReference Include="EPiServer.OpenIDConnect" Version="3.2.0"/>
</ItemGroup>
<ItemGroup>
Expand Down
62 changes: 44 additions & 18 deletions src/@episerver/forms-react/src/components/FormBody.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<string>("");
const statusDisplay = useRef<string>("hide");
const stepLocalizations = useRef<Record<string, string>>(form.steps?.filter(s => !isNull(s.formStep.localizations))[0]?.formStep.localizations);

//TODO: these variables should be get from api or sdk
const validateFail = useRef<boolean>(false),
Expand Down Expand Up @@ -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();

Expand All @@ -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,
Expand All @@ -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("<br>");
}
Expand All @@ -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("<br>");
}
//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);
});
Expand Down Expand Up @@ -172,9 +200,7 @@ export const FormBody = (props: FormBodyProps) => {

{/* render step navigation*/}
<FormStepNavigation
stepLocalizations={stepLocalizations}
form={form}
isFormFinalized={isFormFinalized}
isFormFinalized={isFormFinalized.current}
history = {props.history}
/>
</div>
Expand Down
24 changes: 11 additions & 13 deletions src/@episerver/forms-react/src/components/FormStepNavigation.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, string>>
form: FormContainer
isFormFinalized: React.MutableRefObject<boolean>
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<Record<string, string>>(form.steps?.filter(s => !isNull(s.formStep.localizations))[0]?.formStep.localizations);

const submittable = true
const stepCount = form.steps.length;
Expand All @@ -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<HTMLButtonElement>) => {
event.preventDefault()
Expand All @@ -44,7 +42,7 @@ export const FormStepNavigation = (props: FormStepNavigationProps) => {
const goToStep = (stepIndex: number) => {
var step = form.steps[stepIndex].formStep as FormStep

formCache.set<number>(FormConstants.FormCurrentStep + props.form.key, stepIndex)
formCache.set<number>(FormConstants.FormCurrentStep + form.key, stepIndex)
dispatchFuncs.updateCurrentStepIndex(stepIndex)

if (!isNull(step) && !isNull(step.properties.attachedContentLink)) {
Expand Down
5 changes: 3 additions & 2 deletions src/@episerver/forms-react/src/context/dispatchFunctions.ts
Original file line number Diff line number Diff line change
@@ -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[]) => {
Expand Down
3 changes: 1 addition & 2 deletions src/@episerver/forms-react/src/hooks/useElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@ export interface ElementContext {

export const useElement = (element: FormElementBase) => {
const formContext = useForms();
const dispatch = useFormsDispatch();
const extraAttr = useRef<any>({});
const formValidation = new FormValidator(element);
const formCondition = new FormDependConditions(element)
const defaultValue = getDefaultValue(element);
const isVisible = useRef<boolean>(true);
const dispatchFuncs = new DispatchFunctions(dispatch);
const dispatchFuncs = new DispatchFunctions();
const elementRef = useRef<any>(null);

//build element state
Expand Down
7 changes: 6 additions & 1 deletion src/@episerver/forms-sdk/src/form-submit/formSubmit.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 7 additions & 4 deletions src/@episerver/forms-sdk/src/helpers/initFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit a2fcf77

Please sign in to comment.