Skip to content
Snippets Groups Projects
Commit 9202d9c9 authored by Ben Whitmore's avatar Ben Whitmore
Browse files

improvement(form-builder): provide controls to reorder fields

parent cf2dfbdb
No related branches found
No related tags found
No related merge requests found
......@@ -103,7 +103,7 @@ const ComponentForm = ({
isField,
formId,
updateForm,
updateFormElement,
updateField,
}) => {
const [componentType, setComponentType] = useState(fieldOrForm.component)
......@@ -132,7 +132,7 @@ const ComponentForm = ({
initialValues={{ options: [], ...fieldOrForm }}
key={fieldOrForm.id}
onSubmit={values =>
updateFormElement({
updateField({
variables: {
formId,
element: prepareForSubmit(values),
......@@ -160,7 +160,7 @@ ComponentForm.propTypes = {
isField: PropTypes.bool.isRequired,
formId: PropTypes.string.isRequired,
updateForm: PropTypes.func.isRequired,
updateFormElement: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
}
export default ComponentForm
......@@ -19,6 +19,16 @@ const Element = styled.div`
}
`
const MainAction = styled(Action)`
flex-grow: 1;
text-align: left;
`
const IconAction = styled(Action)`
flex-grow: 0;
margin: 0 ${grid(1)};
`
const StatusIcon = withTheme(({ children, theme }) => (
<Icon color={theme.colorPrimary}>{children}</Icon>
))
......@@ -66,8 +76,10 @@ const createMarkup = encodedHtml => ({
const BuilderElement = ({
element,
isActive,
moveFieldDown,
moveFieldUp,
setActiveFieldId,
deleteFormElement,
deleteField,
formId,
}) => {
return (
......@@ -76,24 +88,38 @@ const BuilderElement = ({
key={`element-${element.id}`}
onClick={() => setActiveFieldId(element.id)}
>
<Action>
<MainAction>
<ElementTitle
dangerouslySetInnerHTML={createMarkup(
element.shortDescription ?? element.title,
)}
/>{' '}
({element.component})
</Action>
<Action
</MainAction>
<IconAction
onClick={event => {
moveFieldUp(element.id)
}}
>
</IconAction>
<IconAction
onClick={event => {
moveFieldDown(element.id)
}}
>
</IconAction>
<IconAction
onClick={event => {
event.stopPropagation()
deleteFormElement({
deleteField({
variables: { formId, elementId: element.id },
})
}}
>
🗙
</Action>
</IconAction>
</Element>
)
}
......@@ -107,8 +133,10 @@ BuilderElement.propTypes = {
order: PropTypes.string,
}).isRequired,
isActive: PropTypes.bool.isRequired,
moveFieldUp: PropTypes.func.isRequired,
moveFieldDown: PropTypes.func.isRequired,
setActiveFieldId: PropTypes.func.isRequired,
deleteFormElement: PropTypes.func.isRequired,
deleteField: PropTypes.func.isRequired,
formId: PropTypes.string.isRequired,
}
......@@ -140,28 +168,28 @@ const FormBuilder = ({
activeFieldId,
form,
setActiveFieldId,
addFormElement,
deleteFormElement,
addField,
deleteField,
moveFieldUp,
moveFieldDown,
}) => {
const orderedElements = [...form.children].sort(
(obj1, obj2) => parseInt(obj1.order, 10) - parseInt(obj2.order, 10),
)
return (
<Page>
{orderedElements.map(element => (
{form.children.map(element => (
<BuilderElement
deleteFormElement={deleteFormElement}
deleteField={deleteField}
element={element}
formId={form.id}
isActive={activeFieldId === element.id}
key={`element-${element.id}`}
moveFieldDown={moveFieldDown}
moveFieldUp={moveFieldUp}
setActiveFieldId={setActiveFieldId}
/>
))}
<AddElementButton
addElement={newElement => {
addFormElement({
addField({
variables: { element: JSON.stringify(newElement), formId: form.id },
})
setActiveFieldId(newElement.id)
......@@ -185,8 +213,10 @@ FormBuilder.propTypes = {
).isRequired,
}).isRequired,
setActiveFieldId: PropTypes.func.isRequired,
addFormElement: PropTypes.func.isRequired,
deleteFormElement: PropTypes.func.isRequired,
addField: PropTypes.func.isRequired,
deleteField: PropTypes.func.isRequired,
moveFieldUp: PropTypes.func.isRequired,
moveFieldDown: PropTypes.func.isRequired,
}
FormBuilder.defaultProps = {
......
......@@ -24,10 +24,12 @@ const FormBuilderLayout = ({
activeFormId,
activeFieldId,
deleteForm,
deleteFormElement,
deleteField,
moveFieldDown,
moveFieldUp,
updateForm,
createForm,
updateFormElement,
updateField,
setActiveFieldId,
setActiveFormId,
}) => {
......@@ -39,9 +41,11 @@ const FormBuilderLayout = ({
<SectionRow>
<FormBuilder
activeFieldId={activeFieldId}
addFormElement={updateFormElement}
deleteFormElement={deleteFormElement}
addField={updateField}
deleteField={deleteField}
form={form}
moveFieldDown={fieldId => moveFieldDown(form, fieldId)}
moveFieldUp={fieldId => moveFieldUp(form, fieldId)}
setActiveFieldId={setActiveFieldId}
/>
</SectionRow>
......@@ -112,8 +116,8 @@ const FormBuilderLayout = ({
formId={activeForm.id}
isField={!!activeField}
key={fieldOrForm.id}
updateField={updateField}
updateForm={updateForm}
updateFormElement={updateFormElement}
/>
</SectionRow>
</SectionContent>
......@@ -138,10 +142,12 @@ FormBuilderLayout.propTypes = {
activeFormId: PropTypes.string.isRequired,
activeFieldId: PropTypes.string,
deleteForm: PropTypes.func.isRequired,
deleteFormElement: PropTypes.func.isRequired,
deleteField: PropTypes.func.isRequired,
moveFieldDown: PropTypes.func.isRequired,
moveFieldUp: PropTypes.func.isRequired,
updateForm: PropTypes.func.isRequired,
createForm: PropTypes.func.isRequired,
updateFormElement: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
setActiveFieldId: PropTypes.func.isRequired,
setActiveFormId: PropTypes.func.isRequired,
}
......
import React, { useState, useEffect } from 'react'
import { useQuery, useMutation, gql } from '@apollo/client'
import { omitBy } from 'lodash'
import FormBuilderLayout from './FormBuilderLayout'
import { Spinner } from '../../../shared'
......@@ -39,6 +40,11 @@ const query = gql`
}
`
const prepareForSubmit = values => {
const cleanedValues = omitBy(values, value => value === '')
return JSON.stringify(cleanedValues)
}
const FormBuilderPage = () => {
const { loading, data, error } = useQuery(query)
......@@ -66,6 +72,40 @@ const FormBuilderPage = () => {
const [activeFormId, setActiveFormId] = useState()
const [activeFieldId, setActiveFieldId] = useState()
const moveFieldUp = (form, fieldId) => {
const currentIndex = form.children.findIndex(field => field.id === fieldId)
if (currentIndex < 1) return
const fieldsToSwapA = form.children[currentIndex - 1]
const fieldsToSwapB = form.children[currentIndex]
const newFields = [...form.children]
newFields.splice(currentIndex - 1, 2, fieldsToSwapB, fieldsToSwapA)
updateForm({
variables: {
formId: form.id,
form: prepareForSubmit({ ...form, children: newFields }),
},
})
}
const moveFieldDown = (form, fieldId) => {
const currentIndex = form.children.findIndex(field => field.id === fieldId)
if (currentIndex < 0 || currentIndex >= form.children.length - 1) return
const fieldsToSwapA = form.children[currentIndex]
const fieldsToSwapB = form.children[currentIndex + 1]
const newFields = [...form.children]
newFields.splice(currentIndex, 2, fieldsToSwapB, fieldsToSwapA)
updateForm({
variables: {
formId: form.id,
form: prepareForSubmit({ ...form, children: newFields }),
},
})
}
useEffect(() => {
if (!loading && data) {
if (data.getForms.length) {
......@@ -84,13 +124,15 @@ const FormBuilderPage = () => {
activeFieldId={activeFieldId}
activeFormId={activeFormId}
createForm={createForm}
deleteField={deleteFormElement}
deleteForm={deleteForm}
deleteFormElement={deleteFormElement}
forms={data.getForms}
moveFieldDown={moveFieldDown}
moveFieldUp={moveFieldUp}
setActiveFieldId={setActiveFieldId}
setActiveFormId={setActiveFormId}
updateField={updateFormElement}
updateForm={updateForm}
updateFormElement={updateFormElement}
/>
)
}
......
......@@ -2,6 +2,31 @@
"name": "Research Object Submission Form",
"id": "submit",
"children": [
{
"title": "Research object links",
"id": "1591192678688",
"component": "LinksInput",
"name": "submission.links",
"placeholder": "Enter your links, separated by commas",
"order": "-1",
"parse": {
"value": "split",
"label": "Split"
},
"format": {
"value": "join",
"label": "Join"
}
},
{
"title": "Title",
"id": "1593634996801",
"component": "TextField",
"name": "meta.title",
"placeholder": "Enter the manuscript's title",
"description": "<p></p>",
"order": "0"
},
{
"title": "Name",
"id": "1531303631370",
......@@ -36,28 +61,29 @@
"placeholder": "Enter your contact information"
},
{
"title": "Keywords",
"id": "1531303777701",
"component": "TextField",
"name": "submission.keywords",
"placeholder": "Enter keywords...",
"parse": "split",
"format": "join",
"order": "11",
"validateValue": {
"minChars": "2",
"maxChars": "6"
},
"validate": [
{
"value": "minChars",
"label": "minimum Characters"
},
{
"value": "required",
"label": "Required"
}
]
"title": "Cover letter",
"id": "1591172762874",
"component": "AbstractEditor",
"name": "submission.cover",
"description": "<p>Cover letter describing submission, relevant implications, and timely information to consider</p>",
"order": "4",
"placeholder": "Enter your cover letter"
},
{
"title": "Data and Code availability statements",
"id": "1591173076622",
"component": "AbstractEditor",
"name": "submission.datacode",
"placeholder": "Enter your data and code availability statement",
"order": "5"
},
{
"title": "Ethics statement",
"id": "1591173029656",
"component": "AbstractEditor",
"name": "submission.ethics",
"placeholder": "Enter your ethics statements",
"order": "6"
},
{
"title": "Type of Research Object",
......@@ -103,45 +129,28 @@
"order": "10"
},
{
"title": "Cover letter",
"id": "1591172762874",
"component": "AbstractEditor",
"name": "submission.cover",
"description": "<p>Cover letter describing submission, relevant implications, and timely information to consider</p>",
"order": "4",
"placeholder": "Enter your cover letter"
},
{
"title": "Ethics statement",
"id": "1591173029656",
"component": "AbstractEditor",
"name": "submission.ethics",
"placeholder": "Enter your ethics statements",
"order": "6"
},
{
"title": "Data and Code availability statements",
"id": "1591173076622",
"component": "AbstractEditor",
"name": "submission.datacode",
"placeholder": "Enter your data and code availability statement",
"order": "5"
},
{
"title": "Research object links",
"id": "1591192678688",
"component": "LinksInput",
"name": "submission.links",
"placeholder": "Enter your links, separated by commas",
"order": "-1",
"parse": {
"value": "split",
"label": "Split"
"title": "Keywords",
"id": "1531303777701",
"component": "TextField",
"name": "submission.keywords",
"placeholder": "Enter keywords...",
"parse": "split",
"format": "join",
"order": "11",
"validateValue": {
"minChars": "2",
"maxChars": "6"
},
"format": {
"value": "join",
"label": "Join"
}
"validate": [
{
"value": "minChars",
"label": "minimum Characters"
},
{
"value": "required",
"label": "Required"
}
]
},
{
"title": "Did your study involve healthy subjects only or patients (note that patient studies may also involve healthy subjects)",
......@@ -379,15 +388,6 @@
"order": "21",
"shortDescription": "References"
},
{
"title": "Title",
"id": "1593634996801",
"component": "TextField",
"name": "meta.title",
"placeholder": "Enter the manuscript's title",
"description": "<p></p>",
"order": "0"
},
{
"title": "Visual Abstract",
"id": "1601471819978",
......@@ -409,4 +409,4 @@
"haspopup": "true",
"popuptitle": "By submitting the manuscript, you agree to the following statements.",
"popupdescription": "<p>The corresponding author confirms that all co-authors are included, and that everyone listed as a co-author agrees to that role and all the following requirements and acknowledgements.</p><p></p><p>The submission represents original work and that sources are given proper attribution. The journal employs CrossCheck to compare submissions against a large and growing database of published scholarly content. If in the judgment of a senior editor a submission is genuinely suspected of plagiarism, it will be returned to the author(s) with a request for explanation.</p><p></p><p>The research was conducted in accordance with ethical principles.</p><p></p><p>There is a Data Accessibility Statement, containing information about the location of open data and materials, in the manuscript.</p><p>n</p><p>A conflict of interest statement is present in the manuscript, even if to state no conflicts of interest.</p>"
}
\ No newline at end of file
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment