Skip to content

Commit

Permalink
feat: Render Custom widget for standlone array fields
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsoar committed Feb 11, 2022
1 parent c6ad877 commit 90f7120
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ should change the heading of the (upcoming) version to include a major version b
- Differentiated the material-ui 4 and 5 themes
- Added chakra-ui theme

# v3.4.0
## @rjsf/core
- Introduce `idSeparator` prop to change the path separator used to generate field names (https://github.com/rjsf-team/react-jsonschema-form/pull/2628)
- Array fields support custom widgets (previously, only multiple-choice arrays with `enums` or `uniqueItems` support it)

# v3.3.0

## @rjsf/semantic-ui
Expand Down
5 changes: 3 additions & 2 deletions docs/advanced-customization/custom-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ _ | Custom Field | Custom Template | Custom Widget

## ArrayFieldTemplate

You can use an `ArrayFieldTemplate` to customize how your arrays are rendered.
This allows you to customize your array, and each element in the array.
You can use an `ArrayFieldTemplate` to customize how your arrays are rendered.
This allows you to customize your array, and each element in the array. You can also customize arrays by specifying a widget in the relevant `ui:widget` schema, more details over on [Custom Widgets](../usage/arrays.md#custom-widgets).


```jsx
const schema = {
Expand Down
1 change: 1 addition & 0 deletions docs/advanced-customization/custom-widgets-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ You can provide your own custom widgets to a uiSchema for the following json dat
- `number`
- `integer`
- `boolean`
- `array`

```jsx
const schema = {
Expand Down
24 changes: 24 additions & 0 deletions docs/api-reference/form-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ render((

This will render `<input id="rjsf_prefix_key">` instead of `<input id="root_key">`

## idSeparator

To avoid using a path separator that is present in field names, it is possible to change the separator used for ids (the default is `_`).

```jsx
const schema = {
type: "object",
properties: {
first: {
type: "string"
}
}
};

render((
<Form schema={schema}
idSeparator={"/"}/>
), document.getElementById("app"));
```

This will render `<input id="root/first">` instead of `<input
id="root_first">` when rendering `first`.


## liveOmit

If `omitExtraData` and `liveOmit` are both set to true, then extra form data values that are not in any form field will be removed whenever `onChange` is called. Set to `false` by default.
Expand Down
58 changes: 58 additions & 0 deletions docs/usage/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,64 @@ render((
), document.getElementById("app"));
```

You can also render your own custom widgets by omitting the `enum` and `uniqueItems` options.

```js
const schema = {
type: "array",
title: "A multiple-choice list",
items: {
type: "string",
},
};

const uiSchema = {
"ui:widget": "MyCustomWidget"
};

render((
<Form schema={schema} uiSchema={uiSchema} />
), document.getElementById("app"));
```


## Custom widgets

In addition to [ArrayFieldTemplate](../advanced-customization/custom-templates.md#arrayfieldtemplate) you use your own widget by providing it to the providing it via uiSchema property of `ui:widget`.

Example:

```jsx
const CustomSelectComponent = props => {
return (
<select>
{props.value.map((item, index) => (
<option key={index} id="custom-select">
{item}
</option>
))}
</select>
);
};

const schema = {
type: "array",
title: "A multiple-choice list",
items: {
type: "string",
},
};

const uiSchema = {
"ui:widget": "CustomSelect"
};

const widgets = {
CustomSelect: CustomSelectComponent,
},

render((<Form schema={schema} uiSchema={uiSchema} widgets={widgets} />), document.getElementById("app"));
```

## Specifying the minimum or maximum number of items

Expand Down
1 change: 1 addition & 0 deletions packages/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ declare module '@rjsf/core' {
formData?: T;
id?: string;
idPrefix?: string;
idSeparator?: string;
liveOmit?: boolean;
liveValidate?: boolean;
method?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"tdd": "cross-env NODE_ENV=test mocha --require @babel/register --watch --require ./test/setup-jsdom.js test/**/*_test.js",
"test": "cross-env BABEL_ENV=test NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js test/**/*_test.js",
"test-coverage": "cross-env NODE_ENV=test nyc --reporter=lcov mocha --require @babel/register --require ./test/setup-jsdom.js test/**/*_test.js",
"test-debug": "cross-env NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js --debug-brk --inspect test/Form_test.js"
"test-debug": "cross-env NODE_ENV=test mocha --require @babel/register --require ./test/setup-jsdom.js --inspect-brk --inspect test/Form_test.js"
},
"lint-staged": {
"{src,test}/**/*.js": [
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ export default class Form extends Component {
uiSchema["ui:rootFieldId"],
rootSchema,
formData,
props.idPrefix
props.idPrefix,
props.idSeparator
);
const nextState = {
schema,
Expand Down Expand Up @@ -427,6 +428,7 @@ export default class Form extends Component {
children,
id,
idPrefix,
idSeparator,
className,
tagName,
name,
Expand Down Expand Up @@ -479,6 +481,7 @@ export default class Form extends Component {
errorSchema={errorSchema}
idSchema={idSchema}
idPrefix={idPrefix}
idSeparator={idSeparator}
formContext={formContext}
formData={formData}
onChange={this.onChange}
Expand Down
66 changes: 61 additions & 5 deletions packages/core/src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isFilesArray,
isFixedItems,
allowAdditionalItems,
isCustomWidget,
optionsList,
retrieveSchema,
toIdSchema,
Expand Down Expand Up @@ -442,15 +443,19 @@ class ArrayField extends Component {
/>
);
}
if (isMultiSelect(schema, rootSchema)) {
// If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
return this.renderMultiSelect();
}
if (isCustomWidget(uiSchema)) {
return this.renderCustomWidget();
}
if (isFixedItems(schema)) {
return this.renderFixedArray();
}
if (isFilesArray(schema, uiSchema, rootSchema)) {
return this.renderFiles();
}
if (isMultiSelect(schema, rootSchema)) {
return this.renderMultiSelect();
}
return this.renderNormalArray();
}

Expand All @@ -469,6 +474,7 @@ class ArrayField extends Component {
onBlur,
onFocus,
idPrefix,
idSeparator,
rawErrors,
} = this.props;
const title = schema.title === undefined ? name : schema.title;
Expand All @@ -488,7 +494,8 @@ class ArrayField extends Component {
itemIdPrefix,
rootSchema,
item,
idPrefix
idPrefix,
idSeparator
);
return this.renderArrayFieldItem({
key,
Expand Down Expand Up @@ -530,6 +537,53 @@ class ArrayField extends Component {
return <Component {...arrayProps} />;
}

renderCustomWidget() {
const {
schema,
idSchema,
uiSchema,
disabled,
readonly,
required,
placeholder,
autofocus,
onBlur,
onFocus,
formData: items,
registry = getDefaultRegistry(),
rawErrors,
name,
} = this.props;
const { widgets, formContext } = registry;
const title = schema.title || name;

const { widget, ...options } = {
...getUiOptions(uiSchema),
};
const Widget = getWidget(schema, widget, widgets);
return (
<Widget
id={idSchema && idSchema.$id}
multiple
onChange={this.onSelectChange}
onBlur={onBlur}
onFocus={onFocus}
options={options}
schema={schema}
registry={registry}
value={items}
disabled={disabled}
readonly={readonly}
required={required}
label={title}
placeholder={placeholder}
formContext={formContext}
autofocus={autofocus}
rawErrors={rawErrors}
/>
);
}

renderMultiSelect() {
const {
schema,
Expand Down Expand Up @@ -626,6 +680,7 @@ class ArrayField extends Component {
formData,
errorSchema,
idPrefix,
idSeparator,
idSchema,
name,
required,
Expand Down Expand Up @@ -673,7 +728,8 @@ class ArrayField extends Component {
itemIdPrefix,
rootSchema,
item,
idPrefix
idPrefix,
idSeparator
);
const itemUiSchema = additional
? uiSchema.additionalItems || {}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/components/fields/MultiSchemaField.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class AnyOfField extends Component {
errorSchema,
formData,
idPrefix,
idSeparator,
idSchema,
onBlur,
onChange,
Expand Down Expand Up @@ -160,6 +161,7 @@ class AnyOfField extends Component {
errorSchema={errorSchema}
idSchema={idSchema}
idPrefix={idPrefix}
idSeparator={idSeparator}
formData={formData}
onChange={onChange}
onBlur={onBlur}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/components/fields/ObjectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class ObjectField extends Component {
disabled,
readonly,
idPrefix,
idSeparator,
onBlur,
onFocus,
registry = getDefaultRegistry(),
Expand Down Expand Up @@ -245,6 +246,7 @@ class ObjectField extends Component {
errorSchema={errorSchema[name]}
idSchema={idSchema[name]}
idPrefix={idPrefix}
idSeparator={idSeparator}
formData={(formData || {})[name]}
wasPropertyKeyModified={this.state.wasPropertyKeyModified}
onKeyChange={this.onKeyChange(name)}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/components/fields/SchemaField.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ function SchemaFieldRender(props) {
formData,
errorSchema,
idPrefix,
idSeparator,
name,
onChange,
onKeyChange,
Expand All @@ -251,7 +252,7 @@ function SchemaFieldRender(props) {
let idSchema = props.idSchema;
const schema = retrieveSchema(props.schema, rootSchema, formData);
idSchema = mergeObjects(
toIdSchema(schema, null, rootSchema, formData, idPrefix),
toIdSchema(schema, null, rootSchema, formData, idPrefix, idSeparator),
idSchema
);
const FieldComponent = getFieldComponent(schema, uiSchema, idSchema, fields);
Expand Down
Loading

0 comments on commit 90f7120

Please sign in to comment.