Skip to content

Commit

Permalink
Feature: Add auto-generated, unique field names (impress-org#65)
Browse files Browse the repository at this point in the history
* wip

* wip

* wip

* wip

* refactor: Scope field name to custom field

* wip: Add help text to field name control

* feature: Validate field name as required, slug

* refactor: Use field name in fields API

* refactor: move settings into seperate folder and destrcut props inline the function

* fix: missing useSelect deps

* refactor: move slugify into common folder

* refactor: destruct FieldSettings props inline and rename useFieldNames to useFieldNameValidator

Co-authored-by: Jon Waldstein <jonwaldstein@jons-air.mynetworksettings.com>
  • Loading branch information
kjohnson and Jon Waldstein authored Oct 3, 2022
1 parent ecc3a31 commit 826bb7a
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 115 deletions.
3 changes: 0 additions & 3 deletions packages/form-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@
"setupFiles": [
"react-app-polyfill/jsdom"
],
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.js"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
Expand Down
Empty file modified packages/form-builder/src/App.js
100755 → 100644
Empty file.
1 change: 1 addition & 0 deletions packages/form-builder/src/blocks/fields/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const email = {
multiple: false,
},
attributes: {
...settings.attributes,
lock: {remove: true},
label: {
default: __('Email Address'),
Expand Down
103 changes: 90 additions & 13 deletions packages/form-builder/src/blocks/fields/field.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,100 @@
import {Icon} from '@wordpress/icons';
import {__} from "@wordpress/i18n";
import settings from "./settings";
import {__} from '@wordpress/i18n';
import defaultSettings from './settings';
import DefaultFieldSettings from './settings/DefaultFieldSettings';
import {useFieldNameValidator} from '../../hooks';
import {InspectorAdvancedControls} from '@wordpress/block-editor';
import {ExternalLink, PanelRow, TextControl} from '@wordpress/components';
import {slugify} from '../../common';
import {useCallback} from '@wordpress/element';

function FieldSettings({attributes, setAttributes}) {
const {fieldName, label} = attributes;
const validateFieldName = useFieldNameValidator();

const updateFieldName = useCallback(
(newFieldName) => {
setAttributes({
fieldName: slugify(newFieldName),
});
},
[setAttributes]
);

const enforceUniqueFieldName = useCallback(() => {
const [isUnique, suggestedName] = validateFieldName(fieldName);
if (!isUnique) {
updateFieldName(suggestedName);
}
}, [fieldName, updateFieldName, validateFieldName]);

const enforceRequiredValue = useCallback(() => {
if (!fieldName) {
updateFieldName(label);
}
}, [fieldName, updateFieldName, label]);

return (
<>
<DefaultFieldSettings
attributes={attributes}
setAttributes={setAttributes}
onLabelTextControlBlur={(event) => {
if (!fieldName) {
updateFieldName(event.target.value);
enforceUniqueFieldName();
}
}}
/>
<InspectorAdvancedControls>
<PanelRow>
<TextControl
label={__('Field Name', 'give')}
value={fieldName}
help={[
<>{__('The programmatic name of the field used by the Fields API.', 'give')}</>,
<ExternalLink
style={{display: 'block', marginTop: '8px'}}
href="https://github.com/impress-org/givewp/tree/develop/src/Framework/FieldsAPI"
>
{__('Learn more about the Fields API', 'give')}
</ExternalLink>,
]}
onChange={updateFieldName}
onBlur={() => {
enforceRequiredValue();
enforceUniqueFieldName();
}}
/>
</PanelRow>
</InspectorAdvancedControls>
</>
);
}

const field = {
name: 'custom-block-editor/field',
category: 'custom',
settings: {
...settings,
...defaultSettings,
title: __('Text', 'custom-block-editor'),
icon: () => <Icon icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.8449 6.89062L7.05176 16.5H9.1684L9.89932 14.6484H14.1006L14.8316 16.5H16.9482L13.155 6.89062H10.8449ZM10.6765 12.6797L12 9.32658L13.3235 12.6797H10.6765Z"
fill="#000C00" />
<path
d="M18 2.625H6V0.75H0.75V6H2.625V18H0.75V23.25H6V21.375H18V23.25H23.25V18H21.375V6H23.25V0.75H18V2.625ZM2.25 4.5V2.25H4.5V4.5H2.25ZM4.5 21.75H2.25V19.5H4.5V21.75ZM18 19.875H6V18H4.125V6H6V4.125H18V6H19.875V18H18V19.875ZM21.75 19.5V21.75H19.5V19.5H21.75ZM19.5 2.25H21.75V4.5H19.5V2.25Z"
fill="#000C00" />
</svg>
} />,
icon: () => (
<Icon
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.8449 6.89062L7.05176 16.5H9.1684L9.89932 14.6484H14.1006L14.8316 16.5H16.9482L13.155 6.89062H10.8449ZM10.6765 12.6797L12 9.32658L13.3235 12.6797H10.6765Z"
fill="#000C00"
/>
<path
d="M18 2.625H6V0.75H0.75V6H2.625V18H0.75V23.25H6V21.375H18V23.25H23.25V18H21.375V6H23.25V0.75H18V2.625ZM2.25 4.5V2.25H4.5V4.5H2.25ZM4.5 21.75H2.25V19.5H4.5V21.75ZM18 19.875H6V18H4.125V6H6V4.125H18V6H19.875V18H18V19.875ZM21.75 19.5V21.75H19.5V19.5H21.75ZM19.5 2.25H21.75V4.5H19.5V2.25Z"
fill="#000C00"
/>
</svg>
}
/>
),
edit: FieldSettings,
},
};

Expand Down
4 changes: 0 additions & 4 deletions packages/form-builder/src/blocks/fields/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,5 @@ const fieldBlocks = [
amount,
];

const fieldBlockNames = fieldBlocks.map(field => field.name);

export default fieldBlocks;
export {
fieldBlockNames,
};
89 changes: 0 additions & 89 deletions packages/form-builder/src/blocks/fields/settings.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {PanelBody, PanelRow, TextControl, ToggleControl} from '@wordpress/components';
import {__} from '@wordpress/i18n';
import {InspectorControls} from '@wordpress/block-editor';
import {noop} from 'lodash';

export default function DefaultFieldSettings({attributes, setAttributes, onLabelTextControlBlur = noop}) {
const {label, placeholder, isRequired, options} = attributes;
const requiredClass = isRequired ? 'give-is-required' : '';

return (
<>
<div>
{'undefined' === typeof options ? (
<TextControl
label={label}
placeholder={placeholder}
required={isRequired}
className={requiredClass}
/>
) : (
<select>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
)}
</div>

<InspectorControls>
<PanelBody title={__('Field Settings', 'give')} initialOpen={true}>
<PanelRow>
<TextControl
label={__('Label', 'give')}
value={label}
onChange={(val) => setAttributes({label: val})}
onBlur={onLabelTextControlBlur}
/>
</PanelRow>
<PanelRow>
<TextControl
label={__('Placeholder', 'give')}
value={placeholder}
onChange={(val) => setAttributes({placeholder: val})}
/>
</PanelRow>
<PanelRow>
<ToggleControl
label={__('Required', 'give')}
checked={isRequired}
onChange={() => setAttributes({isRequired: !isRequired})}
/>
</PanelRow>
</PanelBody>
</InspectorControls>
</>
);
}
44 changes: 44 additions & 0 deletions packages/form-builder/src/blocks/fields/settings/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {__} from '@wordpress/i18n';
import DefaultFieldSettings from './DefaultFieldSettings';

const defaultSettings = {
title: __('Field', 'custom-block-editor'),

supports: {
html: false, // Removes support for an HTML mode.
multiple: true,
},

attributes: {
fieldName: {
type: 'string',
source: 'attribute',
},
label: {
type: 'string',
source: 'attribute',
default: __('Text Field', 'give'),
},
placeholder: {
type: 'string',
source: 'attribute',
default: '',
},
isRequired: {
type: 'boolean',
source: 'attribute',
default: false,
},
options: {
type: 'array',
},
},

edit: DefaultFieldSettings,

save: function () {
return null; // Save as attributes - not rendered HTML.
},
};

export default defaultSettings;
2 changes: 1 addition & 1 deletion packages/form-builder/src/common/currency.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CurrencyInput, {formatValue} from 'react-currency-input-field';

const {currency = 'USD'} = window.storageData?.currency ?? {};
const {currency = 'USD'} = window?.storageData?.currency ?? {};

const Currency = ({amount}) => {
return formatValue({
Expand Down
1 change: 1 addition & 0 deletions packages/form-builder/src/common/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export {default as Storage} from './storage';
export {default as slugify} from './slugify';
7 changes: 7 additions & 0 deletions packages/form-builder/src/common/slugify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function slugify(value){
return value
.toLowerCase()
.replace(/\s|_/g, '-') // Replace spaces and underscores with dashes
.replace(/[^a-zA-Z\d\s-]/g, '') // Replace non-alphanumeric characters (other than dashes)
.replace(/-$/g, ''); // Remove trailing dash
};
7 changes: 3 additions & 4 deletions packages/form-builder/src/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import useToggleState from "./useToggleState";
import useFieldNameValidator from './useFieldNameValidator';
import useToggleState from './useToggleState';

export {
useToggleState,
};
export {useFieldNameValidator, useToggleState};
Loading

0 comments on commit 826bb7a

Please sign in to comment.