Skip to content

Commit

Permalink
Can now specify idSeparator to control how id is created (#2628)
Browse files Browse the repository at this point in the history
* Can now specify idSeparator to control how id is created

By default, '_' is used as a separator for the different parts that
make up a field's id. This can be problematic when '_' is part of a
field's name.

This commit adds the ability to specify idSeparator when building a
form which allows users use a character that isn't used in field
names.

With the idSeparator option set to '.', we would get:

'root.field_a.field_123' instead of 'root_field_a_field_123'.

* Update CHANGELOG.md

Co-authored-by: Emmanuel Boudreault <[email protected]>
Co-authored-by: Ashwin Ramaswami <[email protected]>
  • Loading branch information
3 people authored Feb 10, 2022
1 parent c6ad877 commit 549b718
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ 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

## @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)

# v3.3.0

## @rjsf/semantic-ui
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
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
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
8 changes: 6 additions & 2 deletions packages/core/src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ class ArrayField extends Component {
onBlur,
onFocus,
idPrefix,
idSeparator,
rawErrors,
} = this.props;
const title = schema.title === undefined ? name : schema.title;
Expand All @@ -488,7 +489,8 @@ class ArrayField extends Component {
itemIdPrefix,
rootSchema,
item,
idPrefix
idPrefix,
idSeparator
);
return this.renderArrayFieldItem({
key,
Expand Down Expand Up @@ -626,6 +628,7 @@ class ArrayField extends Component {
formData,
errorSchema,
idPrefix,
idSeparator,
idSchema,
name,
required,
Expand Down Expand Up @@ -673,7 +676,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
19 changes: 14 additions & 5 deletions packages/core/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,32 +998,41 @@ export function toIdSchema(
id,
rootSchema,
formData = {},
idPrefix = "root"
idPrefix = "root",
idSeparator = "_"
) {
const idSchema = {
$id: id || idPrefix,
};
if ("$ref" in schema || "dependencies" in schema || "allOf" in schema) {
const _schema = retrieveSchema(schema, rootSchema, formData);
return toIdSchema(_schema, id, rootSchema, formData, idPrefix);
return toIdSchema(_schema, id, rootSchema, formData, idPrefix, idSeparator);
}
if ("items" in schema && !schema.items.$ref) {
return toIdSchema(schema.items, id, rootSchema, formData, idPrefix);
return toIdSchema(
schema.items,
id,
rootSchema,
formData,
idPrefix,
idSeparator
);
}
if (schema.type !== "object") {
return idSchema;
}
for (const name in schema.properties || {}) {
const field = schema.properties[name];
const fieldId = idSchema.$id + "_" + name;
const fieldId = idSchema.$id + idSeparator + name;
idSchema[name] = toIdSchema(
isObject(field) ? field : {},
fieldId,
rootSchema,
// It's possible that formData is not an object -- this can happen if an
// array item has just been added, but not populated with data yet
(formData || {})[name],
idPrefix
idPrefix,
idSeparator
);
}
return idSchema;
Expand Down
24 changes: 24 additions & 0 deletions packages/core/test/Form_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,30 @@ describeRepeated("Form common", createFormComponent => {
});
});

describe("Option idSeparator", function() {
it("should change the rendered ids", function() {
const schema = {
type: "object",
title: "root object",
required: ["foo"],
properties: {
count: {
type: "number",
},
},
};
const comp = renderIntoDocument(<Form schema={schema} idSeparator="." />);
const node = findDOMNode(comp);
const inputs = node.querySelectorAll("input");
const ids = [];
for (var i = 0, len = inputs.length; i < len; i++) {
const input = inputs[i];
ids.push(input.getAttribute("id"));
}
expect(ids).to.eql(["root.count"]);
});
});

describe("Custom field template", () => {
const schema = {
type: "object",
Expand Down
21 changes: 21 additions & 0 deletions packages/core/test/utils_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2716,6 +2716,27 @@ describe("utils", () => {
});
});

it("should handle idSeparator parameter", () => {
const schema = {
definitions: {
testdef: {
type: "object",
properties: {
foo: { type: "string" },
bar: { type: "string" },
},
},
},
$ref: "#/definitions/testdef",
};

expect(toIdSchema(schema, undefined, schema, {}, "rjsf", "/")).eql({
$id: "rjsf",
foo: { $id: "rjsf/foo" },
bar: { $id: "rjsf/bar" },
});
});

it("should handle null form data for object schemas", () => {
const schema = {
type: "object",
Expand Down

0 comments on commit 549b718

Please sign in to comment.