├── .gitignore ├── elementNames.md ├── fieldComponents ├── AddressField.js ├── CheckboxField.js ├── EmailField.js ├── HiddenField.js ├── HtmlField.js ├── NameField.js ├── NumberField.js ├── PhoneField.js ├── RadioField.js ├── SectionField.js ├── SelectField.js ├── TextField.js └── index.js ├── gravityforms.zip ├── index.js ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /elementNames.md: -------------------------------------------------------------------------------- 1 | - Global: 2 | - formWrapper 3 | - formHeader 4 | - formTitle 5 | - formFooter 6 | - button 7 | - buttonText 8 | - AddressField: 9 | - fieldSubLabel 10 | - addressFieldSubLabel 11 | - fieldInput 12 | - addressFieldInput 13 | - fieldSubLabel 14 | - addressFieldSubLabel 15 | - fieldWrapper 16 | - addressFieldWrapper 17 | - fieldLabel 18 | - addressFieldLabel 19 | - fieldDescription 20 | - addressFieldDescription 21 | - CheckboxField: 22 | - choiceWrapper 23 | - checkboxChoiceWrapper 24 | - checkboxButtonWrapper 25 | - checkboxButton 26 | - selectedCheckboxButton 27 | - choiceTextWrapper 28 | - checkboxChoiceTextWrapper 29 | - fieldWrapper 30 | - checkboxFieldWrapper 31 | - fieldLabel 32 | - checkboxFieldLabel 33 | - fieldDescription 34 | - checkboxFieldDescription 35 | - EmailField: 36 | - fieldWrapper 37 | - textFieldWrapper 38 | - fieldLabel 39 | - emailFieldLabel 40 | - fieldDescription 41 | - emailFieldDescription 42 | - fieldInput 43 | - textFieldInput 44 | - HiddenField (Just in case you want to un-hide them for any reason): 45 | - fieldWrapper 46 | - hiddenFieldWrapper 47 | - fieldLabel 48 | - hiddenFieldLabel 49 | - fieldDescription 50 | - hiddenFieldDescription 51 | - fieldInput 52 | - hiddenFieldInput 53 | - HiddenField: 54 | - fieldWrapper 55 | - htmlFieldWrapper 56 | - NameField: 57 | - fieldInput 58 | - fieldSelect 59 | - fieldSelectText 60 | - fieldSelectArrow 61 | - fieldSubLabel 62 | - nameFieldSubLabel 63 | - fieldInput 64 | - nameFieldInput 65 | - fieldSubLabel 66 | - nameFieldSubLabel 67 | - fieldWrapper 68 | - nameFieldWrapper 69 | - fieldLabel 70 | - nameFieldLabel 71 | - fieldDescription 72 | - nameFieldDescription 73 | - NumberField: 74 | - fieldWrapper 75 | - numberFieldWrapper 76 | - fieldLabel 77 | - numberFieldLabel 78 | - fieldDescription 79 | - numberFieldDescription 80 | - fieldInput 81 | - numberFieldInput 82 | - PhoneField: 83 | - fieldWrapper 84 | - phoneFieldWrapper 85 | - fieldLabel 86 | - phoneFieldLabel 87 | - fieldDescription 88 | - phoneFieldDescription 89 | - fieldInput 90 | - phoneFieldInput 91 | - RadioField: 92 | - choiceWrapper 93 | - radioChoiceWrapper 94 | - radioButtonWrapper 95 | - radioButton 96 | - selectedRadioButton 97 | - choiceText 98 | - radioChoiceText 99 | - fieldWrapper 100 | - radioFieldWrapper 101 | - fieldLabel 102 | - radioFieldLabel 103 | - fieldDescription 104 | - radioFieldDescription 105 | - SectionField: 106 | - fieldWrapper 107 | - sectionFieldWrapper 108 | - fieldLabel 109 | - sectionFieldLabel 110 | - fieldDescription 111 | - sectionFieldDescription 112 | - headerRow 113 | - sectionHeaderRow 114 | - SelectField: 115 | - fieldWrapper 116 | - selectFieldWrapper 117 | - fieldLabel 118 | - selectFieldLabel 119 | - fieldDescription 120 | - selectFieldDescription 121 | - fieldInput 122 | - fieldSelect 123 | - fieldSelectText 124 | - fieldSelectArrow 125 | - TextField: 126 | - fieldWrapper 127 | - textFieldWrapper 128 | - fieldLabel 129 | - textFieldLabel 130 | - fieldDescription 131 | - textFieldDescription 132 | - fieldInput 133 | - textFieldInput 134 | -------------------------------------------------------------------------------- /fieldComponents/AddressField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput, Picker } from 'react-native' 3 | 4 | export default class AddressField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(field, text, input) { 13 | this.props.onChange(field, text, input) 14 | } 15 | 16 | render() { 17 | const inputs = this.data.inputs.map(input => { 18 | const items = input.choices && input.choices.map((choice, index) => { 19 | return 20 | }) 21 | if (input.isHidden) return 22 | if (input.choices) { 23 | return ( 24 | 25 | this.handleChange(this.data.id, value, input.id)} 28 | > 29 | {items} 30 | 31 | {input.label} 32 | 33 | ) 34 | } 35 | return ( 36 | 37 | this.handleChange(this.data.id, text, input.id)} 40 | placeholder={input.placeholder} 41 | value={this.props.value[input.id]} 42 | /> 43 | {input.label} 44 | 45 | ) 46 | }) 47 | return ( 48 | 49 | {this.data.label.length > 0 && 50 | {this.data.label} 51 | } 52 | {this.data.description.length > 0 && 53 | {this.data.description} 54 | } 55 | {inputs} 56 | 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /fieldComponents/CheckboxField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native' 3 | import HTML from 'react-native-render-html' 4 | 5 | export default class CheckboxField extends Component { 6 | constructor(props) { 7 | super(props) 8 | this.data = this.props.data 9 | this.style = this.props.style 10 | this.handleChange = this.handleChange.bind(this) 11 | } 12 | 13 | handleChange(field, value, input) { 14 | this.props.onChange(field, value, input) 15 | } 16 | 17 | render() { 18 | const inputs = this.data.inputs.map((input, index) => { 19 | return ( 20 | this.handleChange(this.data.id, this.props.value[input.id] == this.data.choices[index].value ? false : this.data.choices[index].value, input.id)} 24 | > 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | ) 37 | }) 38 | return ( 39 | 40 | {this.data.label.length > 0 && 41 | {this.data.label} 42 | } 43 | {this.data.description.length > 0 && 44 | {this.data.description} 45 | } 46 | {inputs} 47 | 48 | ) 49 | } 50 | } 51 | 52 | const styles = StyleSheet.create({ 53 | selectedButton: { 54 | backgroundColor: '#000', 55 | flex: 1, 56 | borderRadius: 1, 57 | }, 58 | buttonWrapper: { 59 | borderColor: '#000', 60 | borderWidth: 2, 61 | borderStyle: 'solid', 62 | borderRadius: 4, 63 | width: 16, 64 | height: 16, 65 | overflow: 'hidden', 66 | padding: 2, 67 | }, 68 | }) -------------------------------------------------------------------------------- /fieldComponents/EmailField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput } from 'react-native' 3 | 4 | export default class EmailField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(text) { 13 | this.props.onChange(this.data.id, text) 14 | } 15 | 16 | render() { 17 | return ( 18 | 19 | {this.data.label.length > 0 && 20 | {this.data.label} 21 | } 22 | {this.data.description.length > 0 && 23 | {this.data.description} 24 | } 25 | this.handleChange(text)} 28 | placeholder={this.data.placeholder} 29 | value={this.props.value} 30 | /> 31 | 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fieldComponents/HiddenField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput } from 'react-native' 3 | 4 | export default class HiddenField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(number) { 13 | this.props.onChange(this.data.id, number) 14 | } 15 | 16 | render() { 17 | return ( 18 | 19 | {this.data.label.length > 0 && 20 | {this.data.label} 21 | } 22 | {this.data.description.length > 0 && 23 | {this.data.description} 24 | } 25 | this.handleChange(number)} 28 | placeholder={this.data.placeholder} 29 | value={this.props.value} 30 | /> 31 | 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fieldComponents/HtmlField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View } from 'react-native' 3 | import HTML from 'react-native-render-html' 4 | 5 | export default class HtmlField extends Component { 6 | constructor(props) { 7 | super(props) 8 | this.data = this.props.data 9 | this.style = this.props.style 10 | } 11 | 12 | render() { 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fieldComponents/NameField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput, Picker, TouchableOpacity, StyleSheet } from 'react-native' 3 | 4 | export default class NameField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | this.state = { 11 | selecting: false, 12 | } 13 | } 14 | 15 | handleChange(field, text, input) { 16 | this.props.onChange(field, text, input) 17 | } 18 | 19 | // TODO: Separate out into components 20 | render() { 21 | const inputs = this.data.inputs.map(input => { 22 | if (input.isHidden) return 23 | if (input.choices) { 24 | const items = input.choices && input.choices.map((choice, index) => { 25 | return 26 | }) 27 | return ( 28 | 29 | {this.state.selecting && 30 | { 33 | this.handleChange(this.data.id, value, input.id) 34 | this.setState({ selecting: false }) 35 | }} 36 | > 37 | {items} 38 | 39 | || 40 | 41 | this.setState({ selecting: true })}> 42 | {this.props.value[input.id]} 43 | 44 | 45 | {input.label} 46 | 47 | } 48 | 49 | ) 50 | } 51 | return ( 52 | 53 | this.handleChange(this.data.id, text, input.id)} 56 | placeholder={input.placeholder} 57 | value={this.props.value[input.id]} 58 | /> 59 | {input.label} 60 | 61 | ) 62 | }) 63 | return ( 64 | 65 | {this.data.label.length > 0 && 66 | {this.data.label} 67 | } 68 | {this.data.description.length > 0 && 69 | {this.data.description} 70 | } 71 | {inputs} 72 | 73 | ) 74 | } 75 | } 76 | 77 | const styles = StyleSheet.create({ 78 | fieldSelect: { 79 | flexDirection: 'row', 80 | justifyContent: 'space-between', 81 | alignItems: 'center', 82 | }, 83 | arrow: { 84 | borderStyle: 'solid', 85 | borderTopColor: '#000', 86 | borderTopWidth: 10, 87 | borderLeftColor: 'transparent', 88 | borderLeftWidth: 7, 89 | borderRightColor: 'transparent', 90 | borderRightWidth: 7, 91 | width: 0, 92 | } 93 | }) -------------------------------------------------------------------------------- /fieldComponents/NumberField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput } from 'react-native' 3 | 4 | export default class NumberField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(text) { 13 | this.props.onChange(this.data.id, text) 14 | } 15 | 16 | render() { 17 | return ( 18 | 19 | {this.data.label.length > 0 && 20 | {this.data.label} 21 | } 22 | {this.data.description.length > 0 && 23 | {this.data.description} 24 | } 25 | this.handleChange(text)} 28 | placeholder={this.data.placeholder} 29 | value={this.props.value} 30 | /> 31 | 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fieldComponents/PhoneField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput } from 'react-native' 3 | 4 | export default class PhoneField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(number) { 13 | this.props.onChange(this.data.id, number) 14 | } 15 | 16 | render() { 17 | return ( 18 | 19 | {this.data.label.length > 0 && 20 | {this.data.label} 21 | } 22 | {this.data.description.length > 0 && 23 | {this.data.description} 24 | } 25 | this.handleChange(number)} 28 | placeholder={this.data.placeholder} 29 | value={this.props.value} 30 | /> 31 | 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fieldComponents/RadioField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TouchableOpacity, StyleSheet } from 'react-native' 3 | 4 | export default class RadioField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.style = this.props.style 9 | this.handleChange = this.handleChange.bind(this) 10 | } 11 | 12 | handleChange(field, value) { 13 | this.props.onChange(field, value) 14 | } 15 | 16 | render() { 17 | const choices = this.data.choices.map((choice, index) => { 18 | return ( 19 | this.handleChange(this.data.id, choice.value)} 23 | > 24 | 25 | 30 | 31 | {choice.text} 32 | 33 | ) 34 | }) 35 | return ( 36 | 37 | {this.data.label.length > 0 && 38 | {this.data.label} 39 | } 40 | {this.data.description.length > 0 && 41 | {this.data.description} 42 | } 43 | {choices} 44 | 45 | ) 46 | } 47 | } 48 | 49 | const styles = StyleSheet.create({ 50 | selectedButton: { 51 | backgroundColor: '#000', 52 | flex: 1, 53 | borderRadius: 999, 54 | }, 55 | buttonWrapper: { 56 | borderColor: '#000', 57 | borderWidth: 2, 58 | borderStyle: 'solid', 59 | borderRadius: 999, 60 | width: 16, 61 | height: 16, 62 | overflow: 'hidden', 63 | padding: 2, 64 | }, 65 | }) -------------------------------------------------------------------------------- /fieldComponents/SectionField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, StyleSheet } from 'react-native' 3 | 4 | export default class SectionField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.style = this.props.style 9 | } 10 | 11 | render() { 12 | return ( 13 | 14 | {this.data.label.length > 0 && 15 | {this.data.label} 16 | } 17 | {this.data.description.length > 0 && 18 | {this.data.description} 19 | } 20 | 21 | 22 | ) 23 | } 24 | } 25 | 26 | const styles = StyleSheet.create({ 27 | headerRow: { 28 | borderBottomColor: '#888', 29 | borderBottomWidth: 1, 30 | borderStyle: 'solid', 31 | }, 32 | }) -------------------------------------------------------------------------------- /fieldComponents/SelectField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, Picker, TouchableOpacity, StyleSheet } from 'react-native' 3 | 4 | export default class SelectField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | this.state = { 11 | selecting: false, 12 | } 13 | } 14 | 15 | handleChange(field, text) { 16 | this.props.onChange(field, text) 17 | this.setState({ selecting: false }) 18 | } 19 | 20 | render() { 21 | const items = this.data.choices && this.data.choices.map((choice, index) => { 22 | return 23 | }) 24 | return ( 25 | 26 | {this.data.label.length > 0 && 27 | {this.data.label} 28 | } 29 | {this.data.description.length > 0 && 30 | {this.data.description} 31 | } 32 | {this.state.selecting && 33 | this.handleChange(this.data.id, value)} 36 | > 37 | {items} 38 | 39 | || 40 | this.setState({ selecting: true })}> 41 | {this.props.value} 42 | 43 | 44 | } 45 | 46 | ) 47 | } 48 | } 49 | 50 | const styles = StyleSheet.create({ 51 | fieldSelect: { 52 | flexDirection: 'row', 53 | justifyContent: 'space-between', 54 | alignItems: 'center', 55 | }, 56 | arrow: { 57 | borderStyle: 'solid', 58 | borderTopColor: '#000', 59 | borderTopWidth: 10, 60 | borderLeftColor: 'transparent', 61 | borderLeftWidth: 7, 62 | borderRightColor: 'transparent', 63 | borderRightWidth: 7, 64 | width: 0, 65 | } 66 | }) -------------------------------------------------------------------------------- /fieldComponents/TextField.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { View, Text, TextInput, StyleSheet } from 'react-native' 3 | 4 | export default class TextField extends Component { 5 | constructor(props) { 6 | super(props) 7 | this.data = this.props.data 8 | this.handleChange = this.handleChange.bind(this) 9 | this.style = this.props.style 10 | } 11 | 12 | handleChange(text) { 13 | this.props.onChange(this.data.id, text) 14 | } 15 | 16 | styles() { 17 | return StyleSheet.create(this.style) 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 | {this.data.label.length > 0 && 24 | {this.data.label} 25 | } 26 | {this.data.description.length > 0 && 27 | {this.data.description} 28 | } 29 | this.handleChange(text)} 32 | placeholder={this.data.placeholder} 33 | value={this.props.value} 34 | /> 35 | 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /fieldComponents/index.js: -------------------------------------------------------------------------------- 1 | // TODO: add support for 'website', 'date', 'time', 'textarea', and 'fileupload' field types 2 | export { default as AddressField } from './AddressField' 3 | export { default as CheckboxField } from './CheckboxField' 4 | export { default as EmailField } from './EmailField' 5 | export { default as HiddenField } from './HiddenField' 6 | export { default as HtmlField } from './HtmlField' 7 | export { default as NameField } from './NameField' 8 | export { default as NumberField } from './NumberField' 9 | export { default as PhoneField } from './PhoneField' 10 | export { default as RadioField } from './RadioField' 11 | export { default as SectionField } from './SectionField' 12 | export { default as SelectField } from './SelectField' 13 | export { default as TextField } from './TextField' 14 | -------------------------------------------------------------------------------- /gravityforms.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sethcwhiting/react-native-gravityform/cf08dc12e8746220727274e8a1da7e66120aa621/gravityforms.zip -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { ScrollView, View, Text, TouchableOpacity } from 'react-native' 3 | // import ValidationComponent from 'react-native-form-validator' 4 | import base64 from 'react-native-base64' 5 | import { 6 | AddressField, 7 | CheckboxField, 8 | EmailField, 9 | HiddenField, 10 | HtmlField, 11 | NameField, 12 | NumberField, 13 | PhoneField, 14 | RadioField, 15 | SectionField, 16 | SelectField, 17 | TextField, 18 | } from './fieldComponents' 19 | 20 | export default class GravityForm extends Component { 21 | constructor(props) { 22 | super(props) 23 | this.siteURL = this.props.siteURL 24 | this.formID = this.props.formID 25 | const credentials = this.props.credentials 26 | const credentialString = `${credentials.userName}:${credentials.password}` 27 | this.encodedCredentials = base64.encode(credentialString) 28 | this.style = this.props.style 29 | this.state = { 30 | formData: {}, 31 | fieldValues: {}, 32 | isLoading: true, 33 | isSending: false, 34 | submitSuccess: false, 35 | submitFailure: false, 36 | } 37 | this.handleFieldChange = this.handleFieldChange.bind(this) 38 | } 39 | 40 | componentDidMount() { 41 | this.fetchFormData() 42 | .then(formData => { 43 | this.setState({ formData }) 44 | return this.setDefaultValues(formData) 45 | }) 46 | .then(() => this.setState({ isLoading: false })) 47 | .catch(err => console.warn('There was a problem retrieving form data: ', err)) 48 | } 49 | 50 | fetchFormData() { 51 | return new Promise((resolve, reject) => { 52 | fetch(`${this.siteURL}/wp-json/gf/v2/forms/${this.formID}`, { 53 | method: 'GET', 54 | headers: { 55 | 'Accept': 'application/json', 56 | 'Content-Type': 'application/json', 57 | 'Authorization': `Basic ${this.encodedCredentials}`, 58 | } 59 | }) 60 | .then(response => response.json().then(formData => resolve(formData))) 61 | .catch(err => reject('ERROR: ', err)) 62 | }) 63 | } 64 | 65 | values = {} 66 | 67 | setDefaultValues(formData) { 68 | return new Promise((resolve, reject) => { 69 | let fieldCount = formData.fields.length 70 | formData.fields.forEach(field => { 71 | switch (field.type) { 72 | case 'html': 73 | case 'section': 74 | fieldCount-- 75 | break 76 | 77 | case 'name': 78 | case 'address': 79 | fieldCount = fieldCount + field.inputs.length 80 | this.populateComplexValues(field) 81 | break 82 | 83 | case 'checkbox': 84 | fieldCount = fieldCount + field.inputs.length 85 | this.populateCheckboxValues(field) 86 | break 87 | 88 | case 'radio': 89 | case 'select': 90 | this.populateChoiceValues(field) 91 | break 92 | 93 | default: 94 | if (field.id == '36') console.log(field) 95 | this.populateSimpleValue(field) 96 | break 97 | } 98 | }) 99 | if (Object.keys(this.state.fieldValues).length == fieldCount) resolve() 100 | setTimeout(() => reject('"setDefaultValues" function timed out. Field values never populated completely.'), 5000) 101 | }) 102 | } 103 | 104 | populateComplexValues(field) { 105 | field.inputs.forEach(input => { 106 | if (input.choices) { 107 | const selected = input.choices.filter(choice => choice.isSelected) 108 | this.setState({ 109 | fieldValues: { 110 | ...this.state.fieldValues, 111 | [input.id]: selected.length ? selected[0].value : '', 112 | [field.id]: { 113 | ...this.state.fieldValues[field.id], 114 | [input.id]: selected[0] ? selected[0].value : '' 115 | } 116 | } 117 | }) 118 | } else { 119 | this.setState({ 120 | fieldValues: { 121 | ...this.state.fieldValues, 122 | [input.id]: input.defaultValue ? input.defaultValue : '', 123 | [field.id]: { 124 | ...this.state.fieldValues[field.id], 125 | [input.id]: input.defaultValue ? input.defaultValue : '' 126 | } 127 | } 128 | }) 129 | } 130 | }) 131 | } 132 | 133 | populateCheckboxValues(field) { 134 | field.inputs.forEach((input, index) => { 135 | this.setState({ 136 | fieldValues: { 137 | ...this.state.fieldValues, 138 | [input.id]: field.choices[index].isSelected ? field.choices[index].value : false, 139 | [field.id]: { 140 | ...this.state.fieldValues[field.id], 141 | [input.id]: field.choices[index].isSelected ? field.choices[index].value : false, 142 | } 143 | } 144 | }) 145 | }) 146 | } 147 | 148 | populateChoiceValues(field) { 149 | const selected = field.choices.filter(choice => { 150 | return choice.isSelected 151 | }) 152 | this.setState({ fieldValues: { ...this.state.fieldValues, [field.id]: selected.length ? selected[0].value : '' } }) 153 | } 154 | 155 | populateSimpleValue(field) { 156 | this.setState({ fieldValues: { ...this.state.fieldValues, [field.id]: field.defaultValue } }) 157 | } 158 | 159 | fieldHidden(field) { 160 | if (field.visibility != 'visible') return true 161 | if (typeof field.conditionalLogic == 'object' && field.conditionalLogic !== null) { 162 | return this.handleConditionalLogic(field) 163 | } 164 | return false 165 | } 166 | 167 | handleConditionalLogic(field) { 168 | const rulesMet = field.conditionalLogic.rules.map(rule => { 169 | let conditionalValue = this.state.fieldValues[rule.fieldId] 170 | if (typeof conditionalValue == 'object') { 171 | matchKey = Object.keys(conditionalValue).filter(key => this.state.fieldValues[key] == rule.value)[0] 172 | conditionalValue = matchKey ? this.state.fieldValues[matchKey] : false 173 | } 174 | switch (rule.operator) { 175 | case 'is': 176 | return conditionalValue == rule.value 177 | 178 | case 'is not': 179 | return conditionalValue != rule.value 180 | 181 | case 'greater than': 182 | return conditionalValue > rule.value 183 | 184 | case 'less than': 185 | return conditionalValue < rule.value 186 | 187 | case 'contains': 188 | return conditionalValue.indexOf(rule.value) >= 0 189 | 190 | case 'starts with': 191 | return conditionalValue.indexOf(rule.value) == 0 192 | 193 | case 'ends with': 194 | return conditionalValue.indexOf(rule.value) == conditionalValue.length - rule.value.length 195 | 196 | } 197 | }) 198 | if (field.conditionalLogic.actionType == 'show') { 199 | return field.conditionalLogic.logicType == 'all' ? rulesMet.indexOf(false) >= 0 : rulesMet.indexOf(true) < 0 200 | } else { 201 | return field.conditionalLogic.logicType == 'all' ? rulesMet.indexOf(true) < 0 : rulesMet.indexOf(false) >= 0 202 | } 203 | } 204 | 205 | handleFieldChange(fieldId, value, inputId) { 206 | if (inputId) { 207 | this.setState({ 208 | fieldValues: { 209 | ...this.state.fieldValues, 210 | [inputId]: value, 211 | [fieldId]: { 212 | ...this.state.fieldValues[fieldId], 213 | [inputId]: value, 214 | }, 215 | } 216 | }) 217 | } else { 218 | this.setState({ 219 | fieldValues: { 220 | ...this.state.fieldValues, 221 | [fieldId]: value, 222 | } 223 | }) 224 | } 225 | } 226 | 227 | submitForm() { 228 | this.setState({ isSending: true }) 229 | let formData = {} 230 | let fieldCount = Object.keys(this.state.fieldValues).length 231 | Object.keys(this.state.fieldValues).forEach(key => { 232 | if (typeof this.state.fieldValues[key] == 'object' && this.state.fieldValues[key] !== null) { 233 | fieldCount-- 234 | } else { 235 | formData = { ...formData, [key]: this.state.fieldValues[key] } 236 | } 237 | if (Object.keys(formData).length == fieldCount) this.postFormData(formData) 238 | }) 239 | } 240 | 241 | postFormData(formData) { 242 | fetch(`${this.siteURL}/wp-json/gf/v2/forms/${this.formID}/entries`, { 243 | method: 'POST', 244 | headers: { 245 | 'Accept': 'application/json', 246 | 'Content-Type': 'application/json', 247 | 'Authorization': `Basic ${this.encodedCredentials}`, 248 | }, 249 | body: JSON.stringify(formData), 250 | }) 251 | .then(() => this.setState({ isSending: false })) 252 | .catch(err => console.error('ERROR: ', err)); 253 | } 254 | 255 | fieldComponents = { 256 | address: AddressField, 257 | checkbox: CheckboxField, 258 | email: EmailField, 259 | hidden: HiddenField, 260 | html: HtmlField, 261 | name: NameField, 262 | number: NumberField, 263 | phone: PhoneField, 264 | radio: RadioField, 265 | section: SectionField, 266 | select: SelectField, 267 | text: TextField, 268 | } 269 | 270 | render() { 271 | if (this.state.isLoading) { 272 | return ( 273 | 274 | Fetching Gravity Form... 275 | 276 | ) 277 | } 278 | if (this.state.isSending) { 279 | return ( 280 | 281 | Submitting Form... 282 | 283 | ) 284 | } 285 | let parentHidden = false 286 | const fields = this.state.formData.fields && this.state.formData.fields.map((field) => { 287 | if (Object.keys(this.fieldComponents).indexOf(field.type) < 0) { 288 | console.warn(`React Native Gravityform: No field component currently available for type "${field.type}".`) 289 | return 290 | } 291 | const FieldComponent = this.fieldComponents[field.type || 'text'] 292 | if (field.type == 'section') parentHidden = this.fieldHidden(field) 293 | return ( 294 | 298 | 304 | 305 | ) 306 | }) 307 | return ( 308 | 309 | {this.state.formData.title.length > 0 && !this.props.hideFormTitle && 310 | 311 | {this.state.formData.title} 312 | 313 | } 314 | {fields} 315 | {this.state.formData.button && 316 | 317 | this.submitForm()} style={this.style.button}> 318 | {this.state.formData.button.text} 319 | 320 | 321 | } 322 | 323 | ) 324 | } 325 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-gravityform", 3 | "version": "1.0.14", 4 | "description": "A component for including Gravity Forms on React Native apps via the Wordpress API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sethcwhiting/react-native-gravityform.git" 12 | }, 13 | "keywords": [ 14 | "React", 15 | "Native", 16 | "Gravity", 17 | "Form", 18 | "Forms", 19 | "GravityForms", 20 | "Component", 21 | "Wordpress", 22 | "API", 23 | "Headless", 24 | "CMS" 25 | ], 26 | "author": "Seth C Whiting", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/sethcwhiting/react-native-gravityform/issues" 30 | }, 31 | "homepage": "https://github.com/sethcwhiting/react-native-gravityform#readme", 32 | "devDependencies": { 33 | "react": "^16.7.0", 34 | "react-native": "^0.58.3", 35 | "react-native-base64": "0.0.2", 36 | "react-native-form-validator": "^0.2.0", 37 | "react-native-render-html": "^4.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ✨ React Native Gravityform ✨ 2 | 3 | [![Version](https://img.shields.io/npm/v/react-native-gravityform.svg)](https://www.npmjs.com/package/react-native-gravityform) 4 | 5 | This module includes a react native component for dropping Gravity Forms from your Wordpress site into your native applications. 6 | 7 | The package is both **Android** and **iOS** compatible. 8 | 9 | This project is compatible with Expo/CRNA without ejecting. 10 | 11 | ## Installation 12 | 13 | ``` 14 | $ npm install --save react-native-gravityform 15 | ``` 16 | 17 | The solution is implemented in JavaScript so no native module linking is required. 18 | 19 | ## Usage 20 | 21 | ### Authentication 22 | 23 | Gravity Forms requires that API requests be authenticated. In order to get this working, install Wordpress's [JSON Basic Authentication](https://github.com/WP-API/Basic-Auth) plugin. 24 | 25 | Once you've done this, make a file named something to the effect of `credentials.js` and add it anywhere in your project. The entire contents of this file should look something like this: 26 | 27 | ```javascript 28 | export default { 29 | userName: 'your-wp-username', 30 | password: 'your-wp-password', 31 | }; 32 | ``` 33 | 34 | It may be a good idea to make a new Wordpress user with read/write permissions for the sole purpose of posting to your Gravity Forms and include that new user's information to the file above. Also, **make sure to include this file in your `.gitignore` so no one ever sees this information.** 35 | 36 | Once your `credentials.js` file is all set, you can import it into any file: 37 | 38 | ```javascript 39 | import credentials from '...path_to/credentials'; 40 | ``` 41 | 42 | ### ✨ The GravityForm Component ✨ 43 | 44 | Import the GravityForm component: 45 | 46 | ```javascript 47 | import GravityForm from 'react-native-gravityform'; 48 | ``` 49 | 50 | Include the component anywhere inside your own components: 51 | 52 | ```javascript 53 | 60 | ``` 61 | 62 | ## Props 63 | 64 | ### siteURL 65 | 66 | The base URL for your Wordpress site where your Gravity Forms are hosted. 67 | 68 | ### formID 69 | 70 | The ID of the specific Gravity Form you want to display/post to. 71 | 72 | ### credentials 73 | 74 | The credentials you imported from the file you created in the [Authentication](#authentication) step above. 75 | 76 | ### style 77 | 78 | Out of the box, the GravityForm component is almost entirely unstyled, so you'll probably want to write your own styles for your fields. 79 | 80 | This can be done by including a new StyleSheet and referencing the _built-in element names_ ([see full list](https://github.com/sethcwhiting/react-native-gravityform/blob/master/elementNames.md)), like so: 81 | 82 | ```javascript 83 | const gformStyles = StyleSheet.create({ 84 | fieldInput: { 85 | color: '#224', 86 | backgroundColor: '#eee', 87 | padding: 15, 88 | marginBottom: 15, 89 | fontSize: 18, 90 | }, 91 | }); 92 | ``` 93 | 94 | ### hideFormTitle 95 | 96 | Choose wether you want your form title to be hidden or not. 97 | 98 | ## All Together Now 99 | 100 | Here is a basic example of how you would use the GravityForm component within one of your components: 101 | 102 | ```javascript 103 | import React, { Component } from 'react'; 104 | import { StyleSheet, View } from 'react-native'; 105 | import GravityForm from 'react-native-gravityform'; 106 | import credentials from '../Credentials'; 107 | 108 | const styles = StyleSheet.create({ 109 | container: { 110 | flex: 1, 111 | backgroundColor: '#fff', 112 | }, 113 | }); 114 | 115 | const gformStyles = StyleSheet.create({ 116 | fieldLabel: { 117 | fontWeight: 'bold', 118 | fontSize: 16, 119 | color: '#224', 120 | }, 121 | fieldInput: { 122 | color: '#224', 123 | backgroundColor: '#eee', 124 | padding: 15, 125 | marginBottom: 15, 126 | fontSize: 18, 127 | }, 128 | button: { 129 | backgroundColor: '#1c9', 130 | padding: 15, 131 | borderRadius: 15, 132 | }, 133 | buttonText: { 134 | color: '#fff', 135 | fontSize: 20, 136 | textAlign: 'center', 137 | fontWeight: 'bold', 138 | }, 139 | }); 140 | 141 | export default class ContactScreen extends Component { 142 | render() { 143 | return ( 144 | 145 | 152 | 153 | ); 154 | } 155 | } 156 | ``` 157 | 158 | ## Supported Fields 159 | 160 | The goal for this component is to support all [Standard](https://docs.gravityforms.com/form-fields/#standard-fields) and [Advanced](https://docs.gravityforms.com/form-fields/#advanced-fields) fields offered by Gravity Forms. 161 | 162 | The list of the fields currently supported by the GravityForm component are marked with a check mark below: 163 | 164 | **Standard:** 165 | 166 | - [x] Single Line Text 167 | - [ ] Paragraph Text 168 | - [x] Drop Down 169 | - [ ] Multi Select 170 | - [x] Number 171 | - [x] Checkboxes 172 | - [x] Radio Buttons 173 | - [x] Hidden 174 | - [x] HTML 175 | - [x] Section Break 176 | - [ ] Page Break 177 | 178 | **Advanced:** 179 | 180 | - [x] Name 181 | - [ ] Date 182 | - [ ] Time 183 | - [x] Phone 184 | - [x] Address 185 | - [ ] Website 186 | - [x] Email 187 | - [ ] File Upload 188 | - [ ] CAPTCHA 189 | - [ ] Password 190 | - [ ] List 191 | 192 | ## Conditional Logic 193 | 194 | Conditional Logic is included and should work right out of the box! 195 | 196 | ## Validation 197 | 198 | There is currently no form validation included with the GravityForm component. This is a major priority for the team and will be coming as soon as we can possibly get to it. 199 | 200 | ## Authors 201 | 202 | - [Seth C Whiting](https://github.com/sethcwhiting/) - Initial code - [@sethcwhiting](https://twitter.com/sethcwhiting) 203 | 204 | ## Contributing 205 | 206 | Pull requests are very welcome. 207 | --------------------------------------------------------------------------------