-
-
Save kmelve/e34feccafe8e9ee3c7e09d60ee3fd4d2 to your computer and use it in GitHub Desktop.
| import PropTypes from 'prop-types' | |
| import React from 'react' | |
| import Fieldset from 'part:@sanity/components/fieldsets/default' | |
| import {setIfMissing} from 'part:@sanity/form-builder/patch-event' | |
| // FormBuilderInput automatically generates fields from a schema | |
| import {FormBuilderInput} from 'part:@sanity/form-builder' | |
| // a Higher Order Component that passes document values as props | |
| import {withDocument} from 'part:@sanity/form-builder' | |
| class confitionalFields extends React.PureComponent { | |
| static propTypes = { | |
| type: PropTypes.shape({ | |
| title: PropTypes.string, | |
| name: PropTypes.string | |
| }).isRequired, | |
| level: PropTypes.number, | |
| value: PropTypes.shape({ | |
| _type: PropTypes.string | |
| }), | |
| focusPath: PropTypes.array.isRequired, | |
| onFocus: PropTypes.func.isRequired, | |
| onChange: PropTypes.func.isRequired, | |
| onBlur: PropTypes.func.isRequired | |
| } | |
| firstFieldInput = React.createRef() | |
| handleFieldChange = (field, fieldPatchEvent) => { | |
| const {onChange, type} = this.props | |
| // Whenever the field input emits a patch event, we need to make sure to each of the included patches | |
| // are prefixed with its field name, e.g. going from: | |
| // {path: [], set: <nextvalue>} to {path: [<fieldName>], set: <nextValue>} | |
| // and ensure this input's value exists | |
| onChange(fieldPatchEvent.prefixAll(field.name).prepend(setIfMissing({_type: type.name}))) | |
| } | |
| focus() { | |
| this.firstFieldInput.current.focus() | |
| } | |
| render() { | |
| console.log(this.props) | |
| const {document, type, value, level, focusPath, onFocus, onBlur} = this.props | |
| let condition = this.props && this.props.value && this.props.value.condition; | |
| return ( | |
| <Fieldset level={level} legend={type.title} description={type.description}> | |
| <div> | |
| {type.fields[0].type.fields | |
| .map((field, i) => ( | |
| // Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component | |
| // for the given field type | |
| <FormBuilderInput | |
| level={level + 1} | |
| ref={i === 0 ? this.firstFieldInput : null} | |
| key={field.name} | |
| type={field.type} | |
| value={value && value[field.name]} | |
| onChange={patchEvent => this.handleFieldChange(field, patchEvent)} | |
| path={[field.name]} | |
| focusPath={focusPath} | |
| onFocus={onFocus} | |
| onBlur={onBlur} | |
| /> | |
| ))} | |
| <br/> | |
| {type.fields[1].type.fields | |
| .filter(field => ( | |
| field.name === condition ? true : false | |
| )) | |
| .map((field, i) => ( | |
| // Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component | |
| // for the given field type | |
| <FormBuilderInput | |
| level={level + 1} | |
| ref={i === 0 ? this.firstFieldInput : null} | |
| key={field.name} | |
| type={field.type} | |
| value={value && value[field.name]} | |
| onChange={patchEvent => this.handleFieldChange(field, patchEvent)} | |
| path={[field.name]} | |
| focusPath={focusPath} | |
| onFocus={onFocus} | |
| onBlur={onBlur} | |
| /> | |
| ))} | |
| </div> | |
| </Fieldset> | |
| ) | |
| } | |
| } | |
| export default withDocument(confitionalFields) |
| import React from 'react'; | |
| import conditionalFields from "../objects/confitionalFields"; | |
| export default { | |
| title: "Link", | |
| name: "link", | |
| type: 'object', | |
| fields: [ | |
| { | |
| title: 'Link Label', | |
| name: 'label', | |
| type: 'localeString', | |
| }, | |
| { | |
| title: "Select the link type", | |
| name: 'aFieldWithCondition', | |
| type: 'object', | |
| inputComponent: conditionalFields, | |
| fields: [ | |
| { | |
| type: 'object', | |
| name: 'input', | |
| fields: [ | |
| { | |
| name: 'condition', | |
| title: 'Link Type', | |
| type: 'string', | |
| options: { | |
| list: [ | |
| {title: 'Internal, inside this website', value: 'linkInternal'}, | |
| {title: 'External, outside this website', value: 'linkExternal'}, | |
| {title: 'String', value: 'name'}, | |
| ], | |
| // layout: 'radio', // <-- leave out to make it a dropdown menu | |
| }, | |
| }, | |
| ] | |
| }, | |
| { | |
| type: 'object', | |
| name: 'options', | |
| fields: [ | |
| { | |
| title: 'Page Reference', | |
| name: 'linkInternal', | |
| type: 'reference', | |
| to: [{type: 'landing'}] | |
| }, | |
| { | |
| name: "linkExternal", | |
| title: "URL", | |
| type: "url", | |
| validation: false | |
| }, | |
| { | |
| title: 'This one is just to show you can add more options', | |
| name: 'name', | |
| type: 'string' | |
| }, | |
| ] | |
| } | |
| ] | |
| }, | |
| ] | |
| }; |
This is useful, but all the inline objects need to be separated out and defined as a global schema type for use with GraphQL which is not ideal since they are specific to the parent object (link in this case).
I found the same issue. Is there a way to use this solution with GraphQL?
Hey! Perfect. Currently, I try to wrap my head around building a custom input component, based on a document.
I still wonder if I need to do it in a class component. However, let me describe my problem.
What I try to achieve: Set a document were I define all the fields that need to be rendered (reviewTemplate).
-> create another document (review) where I select the template and the field will be rendered accordingly.
The Input fields shall show up like a "native" sanity ui component.
It works with an HTML input field. but on every change event, it loses the focus and I have to click the input field again to type any further (just one character at a time).
So I played around with the formbuilderinput. But unfortunately, this error message pops up: func.apply is not a function.
Is this the right place to comment with code? Or where can I ask for help? Do you have documentations for the api to
build such a custom form?
THX. :)
import React, { Component } from 'react'
import { withDocument } from 'part:@sanity/form-builder'
import FormField from 'part:@sanity/components/formfields/default'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import { FormBuilderInput } from 'part:@sanity/form-builder'
import { Document, Type } from '../../@types'
import PatchEvent, { set, unset } from 'part:@sanity/form-builder/patch-event'
import client from 'part:@sanity/base/client'
import { v4 } from 'uuid'
interface InputComponentProps {
type: Type
document?: Document
value: any | any[]
onChange: Function
markers: any[]
level: number
onBlur: Function
}
interface Evaluation {
document: {
productReviewFeatures: any
}
}
const EvaluationContainer: React.FC = ({
type,
document,
value,
markers,
level,
onChange,
onBlur,
onFocus,
focusPath
}) => {
const { productReviewFeatures } = document
const { title, description } = type
console.log('focus path', focusPath)
const [isLoading, setIsloading] = React.useState<Boolean>(true)
const [evaluationFeatures, setEvaluationFeautures] = React.useState([])
const [evaluation, setEvaluation] = React.useState([])
const getSubEvaluationStructure = subFeatures => {
let subFeatureStructure = []
subFeatures.map(sub => {
subFeatureStructure.push({
_type: 'evaluation',
type: sub.type,
name: sub.name
})
})
return subFeatureStructure
}
const getMainEvaluationStructure = mainFeatures => {
let mainEvaluationStructure = []
mainFeatures.map(mainFeature => {
console.log('mainFeature', mainFeature)
mainEvaluationStructure.push({
_type: 'mainFeatureEvaluation',
name: mainFeature.name,
subFeatureEvaluation: getSubEvaluationStructure(
mainFeature.subFeatureEvaluation || mainFeature.subFeatures
)
})
})
return mainEvaluationStructure
}
React.useEffect(async () => {
if (productReviewFeatures && productReviewFeatures._ref) {
try {
const document = await client.fetch(`*[_id =="${productReviewFeatures._ref}"][0]`)
if (document && document.mainFeatures) {
console.log('document', document)
setEvaluationFeautures(document.mainFeatures)
setEvaluation(
getMainEvaluationStructure(document.reviewEvaluation || document.mainFeatures)
)
setIsloading(false)
}
} catch (error) {
console.log(error)
}
}
}, [productReviewFeatures._ref])
if (isLoading) {
return <div>Data Loading</div>
}
const handleChange = (event, { j, i }) => {
const newEvaluation = evaluation
console.log('handle', (newEvaluation[j].subFeatureEvaluation[i].value = event.target.value))
setEvaluation([...newEvaluation])
console.log('evaluation', evaluation)
onChange(PatchEvent.from(set(evaluation)))
}
console.log('value', value)
return (
<Fieldset legend={title} description={description}>
{evaluationFeatures &&
evaluationFeatures.map((feature, j) => {
return (
<div key={v4()}>
<p>{feature.name}</p>
{feature.subFeatures &&
feature.subFeatures.map((subFeature, i) => (
<FormField label={subFeature.name} description={subFeature.description}>
<input
type="text"
value={value[j].subFeatureEvaluation[i].value}
name={subFeature.name}
onChange={event => handleChange(event, { j, i })}
/>
<FormBuilderInput
level={level + 1}
ref={i === 0 ? React.createRef() : null}
key={subFeature.name}
type={subFeature.type}
value={value[j].subFeatureEvaluation[i].value}
onChange={event => handleChange(event, { j, i })}
path={[`${j}${i}`]}
focusPath={focusPath}
onFocus={onFocus}
onBlur={onBlur}
/>
</FormField>
))}
</div>
)
})}
</Fieldset>
)
}
const exportData = [
{
type: 'mainFeature',
title: 'Connectivity',
subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }]
},
{ type: 'mainFeature', subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }] }
]
export default withDocument(EvaluationContainer)
Thanks for this! Exactly what I needed.