Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 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 @@ -870,6 +871,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 generated `DescriptionField` (if you wanted to utilize it)
- `TitleField`: The generated `TitleField` (if you wanted to utilize it).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm kind of ambivalent about passing these field components to the template. Client code already has the opportunity to override the entire template, using any DescriptionField and TitleField definition they want, and they can re-import DescriptionField and TitleField themselves if they want them. So what's the value?

I don't think generated is the right word here -- it's the one taken from the registry.

How about changing if you wanted to to in case you wanted to?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that passing DescriptionField and TitleField is useless. However, ArrayField also passes them to ArrayFieldTemplate, so at least we are consistenly useless :)

I copy pasted the descriptions from ArrayFieldTemplate manual entry. I changed the descriptions in both manual entries now to "The DescriptionField/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 `items`:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is items here a typo for properties?


- `children`: The html for the property's content.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like children as the name for this property. How about content, or rendered?

- `name`: A string representing the property name.
- `disabled`: A boolean value stating if the object property is disabled.
- `index`: A number stating the index the property occurs in the `properties`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to expose this, because it should be the same as the index in the children array, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't really understand why we need to expose this. Knowing that it comes from ArrayField, I can understand why it's valuable there -- but here it seems like the property name is more valuable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this one... You're right, I removed it.

- `readonly`: A boolean value stating if the property is readonly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing blank line.

### 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
72 changes: 37 additions & 35 deletions src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,30 @@ function DefaultArrayItem(props) {
className="btn-group"
style={{ display: "flex", justifyContent: "space-around" }}>
{(props.hasMoveUp || props.hasMoveDown) && (
<IconBtn
icon="arrow-up"
className="array-item-move-up"
tabIndex="-1"
style={btnStyle}
disabled={props.disabled || props.readonly || !props.hasMoveUp}
onClick={props.onReorderClick(props.index, props.index - 1)}
/>
)}
<IconBtn
icon="arrow-up"
className="array-item-move-up"
tabIndex="-1"
style={btnStyle}
disabled={
props.disabled || props.readonly || !props.hasMoveUp
}
onClick={props.onReorderClick(props.index, props.index - 1)}
/>
)}

{(props.hasMoveUp || props.hasMoveDown) && (
<IconBtn
icon="arrow-down"
className="array-item-move-down"
tabIndex="-1"
style={btnStyle}
disabled={
props.disabled || props.readonly || !props.hasMoveDown
}
onClick={props.onReorderClick(props.index, props.index + 1)}
/>
)}
<IconBtn
icon="arrow-down"
className="array-item-move-down"
tabIndex="-1"
style={btnStyle}
disabled={
props.disabled || props.readonly || !props.hasMoveDown
}
onClick={props.onReorderClick(props.index, props.index + 1)}
/>
)}

{props.hasRemove && (
<IconBtn
Expand Down Expand Up @@ -119,12 +121,12 @@ function DefaultFixedArrayFieldTemplate(props) {
/>

{(props.uiSchema["ui:description"] || props.schema.description) && (
<div
className="field-description"
key={`field-description-${props.idSchema.$id}`}>
{props.uiSchema["ui:description"] || props.schema.description}
</div>
)}
<div
className="field-description"
key={`field-description-${props.idSchema.$id}`}>
{props.uiSchema["ui:description"] || props.schema.description}
</div>
)}

<div
className="row array-item-list"
Expand Down Expand Up @@ -154,15 +156,15 @@ function DefaultNormalArrayFieldTemplate(props) {
/>

{(props.uiSchema["ui:description"] || props.schema.description) && (
<ArrayFieldDescription
key={`array-field-description-${props.idSchema.$id}`}
DescriptionField={props.DescriptionField}
idSchema={props.idSchema}
description={
props.uiSchema["ui:description"] || props.schema.description
}
/>
)}
<ArrayFieldDescription
key={`array-field-description-${props.idSchema.$id}`}
DescriptionField={props.DescriptionField}
idSchema={props.idSchema}
description={
props.uiSchema["ui:description"] || props.schema.description
}
/>
)}

<div
className="row array-item-list"
Expand Down
76 changes: 53 additions & 23 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.children)}
</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,25 +91,17 @@ 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, index) => {
return {
children: (
<SchemaField
key={index}
name={name}
Expand All @@ -100,10 +118,22 @@ class ObjectField extends Component {
disabled={disabled}
readonly={readonly}
/>
);
})}
</fieldset>
);
),
name,
index,
readonly,
disabled,
required,
};
}),
required,
idSchema,
uiSchema,
schema,
formData,
formContext,
};
return <Template {...templateProps} />;
}
}

Expand Down
Loading