Repository: mozilla-services/react-jsonschema-form
Pull request #484 (2017-02-22 23:05:01)

#434 - Render empty array item fields when minItems is specified
### Reasons for making this change

Trying to get my feet wet with this library and doing something useful on the way :smile: 

Implemented #434

BTW thanks for this awesome stuff!

### Checklist

* [ ] **I'm updating documentation**
  - [ ] I've checked the rendering of the Markdown text I've added
  - [ ] 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
  - [ ] I've updated docs if needed
* [x] **I'm adding a new feature**
  - [x] I've updated the playground with an example use of the feature



Patch:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..2c01c1402
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+indent_style = space
+indent_size = 2
+charset = utf-8
+insert_final_newline = true
diff --git a/.gitignore b/.gitignore
index 7e671b889..03c1b53ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,4 @@ node_modules
 build
 dist
 lib
-
+yarn.lock
diff --git a/README.md b/README.md
index 8f43aedb6..f855c59cc 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
      - [Form action buttons](#form-action-buttons)
      - [Help texts](#help-texts)
      - [Auto focus](#auto-focus)
+     - [Textarea rows option](#textarea-rows-option)
      - [Placeholders](#placeholders)
      - [Form attributes](#form-attributes)
   - [Advanced customization](#advanced-customization)
@@ -74,6 +75,7 @@ A [live playground](https://mozilla-services.github.io/react-jsonschema-form/) i
   - [Styling your forms](#styling-your-forms)
   - [Schema definitions and references](#schema-definitions-and-references)
   - [JSON Schema supporting status](#json-schema-supporting-status)
+  - [Tips and tricks](#tips-and-tricks)
   - [Contributing](#contributing)
      - [Development server](#development-server)
      - [Tests](#tests)
@@ -646,6 +648,20 @@ const uiSchema = {
 }
 ```
 
+### Textarea `rows` option
+
+You can set initial height of a textarea widget by specifying `rows` option.
+
+```js
+const schema = {type: "string"};
+const uiSchema = {
+  "ui:widget": "textarea",
+  "ui:options": {
+    rows: 15
+  }
+}
+```
+
 ### Placeholders
 
 Text fields can benefit from placeholders by using the `ui:placeholder` uiSchema directive:
@@ -1297,6 +1313,17 @@ This component follows [JSON Schema](http://json-schema.org/documentation.html)
 * `additionalItems` keyword for arrays
     This keyword works when `items` is an array. `additionalItems: true` is not supported because there's no widget to represent an item of any type. In this case it will be treated as no additional items allowed. `additionalItems` being a valid schema is supported.
 
+## Tips and tricks
+
+ - Custom field template : https://jsfiddle.net/hdp1kgn6/1/
+ - Multi-step wizard : https://jsfiddle.net/sn4bnw9h/1/
+ - Using classNames with uiSchema : https://jsfiddle.net/gfwp25we/1/
+ - Conditionnal fields : https://jsfiddle.net/69z2wepo/68259/
+ - Use radio list for enums : https://jsfiddle.net/f2y3fq7L/2/
+ - Reading file input data : https://jsfiddle.net/f9vcb6pL/1/
+ 
+ 
+ 
 ## Contributing
 
 ### Development server
diff --git a/playground/samples/arrays.js b/playground/samples/arrays.js
index 7e4baf79a..f09720d77 100644
--- a/playground/samples/arrays.js
+++ b/playground/samples/arrays.js
@@ -1,5 +1,16 @@
 module.exports = {
   schema: {
+    definitions: {
+      Thing: {
+        type: "object",
+        properties: {
+          name: {
+            type: "string",
+            default: "Default name"
+          }
+        }
+      }
+    },
     type: "object",
     properties: {
       listOfStrings: {
@@ -15,7 +26,7 @@ module.exports = {
         title: "A multiple choices list",
         items: {
           type: "string",
-          enum: ["foo", "bar", "fuzz", "qux"],
+          enum: ["foo", "bar", "fuzz", "qux"]
         },
         uniqueItems: true
       },
@@ -38,6 +49,14 @@ module.exports = {
           type: "number"
         }
       },
+      minItemsList: {
+        type: "array",
+        title: "A list with a minimal number of items",
+        minItems: 3,
+        items: {
+          $ref: "#/definitions/Thing"
+        }
+      },
       nestedList: {
         type: "array",
         title: "Nested list",
diff --git a/playground/samples/widgets.js b/playground/samples/widgets.js
index a91820598..cca38b1f0 100644
--- a/playground/samples/widgets.js
+++ b/playground/samples/widgets.js
@@ -95,7 +95,10 @@ module.exports = {
     },
     string: {
       textarea: {
-        "ui:widget": "textarea"
+        "ui:widget": "textarea",
+        "ui:options": {
+          rows: 5
+        }
       },
       color: {
         "ui:widget": "color"
diff --git a/src/components/Form.js b/src/components/Form.js
index 7f8f2f31e..078c6bbd0 100644
--- a/src/components/Form.js
+++ b/src/components/Form.js
@@ -18,7 +18,7 @@ export default class Form extends Component {
     liveValidate: false,
     safeRenderCompletion: false,
     noHtml5Validate: false
-  }
+  };
 
   constructor(props) {
     super(props);
@@ -93,7 +93,7 @@ export default class Form extends Component {
     if (this.props.onBlur) {
       this.props.onBlur(...args);
     }
-  }
+  };
 
   onSubmit = (event) => {
     event.preventDefault();
diff --git a/src/components/widgets/TextareaWidget.js b/src/components/widgets/TextareaWidget.js
index 7bcb03b26..7c9aa200e 100644
--- a/src/components/widgets/TextareaWidget.js
+++ b/src/components/widgets/TextareaWidget.js
@@ -4,6 +4,7 @@ import React, {PropTypes} from "react";
 function TextareaWidget({
   schema,
   id,
+  options,
   placeholder,
   value,
   required,
@@ -26,13 +27,15 @@ function TextareaWidget({
       disabled={disabled}
       readOnly={readonly}
       autoFocus={autofocus}
+      rows={options.rows}
       onBlur={onBlur && (event => onBlur(id, event.target.value))}
       onChange={_onChange}/>
   );
 }
 
 TextareaWidget.defaultProps = {
-  autofocus: false
+  autofocus: false,
+  options: {}
 };
 
 if (process.env.NODE_ENV !== "production") {
@@ -40,6 +43,9 @@ if (process.env.NODE_ENV !== "production") {
     schema: PropTypes.object.isRequired,
     id: PropTypes.string.isRequired,
     placeholder: PropTypes.string,
+    options: PropTypes.shape({
+      rows: PropTypes.number
+    }),
     value: PropTypes.string,
     required: PropTypes.bool,
     autofocus: PropTypes.bool,
diff --git a/src/utils.js b/src/utils.js
index 74797b7c8..da860bb2a 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -123,15 +123,22 @@ function computeDefaults(schema, parentDefaults, definitions={}) {
   if (typeof(defaults) === "undefined") {
     defaults = schema.default;
   }
-  // We need to recur for object schema inner default values.
-  if (schema.type === "object") {
-    return Object.keys(schema.properties).reduce((acc, key) => {
-      // Compute the defaults for this node, with the parent defaults we might
-      // have from a previous run: defaults[key].
-      acc[key] = computeDefaults(
-        schema.properties[key], (defaults || {})[key], definitions);
-      return acc;
-    }, {});
+
+  switch (schema.type) {
+    // We need to recur for object schema inner default values.
+    case "object":
+      return Object.keys(schema.properties).reduce((acc, key) => {
+        // Compute the defaults for this node, with the parent defaults we might
+        // have from a previous run: defaults[key].
+        acc[key] = computeDefaults(
+          schema.properties[key], (defaults || {})[key], definitions);
+        return acc;
+      }, {});
+
+    case "array":
+      if (schema.minItems) {
+        return new Array(schema.minItems).fill(computeDefaults(schema.items, defaults, definitions));
+      }
   }
   return defaults;
 }


Test patch:
diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js
index 5d67e6029..89b0e71a4 100644
--- a/test/ArrayField_test.js
+++ b/test/ArrayField_test.js
@@ -295,6 +295,67 @@ describe("ArrayField", () => {
       expect(inputs[2].id).eql("root_foo_1_bar");
       expect(inputs[3].id).eql("root_foo_1_baz");
     });
+
+    it("should render enough inputs with proper defaults to match minItems in schema when no formData is set", () => {
+      const complexSchema = {
+        type: "object",
+        definitions: {
+          Thing: {
+            type: "object",
+            properties: {
+              name: {
+                type: "string",
+                default: "Default name"
+              }
+            }
+          }
+        },
+        properties: {
+          foo: {
+            type: "array",
+            minItems: 2,
+            items: {
+              $ref: "#/definitions/Thing"
+            }
+          }
+        }
+      };
+      let form = createFormComponent({schema: complexSchema, formData: { }});
+      let inputs = form.node.querySelectorAll("input[type=text]");
+      expect(inputs[0].value).eql("Default name");
+      expect(inputs[1].value).eql("Default name");
+    });
+
+    it("should honor given formData, even when it does not meet ths minItems-requirement", () => {
+      const complexSchema = {
+        type: "object",
+        definitions: {
+          Thing: {
+            type: "object",
+            properties: {
+              name: {
+                type: "string",
+                default: "Default name"
+              }
+            }
+          }
+        },
+        properties: {
+          foo: {
+            type: "array",
+            minItems: 2,
+            items: {
+              $ref: "#/definitions/Thing"
+            }
+          }
+        }
+      };
+      const form = createFormComponent({schema: complexSchema, formData: {foo: []}});
+      const inputs = form.node.querySelectorAll("input[type=text]");
+      expect(inputs.length).eql(0);
+    });
+
+
   });
 
   describe("Multiple choices list", () => {
diff --git a/test/StringField_test.js b/test/StringField_test.js
index 92b94a9de..60e1569cd 100644
--- a/test/StringField_test.js
+++ b/test/StringField_test.js
@@ -89,7 +89,7 @@ describe("StringField", () => {
 
       expect(onBlur.calledWith(input.id, "yo")).to.be.true;
     });
-    
+
     it("should handle an empty string change event", () => {
       const {comp, node} = createFormComponent({
         schema: {type: "string"},
@@ -294,6 +294,19 @@ describe("StringField", () => {
 
       expect(comp.state.formData).eql(undefined);
     });
+
+    it("should render a textarea field with rows", () => {
+      const {node} = createFormComponent({
+        schema: {type: "string"},
+        uiSchema: {
+          "ui:widget": "textarea",
+          "ui:options": {rows: 20}
+        },
+        formData: "x",
+      });
+
+      expect(node.querySelector("textarea").getAttribute("rows")).eql("20");
+    });
   });
 
   describe("DateTimeWidget", () => {