Skip to content

Commit

Permalink
added schema stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
joepuzzo committed May 11, 2020
1 parent a2d94c4 commit 45677dd
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 23 deletions.
11 changes: 10 additions & 1 deletion src/components/form-fields/RadioGroup.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import { GroupContext } from '../../Context';
import Radio from './Radio';

import asField from '../../HOC/asField';

Expand All @@ -16,9 +17,17 @@ class RadioGroup extends Component {
}

render() {
const { options, children } = this.props;
return (
<GroupContext.Provider value={this.groupContext}>
{this.props.children}
{options
? options.map(option => (
<label key={option.value}>
{option.label}{' '}
<Radio value={option.value} disabled={option.disabled} />
</label>
))
: children}
</GroupContext.Provider>
);
}
Expand Down
36 changes: 20 additions & 16 deletions src/components/form-fields/Select.js
Expand Up @@ -17,37 +17,33 @@ const Select = ({ fieldApi, fieldState, ...props }) => {
forwardedRef,
debug,
multiple,
options,
...rest
} = props;

const selectRef = useRef();

const handleChange = (e) => {

const handleChange = e => {
let selected = Array.from((forwardedRef || selectRef).current)
.filter(option => option.selected)
.map(option => option.value);

fieldApi.setValue(
multiple ? selected : selected[0] || ''
);
fieldApi.setValue(multiple ? selected : selected[0] || '');

if (onChange && e) {
onChange(e);
}
};

// for debugging
useLayoutEffect(
() => {
if (debug && forwardedRef) {
forwardedRef.current.style.background = 'red';
setTimeout(() => {
forwardedRef.current.style.background = 'white';
}, 500);
}
useLayoutEffect(() => {
if (debug && forwardedRef) {
forwardedRef.current.style.background = 'red';
setTimeout(() => {
forwardedRef.current.style.background = 'white';
}, 500);
}
);
});

logger('Render', field, value);

Expand All @@ -65,10 +61,18 @@ const Select = ({ fieldApi, fieldState, ...props }) => {
onBlur(e);
}
}}>
{children}
{options
? options.map(option => (
<option
key={option.value}
value={option.value}
disabled={option.disabled}>
{option.label}
</option>
))
: children}
</select>
);

};

export { Select as BasicSelect };
Expand Down
15 changes: 15 additions & 0 deletions src/fieldMap.js
@@ -0,0 +1,15 @@
import Text from './components/form-fields/Text';
import Radio from './components/form-fields/Radio';
import TextArea from './components/form-fields/TextArea';
import Select from './components/form-fields/Select';
import Option from './components/form-fields/Option';
import Checkbox from './components/form-fields/Checkbox';
import RadioGroup from './components/form-fields/RadioGroup';

export default {
select: Select,
input: Text,
textarea: TextArea,
checkbox: Checkbox,
radio: RadioGroup
};
12 changes: 12 additions & 0 deletions src/hooks/useForm.js
Expand Up @@ -3,6 +3,8 @@ import Debug from '../debug';
import FormController from '../FormController';
import FormProvider from '../components/FormProvider';
import useLayoutEffect from './useIsomorphicLayoutEffect';
import { computeFieldsFromSchema } from '../utils';
import fieldMap from '../fieldMap';

const logger = Debug('informed:useForm' + '\t\t');

Expand All @@ -20,6 +22,7 @@ const useForm = ({
onValueChange,
onSubmitFailure,
validationSchema,
schema,
...userProps
}) => {

Expand Down Expand Up @@ -87,6 +90,9 @@ const useForm = ({
// We dont want this to happen on every render so thats why useState is used here
const [formApi] = useState(() => formController.getFormApi());

// Get fields from scheama
const schemaFields = computeFieldsFromSchema(schema);

// TODO technically speaking this can be unsafe as there is circular dependency
// between form provider and useForm.. Its ok because anyone that in theory
// Uses a form provider and a use form hook themselves will never call the render
Expand All @@ -95,6 +101,12 @@ const useForm = ({
const render = (children) => (
<FormProvider formApi={formApi} formState={formState} formController={formController}>
{children}
{schemaFields.map((field, i) => {
const componentType = field.componentType;
const props = field.props;
const Component = fieldMap[componentType];
return <Component key={`ScheamField-${i}`} field={field.field} {...props} />;
})}
</FormProvider>
);

Expand Down
58 changes: 53 additions & 5 deletions src/utils.js
Expand Up @@ -9,7 +9,7 @@ export const getChildDisplayName = WrappedComponent => {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
};

export const yupToFormErrors = (yupError) => {
export const yupToFormErrors = yupError => {
const errors = {};
if (yupError.inner) {
if (yupError.inner.length === 0) {
Expand All @@ -36,12 +36,12 @@ export const validateYupSchema = (schema, values) => {
}
};

export const yupToFormError = (yupError) => {
export const yupToFormError = yupError => {
if (yupError.inner) {
if (yupError.inner.length === 0) {
return;
}
const err = yupError.inner[0]
const err = yupError.inner[0];
return err.message;
}
};
Expand All @@ -56,9 +56,57 @@ export const validateYupField = (schema, value) => {

// https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
export const uuidv4 = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
};

export const computeFieldsFromSchema = schema => {
if(!schema){
return [];
}
const { properties = {}, propertyOrder = [] } = schema;
const fields = Object.keys(properties)
.sort((a, b) => {
const aIndex = propertyOrder.indexOf(a);
const bIndex = propertyOrder.indexOf(b);

return (
(aIndex > -1 ? aIndex : propertyOrder.length + 1) -
(bIndex > -1 ? bIndex : propertyOrder.length + 1)
);
})
.map(propertyName => {
const property = properties[propertyName];

const {
'ui:control': uiControl,
oneOf,
...rest
} = property;

const field = {
componentType: uiControl,
field: propertyName,
props: rest
};

if( oneOf ){
const options = property.oneOf.map( option => ({
value: option.const,
label: option.title,
disabled: option.disabled,
}));
field.props.options = options;
}

return field;

});

return fields;
};


40 changes: 40 additions & 0 deletions stories/Form/Schema/README.md
@@ -0,0 +1,40 @@
# JSON Schema Form

```jsx
import { Form } from 'informed';

const schema = {
properties: {
name: {
type: 'string',
title: 'First name',
'ui:control': 'input'
},
model: {
type: 'string',
title: 'Model',
'ui:control': 'select',
oneOf: [
{
const: '',
title: '- Select -',
'ui:disabled': true
},
{ const: 'ms', title: 'Model S' },
{ const: 'm3', title: 'Model 3' },
{ const: 'mx', title: 'Model X' },
{ const: 'my', title: 'Model Y' }
]
}
}
};

const Schema = () => (
<Form schema={schema}>
<FormState />
</Form>
);
```

<!-- STORY -->

51 changes: 51 additions & 0 deletions stories/Form/Schema/index.js
@@ -0,0 +1,51 @@
import React from 'react';
import withDocs from '../../utils/withDocs';
import readme from './README.md';
import FormState from '../../utils/FormState';

import { Form } from '../../../src';

const schema = {
properties: {
name: {
type: 'string',
title: 'First name',
'ui:control': 'input'
},
color: {
type: 'string',
title: 'Color',
'ui:control': 'select',
oneOf: [
{
const: '',
title: '- Select -',
'ui:disabled': true
},
{ const: 'red', title: 'Red' },
{ const: 'black', title: 'Black' },
{ const: 'white', title: 'White' }
]
},
model: {
type: 'string',
title: 'Model',
oneOf: [
{ const: 'ms', title: 'Model S' },
{ const: 'm3', title: 'Model 3' },
{ const: 'mx', title: 'Model X' },
{ const: 'my', title: 'Model Y' }
],
'ui:control': 'radio',
default: null
}
}
};

const Schema = () => (
<Form schema={schema}>
<FormState />
</Form>
);

export default withDocs(readme, Schema);
4 changes: 3 additions & 1 deletion stories/index.js
Expand Up @@ -4,6 +4,7 @@ import { configureReadme } from 'storybook-readme';
import StoryWrapper from './utils/StoryWrapper';
import Intro from './Intro';
import Basic from './Form/Basic';
import Schema from './Form/Schema';
import Dynamic from './Form/Dynamic';
import Complex from './Form/Complex';
import State from './Form/State';
Expand Down Expand Up @@ -129,7 +130,8 @@ storiesOf('Form', module)
.add('State', State)
.add('Api', Api)
.add('Props', Props)
.add('Dynamic', Dynamic);
.add('Dynamic', Dynamic)
.add('Schema', Schema);

storiesOf('Inputs', module)
.add('Intro', InputIntro)
Expand Down

0 comments on commit 45677dd

Please sign in to comment.