Skip to content

Commit

Permalink
Merge 17831bb into 1e35d58
Browse files Browse the repository at this point in the history
  • Loading branch information
joepuzzo committed Apr 24, 2021
2 parents 1e35d58 + 17831bb commit 52185b7
Show file tree
Hide file tree
Showing 13 changed files with 527 additions and 11 deletions.
4 changes: 3 additions & 1 deletion src/Context.js
Expand Up @@ -34,6 +34,7 @@ const ArrayFieldItemStateContext = React.createContext();
const MultistepStateContext = React.createContext();
const MultistepApiContext = React.createContext();
const MultistepStepContext = React.createContext();
const RelevantContext = React.createContext();

export {
FormRegisterContext,
Expand All @@ -47,5 +48,6 @@ export {
ArrayFieldItemStateContext,
MultistepStateContext,
MultistepApiContext,
MultistepStepContext
MultistepStepContext,
RelevantContext
};
13 changes: 11 additions & 2 deletions src/components/FormField.js
Expand Up @@ -2,11 +2,12 @@ import React, { useContext } from 'react';
import useFormApi from '../hooks/useFormApi';
import { computeFieldFromProperty, getSchemaPathFromJsonPath } from '../utils';
import ObjectMap from '../ObjectMap';
import { FormRegisterContext } from '../Context';
import { FormRegisterContext, RelevantContext } from '../Context';

const FormField = ({ field }) => {
// Get the field map off the forms context
const { fieldMap } = useContext(FormRegisterContext);
const relevant = useContext(RelevantContext);

// Grab the form api ( we need it to get the actual field name because might be in scope )
const { getFullField, getOptions } = useFormApi();
Expand All @@ -24,7 +25,15 @@ const FormField = ({ field }) => {
// field = "brother.name" ---> properties.brother.properties.name
// field = "brother.siblings[1].friend.name" ---> properties.brother.properties.siblings.items[1].properties.friend.properties.name
const path = getSchemaPathFromJsonPath(fullField);
const property = ObjectMap.get(schema, path);
let property = ObjectMap.get(schema, path);

// We might have definition inside of conditional so check there
if (relevant) {
// Find conditional from schema
const conditional = schema.allOf.find(c => c.relevant === relevant);
const subSchema = conditional.then;
property = ObjectMap.get(subSchema, path);
}

// If property was not found return null
if (!property) {
Expand Down
10 changes: 9 additions & 1 deletion src/components/FormFields.js
@@ -1,7 +1,7 @@
import React, { useMemo, useContext } from 'react';
import { computeFieldsFromSchema } from '../utils';
import ArrayField from './form-fields/ArrayField';
import Relevant from './Relevant';
import Relevant, { RelevantFields } from './Relevant';
import Debug from '../debug';
import { FormRegisterContext } from '../Context';

Expand Down Expand Up @@ -29,6 +29,7 @@ const FormFields = ({ schema, prefix, onlyValidateSchema }) => {
properties,
items,
componentType,
relevant,
uiBefore,
uiAfter,
allOf
Expand All @@ -39,6 +40,13 @@ const FormFields = ({ schema, prefix, onlyValidateSchema }) => {
// console.log('WTF', schemaField);
logger('Rendering Field', field, schemaField);

// For Relevant Fields
if (componentType === 'relevantFields') {
return (
<RelevantFields key={`ScheamField-${i}`} relevant={relevant} />
);
}

// Scope for nested
if (!Component && type === 'object' && properties) {
return (
Expand Down
5 changes: 5 additions & 0 deletions src/components/Label.js
@@ -0,0 +1,5 @@
import React from 'react';

const Label = ({ title }) => <label>{title}</label>;

export default Label;
60 changes: 59 additions & 1 deletion src/components/Relevant.js
@@ -1,16 +1,74 @@
import React from 'react';
import useFormApi from '../hooks/useFormApi';
import useFormState from '../hooks/useFormState';
import FormFields from '../components/FormFields';
import { RelevantContext } from '../Context';

const Relevant = ({ when, children }) => {
const Relevant = ({ when, children, relevant }) => {
const formState = useFormState();

const isRelevant = when(formState);

if (isRelevant && relevant) {
return (
<RelevantContext.Provider value={relevant}>
{children}
</RelevantContext.Provider>
);
}

if (isRelevant) {
return children;
}

return null;
};

export const RelevantFields = ({ relevant, children }) => {
// Grab the form api ( we need it to get the actual field name because might be in scope )
const { getOptions } = useFormApi();

// Grap the schema
const { schema } = getOptions();

// Find conditional from schema
const conditional = schema.allOf.find(c => c.relevant === relevant);

// Example then ( its a subschema )
// then: {
// properties: {
// spouse: {
// type: 'string',
// title: 'Spouse name',
// 'ui:control': 'input'
// }
// }
// }
const subSchema = conditional.then;

// Turn the if into a when function for informed
// Example if condition
// if: {
// properties: {
// married: { const: 'yes' }
// },
// required: ['married']
// },
const { properties: conditions } = conditional.if;
const when = ({ values }) => {
// Example key "married, Example condition: "{ const: 'yes' }"
return Object.keys(conditions).every(key => {
const condition = conditions[key];
// values.married === 'yes'
return values[key] === condition.const;
});
};

return (
<Relevant when={when} relevant={relevant}>
{children ? children : <FormFields schema={subSchema} />}
</Relevant>
);
};

export default Relevant;
4 changes: 3 additions & 1 deletion src/fieldMap.js
Expand Up @@ -6,6 +6,7 @@ import RadioGroup from './components/form-fields/RadioGroup';
import AddButton from './components/form-fields/AddButton';
import RemoveButton from './components/form-fields/RemoveButton';
import ArrayField from './components/form-fields/ArrayField';
import Label from './components/Label';

export default {
select: Select,
Expand All @@ -15,5 +16,6 @@ export default {
radio: RadioGroup,
add: AddButton,
remove: RemoveButton,
array: ArrayField
array: ArrayField,
label: Label
};
2 changes: 2 additions & 0 deletions src/index.js
Expand Up @@ -44,6 +44,7 @@ import { BasicRadioGroup } from './components/form-fields/RadioGroup';
import { BasicTextArea } from './components/form-fields/TextArea';
import { BasicSelect } from './components/form-fields/Select';
import { BasicCheckbox } from './components/form-fields/Checkbox';
import { RelevantFields } from './components/Relevant';

import * as utils from './utils';

Expand Down Expand Up @@ -93,5 +94,6 @@ export {
SchemaFields,
FormFields,
FormComponents,
RelevantFields,
utils
};
36 changes: 31 additions & 5 deletions src/utils.js
Expand Up @@ -100,6 +100,29 @@ export const debounce = (func, wait) => {
};

export const computeFieldFromProperty = (propertyName, property, prefix) => {
// Special case for relevant fields
// Example 'conditional:over21'
if (/conditional:.*/.test(propertyName)) {
return {
componentType: 'relevantFields',
// Example 'conditional:over21' ---> over21
relevant: propertyName.replace(/conditional:(.*)/, '$1')
};
}

// Special case for component fields
// Example 'component:marriedLabel'
if (/component:.*/.test(propertyName)) {
const { 'ui:control': uiControl, ...props } = property;

return {
componentType: uiControl,
// Not needed as this is not an informed field
field: undefined,
props
};
}

const {
'ui:control': uiControl,
'informed:props': informedProps,
Expand Down Expand Up @@ -213,11 +236,14 @@ export const computeFieldsFromSchema = (schema, onlyValidateSchema, prefix) => {

// Check for all of ( we have conditionals )
if (allOf) {
fields.push({
componentType: 'conditionals',
// Each element of the "allOf" array is a conditional
allOf: allOf
});
// Only do this if the user did not chose to control location
if (!allOf[0].relevant) {
fields.push({
componentType: 'conditionals',
// Each element of the "allOf" array is a conditional
allOf: allOf
});
}
}

return fields;
Expand Down
100 changes: 100 additions & 0 deletions stories/Schema/FormattedConditionalSchema/README.md
@@ -0,0 +1,100 @@
# Formatted Conditional Schema

** Note: This is in beta and is subject to change! **

<!-- STORY -->

```jsx
import { Form, FormField, RelevantFields } from 'informed';

const schema = {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
title: 'First name',
'ui:control': 'input'
},
married: {
type: 'string',
title: 'Are you married?',
enum: ['yes', 'no'],
'ui:control': 'radio'
},
ofAge: {
type: 'string',
title: 'Are you over 21?',
enum: ['yes', 'no'],
'ui:control': 'radio'
}
},
allOf: [
{
relevant: 'married',
if: {
properties: {
married: { const: 'yes' }
},
required: ['married']
},
then: {
properties: {
spouse: {
type: 'string',
title: 'Spouse name',
'ui:control': 'input'
}
}
}
},
{
relevant: 'over21',
if: {
properties: {
ofAge: { const: 'yes' }
},
required: ['ofAge']
},
then: {
properties: {
age: {
type: 'number',
title: 'Age',
'ui:control': 'input',
'input:props': {
type: 'number'
}
},
drink: {
type: 'string',
title: 'What do you want to drink?',
'ui:control': 'input'
}
}
}
}
]
};

const Component = () => {
return (
<Form schema={schema}>
<FormField field="name" />
<label>Are you married?</label>
<FormField field="married" />
<RelevantFields relevant="married">
<FormField field="spouse" />
</RelevantFields>
<label>Are you over21?</label>
<FormField field="ofAge" />
<RelevantFields relevant="over21">
<FormField field="age" />
<FormField field="drink" />
</RelevantFields>
<button type="submit">Submit</button>
<FormState />
</Form>
);
};
```

0 comments on commit 52185b7

Please sign in to comment.