Repository: mozilla-services/react-jsonschema-form
Pull request #38 (2016-02-22 11:07:12)

Allow to customize form titles.
Use a registry rather than passing yet another prop to children.

r? @n1k0 


Patch:
diff --git a/README.md b/README.md
index 3dc908104..60cf79e9a 100644
--- a/README.md
+++ b/README.md
@@ -231,6 +231,25 @@ render((
 
 If you're curious how this could ever be useful, have a look at the [Kinto formbuilder](https://github.com/Kinto/formbuilder) repository to see how it's used to provide editing capabilities to any form field.
 
+## Custom titles
+
+You can provide your own implementation of the `TitleField` base React component for rendering any title. This is useful when you want to augment how titles are handled.
+
+
+To proceed so, you can pass a `TitleField` prop to the `Form` component instance:
+
+```jsx
+
+const CustomTitleField = ({title}) => <div id="custom">{title}</div>;
+
+render((
+  <Form schema={schema}
+        uiSchema={uiSchema}
+        formData={formData}
+        TitleField={CustomTitleField} />
+), document.getElementById("app"));
+```
+
 ## Custom buttons
 
 You can provide custom buttons to your form via the `Form` component's `children`. A default submit button will be rendered if you don't provide children to the `Form` component.
diff --git a/src/components/Form.js b/src/components/Form.js
index 6ca7dfede..933ba6d14 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -1,6 +1,7 @@
 import React, { Component, PropTypes } from "react";
 import { Validator } from "jsonschema";
 import SchemaField from "./fields/SchemaField";
+import TitleField from "./fields/TitleField";
 import { getDefaultFormState } from "../utils";
 import ErrorList from "./ErrorList";
 
@@ -73,10 +74,18 @@ export default class Form extends Component {
     this.setState({status: "initial"});
   }
 
+  getRegistry() {
+    return {
+      SchemaField: this.props.SchemaField || SchemaField,
+      TitleField: this.props.TitleField || TitleField,
+    };
+  }
+
   render() {
     const {children, schema, uiSchema} = this.props;
     const {formData} = this.state;
-    const _SchemaField = this.props.SchemaField || SchemaField;
+    const registry = this.getRegistry();
+    const _SchemaField = registry.SchemaField;
     return (
       <form className="rjsf" onSubmit={this.onSubmit.bind(this)}>
         {this.renderErrors()}
@@ -85,7 +94,7 @@ export default class Form extends Component {
           uiSchema={uiSchema}
           formData={formData}
           onChange={this.onChange.bind(this)}
-          SchemaField={_SchemaField}/>
+          registry={registry}/>
         { children ? children : <p><button type="submit">Submit</button></p> }
       </form>
     );
@@ -101,6 +110,7 @@ if (process.env.NODE_ENV !== "production") {
     onError: PropTypes.func,
     onSubmit: PropTypes.func,
     SchemaField: PropTypes.func,
+    TitleField: PropTypes.func,
   };
 }
 
diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js
index 67b02ca07..168704ea3 100644
--- a/src/components/fields/ArrayField.js
+++ b/src/components/fields/ArrayField.js
@@ -64,7 +64,7 @@ class ArrayField extends Component {
     const {schema, uiSchema, name} = this.props;
     const title = schema.title || name;
     const {items} = this.state;
-    const SchemaField = this.props.SchemaField;
+    const SchemaField = this.props.registry.SchemaField;
     return (
       <fieldset
         className={`field field-array field-array-of-${schema.items.type}`}>
@@ -81,7 +81,7 @@ class ArrayField extends Component {
                   formData={items[index]}
                   required={this.isItemRequired(schema.items)}
                   onChange={this.onChange.bind(this, index)}
-                  SchemaField={SchemaField}/>
+                  registry={this.props.registry}/>
                 <p className="array-item-remove">
                   <button type="button"
                     onClick={this.onDropClick.bind(this, index)}>-</button></p>
@@ -103,7 +103,10 @@ if (process.env.NODE_ENV !== "production") {
     uiSchema: PropTypes.object,
     onChange: PropTypes.func.isRequired,
     formData: PropTypes.array,
-    SchemaField: PropTypes.func.isRequired,
+    registry: PropTypes.shape({
+      SchemaField: PropTypes.func.isRequired,
+      TitleField: PropTypes.func.isRequired
+    })
   };
 }
 
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js
index c71a3b572..f7603f103 100644
--- a/src/components/fields/ObjectField.js
+++ b/src/components/fields/ObjectField.js
@@ -39,7 +39,7 @@ class ObjectField extends Component {
   render() {
     const {schema, uiSchema, name} = this.props;
     const title = schema.title || name;
-    const SchemaField = this.props.SchemaField;
+    const {SchemaField, TitleField} = this.props.registry;
     try {
       var orderedProperties = orderProperties(
         Object.keys(schema.properties), uiSchema["ui:order"]);
@@ -53,7 +53,7 @@ class ObjectField extends Component {
     }
     return (
       <fieldset>
-        {title ? <legend>{title}</legend> : null}
+        {title ? <TitleField title={title}/> : null}
         {schema.description ?
           <div className="field-description">{schema.description}</div> : null}
         {
@@ -66,7 +66,7 @@ class ObjectField extends Component {
               uiSchema={uiSchema[name]}
               formData={this.state[name]}
               onChange={this.onChange.bind(this, name)}
-              SchemaField={SchemaField}/>
+              registry={this.props.registry} />
             );
         })
       }</fieldset>
@@ -81,7 +81,10 @@ if (process.env.NODE_ENV !== "production") {
     onChange: PropTypes.func.isRequired,
     formData: PropTypes.object,
     required: PropTypes.bool,
-    SchemaField: PropTypes.func.isRequired,
+    registry: PropTypes.shape({
+      SchemaField: PropTypes.func.isRequired,
+      TitleField: PropTypes.func.isRequired
+    })
   };
 }
 
diff --git a/src/components/fields/TitleField.js b/src/components/fields/TitleField.js
new file mode 100644
index 000000000..bd4b648b0
--- /dev/null
+++ b/src/components/fields/TitleField.js
@@ -0,0 +1,13 @@
+import React, {PropTypes} from "react";
+
+function TitleField(props) {
+  return <legend>{props.title}</legend>;
+}
+
+if (process.env.NODE_ENV !== "production") {
+  TitleField.propTypes = {
+    title: PropTypes.string
+  };
+}
+
+export default TitleField;


Test patch:
diff --git a/test/index_test.js b/test/index_test.js
index a828f085f..7b45e8aa9 100644
--- a/test/index_test.js
+++ b/test/index_test.js
@@ -541,6 +541,14 @@ describe("Form", () => {
         .eql("my object");
     });
 
+    it("should render a customized title", () => {
+      const CustomTitleField = ({title}) => <div id="custom">{title}</div>;
+
+      const {node} = createComponent({schema, TitleField: CustomTitleField});
+      expect(node.querySelector("fieldset > #custom").textContent)
+      .to.eql("my object");
+    });
+
     it("should render a default property label", () => {
       const {node} = createComponent({schema});