UNPKG

gatsby-drupal-webform

Version:
216 lines 7.32 kB
import { useState, useEffect } from 'react'; import { formToJSON } from './formToJSON'; /** Webform attribute names that map to object that can be spread to <input> elements. */ export const HTML_ATTRIBUTE_WHITELIST = ['required', 'defaultValue', 'disabled', 'readonly', 'placeholder', 'multiple']; // Re-export export { formToJSON }; /** * Check if form element passes webform state conditions. * * @param element controlling element * @param conditions webform conditions * * @return true if element passes all conditions */ function checkConditions(element, conditions) { return Object.entries(conditions).every(([condition, value]) => { // Value might be null if this condition is not defined for this state. // Return true indicates this condition is ignored. if (value == null) { return true; } // Check condition: switch (condition) { case 'checked': return element.checked === true; case 'unchecked': return element.checked === false; case 'empty': return element.value === ''; case 'filled': return element.value !== ''; case 'value': if (element.type === 'checkbox' || element.type === 'radio') { return element.value === value && element.checked; } return element.value === value; default: return false; } }); } /** * Check if group of form element passes webform state conditions. * * @param element controlling elements * @param conditions webform conditions * * @return true if any element in the group passes all conditions */ function checkGroupConditions(elements, conditions) { for (let i = 0; i < elements.length; ++i) { const element = elements[i]; // Return true if any element passes all conditions. if (checkConditions(element, conditions)) { return true; } } return false; } /** * Attempt to guess initial state. * * This is to have something for the initial render. This is an attempt to * reduce flickering and elements jumping around. * * Guesses: * - If invisible|visible states are present assume they are used to initally hide. * * @param states */ function guessInitialState(states) { const initialState = {}; for (const state of states) { switch (state.state) { case 'invisible': initialState[state.state] = true; break; case 'visible': default: initialState[state.state] = false; break; } } return initialState; } function handleOnChange(state, setState, event) { const { state: stateName, condition } = state; const element = event.currentTarget; setState(prevState => ({ ...prevState, [stateName]: checkConditions(element, condition) })); } function useWebformStates(webformStates) { const [states, setStates] = useState(() => guessInitialState(webformStates)); useEffect(() => { const initialState = {}; // Keep track of all elements and their callbacks so they can be cleaned. const callbacks = []; // This loop does nothing if webformStates is empty. for (const state of webformStates) { // Webform uses jQuery selector syntax. The selector is prefixed with ':input'. // Strip leading ':' and hope this will not break horribly. :) // // This does not work with textarea or select elements... // // https://api.jquery.com/input-selector/ const selector = state.selector.substr(1); const elements = document.querySelectorAll(selector); // Compute initial value for each state. initialState[state.state] = checkGroupConditions(elements, state.condition); // Setup listener for state change. elements.forEach(element => { const callback = handleOnChange.bind(null, state, setStates); element.addEventListener('change', callback); callbacks.push([element, callback]); }); } // Set calculated initial state. if (Object.entries(initialState).length > 0) { setStates(initialState); } return () => { // Remove all callbacks. for (const [element, callback] of callbacks) { element.removeEventListener('change', callback); } }; }, webformStates); return states; } /** * Return true if attribute should be spred to <input /> elements. * * @param attribute attribute name */ function isHTMLAttribute(attribute) { // Compare attribute name against whitelist. return HTML_ATTRIBUTE_WHITELIST.includes(attribute); } /** * Transform webform attribute name * * @param attribute attribute name */ function transformAttributeName(attribute) { // Handle special cases in a switch statement. switch (attribute) { case '#readonly': return 'readOnly'; case '#default_value': return 'defaultValue'; // Remove leading '#': default: return attribute.substr(1); } } /** * Transform WebformElement * * @param element webform element * @param options custom properties */ export function useWebformElement(element, options) { const states = useWebformStates(element.states || []); const inputProps = {}; const attributes = {}; /** * This will parse webform attributes and return object containing * the props that you can spread over the <input /> element and other * unrecognized attributes. */ element.attributes.forEach(attribute => { const transformedAttribute = transformAttributeName(attribute.name); if (isHTMLAttribute(transformedAttribute)) { inputProps[transformedAttribute] = attribute.value; } attributes[transformedAttribute] = attribute.value; }); // Convert inputProps to correct type. Object.assign(inputProps, { required: inputProps.required === '1' || inputProps.required === 'true' }); if (options) { // Overwrite inputProps with option props. Object.assign(inputProps, options); } return [inputProps, { attributes, states }]; } export function getOptionId(name, value) { return `form-${name}-${value.toLowerCase()}`.replace(/[_\s]/g, '-'); } /** * Generate id for form element * * @param name element name */ export function getElementId(name) { return `form-${name}`.replace(/[_\s]/g, '-'); } /** * Get and return attribute from array of WebformAttributes. * * @param attributeName name of the attribute to find. * @param attributes list of attributes to search. * @returns value of attribute if found or undefined. */ export function getAttributeValue(attributeName, element) { // Find desired attribte const attribute = element.attributes.find(attribute => attribute.name === attributeName); if (attribute) { return attribute.value; } return undefined; } //# sourceMappingURL=utils.jsx.map