Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forms: Allow ability to add hidden fields #40860

Draft
wants to merge 16 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d7025d0
add hidden fields PoC/draft
CGastrell Nov 25, 2024
bfe9d55
Allow form hidden fields with no values to be displayed on the front-…
coder-karen Jan 6, 2025
7883243
Ensure new hidden field name / value pairs can be added with a button…
coder-karen Jan 6, 2025
21a3c42
Ensure hidden field name duplicates result in a warning message.
coder-karen Jan 6, 2025
d37c00e
Remove previously added error_log and console.log, as well as fixing …
coder-karen Jan 6, 2025
b9c76d5
Use fixed-name hidden field to save organization ID, for salesforce v…
coder-karen Jan 7, 2025
6326ba8
Ensure hidden fields are correctly located and not duplicated on CSV …
coder-karen Jan 8, 2025
00c577b
Add new formID attribute for future extensibility
coder-karen Jan 8, 2025
2b83b36
Move hidden fields check and add filters so fields can be dynamically…
coder-karen Jan 13, 2025
ab7b558
Allow for re-usable 'template' hidden fields.
coder-karen Jan 13, 2025
00a6840
Removing salesforce org_id conversion to hiddenFields for now but lea…
coder-karen Jan 13, 2025
40ce460
Remove formID attribute for now, but keep currentPostId from original…
coder-karen Jan 13, 2025
02635a4
Filter cleanup: Correct documentation, ensuring one filter does not d…
coder-karen Jan 13, 2025
ec10b1e
Merge branch 'trunk' into add/forms-hidden-field-option
coder-karen Jan 13, 2025
9551f1b
Further filter tweaks - ensuring name and value fields are in correct…
coder-karen Jan 13, 2025
09a304b
Merge branch 'trunk' into add/forms-hidden-field-option
coder-karen Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/packages/forms/changelog/add-forms-hidden-fields
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Forms: add handling for hidden fields
12 changes: 12 additions & 0 deletions projects/packages/forms/src/blocks/contact-form/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export default {
type: 'string',
default: '',
},
formID: {
type: 'string',
default: '',
},
// salesforce integration: these don't make sense except on the variation.
// needed to persist in order show editor options and backend submit process
salesforceData: {
Expand All @@ -42,4 +46,12 @@ export default {
sendToSalesforce: false,
},
},
hiddenFields: {
type: 'array',
default: [],
},
warningMessage: {
type: 'string',
default: '',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,18 @@ public static function gutenblock_render_form( $atts, $content ) {

self::load_view_scripts();

return Contact_Form::parse( $atts, do_blocks( $content ) );
$form_html = Contact_Form::parse( $atts, do_blocks( $content ) );

/**
* Custom filter to modify the rendered contact form HTML,
* useful for populating hidden fields.
*
* @since $$next-version$$
*
* @param string $form_html The complete form HTML.
* @param array $atts The block attributes.
*/
return apply_filters( 'grunion_modify_rendered_contact_form_html', $form_html, $atts );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we've used grunion_ a lot in code (and probably filters) in the past but I thin we could move towards jetpack_ prefix by now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I noticed that later as well, but instead of changing it I've removed this filter entirely and will be replacing with another elsewhere. I'll send you a message when ready (shortly), as the PR isn't good for reviewing just yet (additional items to be removed as well, will cause confusion otherwise.).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool 👍 sorry for jumping early onto this, I appreciate it's in draft still :-)

}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseControl, PanelBody, TextControl, ExternalLink, Path } from '@wordpress/components';
import { Fragment, useState } from '@wordpress/element';
import { Fragment, useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import HelpMessage from '../../components/help-message';
import { getIconColor } from '../../util/block-icons';
Expand Down Expand Up @@ -82,6 +82,14 @@ export const salesforceLeadFormVariation = {
organizationId: '',
sendToSalesforce: true,
},
hiddenFields: [
{
uuid: crypto.getRandomValues( new Uint32Array( 1 ) )[ 0 ],
name: 'organization_id',
value: '',
edit: 'value',
},
],
style: {
spacing: {
padding: {
Expand All @@ -95,22 +103,18 @@ export const salesforceLeadFormVariation = {
},
};

export default ( { salesforceData, setAttributes, instanceId } ) => {
export default ( { salesforceData, hiddenFields, setAttributes, instanceId } ) => {
const [ organizationIdError, setOrganizationIdError ] = useState( false );

const setSalesforceData = attributePair => {
setAttributes( {
salesforceData: {
...salesforceData,
...attributePair,
},
} );
};

const setOrganizationId = value => {
setOrganizationIdError( false );
setSalesforceData( { organizationId: value.trim() } );
};
useEffect( () => {
if (
salesforceData.organizationId &&
! hiddenFields.find( field => field.name === 'organization_id' )?.value
) {
setOrganizationId( salesforceData.organizationId );
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- we only want to run this once
}, [] );

const onBlurOrgIdField = e => {
setOrganizationIdError( ! e.target.value.trim().match( /^[a-zA-Z0-9]{15,18}$/ ) );
Expand All @@ -126,13 +130,29 @@ export default ( { salesforceData, setAttributes, instanceId } ) => {
'font-size': '11px',
};

const setOrganizationId = newValue => {
const updatedFields = hiddenFields.map( field => {
if ( field.name === 'organization_id' ) {
return {
...field,
value: newValue,
};
}
return field;
} );

setAttributes( {
hiddenFields: updatedFields,
} );
};

return (
<Fragment>
<PanelBody title={ __( 'Salesforce', 'jetpack-forms' ) } initialOpen={ true }>
<BaseControl __nextHasNoMarginBottom={ true }>
<TextControl
label={ __( 'Organization ID', 'jetpack-forms' ) }
value={ salesforceData.organizationId || '' }
value={ hiddenFields.find( field => field.name === 'organization_id' )?.value || '' }
placeholder={ __( 'Enter your Organization ID', 'jetpack-forms' ) }
onBlur={ onBlurOrgIdField }
onChange={ setOrganizationId }
Expand Down
176 changes: 174 additions & 2 deletions projects/packages/forms/src/blocks/contact-form/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import {
Notice,
} from '@wordpress/components';
import { compose, withInstanceId } from '@wordpress/compose';
import { withDispatch, withSelect } from '@wordpress/data';
import { withDispatch, withSelect, useSelect } from '@wordpress/data';
import { forwardRef, Fragment, useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { filter, get, isArray, map } from 'lodash';
import { filter, get, isArray, map, remove } from 'lodash';
import { childBlocks } from './child-blocks';
import InspectorHint from './components/inspector-hint';
import { ContactFormPlaceholder } from './components/jetpack-contact-form-placeholder';
Expand Down Expand Up @@ -100,9 +100,15 @@ export const JetpackContactFormEdit = forwardRef(
customThankyouMessage,
customThankyouRedirect,
jetpackCRM,
formID,
salesforceData,
hiddenFields,
warningMessage,
} = attributes;
const [ isPatternsModalOpen, setIsPatternsModalOpen ] = useState( false );
const [ localWarningText, setLocalWarningText ] = useState( '' );

const currentPostId = useSelect( select => select( 'core/editor' ).getCurrentPostId(), [] );

const blockProps = useBlockProps();
const { isLoadingModules, isChangingStatus, isModuleActive, changeStatus } =
Expand Down Expand Up @@ -159,6 +165,31 @@ export const JetpackContactFormEdit = forwardRef(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] );

useEffect( () => {
if ( ! hiddenFields.length ) {
setAttributes( {
hiddenFields: [
{
uuid: crypto.getRandomValues( new Uint32Array( 1 ) )[ 0 ],
name: '',
value: '',
edit: 'both',
},
],
} );
}
} );

// Here we're using a default value for the formID, but this will be expanded to allow custom form IDs in the future.
useEffect( () => {
if ( ! formID ) {
if ( currentPostId ) {
setAttributes( { formID: currentPostId || '' } );
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this to run once.
}, [] );

const renderSubmissionSettings = () => {
return (
<>
Expand Down Expand Up @@ -273,6 +304,118 @@ export const JetpackContactFormEdit = forwardRef(
);
};

const setHiddenField = ( key, newName, newValue, editMode ) => {
const newHiddenFields = map( hiddenFields, ( { uuid, name, value, edit } ) => {
const hiddenField = {
uuid,
name,
value,
edit,
};
if ( key === uuid ) {
hiddenField.name = editMode === 'both' || editMode === 'name' ? newName : name;
hiddenField.value = editMode === 'both' || editMode === 'value' ? newValue : value;
}
return hiddenField;
} );

remove( newHiddenFields, hf => ! hf.name.trim() && ! hf.value.trim() );
checkForDuplicatesAndUpdateMessage( newHiddenFields );

setAttributes( {
hiddenFields: newHiddenFields,
} );
};

const addNewHiddenField = () => {
const newField = {
uuid: crypto.getRandomValues( new Uint32Array( 1 ) )[ 0 ],
name: '',
value: '',
edit: 'both',
};

const updatedHiddenFields = [ ...hiddenFields, newField ];

setAttributes( {
hiddenFields: updatedHiddenFields,
} );
};

const checkForDuplicatesAndUpdateMessage = () => {
const warningMessageText = __(
'Duplicate field names are not recommended.',
'jetpack-forms'
);

const hasAnyDuplicates = hiddenFields.some(
field =>
field.name.trim() !== '' &&
hiddenFields.filter( f => f.name.trim() === field.name.trim() ).length > 1
);

if ( hasAnyDuplicates ) {
setAttributes( {
warningMessage: warningMessageText,
} );
} else {
setAttributes( { warningMessage: '' } );
}
};

const validateHiddenFieldName = ( uuid, name ) => {
const isDuplicateName = hiddenFields.some(
field => field.uuid !== uuid && field.name.trim() === name.trim()
);
const warningMessageText = __(
'Duplicate field names are not recommended.',
'jetpack-forms'
);

if ( isDuplicateName ) {
setAttributes( {
warningMessage: warningMessageText,
} );
setLocalWarningText( warningMessageText );
} else {
checkForDuplicatesAndUpdateMessage( hiddenFields );
setLocalWarningText( '' );
}
};

const HiddenFieldInspector = ( props, setter ) => {
const { uuid, name, value, edit = 'both' } = props;
return (
<div
key={ uuid }
style={ { display: 'flex' } }
className="jetpack-contact-form__hidden-fields-panel"
>
{ ( edit === 'both' || edit === 'name' ) && (
<TextControl
value={ name }
placeholder={ __( 'Field name', 'jetpack-forms' ) }
onChange={ fieldName => setter( uuid, fieldName, value, edit ) }
onBlur={ () => validateHiddenFieldName( uuid, name ) }
/>
) }
{ ( ! edit || edit === 'value' || edit === 'none' ) && (
<span className="jetpack-contact-form__hidden-fields-panel-name">{ name }</span>
) }
{ ( edit === 'both' || edit === 'value' ) && (
<TextControl
value={ value }
placeholder={ __( 'Field value', 'jetpack-forms' ) }
onChange={ fieldValue => setter( uuid, name, fieldValue, edit ) }
/>
) }
{ ( ! edit || edit === 'name' || edit === 'none' ) && (
<span className="jetpack-contact-form__hidden-fields-panel-value">{ value }</span>
) }
</div>
);
};

let elt;

if ( ! isModuleActive ) {
Expand Down Expand Up @@ -322,6 +465,7 @@ export const JetpackContactFormEdit = forwardRef(
{ isSalesForceExtensionEnabled && salesforceData?.sendToSalesforce && (
<SalesforceLeadFormSettings
salesforceData={ salesforceData }
hiddenFields={ hiddenFields }
setAttributes={ setAttributes }
instanceId={ instanceId }
/>
Expand All @@ -344,6 +488,34 @@ export const JetpackContactFormEdit = forwardRef(
</PanelBody>
</Fragment>
) }
<PanelBody title={ __( 'Hidden Fields', 'jetpack-forms' ) }>
<InspectorHint>
{ __(
"Use hidden fields to get fixed data alongside visitor's submissions.",
'jetpack-forms'
) }
</InspectorHint>
<div className="jetpack-contact-form__hidden-fields-panel-wrapper">
{ map( hiddenFields, ( { uuid, name, value, edit } ) => {
return HiddenFieldInspector( { uuid, name, value, edit }, setHiddenField );
} ) }
{ ( localWarningText || warningMessage ) && (
<p className="jetpack-contact-form__hidden-fields-warning-message">
{ localWarningText || warningMessage }
</p>
) }
<Button
variant="secondary"
className="jetpack-contact-form__add-hidden-field"
onClick={ () => addNewHiddenField() }
disabled={ hiddenFields.some(
field => ! field.name.trim() && ! field.value.trim()
) }
>
{ __( 'Add Hidden Field', 'jetpack-forms' ) }
</Button>
</div>
</PanelBody>
</InspectorControls>

<div className={ formClassnames } style={ style } ref={ ref }>
Expand Down
Loading
Loading