gatsby-drupal-webform
Version:
React component for Drupal webforms
149 lines • 6.52 kB
JSX
import React, { useState, useMemo } from 'react';
import axios from 'axios';
import { getAttributeValue, formToJSON } from './utils';
import { WebformDebug, WebformInput, WebformSelect, WebformTextarea, WebformCheckbox, WebformCheckboxGroup, WebformButton, WebformText } from './components';
export const DEFAULT_SUBMIT_LABEL = 'Submit';
export class WebformError extends Error {
constructor(response) {
super();
this.response = response;
}
}
/**
* Render single webform element.
*/
export function renderWebformElement(customComponents, Button, element, error) {
const customComponentAPI = {
error
};
// Render using custom component if provided:
if (customComponents[element.type]) {
const CustomComponent = customComponents[element.type];
return <CustomComponent element={element} {...customComponentAPI}/>;
}
// Othervise select renderer based on element type:
switch (element.type) {
case 'textfield':
return <WebformInput element={{ ...element, type: 'text' }} {...customComponentAPI}/>;
case 'textarea':
return <WebformTextarea element={element} {...customComponentAPI}/>;
case 'tel':
case 'number':
case 'email':
case 'hidden':
return <WebformInput element={element} {...customComponentAPI}/>;
case 'checkbox':
case 'radio':
/** Render single checkbox or radio element. */
return <WebformCheckbox element={element} {...customComponentAPI}/>;
case 'checkboxes':
return <WebformCheckboxGroup element={{ ...element, type: 'checkbox' }} {...customComponentAPI}/>;
case 'radios':
return <WebformCheckboxGroup element={{ ...element, type: 'radio' }} {...customComponentAPI}/>;
case 'select':
return <WebformSelect element={element} {...customComponentAPI}/>;
case 'webform_markup':
case 'processed_text':
return <WebformText element={element} {...customComponentAPI}/>;
// Submit button
case 'webform_actions':
return (<div className="form-group">
<Button type="submit">{getAttributeValue('#submit__label', element) || DEFAULT_SUBMIT_LABEL}</Button>
</div>);
// Unknown element type -> render as json string
default:
return <WebformDebug element={element} error={error}/>;
}
}
/**
* Drupal webform react component.
*/
const Webform = ({ webform, customComponents, buttonComponent, ...props }) => {
const [errors, setErrors] = useState({});
const submitHandler = async (event) => {
event.preventDefault();
const target = event.currentTarget;
// Clear errors from previous submit.
setErrors({});
// Remove lingering css classes from previous submits.
target.classList.remove('form-submitting', 'form-error', 'form-submitted');
if ((!props.onValidate || props.onValidate(event)) && target.checkValidity()) {
// Let css know that this form was validated and is being submitted.
target.classList.add('was-validated', 'form-submitting');
// Serialize form data.
const data = formToJSON(target.elements);
// Post process serialized data:
// Some webform elements require specialized data formatting.
for (const element of webform.elements) {
if (data[element.name]) {
switch (element.type) {
case 'checkbox':
data[element.name] = data[element.name][0];
break;
}
}
}
try {
// If onSubmit returns false skip submitting to API.
if (props.onSubmit && (await props.onSubmit(data)) === false) {
target.classList.remove('form-submitting');
target.classList.add('form-submitted');
return;
}
// Submit form to API.
const response = await axios.post(props.endpoint, {
...props.extraData,
...data,
webform_id: webform.drupal_internal__id
});
if (response.data.error) {
throw new WebformError(response);
}
// Convey current form state.
target.classList.remove('form-submitting');
target.classList.add('form-submitted');
props.onSuccess && props.onSuccess(response.data, data);
}
catch (err) {
// API should return error structure if validation fails.
// We use that to render error messages to the form.
if (err.response && err.response.data.error) {
setErrors(err.response.data.error);
}
// Convey current form state.
target.classList.remove('form-submitting');
target.classList.add('form-error');
props.onError && props.onError(err, data);
}
}
else {
// Let css know this form was validated.
target.classList.add('was-validated', 'form-error');
}
};
/**
* Build and memonize webform elements
*
* Webform object should rarely change.
*/
const elements = useMemo(() => {
const Button = buttonComponent;
const ret = webform.elements.map((element) => (<React.Fragment key={element.name}>{renderWebformElement(customComponents, Button, element, errors[element.name])}</React.Fragment>));
// Render default submit button if it is not defined in elements array.
if (webform.elements.find((element) => element.type === 'webform_actions') === undefined) {
ret.push(<Button key="webform_actions__default_button" type="submit">
{DEFAULT_SUBMIT_LABEL}
</Button>);
}
return ret;
}, [webform, customComponents, buttonComponent, errors]);
return (<form onSubmit={submitHandler} id={props.id} className={props.className} style={props.style} noValidate={props.noValidate} data-webform-id={webform.drupal_internal__id}>
{elements}
</form>);
};
Webform.defaultProps = {
customComponents: {},
buttonComponent: WebformButton
};
export default Webform;
//# sourceMappingURL=Webform.jsx.map