diff --git a/README.md b/README.md index db0d09fe8f..ab4609d799 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i - [Advanced customization](#advanced-customization) - [Field template](#field-template) - [Array Field Template](#array-field-template) + - [Object Field Template](#object-field-template) - [Error List template](#error-list-template) - [Custom widgets and fields](#custom-widgets-and-fields) - [Custom widget components](#custom-widget-components) @@ -841,8 +842,8 @@ Please see [customArray.js](https://github.com/mozilla-services/react-jsonschema The following props are passed to each `ArrayFieldTemplate`: -- `DescriptionField`: The generated `DescriptionField` (if you wanted to utilize it) -- `TitleField`: The generated `TitleField` (if you wanted to utilize it). +- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) +- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). - `canAdd`: A boolean value stating whether new elements can be added to the array. - `className`: The className string. - `disabled`: A boolean value stating if the array is disabled. @@ -871,6 +872,51 @@ The following props are part of each element in `items`: - `onReorderClick: (index, newIndex) => (event) => void`: Returns a function that swaps the items at `index` with `newIndex`. - `readonly`: A boolean value stating if the array item is readonly. +### Object Field Template + +Similarly to the `FieldTemplate` you can use an `ObjectFieldTemplate` to customize how your +objects are rendered. + +```jsx +function ObjectFieldTemplate(props) { + return ( +
+ {props.title} + {props.description} + {props.properties.map(element =>
{element.children}
)} +
+ ); +} + +render(( +
, +), document.getElementById("app")); +``` + +Please see [customObject.js](https://github.com/mozilla-services/react-jsonschema-form/blob/master/playground/samples/customObject.js) for a better example. + +The following props are passed to each `ObjectFieldTemplate`: + +- `DescriptionField`: The `DescriptionField` from the registry (in case you wanted to utilize it) +- `TitleField`: The `TitleField` from the registry (in case you wanted to utilize it). +- `title`: A string value containing the title for the object. +- `description`: A string value containing the description for the object. +- `properties`: An array of object representing the properties in the array. Each of the properties represent a child with properties described below. +- `required`: A boolean value stating if the object is required. +- `schema`: The schema object for this object. +- `uiSchema`: The uiSchema object for this object field. +- `idSchema`: An object containing the id for this object & ids for it's properties. +- `formData`: The form data for the object. +- `formContext`: The `formContext` object that you passed to Form. + +The following props are part of each element in `properties`: + +- `content`: The html for the property's content. +- `name`: A string representing the property name. +- `disabled`: A boolean value stating if the object property is disabled. +- `readonly`: A boolean value stating if the property is readonly. + ### Error List template To take control over how the form errors are displayed, you can define an *error list template* for your form. This list is the form global error list that appears at the top of your forms. diff --git a/package.json b/package.json index bb0b80de07..f1c8071d44 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "publish-to-npm": "npm run build:readme && npm run dist && npm publish", "start": "node devServer.js", "tdd": "cross-env NODE_ENV=test mocha --compilers js:babel-register --watch --require ./test/setup-jsdom.js test/**/*_test.js", - "test": " cross-env NODE_ENV=test mocha --compilers js:babel-register --require ./test/setup-jsdom.js test/**/*_test.js" + "test": "cross-env NODE_ENV=test mocha --compilers js:babel-register --require ./test/setup-jsdom.js test/**/*_test.js" }, "prettierOptions": "--jsx-bracket-same-line --trailing-comma es5 --semi", "lint-staged": { diff --git a/playground/app.js b/playground/app.js index 144c36ebe6..33808637d1 100644 --- a/playground/app.js +++ b/playground/app.js @@ -365,10 +365,15 @@ class App extends Component { load = data => { // Reset the ArrayFieldTemplate whenever you load new data - const { ArrayFieldTemplate } = data; + const { ArrayFieldTemplate, ObjectFieldTemplate } = data; // force resetting form component instance this.setState({ form: false }, _ => - this.setState({ ...data, form: true, ArrayFieldTemplate }) + this.setState({ + ...data, + form: true, + ArrayFieldTemplate, + ObjectFieldTemplate, + }) ); }; @@ -412,6 +417,7 @@ class App extends Component { theme, editor, ArrayFieldTemplate, + ObjectFieldTemplate, transformErrors, } = this.state; @@ -466,6 +472,7 @@ class App extends Component { {this.state.form && ( + +
+ {properties.map(prop => ( +
{prop}
+ ))} +
+ {description} + + ); +} + +module.exports = { + schema: { + title: "A registration form", + description: + "This is the same as the simple form, but it is rendered as a bootstrap grid. Try shrinking the browser window to see it in action.", + type: "object", + required: ["firstName", "lastName"], + properties: { + firstName: { + type: "string", + title: "First name", + }, + lastName: { + type: "string", + title: "Last name", + }, + age: { + type: "integer", + title: "Age", + }, + bio: { + type: "string", + title: "Bio", + }, + password: { + type: "string", + title: "Password", + minLength: 3, + }, + telephone: { + type: "string", + title: "Telephone", + minLength: 10, + }, + }, + }, + formData: { + firstName: "Chuck", + lastName: "Norris", + age: 75, + bio: "Roundhouse kicking asses since 1940", + password: "noneed", + }, + ObjectFieldTemplate, +}; diff --git a/playground/samples/index.js b/playground/samples/index.js index 68fa8d22d8..ddc7b18c0c 100644 --- a/playground/samples/index.js +++ b/playground/samples/index.js @@ -13,6 +13,7 @@ import validation from "./validation"; import files from "./files"; import single from "./single"; import customArray from "./customArray"; +import customObject from "./customObject"; import alternatives from "./alternatives"; export const samples = { @@ -31,5 +32,6 @@ export const samples = { Files: files, Single: single, "Custom Array": customArray, + "Custom Object": customObject, Alternatives: alternatives, }; diff --git a/src/components/Form.js b/src/components/Form.js index 59237ba97a..14c90c8aef 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -151,6 +151,7 @@ export default class Form extends Component { fields: { ...fields, ...this.props.fields }, widgets: { ...widgets, ...this.props.widgets }, ArrayFieldTemplate: this.props.ArrayFieldTemplate, + ObjectFieldTemplate: this.props.ObjectFieldTemplate, FieldTemplate: this.props.FieldTemplate, definitions: this.props.schema.definitions || {}, formContext: this.props.formContext || {}, @@ -227,6 +228,7 @@ if (process.env.NODE_ENV !== "production") { ), fields: PropTypes.objectOf(PropTypes.func), ArrayFieldTemplate: PropTypes.func, + ObjectFieldTemplate: PropTypes.func, FieldTemplate: PropTypes.func, ErrorList: PropTypes.func, onChange: PropTypes.func, diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index e16c367cd7..bfbb034ad3 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -7,6 +7,30 @@ import { getDefaultRegistry, } from "../../utils"; +function DefaultObjectFieldTemplate(props) { + const { TitleField, DescriptionField } = props; + return ( +
+ {(props.uiSchema["ui:title"] || props.title) && ( + + )} + {props.description && ( + + )} + {props.properties.map(prop => prop.content)} +
+ ); +} + class ObjectField extends Component { static defaultProps = { uiSchema: {}, @@ -50,7 +74,9 @@ class ObjectField extends Component { const { SchemaField, TitleField, DescriptionField } = fields; const schema = retrieveSchema(this.props.schema, definitions); const title = schema.title === undefined ? name : schema.title; + const description = uiSchema["ui:description"] || schema.description; let orderedProperties; + try { const properties = Object.keys(schema.properties); orderedProperties = orderProperties(properties, uiSchema["ui:order"]); @@ -65,27 +91,19 @@ class ObjectField extends Component { ); } - return ( -
- {(uiSchema["ui:title"] || title) && ( - - )} - {(uiSchema["ui:description"] || schema.description) && ( - - )} - {orderedProperties.map((name, index) => { - return ( + + const Template = registry.ObjectFieldTemplate || DefaultObjectFieldTemplate; + + const templateProps = { + title: uiSchema["ui:title"] || title, + description, + TitleField, + DescriptionField, + properties: orderedProperties.map(name => { + return { + content: ( - ); - })} -
- ); + ), + name, + readonly, + disabled, + required, + }; + }), + required, + idSchema, + uiSchema, + schema, + formData, + formContext, + }; + return