From 5c2e56e5f226c7fbff91808f41cea2893a6be3eb Mon Sep 17 00:00:00 2001 From: mc_fish Date: Thu, 2 Feb 2017 12:17:08 +0100 Subject: [PATCH 1/4] added additional support --- .gitignore | 2 + package.json | 1 + src/components/Form.js | 4 +- src/components/fields/ObjectField.js | 176 +++++++++++++++++---------- src/utils.js | 40 +++--- src/validate.js | 5 +- test/utils_test.js | 72 ----------- 7 files changed, 145 insertions(+), 155 deletions(-) diff --git a/.gitignore b/.gitignore index 7e671b889b..02aeee3be5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ build dist lib +/*.iml +.idea diff --git a/package.json b/package.json index de778a2702..4b989e18e1 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "jsonschema": "^1.0.2", "lodash.topath": "^4.5.2", + "lodash.merge": "^4.5.2", "setimmediate": "^1.0.5" }, "devDependencies": { diff --git a/src/components/Form.js b/src/components/Form.js index 7f8f2f31ea..078c6bbd07 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/fields/ObjectField.js b/src/components/fields/ObjectField.js index 1512aa1dbc..9db718a052 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -21,10 +21,7 @@ function objectKeysHaveChanged(formData, state) { return true; } // deep check on sorted keys - if (!deepEquals(newKeys.sort(), oldKeys.sort())) { - return true; - } - return false; + return !deepEquals(newKeys.sort(), oldKeys.sort()); } class ObjectField extends Component { @@ -36,7 +33,7 @@ class ObjectField extends Component { required: false, disabled: false, readonly: false, - } + }; constructor(props) { super(props); @@ -84,67 +81,124 @@ class ObjectField extends Component { }; render() { - const { - uiSchema, - errorSchema, - idSchema, - name, - required, - disabled, - readonly, - onBlur - } = this.props; - const {definitions, fields, formContext} = this.props.registry; - const {SchemaField, TitleField, DescriptionField} = fields; + const {name} = this.props; + const {definitions} = this.props.registry; const schema = retrieveSchema(this.props.schema, definitions); const title = (schema.title === undefined) ? name : schema.title; - let orderedProperties; - try { - const properties = Object.keys(schema.properties); - orderedProperties = orderProperties(properties, uiSchema["ui:order"]); - } catch (err) { - return ( -
-

- Invalid {name || "root"} object field configuration: - {err.message}. -

-
{JSON.stringify(schema)}
-
- ); + if("additionalProperties" in schema){ + return this.renderAdditionalProperties(schema, title); } - return ( -
- {title ? : null} - {schema.description ? - : null} - { - orderedProperties.map((name, index) => { + + return this.renderProperties(schema, title); + } + + renderProperties(schema, title){ + const { + uiSchema, + errorSchema, + idSchema, + name, + required, + disabled, + readonly, + onBlur + } = this.props; + const {fields, formContext} = this.props.registry; + const {SchemaField, TitleField, DescriptionField} = fields; + + let orderedProperties; + try { + const properties = Object.keys(schema.properties); + orderedProperties = orderProperties(properties, uiSchema["ui:order"]); + } catch (err) { return ( - +
+

+ Invalid {name || "root"} object field configuration: + {err.message}. +

+
{JSON.stringify(schema)}
+
); - }) - }
- ); + } + return ( +
+ {title ? : null} + {schema.description ? + : null} + {orderedProperties.map((name, index) => { + return ( + + ); + })} +
+ ); + } + + renderAdditionalProperties(schema, title){ + const { + uiSchema, + errorSchema, + idSchema, + required, + disabled, + readonly, + onBlur + } = this.props; + const {fields, formContext} = this.props.registry; + const {SchemaField, TitleField, DescriptionField} = fields; + + return ( +
+ {title ? : null} + {schema.description ? + : null} + {Object.keys(this.state).map((name, index) => { + const childIdSchema = {'$id': `${idSchema.$id}__${name}`}; + return ( + + ); + })} +
+ ); } } diff --git a/src/utils.js b/src/utils.js index 4656c99013..95bfd7c6ab 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ +import merge from "lodash.merge"; import React from "react"; import "setimmediate"; - const widgetMap = { boolean: { checkbox: "CheckboxWidget", @@ -112,7 +112,7 @@ function computeDefaults(schema, parentDefaults, definitions={}) { if (isObject(defaults) && isObject(schema.default)) { // For object defaults, only override parent defaults that are defined in // schema.default. - defaults = mergeObjects(defaults, schema.default); + defaults = merge(defaults, schema.default); } else if ("default" in schema) { // Use schema defaults for this node. defaults = schema.default; @@ -129,6 +129,10 @@ function computeDefaults(schema, parentDefaults, definitions={}) { } // We need to recur for object schema inner default values. if (schema.type === "object") { + if(!schema.properties){ + return defaults; + } + 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]. @@ -150,7 +154,7 @@ export function getDefaultFormState(_schema, formData, definitions={}) { return defaults; } if (isObject(formData)) { // Override schema defaults with form data. - return mergeObjects(defaults, formData); + return merge(defaults, formData); } return formData || defaults; } @@ -175,21 +179,21 @@ export function isObject(thing) { return typeof thing === "object" && thing !== null && !Array.isArray(thing); } -export function mergeObjects(obj1, obj2, concatArrays = false) { - // Recursively merge deeply nested objects. - var acc = Object.assign({}, obj1); // Prevent mutation of source object. - return Object.keys(obj2).reduce((acc, key) =>{ - const left = obj1[key], right = obj2[key]; - if (obj1.hasOwnProperty(key) && isObject(right)) { - acc[key] = mergeObjects(left, right, concatArrays); - } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) { - acc[key] = left.concat(right); - } else { - acc[key] = right; - } - return acc; - }, acc); -} +// export function mergeObjects(obj1, obj2, concatArrays = false) { +// // Recursively merge deeply nested objects. +// var acc = Object.assign({}, obj1); // Prevent mutation of source object. +// return Object.keys(obj2).reduce((acc, key) =>{ +// const left = obj1[key], right = obj2[key]; +// if (obj1.hasOwnProperty(key) && isObject(right)) { +// acc[key] = mergeObjects(left, right, concatArrays); +// } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) { +// acc[key] = left.concat(right); +// } else { +// acc[key] = right; +// } +// return acc; +// }, acc); +// } export function asNumber(value) { if (value === "") { diff --git a/src/validate.js b/src/validate.js index 0a7fe27a96..4157d3eb44 100644 --- a/src/validate.js +++ b/src/validate.js @@ -1,7 +1,8 @@ +import merge from "lodash.merge"; import toPath from "lodash.topath"; import {validate as jsonValidate} from "jsonschema"; -import {isObject, mergeObjects} from "./utils"; +import {isObject} from "./utils"; function toErrorSchema(errors) { // Transforms a jsonschema validation errors list: @@ -109,7 +110,7 @@ export default function validateFormData(formData, schema, customValidate, trans const errorHandler = customValidate(formData, createErrorHandler(formData)); const userErrorSchema = unwrapErrorHandler(errorHandler); - const newErrorSchema = mergeObjects(errorSchema, userErrorSchema, true); + const newErrorSchema = merge(errorSchema, userErrorSchema); // XXX: The errors list produced is not fully compliant with the format // exposed by the jsonschema lib, which contains full field paths and other // properties. diff --git a/test/utils_test.js b/test/utils_test.js index 97fbfc07cc..a8380ce420 100644 --- a/test/utils_test.js +++ b/test/utils_test.js @@ -6,7 +6,6 @@ import { deepEquals, getDefaultFormState, isMultiSelect, - mergeObjects, pad, parseDateString, retrieveSchema, @@ -264,77 +263,6 @@ describe("utils", () => { }); }); - describe("mergeObjects()", () => { - it("should't mutate the provided objects", () => { - const obj1 = {a: 1}; - mergeObjects(obj1, {b: 2}); - expect(obj1).eql({a: 1}); - }); - - it("should merge two one-level deep objects", () => { - expect(mergeObjects({a: 1}, {b: 2})).eql({a: 1, b: 2}); - }); - - it("should override the first object with the values from the second", () => { - expect(mergeObjects({a: 1}, {a: 2})).eql({a: 2}); - }); - - it("should recursively merge deeply nested objects", () => { - const obj1 = { - a: 1, - b: { - c: 3, - d: [1, 2, 3], - e: {f: {g: 1}} - }, - c: 2 - }; - const obj2 = { - a: 1, - b: { - d: [3, 2, 1], - e: {f: {h: 2}}, - g: 1 - }, - c: 3 - }; - const expected = { - a: 1, - b: { - c: 3, - d: [3, 2, 1], - e: {f: {g: 1, h: 2}}, - g: 1 - }, - c: 3 - }; - expect(mergeObjects(obj1, obj2)).eql(expected); - }); - - describe("concatArrays option", () => { - it("should not concat arrays by default", () => { - const obj1 = {a: [1]}; - const obj2 = {a: [2]}; - - expect(mergeObjects(obj1, obj2)).eql({a: [2]}); - }); - - it("should concat arrays when concatArrays is true", () => { - const obj1 = {a: [1]}; - const obj2 = {a: [2]}; - - expect(mergeObjects(obj1, obj2, true)).eql({a: [1, 2]}); - }); - - it("should concat nested arrays when concatArrays is true", () => { - const obj1 = {a: {b: [1]}}; - const obj2 = {a: {b: [2]}}; - - expect(mergeObjects(obj1, obj2, true)).eql({a: {b: [1, 2]}}); - }); - }); - }); - describe("retrieveSchema()", () => { it("should 'resolve' a schema which contains definitions", () => { const schema = {$ref: "#/definitions/address"}; From d1f5f188025f8aa7dadbff45490aefed4e349598 Mon Sep 17 00:00:00 2001 From: mc_fish Date: Thu, 2 Feb 2017 12:19:20 +0100 Subject: [PATCH 2/4] cleanup --- src/utils.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/utils.js b/src/utils.js index 95bfd7c6ab..a00f7eadd5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -179,22 +179,6 @@ export function isObject(thing) { return typeof thing === "object" && thing !== null && !Array.isArray(thing); } -// export function mergeObjects(obj1, obj2, concatArrays = false) { -// // Recursively merge deeply nested objects. -// var acc = Object.assign({}, obj1); // Prevent mutation of source object. -// return Object.keys(obj2).reduce((acc, key) =>{ -// const left = obj1[key], right = obj2[key]; -// if (obj1.hasOwnProperty(key) && isObject(right)) { -// acc[key] = mergeObjects(left, right, concatArrays); -// } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) { -// acc[key] = left.concat(right); -// } else { -// acc[key] = right; -// } -// return acc; -// }, acc); -// } - export function asNumber(value) { if (value === "") { return undefined; From 19520bf1c37f3b9d1ea25bbaf93e0a41f4a723a0 Mon Sep 17 00:00:00 2001 From: mc_fish Date: Thu, 2 Feb 2017 12:31:34 +0100 Subject: [PATCH 3/4] fix --- src/components/fields/ObjectField.js | 42 ++++++++++++++-------------- src/utils.js | 18 +++++++++++- src/validate.js | 5 ++-- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 9db718a052..011efbeeda 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -85,15 +85,15 @@ class ObjectField extends Component { const {definitions} = this.props.registry; const schema = retrieveSchema(this.props.schema, definitions); const title = (schema.title === undefined) ? name : schema.title; - if("additionalProperties" in schema){ - return this.renderAdditionalProperties(schema, title); + if ("additionalProperties" in schema){ + return this.renderAdditionalProperties(schema, title); } return this.renderProperties(schema, title); } renderProperties(schema, title){ - const { + const { uiSchema, errorSchema, idSchema, @@ -103,15 +103,15 @@ class ObjectField extends Component { readonly, onBlur } = this.props; - const {fields, formContext} = this.props.registry; - const {SchemaField, TitleField, DescriptionField} = fields; - - let orderedProperties; - try { - const properties = Object.keys(schema.properties); - orderedProperties = orderProperties(properties, uiSchema["ui:order"]); - } catch (err) { - return ( + const {fields, formContext} = this.props.registry; + const {SchemaField, TitleField, DescriptionField} = fields; + + let orderedProperties; + try { + const properties = Object.keys(schema.properties); + orderedProperties = orderProperties(properties, uiSchema["ui:order"]); + } catch (err) { + return (

Invalid {name || "root"} object field configuration: @@ -120,8 +120,8 @@ class ObjectField extends Component {

{JSON.stringify(schema)}
); - } - return ( + } + return (
{title ? : null} {orderedProperties.map((name, index) => { - return ( + return ( {title ? : null} {Object.keys(this.state).map((name, index) => { - const childIdSchema = {'$id': `${idSchema.$id}__${name}`}; - return ( + const childIdSchema = {"$id": `${idSchema.$id}__${name}`}; + return ( { + const left = obj1[key], right = obj2[key]; + if (obj1.hasOwnProperty(key) && isObject(right)) { + acc[key] = mergeObjects(left, right, concatArrays); + } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) { + acc[key] = left.concat(right); + } else { + acc[key] = right; + } + return acc; + }, acc); +} + export function asNumber(value) { if (value === "") { return undefined; diff --git a/src/validate.js b/src/validate.js index 4157d3eb44..0a7fe27a96 100644 --- a/src/validate.js +++ b/src/validate.js @@ -1,8 +1,7 @@ -import merge from "lodash.merge"; import toPath from "lodash.topath"; import {validate as jsonValidate} from "jsonschema"; -import {isObject} from "./utils"; +import {isObject, mergeObjects} from "./utils"; function toErrorSchema(errors) { // Transforms a jsonschema validation errors list: @@ -110,7 +109,7 @@ export default function validateFormData(formData, schema, customValidate, trans const errorHandler = customValidate(formData, createErrorHandler(formData)); const userErrorSchema = unwrapErrorHandler(errorHandler); - const newErrorSchema = merge(errorSchema, userErrorSchema); + const newErrorSchema = mergeObjects(errorSchema, userErrorSchema, true); // XXX: The errors list produced is not fully compliant with the format // exposed by the jsonschema lib, which contains full field paths and other // properties. From 71363a8758fcc56945f8ef0eddabbcf6506e7dbf Mon Sep 17 00:00:00 2001 From: mc_fish Date: Thu, 2 Feb 2017 12:53:07 +0100 Subject: [PATCH 4/4] samples --- playground/samples/additionalProperties.js | 27 ++++++++++++++++++++++ playground/samples/index.js | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 playground/samples/additionalProperties.js diff --git a/playground/samples/additionalProperties.js b/playground/samples/additionalProperties.js new file mode 100644 index 0000000000..9a9348bc8e --- /dev/null +++ b/playground/samples/additionalProperties.js @@ -0,0 +1,27 @@ +module.exports = { + schema: { + "title": "A registration form", + "description": "A simple form example.", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "devMap": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + uiSchema: {}, + formData: { + "name": "test", + "devMap": { + "key": 2, + "value": 12 + } + } +}; diff --git a/playground/samples/index.js b/playground/samples/index.js index 1527696cc1..08cb748499 100644 --- a/playground/samples/index.js +++ b/playground/samples/index.js @@ -13,6 +13,7 @@ import validation from "./validation"; import files from "./files"; import single from "./single"; import customArray from "./customArray"; +import additionalProperties from "./additionalProperties"; export const samples = { Simple: simple, @@ -30,4 +31,5 @@ export const samples = { Files: files, Single: single, "Custom Array": customArray, + AdditionalProperties: additionalProperties };