Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit 376f610

Browse files
author
Greg Rickaby
authored
Merge pull request #175 from WebDevStudios/feature/114-file-upload-field
Feature/114 file upload field
2 parents 54a5c7a + bee842d commit 376f610

File tree

9 files changed

+234
-20
lines changed

9 files changed

+234
-20
lines changed

components/blocks/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ export {default as BlockShortcode} from './Gutenberg/BlockShortcode'
1717
export {default as BlockSpacer} from './Gutenberg/BlockSpacer'
1818
export {default as BlockTable} from './Gutenberg/BlockTable'
1919
export {default as BlockEmbed} from './Gutenberg/BlockEmbed'
20+
export {default as BlockGravityForm} from './Gutenberg/BlockGravityForm'
2021
export {default as LzbBlockHero} from './LazyBlocks/LzbBlockHero'
2122
export {default as LzbBlockMediaText} from './LazyBlocks/LzbBlockMediaText'

components/molecules/Form/Form.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import cn from 'classnames'
77
/**
88
* Render Form component.
99
*
10-
* @param {object} props The component attributes as props.
11-
* @param {Element} props.children Form children elements.
12-
* @param {string} props.className Form wrapper class.
13-
* @param {object} props.formDefaults Formik default data.
14-
* @param {string|number} props.id Form id.
15-
* @param {object} props.validationSchema Yup validation schema object.
16-
* @param {Function} props.onSubmit Function to execute when form is submitted
17-
* @return {Element} The Form component.
10+
* @param {object} props The component attributes as props.
11+
* @param {Element|Function} props.children Form children elements.
12+
* @param {string} props.className Form wrapper class.
13+
* @param {object} props.formDefaults Formik default data.
14+
* @param {string|number} props.id Form id.
15+
* @param {object} props.validationSchema Yup validation schema object.
16+
* @param {Function} props.onSubmit Function to execute when form is submitted
17+
* @return {Element} The Form component.
1818
*/
1919
export default function Form({
2020
children,
@@ -24,22 +24,29 @@ export default function Form({
2424
validationSchema,
2525
onSubmit
2626
}) {
27+
let formattedChildren = children
28+
if ('function' !== typeof children) {
29+
formattedChildren = () => children
30+
}
31+
2732
return (
2833
<Formik
2934
initialValues={formDefaults}
3035
validationSchema={validationSchema}
3136
onSubmit={onSubmit}
3237
>
33-
<FormikForm id={id} className={cn(styles.form, className)}>
34-
{children}
35-
<button type="submit">Submit</button>
36-
</FormikForm>
38+
{(formikProps) => (
39+
<FormikForm id={id} className={cn(styles.form, className)}>
40+
{formattedChildren(formikProps)}
41+
<button type="submit">Submit</button>
42+
</FormikForm>
43+
)}
3744
</Formik>
3845
)
3946
}
4047

4148
Form.propTypes = {
42-
children: PropTypes.node,
49+
children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
4350
className: PropTypes.string,
4451
formDefaults: PropTypes.object,
4552
id: PropTypes.string,

components/molecules/GravityForm/Fields/Fields.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import * as GfFields from '.'
55
* Render the Fields component.
66
*
77
* @author WebDevStudios
8-
* @param {object} props The component attributes as props.
9-
* @param {Array} props.fields GravityForm fields data.
10-
* @return {Element} The Fields component.
8+
* @param {object} props The component attributes as props.
9+
* @param {Array} props.fields GravityForm fields data.
10+
* @param {object} props.formikProps Props available to Formik components.
11+
* @return {Element} The Fields component.
1112
*/
12-
export default function Fields({fields}) {
13+
export default function Fields({fields, formikProps}) {
1314
return (
1415
<>
1516
{fields.length > 0 &&
@@ -42,6 +43,20 @@ export default function Fields({fields}) {
4243
fieldToRender = <GfFields.Text {...field.node} key={id} />
4344
break
4445

46+
case 'textarea':
47+
fieldToRender = <GfFields.Textarea {...field.node} key={id} />
48+
break
49+
50+
case 'fileupload':
51+
fieldToRender = (
52+
<GfFields.File
53+
{...field.node}
54+
setFieldValue={formikProps.setFieldValue}
55+
key={id}
56+
/>
57+
)
58+
break
59+
4560
default:
4661
fieldToRender = (
4762
<pre key={id}>
@@ -58,7 +73,8 @@ export default function Fields({fields}) {
5873

5974
Fields.propTypes = {
6075
fields: PropTypes.array,
61-
setFormValidation: PropTypes.func
76+
setFormValidation: PropTypes.func,
77+
formikProps: PropTypes.object
6278
}
6379

6480
Fields.defaultProps = {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import PropTypes from 'prop-types'
2+
import {getGfFieldId, getGfHiddenClassName} from '@/functions/gravityForms'
3+
import {Field} from 'formik'
4+
import InputError from '@/components/atoms/Inputs/InputError'
5+
import styles from '@/components/atoms/Inputs/Text/Text.module.css'
6+
import cn from 'classnames'
7+
8+
/**
9+
* Render GravityForms File field component.
10+
*
11+
* @param {object} props GravityForm Select field as props.
12+
* @param {string} props.className GravityForm field wrapper class.
13+
* @param {string} props.description GravityForm field description.
14+
* @param {string|number} props.id GravityForm field id.
15+
* @param {boolean} props.isRequired GravityForm field is required.
16+
* @param {string} props.label GravityForm field label.
17+
* @param {boolean} props.visibility GravityForm visibility option.
18+
* @param {Function} props.setFieldValue Formik function to set state.
19+
* @return {Element} The File component.
20+
*/
21+
export default function File({
22+
className,
23+
description,
24+
id,
25+
isRequired,
26+
label,
27+
visibility,
28+
setFieldValue
29+
}) {
30+
const fieldId = getGfFieldId(id)
31+
const isHiddenClass = getGfHiddenClassName(visibility)
32+
const thisClassName = cn(className, isHiddenClass)
33+
34+
return (
35+
<div className={cn(styles.text, thisClassName)}>
36+
{label && (
37+
<label htmlFor={id} required={isRequired}>
38+
{label}
39+
</label>
40+
)}
41+
<Field
42+
aria-required={isRequired}
43+
id={fieldId}
44+
name={fieldId}
45+
required={isRequired}
46+
type="file"
47+
onChange={(e) => {
48+
// Save to _filedata here so we don't corrupt state.
49+
setFieldValue(`${fieldId}_filedata`, e.currentTarget.files[0])
50+
}}
51+
/>
52+
{description && <p>{description}</p>}
53+
<InputError name={id} />
54+
</div>
55+
)
56+
}
57+
58+
File.propTypes = {
59+
className: PropTypes.string,
60+
description: PropTypes.string,
61+
id: PropTypes.number.isRequired,
62+
isRequired: PropTypes.bool,
63+
label: PropTypes.string,
64+
selectChoices: PropTypes.arrayOf(PropTypes.object),
65+
visibility: PropTypes.string,
66+
setFieldValue: PropTypes.func
67+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import PropTypes from 'prop-types'
2+
import {getGfFieldId, getGfHiddenClassName} from '@/functions/gravityForms'
3+
import {Field} from 'formik'
4+
import InputError from '@/components/atoms/Inputs/InputError'
5+
import styles from '@/components/atoms/Inputs/Text/Text.module.css'
6+
import cn from 'classnames'
7+
8+
/**
9+
* Render GravityForms Textarea field component.
10+
*
11+
* @param {object} props GravityForm Select field as props.
12+
* @param {string} props.className GravityForm field wrapper class.
13+
* @param {string} props.description GravityForm field description.
14+
* @param {string|number} props.id GravityForm field id.
15+
* @param {boolean} props.isRequired GravityForm field is required.
16+
* @param {string} props.label GravityForm field label.
17+
* @param {boolean} props.visibility GravityForm visibility option.
18+
* @return {Element} The File component.
19+
*/
20+
export default function TextArea({
21+
className,
22+
description,
23+
id,
24+
isRequired,
25+
label,
26+
visibility
27+
}) {
28+
const fieldId = getGfFieldId(id)
29+
const isHiddenClass = getGfHiddenClassName(visibility)
30+
const thisClassName = cn(className, isHiddenClass)
31+
32+
return (
33+
<div className={cn(styles.text, thisClassName)}>
34+
{label && (
35+
<label htmlFor={id} required={isRequired}>
36+
{label}
37+
</label>
38+
)}
39+
<Field
40+
aria-required={isRequired}
41+
id={fieldId}
42+
name={fieldId}
43+
required={isRequired}
44+
as="textarea"
45+
style={{borderWidth: '1px'}}
46+
/>
47+
{description && <p>{description}</p>}
48+
<InputError name={id} />
49+
</div>
50+
)
51+
}
52+
53+
TextArea.propTypes = {
54+
className: PropTypes.string,
55+
description: PropTypes.string,
56+
id: PropTypes.number.isRequired,
57+
isRequired: PropTypes.bool,
58+
label: PropTypes.string,
59+
selectChoices: PropTypes.arrayOf(PropTypes.object),
60+
visibility: PropTypes.string
61+
}

components/molecules/GravityForm/Fields/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export {default} from './Fields'
22
export {default as Checkbox} from './Checkbox'
33
export {default as Select} from './Select'
44
export {default as Text} from './Text'
5+
export {default as File} from './File'
6+
export {default as Textarea} from './Textarea'

components/molecules/GravityForm/GravityForm.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,55 @@ export default function GravityForm({
3131
formDefaults={fieldDefaults}
3232
id={formId && `gform-${formId}`}
3333
validationSchema={formValidationSchema}
34+
onSubmit={async (values) => {
35+
const wpBaseRequest = await fetch('/api/wp/getWPUrl')
36+
const wpBaseObject = await wpBaseRequest.json()
37+
const wpBase = wpBaseObject.wpApiUrlBase
38+
39+
const formApiUrl = `${wpBase}wp-json/gf/v2/forms/${formId}/submissions`
40+
const formData = new FormData()
41+
const formKeys = Object.keys(values)
42+
43+
formData.append('form_id', formId)
44+
45+
formKeys.forEach((key) => {
46+
let fieldName = key.replaceAll('-', '_')
47+
if (fieldName.endsWith('_filedata')) {
48+
fieldName = fieldName.slice(0, -9)
49+
}
50+
fieldName = fieldName.replaceAll('field_', 'input_')
51+
52+
switch (typeof values[key]) {
53+
case 'undefined':
54+
break
55+
case 'object':
56+
if (values[key] instanceof Array) {
57+
values[key].forEach((arrayFieldValue, index) => {
58+
formData.append(`${fieldName}_${index + 1}`, arrayFieldValue)
59+
})
60+
} else {
61+
formData.append(fieldName, values[key])
62+
}
63+
break
64+
default:
65+
formData.append(fieldName, values[key])
66+
break
67+
}
68+
})
69+
70+
fetch(formApiUrl, {
71+
method: 'POST',
72+
mimeType: 'multipart/form-data',
73+
body: formData
74+
}).then((response) => response.json())
75+
}}
3476
>
35-
{title && <h2 className={styles.title}>{title}</h2>}
36-
{fieldData && <Fields fields={fieldData} />}
77+
{(formikProps) => (
78+
<>
79+
{title && <h2 className={styles.title}>{title}</h2>}
80+
{fieldData && <Fields fields={fieldData} formikProps={formikProps} />}
81+
</>
82+
)}
3783
</Form>
3884
)
3985
}

functions/displayBlock.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export default function displayBlock(block, index) {
4949
return <Blocks.BlockSeparator {...attributes} key={index} />
5050
case 'core/spacer':
5151
return <Blocks.BlockSpacer {...attributes} key={index} />
52+
case 'gravityforms/form':
53+
return <Blocks.BlockGravityForm attributes={attributes} key={index} />
5254
case 'lazyblock/mediatext':
5355
return <Blocks.LzbBlockMediaText attributes={attributes} key={index} />
5456
case 'lazyblock/hero':

pages/api/wp/getWPUrl.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {wpApiUrlBase} from '@/api/wordpress/connector'
2+
3+
/**
4+
* Load more posts for an archive.
5+
*
6+
* @author WebDevStudios
7+
* @param {object} req Instance of http.IncomingMessage.
8+
* @param {object} res Instance of http.ServerResponse.
9+
*/
10+
export default function getWPUrl(req, res) {
11+
res.status(200).send({wpApiUrlBase})
12+
}

0 commit comments

Comments
 (0)