Progress on form reactivity
This commit is contained in:
parent
423d031210
commit
4909928d3a
@ -212,7 +212,9 @@ const keycloakifyExtraMessages = {
|
|||||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||||
"mustBeAnInteger": "Must be an integer",
|
"mustBeAnInteger": "Must be an integer",
|
||||||
"notAValidOption": "Not a valid option",
|
"notAValidOption": "Not a valid option",
|
||||||
"selectAnOption": "Select an option"
|
"selectAnOption": "Select an option",
|
||||||
|
"remove": "Remove",
|
||||||
|
"add value": "Add value"
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
/* spell-checker: disable */
|
/* spell-checker: disable */
|
||||||
@ -225,7 +227,9 @@ const keycloakifyExtraMessages = {
|
|||||||
"logoutConfirmTitle": "Déconnexion",
|
"logoutConfirmTitle": "Déconnexion",
|
||||||
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
|
||||||
"doLogout": "Se déconnecter",
|
"doLogout": "Se déconnecter",
|
||||||
"selectAnOption": "Sélectionner une option"
|
"selectAnOption": "Sélectionner une option",
|
||||||
|
"remove": "Supprimer",
|
||||||
|
"add value": "Ajouter une valeur"
|
||||||
/* spell-checker: enable */
|
/* spell-checker: enable */
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ export type FormFieldState = {
|
|||||||
/** The index is always 0 for non multi-valued fields */
|
/** The index is always 0 for non multi-valued fields */
|
||||||
index: number;
|
index: number;
|
||||||
value: string;
|
value: string;
|
||||||
displayableError: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,6 +44,11 @@ export type FormAction =
|
|||||||
| {
|
| {
|
||||||
action: "add value to multi-valued attribute";
|
action: "add value to multi-valued attribute";
|
||||||
name: string;
|
name: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: "remove value from multi-valued attribute";
|
||||||
|
name: string;
|
||||||
|
index: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type KcContextLike = {
|
export type KcContextLike = {
|
||||||
@ -59,7 +64,7 @@ export type KcContextLike = {
|
|||||||
export type ParamsOfUseUserProfileForm = {
|
export type ParamsOfUseUserProfileForm = {
|
||||||
kcContext: KcContextLike;
|
kcContext: KcContextLike;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
passwordConfirmationDisabled?: boolean;
|
doMakeUserConfirmPassword: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReturnTypeOfUseUserProfileForm = {
|
export type ReturnTypeOfUseUserProfileForm = {
|
||||||
@ -72,7 +77,7 @@ export type ReturnTypeOfUseUserProfileForm = {
|
|||||||
* artificial password related attributes only if kcContext.passwordRequired === true
|
* artificial password related attributes only if kcContext.passwordRequired === true
|
||||||
*/
|
*/
|
||||||
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
||||||
const { kcContext, i18n, passwordConfirmationDisabled = false } = params;
|
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
||||||
|
|
||||||
const attributesWithPassword = useMemo(() => {
|
const attributesWithPassword = useMemo(() => {
|
||||||
const attributesWithPassword: Attribute[] = [];
|
const attributesWithPassword: Attribute[] = [];
|
||||||
@ -125,26 +130,28 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
i18n
|
i18n
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormFieldState_internal = Omit<FormFieldState, "displayableError"> & {
|
type FormFieldState_internal = Omit<FormFieldState, "displayableErrors"> & {
|
||||||
errors: FormFieldError[];
|
errors: FormFieldError[];
|
||||||
hasLostFocusAtLeastOnce: boolean;
|
hasLostFocusAtLeastOnce: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = FormFieldState_internal[];
|
type State = {
|
||||||
|
formFieldStates: FormFieldState_internal[];
|
||||||
|
};
|
||||||
|
|
||||||
const [state, dispatchFormAction] = useReducer(
|
const [state, dispatchFormAction] = useReducer(
|
||||||
function reducer(state: State, params: FormAction): State {
|
function reducer(state: State, params: FormAction): State {
|
||||||
if (params.action === "add value to multi-valued attribute") {
|
if (params.action === "add value to multi-valued attribute") {
|
||||||
const formFieldStates = state.filter(({ name }) => name === params.name);
|
const formFieldStates = state.formFieldStates.filter(({ name }) => name === params.name);
|
||||||
|
|
||||||
state.splice(state.indexOf(formFieldStates[formFieldStates.length - 1]) + 1, 0, {
|
state.formFieldStates.splice(state.formFieldStates.indexOf(formFieldStates[formFieldStates.length - 1]) + 1, 0, {
|
||||||
"index": formFieldStates.length,
|
"index": formFieldStates.length,
|
||||||
"name": params.name,
|
"name": params.name,
|
||||||
"value": "",
|
"value": "",
|
||||||
"errors": getErrors({
|
"errors": getErrors({
|
||||||
"name": params.name,
|
"name": params.name,
|
||||||
"index": formFieldStates.length,
|
"index": formFieldStates.length,
|
||||||
"fieldValues": state
|
"fieldValues": state.formFieldStates
|
||||||
}),
|
}),
|
||||||
"hasLostFocusAtLeastOnce": false,
|
"hasLostFocusAtLeastOnce": false,
|
||||||
"attribute": formFieldStates[0].attribute
|
"attribute": formFieldStates[0].attribute
|
||||||
@ -153,7 +160,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formFieldState = state.find(({ name, index }) => name === params.name && index === params.index);
|
const formFieldState = state.formFieldStates.find(({ name, index }) => name === params.name && index === params.index);
|
||||||
|
|
||||||
assert(formFieldState !== undefined);
|
assert(formFieldState !== undefined);
|
||||||
|
|
||||||
@ -167,7 +174,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
break update_password_confirm;
|
break update_password_confirm;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!passwordConfirmationDisabled) {
|
if (doMakeUserConfirmPassword) {
|
||||||
break update_password_confirm;
|
break update_password_confirm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,9 +190,12 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
formFieldState.errors = getErrors({
|
formFieldState.errors = getErrors({
|
||||||
"name": params.name,
|
"name": params.name,
|
||||||
"index": params.index,
|
"index": params.index,
|
||||||
"fieldValues": state
|
"fieldValues": state.formFieldStates
|
||||||
});
|
});
|
||||||
return state;
|
return state;
|
||||||
|
case "remove value from multi-valued attribute":
|
||||||
|
state.formFieldStates.splice(state.formFieldStates.indexOf(formFieldState), 1);
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert<Equals<typeof params, never>>(false);
|
assert<Equals<typeof params, never>>(false);
|
||||||
@ -245,18 +255,20 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
return initialFormFieldValues;
|
return initialFormFieldValues;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const initialState: State = initialFormFieldValues.map(({ name, index, value, attribute }) => ({
|
const initialState: State = {
|
||||||
name,
|
"formFieldStates": initialFormFieldValues.map(({ name, index, value, attribute }) => ({
|
||||||
index,
|
|
||||||
value,
|
|
||||||
"errors": getErrors({
|
|
||||||
name,
|
name,
|
||||||
index,
|
index,
|
||||||
"fieldValues": initialFormFieldValues
|
value,
|
||||||
}),
|
"errors": getErrors({
|
||||||
"hasLostFocusAtLeastOnce": false,
|
name,
|
||||||
attribute
|
index,
|
||||||
}));
|
"fieldValues": initialFormFieldValues
|
||||||
|
}),
|
||||||
|
"hasLostFocusAtLeastOnce": false,
|
||||||
|
attribute
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
return initialState;
|
return initialState;
|
||||||
}, [])
|
}, [])
|
||||||
@ -264,14 +276,14 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
|
|
||||||
const formState: FormState = useMemo(
|
const formState: FormState = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
"formFieldStates": state.map(({ name, index, value, errors, hasLostFocusAtLeastOnce, attribute }) => ({
|
"formFieldStates": state.formFieldStates.map(({ name, index, value, errors, hasLostFocusAtLeastOnce, attribute }) => ({
|
||||||
name,
|
name,
|
||||||
index,
|
index,
|
||||||
value,
|
value,
|
||||||
"displayableError": hasLostFocusAtLeastOnce ? errors : [],
|
"displayableErrors": hasLostFocusAtLeastOnce ? errors : [],
|
||||||
attribute
|
attribute
|
||||||
})),
|
})),
|
||||||
"isFormSubmittable": state.every(({ errors }) => errors.length === 0)
|
"isFormSubmittable": state.formFieldStates.every(({ errors }) => errors.length === 0)
|
||||||
}),
|
}),
|
||||||
[state]
|
[state]
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { useEffect, Fragment } from "react";
|
import { useEffect, Fragment } from "react";
|
||||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { useProfileAttributeForm, type KcContextLike } from "keycloakify/login/lib/useProfileAttributeForm";
|
import {
|
||||||
|
useUserProfileForm,
|
||||||
|
type KcContextLike,
|
||||||
|
type FormAction,
|
||||||
|
type FormFieldError,
|
||||||
|
FormFieldState
|
||||||
|
} from "keycloakify/login/lib/useUserProfileForm";
|
||||||
import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext";
|
import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext";
|
||||||
import type { I18n } from "../../i18n";
|
import type { I18n } from "../../i18n";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -11,24 +17,34 @@ export type UserProfileFormFieldsProps = {
|
|||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
getClassName: (classKey: ClassKey) => string;
|
getClassName: (classKey: ClassKey) => string;
|
||||||
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
|
||||||
BeforeField?: (props: { attribute: Attribute }) => JSX.Element | null;
|
BeforeField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
||||||
AfterField?: (props: { attribute: Attribute }) => JSX.Element | null;
|
AfterField?: (props: BeforeAfterFieldProps) => JSX.Element | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type BeforeAfterFieldProps = {
|
||||||
|
attribute: Attribute;
|
||||||
|
index: number;
|
||||||
|
value: string;
|
||||||
|
dispatchFormAction: React.Dispatch<FormAction>;
|
||||||
|
formFieldErrors: FormFieldError[];
|
||||||
|
i18n: I18n;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: Enabled by default but it's a UX best practice to set it to false.
|
||||||
|
const doMakeUserConfirmPassword = true;
|
||||||
|
|
||||||
export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
||||||
const { kcContext, onIsFormSubmittableValueChange, i18n, getClassName, BeforeField, AfterField } = props;
|
const { kcContext, onIsFormSubmittableValueChange, i18n, getClassName, BeforeField, AfterField } = props;
|
||||||
|
|
||||||
const { advancedMsg, msg } = i18n;
|
const { advancedMsg, msg } = i18n;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
formState: { formFieldStates, isFormSubmittable },
|
||||||
formValidationDispatch,
|
dispatchFormAction
|
||||||
attributesWithPassword
|
} = useUserProfileForm({
|
||||||
} = useProfileAttributeForm({
|
|
||||||
kcContext,
|
kcContext,
|
||||||
i18n
|
i18n,
|
||||||
// NOTE: Uncomment the following line if you don't want for force the user to enter the password twice.
|
doMakeUserConfirmPassword
|
||||||
//"requirePasswordConfirmation": false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -39,16 +55,14 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{attributesWithPassword.map((attribute, i) => {
|
{formFieldStates.map(({ index, value, attribute, displayableErrors }) => {
|
||||||
const { displayableErrors, value } = fieldStateByAttributeName[attribute.name];
|
|
||||||
|
|
||||||
const formGroupClassName = clsx(
|
const formGroupClassName = clsx(
|
||||||
getClassName("kcFormGroupClass"),
|
getClassName("kcFormGroupClass"),
|
||||||
displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass")
|
displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={i}>
|
<Fragment key={`${attribute.name}-${index}`}>
|
||||||
{(() => {
|
{(() => {
|
||||||
keycloak_prior_to_24: {
|
keycloak_prior_to_24: {
|
||||||
if (attribute.html5DataAnnotations !== undefined) {
|
if (attribute.html5DataAnnotations !== undefined) {
|
||||||
@ -132,9 +146,23 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
return null;
|
return null;
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
{BeforeField && <BeforeField attribute={attribute} />}
|
{BeforeField && (
|
||||||
|
<BeforeField
|
||||||
|
attribute={attribute}
|
||||||
|
index={index}
|
||||||
|
value={value}
|
||||||
|
dispatchFormAction={dispatchFormAction}
|
||||||
|
formFieldErrors={displayableErrors}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={formGroupClassName}>
|
<div
|
||||||
|
className={formGroupClassName}
|
||||||
|
style={{
|
||||||
|
"display": attribute.name === "password-confirm" && !doMakeUserConfirmPassword ? "none" : undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={getClassName("kcLabelWrapperClass")}>
|
<div className={getClassName("kcLabelWrapperClass")}>
|
||||||
<label htmlFor={attribute.name} className={getClassName("kcLabelClass")}>
|
<label htmlFor={attribute.name} className={getClassName("kcLabelClass")}>
|
||||||
{advancedMsg(attribute.displayName ?? "")}
|
{advancedMsg(attribute.displayName ?? "")}
|
||||||
@ -142,7 +170,80 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
{attribute.required && <>*</>}
|
{attribute.required && <>*</>}
|
||||||
</div>
|
</div>
|
||||||
<div className={getClassName("kcInputWrapperClass")}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
|
{attribute.annotations.inputHelperTextBefore !== undefined && index === 0 && (
|
||||||
|
<div
|
||||||
|
className={getClassName("kcInputHelperTextBeforeClass")}
|
||||||
|
id={`form-help-text-before-${attribute.name}`}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
{advancedMsg(attribute.annotations.inputHelperTextBefore)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<InputFiledByType
|
||||||
|
attribute={attribute}
|
||||||
|
index={index}
|
||||||
|
value={value}
|
||||||
|
formValidationDispatch={dispatchFormAction}
|
||||||
|
getClassName={getClassName}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
{attribute.multivalued && (
|
||||||
|
<AddRemoveButtonsMultiValuedAttribute
|
||||||
|
formFieldStates={formFieldStates}
|
||||||
|
attribute={attribute}
|
||||||
|
index={index}
|
||||||
|
dispatchFormAction={dispatchFormAction}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{displayableErrors.length !== 0 && (
|
||||||
|
<span
|
||||||
|
id={`input-error-${attribute.name}${index === 0 ? "" : `-${index + 1}`}`}
|
||||||
|
className={getClassName("kcInputErrorMessageClass")}
|
||||||
|
style={{
|
||||||
|
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
||||||
|
}}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
{displayableErrors.map(({ errorMessage }, i, arr) => (
|
||||||
|
<>
|
||||||
|
<span key={i}>{errorMessage}</span>
|
||||||
|
{arr.length - 1 !== i && <br />}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{attribute.annotations.inputHelperTextAfter !== undefined && index === 0 && (
|
||||||
|
<div
|
||||||
|
className={getClassName("kcInputHelperTextAfterClass")}
|
||||||
|
id={`form-help-text-before-${attribute.name}`}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
{advancedMsg(attribute.annotations.inputHelperTextAfter)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{AfterField && (
|
||||||
|
<AfterField
|
||||||
|
attribute={attribute}
|
||||||
|
index={index}
|
||||||
|
value={value}
|
||||||
|
dispatchFormAction={dispatchFormAction}
|
||||||
|
formFieldErrors={displayableErrors}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/*
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
<#list profile.html5DataAnnotations?keys as key>
|
||||||
|
<script type="module" src="${url.resourcesPath}/js/${key}.js"></script>
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
*/}
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
|
/*
|
||||||
const { options } = attribute.validators;
|
const { options } = attribute.validators;
|
||||||
|
|
||||||
if (options !== undefined) {
|
if (options !== undefined) {
|
||||||
@ -212,33 +313,179 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
autoComplete={attribute.autocomplete}
|
autoComplete={attribute.autocomplete}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
})()}
|
})()}
|
||||||
{displayableErrors.length !== 0 &&
|
|
||||||
(() => {
|
|
||||||
const divId = `input-error-${attribute.name}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<style>{`#${divId} > span: { display: block; }`}</style>
|
|
||||||
<span
|
|
||||||
id={divId}
|
|
||||||
className={getClassName("kcInputErrorMessageClass")}
|
|
||||||
style={{
|
|
||||||
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
|
||||||
}}
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
{displayableErrors.map(({ errorMessage }) => errorMessage)}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{AfterField && <AfterField attribute={attribute} />}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||||
|
formFieldStates: FormFieldState[];
|
||||||
|
attribute: Attribute;
|
||||||
|
index: number;
|
||||||
|
dispatchFormAction: React.Dispatch<
|
||||||
|
Extract<FormAction, { action: "add value to multi-valued attribute" | "remove value from multi-valued attribute" }>
|
||||||
|
>;
|
||||||
|
i18n: I18n;
|
||||||
|
}) {
|
||||||
|
const { formFieldStates, attribute, index, dispatchFormAction, i18n } = props;
|
||||||
|
|
||||||
|
const { msg } = i18n;
|
||||||
|
|
||||||
|
const currentCount = formFieldStates.filter(({ attribute: attribute_i }) => attribute_i.name === attribute.name).length;
|
||||||
|
|
||||||
|
const hasRemove = (() => {
|
||||||
|
if (currentCount === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minCount = (() => {
|
||||||
|
const { multivalued } = attribute.validators;
|
||||||
|
|
||||||
|
if (multivalued === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minStr = multivalued.min;
|
||||||
|
|
||||||
|
if (minStr === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(minStr);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (minCount === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCount === minCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const hasAdd = (() => {
|
||||||
|
if (index + 1 !== currentCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxCount = (() => {
|
||||||
|
const { multivalued } = attribute.validators;
|
||||||
|
|
||||||
|
if (multivalued === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxStr = multivalued.max;
|
||||||
|
|
||||||
|
if (maxStr === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(maxStr);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (maxCount === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCount === maxCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{hasRemove && (
|
||||||
|
<button
|
||||||
|
id={`kc-remove-${attribute.name}-${index + 1}`}
|
||||||
|
type="button"
|
||||||
|
className="pf-c-button pf-m-inline pf-m-link"
|
||||||
|
onClick={() =>
|
||||||
|
dispatchFormAction({
|
||||||
|
"action": "remove value from multi-valued attribute",
|
||||||
|
"name": attribute.name,
|
||||||
|
index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{msg("remove")}
|
||||||
|
{hasRemove ? <> | </> : null}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{hasAdd && (
|
||||||
|
<button
|
||||||
|
id="kc-add-titles-1"
|
||||||
|
type="button"
|
||||||
|
className="pf-c-button pf-m-inline pf-m-link"
|
||||||
|
onClick={() =>
|
||||||
|
dispatchFormAction({
|
||||||
|
"action": "add value to multi-valued attribute",
|
||||||
|
"name": attribute.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{msg("add value")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InputFiledByType(props: {
|
||||||
|
attribute: Attribute;
|
||||||
|
index: number;
|
||||||
|
value: string;
|
||||||
|
formValidationDispatch: React.Dispatch<FormAction>;
|
||||||
|
getClassName: UserProfileFormFieldsProps["getClassName"];
|
||||||
|
i18n: I18n;
|
||||||
|
}) {
|
||||||
|
const { attribute, formValidationDispatch, getClassName, i18n } = props;
|
||||||
|
|
||||||
|
/*
|
||||||
|
<#macro inputFieldByType attribute>
|
||||||
|
<#switch attribute.annotations.inputType!''>
|
||||||
|
<#case 'textarea'>
|
||||||
|
<@textareaTag attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#case 'select'>
|
||||||
|
<#case 'multiselect'>
|
||||||
|
<@selectTag attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#case 'select-radiobuttons'>
|
||||||
|
<#case 'multiselect-checkboxes'>
|
||||||
|
<@inputTagSelects attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#default>
|
||||||
|
<#if attribute.multivalued && attribute.values?has_content>
|
||||||
|
<#list attribute.values as value>
|
||||||
|
<@inputTag attribute=attribute value=value!''/>
|
||||||
|
</#list>
|
||||||
|
<#else>
|
||||||
|
<@inputTag attribute=attribute value=attribute.value!''/>
|
||||||
|
</#if>
|
||||||
|
</#switch>
|
||||||
|
</#macro>
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (attribute.annotations.inputType) {
|
||||||
|
case "textarea":
|
||||||
|
return <textareaTag {...props} />;
|
||||||
|
case "select":
|
||||||
|
case "multiselect":
|
||||||
|
return <selectTag {...props} />;
|
||||||
|
case "select-radiobuttons":
|
||||||
|
case "multiselect-checkboxes":
|
||||||
|
return <inputTagSelects {...props} />;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user