diff --git a/docs/form-customization.md b/docs/form-customization.md index 8603d991ed..6310c0f1d6 100644 --- a/docs/form-customization.md +++ b/docs/form-customization.md @@ -146,6 +146,20 @@ The `ui:disabled` uiSchema directive will disable all child widgets from a given The `ui:readonly` uiSchema directive will mark all child widgets from a given field as read-only. +You can also set specific fields to read-only by setting the `readOnly` property in the schema. + +```js +const schema = { + type: "object", + properties: { + foo: { + type: "string", + readOnly: true + } + } +}; +``` + > Note: If you're wondering about the difference between a `disabled` field and a `readonly` one: Marking a field as read-only will render it greyed out, but its text value will be selectable. Disabling it will prevent its value to be selected at all. #### Hidden widgets diff --git a/playground/samples/widgets.js b/playground/samples/widgets.js index 9a7d575d13..3530334440 100644 --- a/playground/samples/widgets.js +++ b/playground/samples/widgets.js @@ -73,6 +73,12 @@ module.exports = { title: "A readonly field", default: "I am read-only.", }, + readonly2: { + type: "string", + title: "Another readonly field", + default: "I am also read-only.", + readOnly: true, + }, widgetOptions: { title: "Custom widget with options", type: "string", diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js index 7090ee5d91..3fc14f582e 100644 --- a/src/components/fields/SchemaField.js +++ b/src/components/fields/SchemaField.js @@ -251,7 +251,12 @@ function SchemaFieldRender(props) { const FieldComponent = getFieldComponent(schema, uiSchema, idSchema, fields); const { DescriptionField } = fields; const disabled = Boolean(props.disabled || uiSchema["ui:disabled"]); - const readonly = Boolean(props.readonly || uiSchema["ui:readonly"]); + const readonly = Boolean( + props.readonly || + uiSchema["ui:readonly"] || + props.schema.readOnly || + schema.readOnly + ); const autofocus = Boolean(props.autofocus || uiSchema["ui:autofocus"]); if (Object.keys(schema).length === 0) { return null; diff --git a/test/uiSchema_test.js b/test/uiSchema_test.js index a964cdada5..adbe9fc255 100644 --- a/test/uiSchema_test.js +++ b/test/uiSchema_test.js @@ -2376,4 +2376,272 @@ describe("uiSchema", () => { }); }); }); + + describe("Readonly in schema", () => { + describe("Fields", () => { + describe("ArrayField", () => { + let node; + + beforeEach(() => { + const schema = { + type: "array", + items: { + type: "string", + }, + readOnly: true, + }; + const uiSchema = {}; + const formData = ["a", "b"]; + + let rendered = createFormComponent({ schema, uiSchema, formData }); + node = rendered.node; + }); + + it("should mark as readonly an ArrayField", () => { + const disabled = [].map.call( + node.querySelectorAll("[type=text]"), + node => node.hasAttribute("readonly") + ); + expect(disabled).eql([true, true]); + }); + + it("should disable the Add button", () => { + expect(node.querySelector(".array-item-add button").disabled).eql( + true + ); + }); + + it("should disable the Delete button", () => { + expect(node.querySelector(".array-item-remove").disabled).eql(true); + }); + }); + + describe("ObjectField", () => { + let node; + + beforeEach(() => { + const schema = { + type: "object", + properties: { + foo: { + type: "string", + }, + bar: { + type: "string", + }, + }, + readOnly: true, + }; + const uiSchema = {}; + + let rendered = createFormComponent({ schema, uiSchema }); + node = rendered.node; + }); + + it("should mark as readonly an ObjectField", () => { + const disabled = [].map.call( + node.querySelectorAll("[type=text]"), + node => node.hasAttribute("readonly") + ); + expect(disabled).eql([true, true]); + }); + }); + }); + + describe("Widgets", () => { + function shouldBeReadonly(selector, schema, uiSchema) { + const { node } = createFormComponent({ schema, uiSchema }); + expect(node.querySelector(selector).hasAttribute("readonly")).eql(true); + } + function shouldBeDisabled(selector, schema, uiSchema) { + const { node } = createFormComponent({ schema, uiSchema }); + expect(node.querySelector(selector).disabled).eql(true); + } + + it("should mark as readonly a text widget", () => { + shouldBeReadonly( + "input[type=text]", + { + type: "string", + readOnly: true, + }, + {} + ); + }); + + it("should mark as readonly a file widget", () => { + // We mark a file widget as readonly by disabling it. + const { node } = createFormComponent({ + schema: { + type: "string", + format: "data-url", + readOnly: true, + }, + uiSchema: {}, + }); + expect( + node.querySelector("input[type=file]").hasAttribute("disabled") + ).eql(true); + }); + + it("should mark as readonly a textarea widget", () => { + shouldBeReadonly( + "textarea", + { + type: "string", + readOnly: true, + }, + { + "ui:widget": "textarea", + } + ); + }); + + it("should mark as readonly a number text widget", () => { + shouldBeReadonly( + "input[type=number]", + { + type: "number", + readOnly: true, + }, + {} + ); + }); + + it("should mark as readonly a number widget", () => { + shouldBeReadonly( + "input[type=number]", + { + type: "number", + readOnly: true, + }, + { + "ui:widget": "updown", + } + ); + }); + + it("should mark as readonly a range widget", () => { + shouldBeReadonly( + "input[type=range]", + { + type: "number", + readOnly: true, + }, + { + "ui:widget": "range", + } + ); + }); + + it("should mark readonly as disabled on a select widget", () => { + shouldBeDisabled( + "select", + { + type: "string", + enum: ["a", "b"], + readOnly: true, + }, + {} + ); + }); + + it("should mark as readonly a color widget", () => { + shouldBeReadonly( + "input[type=color]", + { + type: "string", + format: "color", + readOnly: true, + }, + {} + ); + }); + + it("should mark as readonly a password widget", () => { + shouldBeReadonly( + "input[type=password]", + { + type: "string", + readOnly: true, + }, + { + "ui:widget": "password", + } + ); + }); + + it("should mark as readonly a url widget", () => { + shouldBeReadonly( + "input[type=url]", + { + type: "string", + format: "uri", + readOnly: true, + }, + {} + ); + }); + + it("should mark as readonly an email widget", () => { + shouldBeReadonly("input[type=email]", { + type: "string", + format: "email", + readOnly: true, + }); + }); + + it("should mark as readonly a date widget", () => { + shouldBeReadonly("input[type=date]", { + type: "string", + format: "date", + readOnly: true, + }); + }); + + it("should mark as readonly a datetime widget", () => { + shouldBeReadonly("input[type=datetime-local]", { + type: "string", + format: "date-time", + readOnly: true, + }); + }); + + it("should mark readonly as disabled on an alternative date widget", () => { + const { node } = createFormComponent({ + schema: { + type: "string", + format: "date", + readOnly: true, + }, + uiSchema: { + "ui:widget": "alt-date", + }, + }); + + const readonly = [].map.call(node.querySelectorAll("select"), node => + node.hasAttribute("disabled") + ); + expect(readonly).eql([true, true, true]); + }); + + it("should mark readonly as disabled on an alternative datetime widget", () => { + const { node } = createFormComponent({ + schema: { + type: "string", + format: "date-time", + readOnly: true, + }, + uiSchema: { + "ui:widget": "alt-datetime", + }, + }); + + const readonly = [].map.call(node.querySelectorAll("select"), node => + node.hasAttribute("disabled") + ); + expect(readonly).eql([true, true, true, true, true, true]); + }); + }); + }); });