Files
elixforms-web-pages/react/common/src/ElixFormsComponentAbstract.tsx
T

519 lines
21 KiB
TypeScript

import './App.css';
import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState';
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
import type { IElixFormsComponentCustomFormFieldFactory } from './IElixFormsComponentCustomFormFieldFactory';
import type { ElixFormsCheckboxOption, ElixFormsDropdownOption, ElixFormsRadioOption } from './ElixFormsTypes';
import { ElixFormsElement } from './ElixFormsElement';
import { QueryParamHelper } from './QueryParamHelper';
import { Component, type JSX } from 'react';
import React from 'react';
export default abstract class ElixFormsComponentAbstract extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> {
constructor(props: IElixFormsComponentProperties) {
super(props);
this.state = {
formData: {}
};
}
private checkboxValues = new Map<string, number[]>();
private mandatoryFormFieldNames: string[] = [
'RWE2_MODULE_ID',
'RWE2_REQUEST_ID',
'custom-workflow-back-url',
'custom-workflow-generic-id',
'custom-workflow-current-tabrel-genid',
'custom-workflow-source-field',
'crc',
'MODULE_TESTMODE_KEY',
'ELANG'
];
private createMandatoryFormFields(queryParams: URLSearchParams): JSX.Element {
const hiddenInputs: JSX.Element[] = [];
this.mandatoryFormFieldNames.forEach(paramName => {
hiddenInputs.push(this.createHiddenInput(paramName));
});
return <>{hiddenInputs}</>;
}
private createAdditionalFormFields(): React.ReactElement {
const schema = JSON.parse(this.props.additionalFieldsJson || '[]');
return (
<>
{schema.map((field: { key: React.Key | null | undefined; }) => (
<div key={field.key}>
{this.renderField(field)}
</div>
))}
</>
);
}
protected createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
return <></>;
}
private renderCustomFormFields(): JSX.Element {
const formFieldFactory = {
createBooleanInput: (name: string, label: string, required: boolean = false) =>
this.createBooleanInput(name, label, required),
createCheckboxInput: (name: string, label: string, options: Array<ElixFormsCheckboxOption>) =>
this.createCheckboxInput(name, label, options),
createDropdownInput: (name: string, label: string, options: Array<ElixFormsDropdownOption>, required: boolean = false) =>
this.createDropdownInput(name, label, options, required),
createHiddenInput: (name: string) =>
this.createHiddenInput(name),
createNumberInput: (name: string, label: string, required: boolean = false) =>
this.createNumberInput(name, label, required),
createRadioInput: (name: string, label: string, options: Array<ElixFormsRadioOption>, required: boolean = false) =>
this.createRadioInput(name, label, options, required),
createTextInput: (name: string, label: string, required: boolean = false) =>
this.createTextInput(name, label, required),
createTextAreaInput: (name: string, label: string, required: boolean = false) =>
this.createTextAreaInput(name, label, required),
};
return this.createCustomFormFields(formFieldFactory);
}
protected renderExtraContentPre(): JSX.Element | null {
return null;
}
protected renderExtraContentPost(): JSX.Element | null {
return null;
}
public override render(): React.ReactElement<IElixFormsComponentProperties> {
const {
isDarkTheme,
userDisplayName,
moduleName,
headerTitle,
headerHeroImageSrc: headerTitleImageSrc,
cardTitle,
cardDescription,
alertInfoTitle,
alertInfoMessage,
instructionsTitle,
instructionsMessage,
submitText,
} = this.props;
const queryParams = new URLSearchParams(window.location.search);
const missingMandatoryParams = this.mandatoryFormFieldNames.filter(paramName => !queryParams.has(paramName));
const hasAuthenticationError = missingMandatoryParams.length > 0;
return (
<div>
<header className="it-header-wrapper text-white">
{userDisplayName &&
<div className="it-header-slim-wrapper">
<div className="container">
<div className="row">
<div className="col-12">
<div className="it-header-slim-wrapper-content">
{/* <a className="d-none d-lg-block navbar-brand" href="#">Ente appartenenza</a> */}
<div className="nav-mobile">
{/* <nav aria-label="Navigazione accessoria">
<a className="it-opener d-lg-none" data-bs-toggle="collapse" href="#menu1a" role="button"
aria-expanded="false" aria-controls="menu4">
<span>Ente appartenenza</span>
<svg className="icon" aria-hidden="true">
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
</svg>
</a>
<div className="link-list-wrapper collapse" id="menu1a">
<ul className="link-list">
<li><a className="dropdown-item list-item" href="#">Link 1</a></li>
<li><a className="list-item active" href="#" aria-current="page">Link 2 (Attivo)</a></li>
</ul>
</div>
</nav> */}
</div>
<div className="it-header-slim-right-zone">
{/* <div className="nav-item dropdown">
<a className="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
<span className="visually-hidden">Selezione lingua: lingua selezionata</span>
<span>ITA</span>
<svg className="icon d-none d-lg-block">
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
</svg>
</a>
<div className="dropdown-menu">
<div className="row">
<div className="col-12">
<div className="link-list-wrapper">
<ul className="link-list">
<li><a className="dropdown-item list-item" href="#"><span>ITA <span
className="visually-hidden">selezionata</span></span></a></li>
<li><a className="dropdown-item list-item" href="#"><span>ENG</span></a></li>
</ul>
</div>
</div>
</div>
</div>
</div> */}
<div className="it-access-top-wrapper">
{userDisplayName}
</div>
</div>
</div>
</div>
</div>
</div>
</div>}
<div className="it-header-center-wrapper">
<div className="container">
<div className="it-header-center-content-wrapper">
<div className="it-brand-wrapper">
<a href="https://www.unipr.it/" title="Torna alla homepage dell'Università di Parma">
<img className='icon' src="https://procedure.unipr.it/UploadImgs/12_marchio_UNIPR_neg_b1_squareLogo.png" alt="Università di Parma" />
<div className="it-brand-text">
<p className="h2 no_toc">Università di Parma</p>
{/* <p className="h3 no_toc d-none d-md-block">Procedure Online</p> */}
</div>
</a>
</div>
<div className="it-right-zone">
{/* <div className="it-search-wrapper">
<span className="d-none d-md-block">Cerca</span>
<button className="search-link" aria-label="Cerca">
<span className="it-search"></span>
</button>
</div> */}
</div>
</div>
</div>
</div>
</header>
<div className="container my-2 mx-4">
<div className='cmp-breadcrumb' role='navigation'>
<nav className="breadcrumb-container" aria-label="breadcrumb">
<ol className="breadcrumb">
<li className='breadcrumb-item'>Procedure Online<span className="separator">&gt;</span></li>
<li className="breadcrumb-item active" aria-current="page">{moduleName}</li>
</ol>
</nav>
</div>
{headerTitleImageSrc && <img src={headerTitleImageSrc} alt="Hero Image" className="hero-image img-fluid mb-3" />}
{headerTitle && (<p className='h2'>{headerTitle}</p>)}
<div className="container">
<div className="card shadow-sm mb-4">
<div className="card-body">
{cardTitle && <p className="h3 mb-3">{cardTitle}</p>}
{cardDescription && <p className="lead text-secondary">{cardDescription}</p>}
{(alertInfoTitle || alertInfoMessage) &&
<div className="custom-alert custom-alert-warning" role="status">
{alertInfoTitle && <div><b>{alertInfoTitle}</b></div>}
{alertInfoMessage && <div className='small'>{alertInfoMessage}</div>}
</div>}
{hasAuthenticationError ? (
<div className="alert alert-danger" role="alert">Accesso non autorizzato!</div>
) : (
<>
{this.renderExtraContentPre()}
{(instructionsTitle || instructionsMessage) &&
<div className="custom-alert custom-alert-info" role="status">
{instructionsTitle && <div><b>{instructionsTitle}</b></div>}
{instructionsMessage && <div className='small'>{instructionsMessage}</div>}
</div>
}
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1" className="mt-3">
{this.createMandatoryFormFields(queryParams)}
{this.createAdditionalFormFields()}
{this.renderCustomFormFields()}
<div className="mt-4 d-flex justify-content-end">
<button type="submit" className="btn btn-primary">{submitText ?? 'INVIA'} <span className="it-arrow-right text-white ms-2"></span></button>
</div>
</form>
{this.renderExtraContentPost()}
</>
)}
</div>
</div>
</div>
</div>
<footer className="it-footer text-white">
<div className="it-footer-small-prints clearfix">
<div className="container">
<section>
<div className="row clearfix">
<div className="col-sm-12">
<div className="footer-content text-center p-2">
<div className="subfooter-top">powered by <span className="highlight">elixForms</span></div>
{/* <div className="subfooter-bottom"><div>versione 1.24.0</div></div> */}
</div>
</div>
</div>
</section>
</div>
</div>
</footer>
</div>
);
}
private createCheckboxInput(paramName: string, label: string, values: Array<ElixFormsCheckboxOption>): JSX.Element {
const checkedValues = QueryParamHelper.getCheckedFromQuery(paramName);
if (!this.checkboxValues.get(paramName)) {
this.checkboxValues.set(paramName, checkedValues);
}
const currentValue = this.state.formData[paramName] !== undefined
? this.state.formData[paramName]
: (this.checkboxValues.get(paramName)?.join(',').trim() ?? '');
const selectedValues = (currentValue || '').split(',').filter(Boolean).map((value: string) => Number(value));
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<>
<div className="d-flex flex-column gap-2" role="group" aria-label={label}>
{values.map((entry, entryIndex) => {
const id = `${paramName}_${entryIndex}`;
const isChecked = selectedValues.includes(entryIndex);
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="checkbox"
checked={isChecked}
onChange={(event) => {
const currentCheckboxValues = this.checkboxValues.get(paramName) ?? [];
const checkboxIndex = currentCheckboxValues.indexOf(entryIndex);
const nextValues = [...currentCheckboxValues];
if (event.target.checked && checkboxIndex === -1) {
nextValues.push(entryIndex);
} else if (!event.target.checked && checkboxIndex !== -1) {
nextValues.splice(checkboxIndex, 1);
}
this.checkboxValues.set(paramName, nextValues);
const newValue = nextValues.join(',').trim();
this.setState({ formData: { ...this.state.formData, [paramName]: newValue } });
}}
/>
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
</div>
);
})}
</div>
<input type="hidden" id={paramName} name={paramName} value={currentValue} />
</>
).render();
}
private createRadioInput(paramName: string, label: string, values: Array<ElixFormsRadioOption>, required: boolean = false): JSX.Element {
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
return new ElixFormsElement(
<span className="form-label fw-semibold">{label}</span>,
<div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
{values.map((entry) => {
const id = `${paramName}_${entry.value}`;
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="radio"
name={paramName}
value={entry.value}
checked={currentValue === String(entry.value)}
onChange={() => {
this.setState({ formData: { ...this.state.formData, [paramName]: String(entry.value) } });
}}
required={required}
/>
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
</div>
);
})}
</div>
).render();
}
private createBooleanInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
return new ElixFormsElement(
<span className="form-label fw-semibold">{label}</span>,
<div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
{[{ value: 'true', label: 'Sì' }, { value: 'false', label: 'No' }].map((val) => {
const id = `${paramName}_${val.value}`;
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="radio"
name={paramName}
value={val.value}
checked={currentValue === val.value}
onChange={() => {
this.setState({ formData: { ...this.state.formData, [paramName]: val.value } });
}}
required={required}
/>
<label className="form-check-label" htmlFor={id}>{val.label}</label>
</div>
);
})}
</div>
).render();
}
private createTextAreaInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<textarea
id={paramName}
name={paramName}
className="form-control"
rows={4}
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}}
/>
).render();
}
private createTextInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<input
id={paramName}
name={paramName}
className="form-control"
type="text"
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}}
/>
).render();
}
private createHiddenInput(paramName: string): JSX.Element {
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '';
return <input type="hidden" id={`${paramName}_hidden`} name={paramName} value={textValue} />;
}
private createNumberInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<input
id={paramName}
name={paramName}
className="form-control"
type="number"
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}}
/>
).render();
}
private createDropdownInput(paramName: string, label: string, values: Array<ElixFormsDropdownOption>, required: boolean = false): JSX.Element {
const queryValue = QueryParamHelper.getOptionFromQuery(paramName);
const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (queryValue?.toString() ?? '');
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<select
id={paramName}
name={paramName}
className="form-select"
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}}
>
<option value="">Seleziona...</option>
{values.map(entry => (
<option key={entry.value} value={entry.value}>{entry.label}</option>
))}
</select>
).render();
}
private renderField(field: any): JSX.Element {
switch (field.type) {
case 'text':
return this.createTextInput(field.key, field.label, field.required ?? false);
case 'textarea':
return this.createTextAreaInput(field.key, field.label, field.required ?? false);
case 'radio':
return this.createRadioInput(field.key, field.label,
field.options.map((o: any) => ({ value: o.value, label: o.label })),
field.required ?? false);
case 'boolean':
return this.createBooleanInput(field.key, field.label, field.required ?? false);
case 'checkbox':
return this.createCheckboxInput(field.key, field.label,
field.options.map((o: any) => ({ value: o.value, label: o.label, required: o.required ?? false })));
case 'number':
return this.createNumberInput(field.key, field.label, field.required ?? false);
case 'dropdown':
return this.createDropdownInput(field.key, field.label,
field.options.map((o: any) => ({ value: o.value, label: o.label })),
field.required ?? false);
default:
return <></>;
}
}
}