Skip to content

Instantly share code, notes, and snippets.

@Romakita
Last active November 4, 2019 06:59
Show Gist options
  • Save Romakita/16d8881fd991485d3b4d9afa646ccae1 to your computer and use it in GitHub Desktop.
Save Romakita/16d8881fd991485d3b4d9afa646ccae1 to your computer and use it in GitHub Desktop.
React formio formbuilder.jsx
import React from 'react'
import PropTypes from 'prop-types'
import cloneDeep from 'lodash/cloneDeep'
import AllComponents from 'formiojs/components'
import Components from 'formiojs/components/Components'
import FormioFormBuilder from 'formiojs/FormBuilder'
Components.setComponents(AllComponents)
const EVENTS = [
'addComponent',
'updateComponent',
'removeComponent',
'saveComponent',
'cancelComponent',
'moveComponent',
'editComponent',
'editJson',
'copyComponent',
'pasteComponent'
]
const EVENTS_CHANGE = ['addComponent', 'saveComponent', 'updateComponent', 'removeComponent']
function callLast (fn, time) {
let last = null
return (...args) => {
if (last) {
clearTimeout(last)
last = null
}
last = setTimeout(() => fn(...args), time)
}
}
async function createBuilder (el, { components = [], display, options, onChange, events = {} }) {
const form = {
display,
components: [...components]
}
try {
const builder = await (new FormioFormBuilder(el, form, options)).ready
const handleEvent = (event) => {
return (...args) => {
events[event] && events[event](...args)
if (EVENTS_CHANGE.includes(event)) {
onChange(builder.form.components)
}
}
}
EVENTS.forEach((event) => builder.on(event, callLast(handleEvent(event), 200)))
return builder
} catch (er) {
console.error(er)
}
}
export default class FormBuilder extends React.Component {
constructor (props) {
super(props)
this.state = {
display: props.display,
components: cloneDeep(props.components)
}
this.elRef = null
this.builderRef = null
}
async componentDidMount (): void {
const {
options, components,
onAddComponent,
onUpdateComponent,
onRemoveComponent,
onSaveComponent,
onCancelComponent,
onMoveComponent,
onEditComponent,
onEditJson,
onCopyComponent,
onPasteComponent
} = this.props
this.builderRef = await createBuilder(
this.elRef.firstChild,
{
options: { ...options },
components: cloneDeep(components),
onChange: this.whenComponentsChange.bind(this),
events: {
onAddComponent,
onUpdateComponent,
onRemoveComponent,
onSaveComponent,
onCancelComponent,
onMoveComponent,
onEditComponent,
onEditJson,
onCopyComponent,
onPasteComponent
}
}
)
}
componentWillUnmount (): void {
this.builderRef && this.builderRef.destroy()
}
componentWillReceiveProps (nextProps) {
if (this.builderRef) {
if (nextProps.display !== this.state.display) {
this.builderRef.form = {
display: nextProps.display,
components: this.state.components
}
} else if (nextProps.components !== this.state.components) {
this.builderRef.form = {
display: this.state.display,
components: nextProps.components
}
}
}
}
whenComponentsChange (components) {
this.setState({ components }, () => {
this.props.onChange(components)
})
}
render () {
return <div ref={(ref) => {
this.elRef = ref
}} onClick={(e) => e.stopPropagation()}>
<div/>
</div>
}
}
FormBuilder.defaultProps = {
options: {},
onChange: () => {
},
onReady: () => {
},
onDestroy: () => {
}
}
FormBuilder.propTypes = {
components: PropTypes.array,
display: PropTypes.string,
options: PropTypes.object,
onAddComponent: PropTypes.func,
onUpdateComponent: PropTypes.func,
onRemoveComponent: PropTypes.func,
onSaveComponent: PropTypes.func,
onCancelComponent: PropTypes.func,
onMoveComponent: PropTypes.func,
onEditComponent: PropTypes.func,
onEditJson: PropTypes.func,
onCopyComponent: PropTypes.func,
onPasteComponent: PropTypes.func
}
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import cloneDeep from 'lodash/cloneDeep'
import FormEditInformations from './FormEditInformations'
import FormBuilder from './FormBuilder'
function FormEdit ({ builderOptions = {}, formOptions = {}, form, saveForm }) {
const { _id, title, name, path, display, type, tags = ['common'], components, ...formAdditionalProps } = form
const [componentsState, setComponents] = useState(components)
const [displayState, setDisplay] = useState(display)
useEffect(() => {
setComponents(components)
}, [components])
return (
<div>
<FormEditInformations
key={`info-${_id}`}
id={_id}
title={title}
name={name}
path={path}
display={displayState}
type={type}
tags={tags}
options={formOptions}
onChange={(id, value) => {
if (id === 'display') {
setDisplay(value)
}
}}
onSubmit={(form) => {
saveForm({
...formAdditionalProps,
...form,
machineName: form.name,
components: cloneDeep(componentsState),
_id
})
}}
/>
<FormBuilder
key={`builder-${_id}`}
id={_id}
title={title}
display={displayState}
components={componentsState}
options={builderOptions}
onChange={(components) => {
setComponents(components)
}}
/>
</div>
)
}
FormEdit.propTypes = {
form: PropTypes.object.isRequired,
formOptions: PropTypes.object,
builderOptions: PropTypes.object,
saveForm: PropTypes.func
}
export default FormEdit
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import InputTags from './InputTags'
import mergeWith from 'lodash/mergeWith'
import isArray from 'lodash/isArray'
import uniqBy from 'lodash/uniqBy'
import camelCase from 'lodash/camelCase'
function FormControl ({ id, label, required, children }) {
return <div id={`form-group-${id}`} className="form-group">
<label htmlFor={id} className={'control-label ' + (required ? 'field-required' : '')}>{label}</label>
{children}
</div>
}
function Input ({ id, required, value = '', type = 'text', label, onChange, ...props }) {
return <FormControl id={id} required={required} label={label}>
<input
type={type}
className="form-control"
id={id}
name={id}
value={value}
onChange={(evt) => onChange(id, evt.currentTarget.value)}
{...props}
/>
</FormControl>
}
function Select ({ id, value, label, choices, required, onChange, ...props }) {
return <FormControl id={id} required={required} label={label}>
<div className="input-group">
<select
className="form-control"
name={id}
id={id}
value={value}
onChange={(evt) => onChange(id, evt.currentTarget.value)}
{...props}
>
{choices.map(({ value, label }) => <option key={value} value={value}>{label}</option>)}
</select>
</div>
</FormControl>
}
function SubmitButton ({ children, onClick, ...props }) {
return <div className="form-group">
<label>&nbsp;</label>
<div className="input-group">
<span className="btn btn-primary" style={{ 'width': '100%' }} onClick={onClick} {...props}>
{children}
</span>
</div>
</div>
}
function Tags ({ label, value, onChange, required, id, ...props }) {
return <FormControl id={id} label={label} required={required}>
<InputTags
id={id}
name={id}
value={value || ['common']}
onChange={(value) => onChange(id, value)}
{...props}/>
</FormControl>
}
function getFields (options) {
const customizer = (objValue, srcValue) =>
isArray(objValue) ? uniqBy(objValue.concat(srcValue), ({ value }) => value) : undefined
return Object
.entries(FormEditInformations.defaultFormOptions())
.reduce((list, [key, value]) => {
if (options[key] !== false) {
list.push(mergeWith(value, options[key] || {}, customizer))
}
return list
}, [])
}
function getClassName (id, cellClassName) {
return `${cellClassName} ${id === 'submit' ? 'save-buttons pull-right' : ''}`
}
function FormEditInformations ({ options, onChange, onSubmit, id: formId, title, name, path, display, type, tags }) {
const fields = getFields(options)
const [values, setForm] = useState({})
useEffect(() => {
setForm({
title,
name,
path,
display,
type,
tags
})
}, [title, name, path, display, type, tags])
const handleChange = (id, value) => {
if (id === 'title' && !formId) {
setForm({
...values,
[id]: value,
name: camelCase(value),
path: camelCase(value).toLowerCase()
})
} else {
setForm({
...values,
[id]: value
})
}
onChange(id, value)
}
return (
<div className="row">
{
fields
.map(({ id, Component, label, cellClassName, ...props }) => {
if (!id) return null
return (
<div key={id} className={getClassName(id, cellClassName)}>
{
id !== 'submit'
? <Component
{...props}
id={id}
value={values[id]}
label={label}
onChange={handleChange}/>
: <Component id={id} onClick={() => onSubmit(values)}>{label}</Component>
}
</div>
)
})
}
</div>
)
}
FormEditInformations.defaultFormOptions = () => ({
title: {
id: 'title',
label: 'Title',
Component: Input,
placeholder: 'Enter the form title',
required: true,
cellClassName: 'col-lg-6 col-md-6 col-sm-12'
},
name: {
id: 'name',
label: 'Name',
Component: Input,
placeholder: 'Enter the form machine name',
required: true,
cellClassName: 'col-lg-6 col-md-6 col-sm-12'
},
path: {
id: 'path',
label: 'Path',
Component: Input,
placeholder: 'example',
required: true,
cellClassName: 'col-lg-2 col-md-4 col-sm-4'
},
display: {
id: 'display',
label: 'Display as',
Component: Select,
cellClassName: 'col-lg-2 col-md-4 col-sm-4',
choices: [
{
label: 'Form',
value: 'form'
},
{
label: 'Resource',
value: 'resource'
}
]
},
type: {
id: 'type',
label: 'Type',
Component: Select,
cellClassName: 'col-lg-2 col-md-4 col-sm-4',
choices: [
{ label: 'Form', value: 'form' },
{ label: 'Wizard', value: 'wizard' },
{ label: 'PDF', value: 'pdf' }
]
},
tags: {
id: 'tags',
label: 'Tags',
Component: Tags,
cellClassName: 'col-lg-4 col-md-9 col-sm-12'
},
submit: {
id: 'submit',
label: 'Save',
Component: SubmitButton,
cellClassName: 'col-lg-2 col-md-3 col-sm-12'
}
})
FormEditInformations.propTypes = {
title: PropTypes.string,
name: PropTypes.string,
path: PropTypes.string,
display: PropTypes.string,
type: PropTypes.string,
tags: PropTypes.array,
options: PropTypes.object,
saveText: PropTypes.string,
onChange: PropTypes.func
}
FormEditInformations.defaultProps = {
title: '',
name: '',
path: '',
display: '',
type: '',
tags: ['common'],
options: FormEditInformations.defaultFormOptions(),
onChange: () => {
},
onSubmit: () => {
}
}
export default FormEditInformations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment