diff --git a/.node-version b/.node-version index b6a7d89c68..946789e619 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16 +16.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index fa18b1a262..7b3761884c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ should change the heading of the (upcoming) version to include a major version b - Automatically close single-choice Select widget on selection ## @rjsf/core +- BREAKING CHANGE: ShowErrorList prop changed to support `false`, `top` or `bottom`; `true` is no longer a valid value as the default changed from `true` to `top` [#634](https://github.com/rjsf-team/react-jsonschema-form/issues/634) - Added the new generic, `S extends StrictRJSFSchema = RJSFSchema`, for `schema`/`rootSchema` to every component that needed it. ## @rjsf/utils diff --git a/docs/5.x upgrade guide.md b/docs/5.x upgrade guide.md index f801bf71bc..b4729efcde 100644 --- a/docs/5.x upgrade guide.md +++ b/docs/5.x upgrade guide.md @@ -42,6 +42,11 @@ All the rest of the types for RJSF are now exported from the new `@rjsf/utils` p NOTE: The types in `@rjsf/utils` have been improved significantly from those in version 4. Some of the most notable changes are: +- `RJSFSchema` has replaced the use of `JSON7Schema` for future compatibility reasons. + - Currently `RJSFSchema` is simply an alias to `JSON7Schema` so this change is purely a naming one. + - It is highly recommended to update your use of `JSON7Schema` with `RJSFSchema` so that when the RJSF begins supporting a newer JSON Schema version out-of-the-box, your code won't be affected. +- `RJSFSchemaDefinition` has replaced the use of `JSONSchema7Definition` for the same reasons. + - The use of the generic `T` (defaulting to `any`) for the `formData` type has been expanded to cover all type hierarchies that use `formData`. - `StrictRJSFSchema` and `RJSFSchema` have replaced the use of `JSON7Schema` for future compatibility reasons. - `RJSFSchema` is `StrictRJSFSchema` joined with the `GenericObjectType` (i.e. `{ [key: string]: any }`) to allow for additional syntax related to newer draft versions @@ -52,7 +57,7 @@ Some of the most notable changes are: - The new `TemplatesType`, `ArrayFieldDescriptionProps`, `ArrayFieldTitleProps`, `UnsupportedFieldProps`, `IconButtonProps`, `SubmitButtonProps` and `UIOptionsBaseType` were added to support the consolidation (and expansion) of `templates` in the `Registry` and `Form`. - **BREAKING CHANGE** The `DescriptionField` and `TitleField` props were removed from the `ArrayFieldTemplateProps` and `ObjectFieldTemplateProps` as they can now be derived from the `templates` or `uiSchema` via the new `getTemplate()` utility function. - **BREAKING CHANGE** The `fields` prop was removed from the `FieldTemplateProps` as you can simply use `registry.fields` instead. - +- **BREAKING CHANGE** The `showErrorList` prop was changed to accept `false`, `"top"` or `"bottom"`. `true` is no longer a valid value. The default value is `"top"`, which has identical behavior to the default value/`true` in v4. You can view all these [types](https://github.com/rjsf-team/react-jsonschema-form/blob/main/packages/utils/src/types.ts) on Github. #### Form props @@ -121,7 +126,7 @@ formRef.current.formElement.current.reset(); ``` ##### `validate` prop renamed -Additionally, in version 5, the `validate` prop on `Form` was renamed to `customValidate` to avoid confusion with the new `validator` prop. +Additionally, in version 5, the `validate` prop on `Form` was renamed to `customValidate` to avoid confusion with the new `validator` prop. ##### `fields` prop changes In previous versions, it was possible to provide an override to the `DescriptionField`, `TitleField` and/or `UnsupportedField` components by providing a custom implementation in the `fields` prop on the `Form`. @@ -252,8 +257,8 @@ function YourWidget(props: WidgetProps) { const isMultiSelect = schemaUtils.isMultiSelect(schema); const newSchema: RJSFSchema = schemaUtils.retrieveSchema(schema, formData); const options = getUiOptions(uiSchema); - -... + +... } ``` @@ -406,7 +411,7 @@ If you are using `classNames` as follows, simply add the `ui:` prefix to it to r // This uiSchema will log a deprecation warning to the console const uiSchema = { title: { - "classNames": "myClass" + "classNames": "myClass" } }; // This uiSchema will not diff --git a/docs/api-reference/form-props.md b/docs/api-reference/form-props.md index f25bcb7d8b..f2da55c695 100644 --- a/docs/api-reference/form-props.md +++ b/docs/api-reference/form-props.md @@ -246,7 +246,7 @@ render(( ## showErrorList -When this prop is set to true, a list of errors (or the custom error list defined in the `ErrorList`) will also show. When set to false, only inline input validation errors will be shown. Set to `true` by default. See [Validation](../usage/validation.md) for more information. +When this prop is set to `top` or `bottom`, a list of errors (or the custom error list defined in the `ErrorList`) will also show at the `bottom` or `top` of the form. When set to false, only inline input validation errors will be shown. Set to `top` by default. See [Validation](../usage/validation.md) for more information. ## tagName @@ -288,7 +288,7 @@ Form uiSchema. See [uiSchema Reference](uiSchema.md) for more information. ## validator -**Required**! An implementation of the `ValidatorType` interface that is needed for form validation to work. +**Required**! An implementation of the `ValidatorType` interface that is needed for form validation to work. `@rjsf/validator-ajv6` exports the implementation of this interface from RJSF version 4. ## widgets diff --git a/docs/usage/validation.md b/docs/usage/validation.md index 3c235c415d..f04b87960b 100644 --- a/docs/usage/validation.md +++ b/docs/usage/validation.md @@ -7,7 +7,7 @@ React Json Schema Form provides a default `@rjsf/validator-ajv6` implementation It also provides a new `@rjsf/validator-ajv8` implementation that uses version 8 of the [ajv](https://github.com/ajv-validator/ajv) validator. The error messages generated by this new validator differ from those provided by the original validator due to it using a newer version. -If you depend on having specifically formatted messages, then using this validator would constitute a breaking change for you. +If you depend on having specifically formatted messages, then using this validator would constitute a breaking change for you. It is also possible for you to provide your own implementation if you desire, as long as it fulfills the `ValidatorType` interface specified in `@rjsf/utils`. @@ -200,14 +200,14 @@ const schema: RJSFSchema = { render((
), document.getElementById("app")); ``` -> Note: Your custom `ErrorList` template will only render when `showErrorList` is `true`. +> Note: Your custom `ErrorList` template will only render when `showErrorList` is `top` or `botttom`. The following props are passed to `ErrorList` as defined by the `ErrorListProps` interface in `@rjsf/utils`: @@ -220,7 +220,7 @@ The following props are passed to `ErrorList` as defined by the `ErrorListProps` ## The case of empty strings When a text input is empty, the field in form data is set to `undefined`. -However, since `undefined` isn't a valid JSON value according to [the official JSON standard](https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf) (ECMA-404, Section 5), the values get stored as `null`. +However, since `undefined` isn't a valid JSON value according to [the official JSON standard](https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf) (ECMA-404, Section 5), the values get stored as `null`. String fields that use `enum` and a `select` widget will have an empty option at the top of the options list that when selected will result in the field being `null`. @@ -522,7 +522,7 @@ function localize_ru(errors: null | ErrorObject[] = []) { if (!(errors && errors.length)) return; errors.forEach(function(error) { let outMessage = ""; - + switch (error.keyword) { case "pattern": { outMessage = 'должно соответствовать образцу "' + error.params.pattern + '"'; @@ -535,7 +535,7 @@ function localize_ru(errors: null | ErrorObject[] = []) { default: outMessage = error.message; } - + error.message = outMessage; }) } diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index e8b877e127..dfe0672046 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -1,35 +1,35 @@ import React, { Component } from "react"; import { + createSchemaUtils, CustomValidator, + deepEquals, ErrorSchema, ErrorTransformer, GenericObjectType, + getTemplate, + getUiOptions, IdSchema, + isObject, + mergeObjects, + NAME_KEY, PathSchema, - RJSFSchema, StrictRJSFSchema, - RJSFValidationError, Registry, - RegistryWidgetsType, RegistryFieldsType, + RegistryWidgetsType, + RJSFSchema, + RJSFValidationError, + RJSF_ADDITONAL_PROPERTIES_FLAG, SchemaUtilsType, + shouldRender, TemplatesType, UiSchema, ValidationData, ValidatorType, - createSchemaUtils, - deepEquals, - getTemplate, - getUiOptions, - isObject, - mergeObjects, - shouldRender, - NAME_KEY, - RJSF_ADDITONAL_PROPERTIES_FLAG, } from "@rjsf/utils"; -import _pick from "lodash/pick"; import _get from "lodash/get"; import _isEmpty from "lodash/isEmpty"; +import _pick from "lodash/pick"; import getDefaultRegistry from "../getDefaultRegistry"; @@ -161,10 +161,10 @@ export interface FormProps< * called. Set to `false` by default. */ omitExtraData?: boolean; - /** When this prop is set to true, a list of errors (or the custom error list defined in the `ErrorList`) will also - * show. When set to false, only inline input validation errors will be shown. Set to `true` by default + /** When this prop is set to `top` or 'bottom', a list of errors (or the custom error list defined in the `ErrorList`) will also + * show. When set to false, only inline input validation errors will be shown. Set to `top` by default */ - showErrorList?: boolean; + showErrorList?: false | "top" | "bottom"; /** A function can be passed to this prop in order to make modifications to the default errors resulting from JSON * Schema validation */ @@ -426,7 +426,7 @@ export default class Form< /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */ renderErrors(registry: Registry) { const { errors, errorSchema, schema, uiSchema } = this.state; - const { showErrorList, formContext } = this.props; + const { formContext } = this.props; const options = getUiOptions(uiSchema); const ErrorListTemplate = getTemplate<"ErrorListTemplate", T, S, F>( "ErrorListTemplate", @@ -434,7 +434,7 @@ export default class Form< options ); - if (errors && errors.length && showErrorList != false) { + if (errors && errors.length) { return ( - {this.renderErrors(registry)} + {showErrorList === "top" && this.renderErrors(registry)} <_SchemaField name="" schema={schema} @@ -826,6 +827,7 @@ export default class Form< readonly={readonly} /> {children ? children : } + {showErrorList === "bottom" && this.renderErrors(registry)} ); } diff --git a/packages/core/test/validate_test.js b/packages/core/test/validate_test.js index 69b7cfea6f..8407bc9239 100644 --- a/packages/core/test/validate_test.js +++ b/packages/core/test/validate_test.js @@ -22,6 +22,71 @@ describe("Validation", () => { }); describe("JSONSchema validation", () => { + describe("ShowErrorList prop top", () => { + const schema = { + type: "object", + required: ["foo"], + properties: { + foo: { type: "string" }, + bar: { type: "string" }, + }, + }; + + let node; + const compInfo = createFormComponent({ + schema, + formData: { + foo: undefined, + }, + }); + node = compInfo.node; + submitForm(node); + + it("should render errors at the top", () => { + expect(node.querySelectorAll(".errors li")).to.have.length.of(1); + expect(node.querySelector(".errors li").textContent).eql( + ".foo is a required property" + ); + expect(node.childNodes[0].className).to.eql( + "panel panel-danger errors" + ); + }); + }); + + describe("ShowErrorList prop bottom", () => { + const schema = { + type: "object", + required: ["foo"], + properties: { + foo: { type: "string" }, + bar: { type: "string" }, + }, + }; + + let node; + const compInfo = createFormComponent({ + showErrorList: "bottom", + schema, + formData: { + foo: undefined, + }, + }); + node = compInfo.node; + submitForm(node); + + it("should render errors at the bottom", () => { + expect(node.querySelectorAll(".errors li")).to.have.length.of(1); + expect(node.querySelector(".errors li").textContent).eql( + ".foo is a required property" + ); + + // The last child node is the submit button so the one before it will be the error list + expect(node.childNodes[2].className).to.eql( + "panel panel-danger errors" + ); + }); + }); + describe("Required fields", () => { const schema = { type: "object", @@ -63,6 +128,9 @@ describe("Validation", () => { expect(node.querySelector(".errors li").textContent).eql( ".foo is a required property" ); + expect(node.childNodes[0].className).to.eql( + "panel panel-danger errors" + ); }); }); diff --git a/packages/playground/src/app.jsx b/packages/playground/src/app.jsx index 0d4234978f..d1fe5e34e6 100644 --- a/packages/playground/src/app.jsx +++ b/packages/playground/src/app.jsx @@ -21,6 +21,7 @@ const liveSettingsSchema = { omitExtraData: { type: "boolean", title: "Omit extra data" }, liveOmit: { type: "boolean", title: "Live omit" }, noValidate: { type: "boolean", title: "Disable validation" }, + showErrorList:{ type: "string", "default":"top", title: "Show Error List", enum:[false,"top","bottom"] } }, };