Skip to content

Commit

Permalink
Add ObjectFieldTemplate (rjsf-team#653)
Browse files Browse the repository at this point in the history
* Add ObjectFieldTemplate

* ObjectFieldTemplate properties are representative objects instead of React elements

* prettier

* Update README & rename ObjectFieldTemplate props children to content

* Update README

* Fix ObjectFieldTemplate test

* Ran cs-format & cs-check

* Remove index from ObjectFieldTemplate props

* Ran cs-format & cs-check
  • Loading branch information
olzraiti authored and glasserc committed Oct 5, 2017
1 parent e092296 commit 8e9aec5
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 30 deletions.
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 (
<div>
{props.title}
{props.description}
{props.properties.map(element => <div className="property-wrapper">{element.children}</div>)}
</div>
);
}

render((
<Form schema={schema}
ObjectFieldTemplate={ObjectFieldTemplate} />,
), 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.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
11 changes: 9 additions & 2 deletions playground/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
);
};

Expand Down Expand Up @@ -412,6 +417,7 @@ class App extends Component {
theme,
editor,
ArrayFieldTemplate,
ObjectFieldTemplate,
transformErrors,
} = this.state;

Expand Down Expand Up @@ -466,6 +472,7 @@ class App extends Component {
{this.state.form && (
<Form
ArrayFieldTemplate={ArrayFieldTemplate}
ObjectFieldTemplate={ObjectFieldTemplate}
liveValidate={liveValidate}
schema={schema}
uiSchema={uiSchema}
Expand Down
61 changes: 61 additions & 0 deletions playground/samples/customObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";

function ObjectFieldTemplate({ TitleField, properties, title, description }) {
return (
<div>
<TitleField title={title} />
<div className="row">
{properties.map(prop => (
<div className="col-lg-2 col-md-4 col-sm-6 col-xs-12">{prop}</div>
))}
</div>
{description}
</div>
);
}

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,
};
2 changes: 2 additions & 0 deletions playground/samples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -31,5 +32,6 @@ export const samples = {
Files: files,
Single: single,
"Custom Array": customArray,
"Custom Object": customObject,
Alternatives: alternatives,
};
2 changes: 2 additions & 0 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || {},
Expand Down Expand Up @@ -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,
Expand Down
77 changes: 53 additions & 24 deletions src/components/fields/ObjectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ import {
getDefaultRegistry,
} from "../../utils";

function DefaultObjectFieldTemplate(props) {
const { TitleField, DescriptionField } = props;
return (
<fieldset>
{(props.uiSchema["ui:title"] || props.title) && (
<TitleField
id={`${props.idSchema.$id}__title`}
title={props.title || props.uiSchema["ui:title"]}
required={props.required}
formContext={props.formContext}
/>
)}
{props.description && (
<DescriptionField
id={`${props.idSchema.$id}__description`}
description={props.description}
formContext={props.formContext}
/>
)}
{props.properties.map(prop => prop.content)}
</fieldset>
);
}

class ObjectField extends Component {
static defaultProps = {
uiSchema: {},
Expand Down Expand Up @@ -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"]);
Expand All @@ -65,27 +91,19 @@ class ObjectField extends Component {
</div>
);
}
return (
<fieldset>
{(uiSchema["ui:title"] || title) && (
<TitleField
id={`${idSchema.$id}__title`}
title={uiSchema["ui:title"] || title}
required={required}
formContext={formContext}
/>
)}
{(uiSchema["ui:description"] || schema.description) && (
<DescriptionField
id={`${idSchema.$id}__description`}
description={uiSchema["ui:description"] || schema.description}
formContext={formContext}
/>
)}
{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: (
<SchemaField
key={index}
key={name}
name={name}
required={this.isRequired(name)}
schema={schema.properties[name]}
Expand All @@ -100,10 +118,21 @@ class ObjectField extends Component {
disabled={disabled}
readonly={readonly}
/>
);
})}
</fieldset>
);
),
name,
readonly,
disabled,
required,
};
}),
required,
idSchema,
uiSchema,
schema,
formData,
formContext,
};
return <Template {...templateProps} />;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/components/fields/SchemaField.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ if (process.env.NODE_ENV !== "production") {
fields: PropTypes.objectOf(PropTypes.func).isRequired,
definitions: PropTypes.object.isRequired,
ArrayFieldTemplate: PropTypes.func,
ObjectFieldTemplate: PropTypes.func,
FieldTemplate: PropTypes.func,
formContext: PropTypes.object.isRequired,
}),
Expand Down
2 changes: 1 addition & 1 deletion test/ArrayFieldTemplate_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe("ArrayFieldTemplate", () => {
);
}

describe("Statefull ArrayFieldTemplate", () => {
describe("Stateful ArrayFieldTemplate", () => {
class ArrayFieldTemplate extends PureComponent {
render() {
return <div>{this.props.items.map(item => item.element)}</div>;
Expand Down
Loading

0 comments on commit 8e9aec5

Please sign in to comment.