Repository: mozilla-services/react-jsonschema-form
Pull request #572 (2017-05-05 19:05:41)

Update for custom input types
### Reasons for making this change

[Please describe them here]
I needed a way to change the input type without rebuilding an entire widget.

I added a simple ui:option to change input type to anything the user wants

If this is related to existing tickets, include links to them as well.

### Checklist

* [X] **I'm updating documentation**
  - [X] I've checked the rendering of the Markdown text I've added
  - [X] If I'm adding a new section, I've updated the Table of Content
* [X] **I'm adding or updating code**
  - [X] I've added and/or updated tests
  - [X] I've updated docs if needed
  - [X] I've run `npm run cs-format` on my branch to conform my code to [prettier](https://github.com/prettier/prettier) coding style
* [X] **I'm adding a new feature**
  - [X] I've updated the playground with an example use of the feature



Patch:
diff --git a/README.md b/README.md
index aa4953efe..09b0ad744 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
      - [Textarea rows option](#textarea-rows-option)
      - [Placeholders](#placeholders)
      - [Field labels](#field-labels)
+     - [HTML5 Input Types](#html5-input-types)
      - [Form attributes](#form-attributes)
   - [Advanced customization](#advanced-customization)
      - [Field template](#field-template)
@@ -700,6 +701,19 @@ const uiSchema = {
 };
 ```
 
+### HTML5 Input Types
+
+If all you need to do is change the input type (for using things like input type="tel") you can specify the `inputType` from `ui:options` uiSchema directive.
+
+```jsx
+const schema = {type: "string"};
+const uiSchema = {
+  "ui:options": {
+    inputType: 'tel'
+  }
+};
+```
+
 ### Form attributes
 
 Form component supports the following html attributes:
diff --git a/package.json b/package.json
index fffd15ee9..5974f8489 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-jsonschema-form",
-  "version": "0.48.0",
+  "version": "0.48.2",
   "description": "A simple React component capable of building HTML forms out of a JSON schema.",
   "scripts": {
     "build:readme": "toctoc README.md -w",
diff --git a/playground/samples/simple.js b/playground/samples/simple.js
index 78cbd9a78..e2e11b2b1 100644
--- a/playground/samples/simple.js
+++ b/playground/samples/simple.js
@@ -26,6 +26,11 @@ module.exports = {
         title: "Password",
         minLength: 3,
       },
+      telephone: {
+        type: "string",
+        title: "Telephone",
+        minLength: 10,
+      },
     },
   },
   uiSchema: {
@@ -46,6 +51,11 @@ module.exports = {
     date: {
       "ui:widget": "alt-datetime",
     },
+    telephone: {
+      "ui:options": {
+        inputType: "tel",
+      },
+    },
   },
   formData: {
     firstName: "Chuck",
diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js
index b32e90eea..b7fd258ee 100644
--- a/src/components/fields/ArrayField.js
+++ b/src/components/fields/ArrayField.js
@@ -179,7 +179,6 @@ class ArrayField extends Component {
     uiSchema: {},
     formData: [],
     idSchema: {},
-    registry: getDefaultRegistry(),
     required: false,
     disabled: false,
     readonly: false,
@@ -203,7 +202,7 @@ class ArrayField extends Component {
 
   onAddClick = event => {
     event.preventDefault();
-    const { schema, registry, formData } = this.props;
+    const { schema, formData, registry = getDefaultRegistry() } = this.props;
     const { definitions } = registry;
     let itemSchema = schema.items;
     if (isFixedItems(schema) && allowAdditionalItems(schema)) {
@@ -291,7 +290,7 @@ class ArrayField extends Component {
       disabled,
       readonly,
       autofocus,
-      registry,
+      registry = getDefaultRegistry(),
       formContext,
       onBlur,
     } = this.props;
@@ -348,9 +347,10 @@ class ArrayField extends Component {
       readonly,
       autofocus,
       onBlur,
+      registry = getDefaultRegistry(),
     } = this.props;
     const items = this.props.formData;
-    const { widgets, definitions, formContext } = this.props.registry;
+    const { widgets, definitions, formContext } = registry;
     const itemsSchema = retrieveSchema(schema.items, definitions);
     const enumOptions = optionsList(itemsSchema);
     const { widget = "select", ...options } = {
@@ -385,10 +385,11 @@ class ArrayField extends Component {
       readonly,
       autofocus,
       onBlur,
+      registry = getDefaultRegistry(),
     } = this.props;
     const title = schema.title || name;
     const items = this.props.formData;
-    const { widgets, formContext } = this.props.registry;
+    const { widgets, formContext } = registry;
     const { widget = "files", ...options } = getUiOptions(uiSchema);
     const Widget = getWidget(schema, widget, widgets);
     return (
@@ -420,7 +421,7 @@ class ArrayField extends Component {
       disabled,
       readonly,
       autofocus,
-      registry,
+      registry = getDefaultRegistry(),
       onBlur,
     } = this.props;
     const title = schema.title || name;
@@ -502,8 +503,13 @@ class ArrayField extends Component {
       autofocus,
       onBlur,
     } = props;
-    const { SchemaField } = this.props.registry.fields;
-    const { disabled, readonly, uiSchema } = this.props;
+    const {
+      disabled,
+      readonly,
+      uiSchema,
+      registry = getDefaultRegistry(),
+    } = this.props;
+    const { fields: { SchemaField } } = registry;
     const { orderable, removable } = {
       orderable: true,
       removable: true,
diff --git a/src/components/fields/BooleanField.js b/src/components/fields/BooleanField.js
index 80cb2f21a..553148628 100644
--- a/src/components/fields/BooleanField.js
+++ b/src/components/fields/BooleanField.js
@@ -15,7 +15,7 @@ function BooleanField(props) {
     uiSchema,
     idSchema,
     formData,
-    registry,
+    registry = getDefaultRegistry(),
     required,
     disabled,
     readonly,
@@ -72,7 +72,6 @@ if (process.env.NODE_ENV !== "production") {
 
 BooleanField.defaultProps = {
   uiSchema: {},
-  registry: getDefaultRegistry(),
   disabled: false,
   readonly: false,
   autofocus: false,
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js
index 393196cab..1d63a3287 100644
--- a/src/components/fields/ObjectField.js
+++ b/src/components/fields/ObjectField.js
@@ -13,7 +13,6 @@ class ObjectField extends Component {
     formData: {},
     errorSchema: {},
     idSchema: {},
-    registry: getDefaultRegistry(),
     required: false,
     disabled: false,
     readonly: false,
@@ -44,8 +43,9 @@ class ObjectField extends Component {
       disabled,
       readonly,
       onBlur,
+      registry = getDefaultRegistry(),
     } = this.props;
-    const { definitions, fields, formContext } = this.props.registry;
+    const { definitions, fields, formContext } = registry;
     const { SchemaField, TitleField, DescriptionField } = fields;
     const schema = retrieveSchema(this.props.schema, definitions);
     const title = schema.title === undefined ? name : schema.title;
@@ -92,7 +92,7 @@ class ObjectField extends Component {
               formData={formData[name]}
               onChange={this.onPropertyChange(name)}
               onBlur={onBlur}
-              registry={this.props.registry}
+              registry={registry}
               disabled={disabled}
               readonly={readonly}
             />
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js
index 7e3e0d4d6..2a4443b05 100644
--- a/src/components/fields/SchemaField.js
+++ b/src/components/fields/SchemaField.js
@@ -132,7 +132,14 @@ DefaultTemplate.defaultProps = {
 };
 
 function SchemaFieldRender(props) {
-  const { uiSchema, errorSchema, idSchema, name, required, registry } = props;
+  const {
+    uiSchema,
+    errorSchema,
+    idSchema,
+    name,
+    required,
+    registry = getDefaultRegistry(),
+  } = props;
   const {
     definitions,
     fields,
@@ -247,7 +254,6 @@ SchemaField.defaultProps = {
   uiSchema: {},
   errorSchema: {},
   idSchema: {},
-  registry: getDefaultRegistry(),
   disabled: false,
   readonly: false,
   autofocus: false,
diff --git a/src/components/fields/StringField.js b/src/components/fields/StringField.js
index 3591af592..1b87ce675 100644
--- a/src/components/fields/StringField.js
+++ b/src/components/fields/StringField.js
@@ -19,9 +19,9 @@ function StringField(props) {
     disabled,
     readonly,
     autofocus,
-    registry,
     onChange,
     onBlur,
+    registry = getDefaultRegistry(),
   } = props;
   const { title, format } = schema;
   const { widgets, formContext } = registry;
@@ -58,7 +58,7 @@ if (process.env.NODE_ENV !== "production") {
     uiSchema: PropTypes.object.isRequired,
     idSchema: PropTypes.object,
     onChange: PropTypes.func.isRequired,
-    onBlur: PropTypes.func.isRequired,
+    onBlur: PropTypes.func,
     formData: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
     registry: PropTypes.shape({
       widgets: PropTypes.objectOf(
@@ -78,7 +78,6 @@ if (process.env.NODE_ENV !== "production") {
 
 StringField.defaultProps = {
   uiSchema: {},
-  registry: getDefaultRegistry(),
   disabled: false,
   readonly: false,
   autofocus: false,
diff --git a/src/components/widgets/BaseInput.js b/src/components/widgets/BaseInput.js
index dffe3fe08..58f06d30f 100644
--- a/src/components/widgets/BaseInput.js
+++ b/src/components/widgets/BaseInput.js
@@ -16,6 +16,8 @@ function BaseInput(props) {
     registry,
     ...inputProps
   } = props;
+
+  inputProps.type = options.inputType || inputProps.type || "text";
   const _onChange = ({ target: { value } }) => {
     return props.onChange(value === "" ? options.emptyValue : value);
   };
diff --git a/src/utils.js b/src/utils.js
index 31c8b4ca3..d8721d686 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -52,15 +52,13 @@ const widgetMap = {
   },
 };
 
-const defaultRegistry = {
-  fields: require("./components/fields").default,
-  widgets: require("./components/widgets").default,
-  definitions: {},
-  formContext: {},
-};
-
 export function getDefaultRegistry() {
-  return defaultRegistry;
+  return {
+    fields: require("./components/fields").default,
+    widgets: require("./components/widgets").default,
+    definitions: {},
+    formContext: {},
+  };
 }
 
 export function getWidget(schema, widget, registeredWidgets = {}) {
@@ -249,9 +247,9 @@ export function orderProperties(properties, order) {
       return prev;
     }, {});
   const errorPropList = arr =>
-    (arr.length > 1
+    arr.length > 1
       ? `properties '${arr.join("', '")}'`
-      : `property '${arr[0]}'`);
+      : `property '${arr[0]}'`;
   const propertyHash = arrayToHash(properties);
   const orderHash = arrayToHash(order);
   const extraneous = order.filter(prop => prop !== "*" && !propertyHash[prop]);


Test patch:
diff --git a/test/uiSchema_test.js b/test/uiSchema_test.js
index 5e931311b..fe85e3696 100644
--- a/test/uiSchema_test.js
+++ b/test/uiSchema_test.js
@@ -89,6 +89,7 @@ describe("uiSchema", () => {
             funcNone: { type: "string" },
             stringAll: { type: "string" },
             stringNone: { type: "string" },
+            stringTel: { type: "string" },
           },
         };
 
@@ -126,6 +127,11 @@ describe("uiSchema", () => {
           stringNone: {
             "ui:widget": "widget",
           },
+          stringTel: {
+            "ui:options": {
+              inputType: "tel",
+            },
+          },
         };
       });
 
@@ -195,6 +201,12 @@ describe("uiSchema", () => {
         expect(widget.style.margin).to.equal("");
         expect(widget.style.padding).to.equal("");
       });
+
+      it("should ui:option inputType for html5 input types", () => {
+        const { node } = createFormComponent({ schema, uiSchema, widgets });
+        const widget = node.querySelector("input[type='tel']");
+        expect(widget).to.not.be.null;
+      });
     });
 
     describe("nested widget", () => {