From 84aca603889dd9cd6a788392b6b285ffd178f5d1 Mon Sep 17 00:00:00 2001 From: Stathis Date: Mon, 21 Nov 2016 11:05:07 +0000 Subject: [PATCH 1/9] Got something working --- src/components/fields/ArrayField.js | 129 +++++++++++++++++++++------ src/components/fields/ObjectField.js | 10 ++- src/components/fields/SchemaField.js | 24 +++-- 3 files changed, 129 insertions(+), 34 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 6239274bec..955b6526e2 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -56,11 +56,12 @@ class ArrayField extends Component { constructor(props) { super(props); - this.state = this.getStateFromProps(props); + this.state = { formData: this.getStateFromProps(props), anyOfItems: [], selectValues: []}; } componentWillReceiveProps(nextProps) { - this.setState(this.getStateFromProps(nextProps)); + const newState = Object.assign(this.state, {formData: this.getStateFromProps(nextProps)}); + this.setState(newState); } getStateFromProps(props) { @@ -86,32 +87,47 @@ class ArrayField extends Component { asyncSetState(state, options={validate: false}) { setState(this, state, () => { - this.props.onChange(this.state.items, options); + this.props.onChange(this.state.formData.items, options); }); } + anyOfItems1() { + const schema = this.props.schema; + return schema.items.anyOf; + } + onAddClick = (event) => { event.preventDefault(); - const {items} = this.state; - const {schema, registry} = this.props; + const {items} = this.state.formData; + const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; + const anyOfItems = this.anyOfItems1(); if (isFixedItems(schema) && allowAdditionalItems(schema)) { itemSchema = schema.additionalItems; } - this.asyncSetState({ + if (anyOfItems) { + itemSchema = anyOfItems ? anyOfItems[0] : null; + } + + const items1 = { items: items.concat([ getDefaultFormState(itemSchema, undefined, definitions) ]) - }); + }; + const anyOfItems1 = this.state.anyOfItems.concat([anyOfItems[0]]); + const newState = Object.assign(this.state, {formData: items1}, {anyOfItems: anyOfItems1}); + this.asyncSetState(newState); }; onDropIndexClick = (index) => { return (event) => { event.preventDefault(); - this.asyncSetState({ - items: this.state.items.filter((_, i) => i !== index) - }, {validate: true}); // refs #195 + const items1 = { + items: this.state.formData.items.filter((_, i) => i !== index) + }; + const newState = Object.assign(this.state, {formData: items1}); + this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -119,7 +135,7 @@ class ArrayField extends Component { return (event) => { event.preventDefault(); event.target.blur(); - const {items} = this.state; + const {items} = this.state.formData; this.asyncSetState({ items: items.map((item, i) => { if (i === newIndex) { @@ -136,11 +152,13 @@ class ArrayField extends Component { onChangeForIndex = (index) => { return (value) => { - this.asyncSetState({ - items: this.state.items.map((item, i) => { + const items1 = { + items: this.state.formData.items.map((item, i) => { return index === i ? value : item; }) - }); + } + const newState = Object.assign(this.state, {formData: items1}); + this.asyncSetState(items1); }; }; @@ -148,6 +166,33 @@ class ArrayField extends Component { this.asyncSetState({items: value}); }; + anyOfOptions(anyOfItems) { + return anyOfItems.map(item => { return {value: item.type, label: item.type}; }); + } + + setType(index, value) { + // event.preventDefault(); + const {items} = this.state.formData; + const {schema, registry } = this.props; + const {definitions} = registry; + let itemSchema = schema.items; + const anyOfItems = this.anyOfItems1(); + const newItems = items.slice(); + const foundItem = anyOfItems.find((element) => { return element.type === value }); + newItems[index] = getDefaultFormState(foundItem, undefined, definitions); + + const anyOfItems1 = this.state.anyOfItems; + anyOfItems1[index] = foundItem; + + const selectValues = this.state.selectValues; + selectValues[index] = value; + + const newState = Object.assign(this.state, {formData: {items: newItems}}, {anyOfItems: anyOfItems1}, {selectValues: selectValues}); + + this.asyncSetState(newState); + + } + render() { const {schema, uiSchema} = this.props; if (isFilesArray(schema, uiSchema)) { @@ -175,11 +220,13 @@ class ArrayField extends Component { autofocus, } = this.props; const title = (schema.title === undefined) ? name : schema.title; - const {items} = this.state; + const {items} = this.state.formData; const {definitions, fields} = this.props.registry; const {TitleField, DescriptionField} = fields; - const itemsSchema = retrieveSchema(schema.items, definitions); + let itemsSchema = retrieveSchema(schema.items, definitions); const {addable=true} = getUiOptions(uiSchema); + const anyOfItems = this.anyOfItems1(); + const newItems = items; return (
: null}
{ - items.map((item, index) => { + newItems.map((item, index) => { + if (anyOfItems) { + itemsSchema = this.state.anyOfItems[index]; + } const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; const itemIdPrefix = idSchema.$id + "_" + index; const itemIdSchema = toIdSchema(itemsSchema, itemIdPrefix, definitions); return this.renderArrayFieldItem({ index, canMoveUp: index > 0, - canMoveDown: index < items.length - 1, + canMoveDown: index < newItems.length - 1, itemSchema: itemsSchema, itemIdSchema, itemErrorSchema, - itemData: items[index], + itemData: newItems[index], itemUiSchema: uiSchema.items, - autofocus: autofocus && index === 0 + autofocus: autofocus && index === 0, + anyOfItems: anyOfItems }); }) }
@@ -221,7 +272,7 @@ class ArrayField extends Component { renderMultiSelect() { const {schema, idSchema, uiSchema, disabled, readonly, autofocus} = this.props; - const {items} = this.state; + const {items} = this.state.formData; const {widgets, definitions} = this.props.registry; const itemsSchema = retrieveSchema(schema.items, definitions); const enumOptions = optionsList(itemsSchema); @@ -244,7 +295,7 @@ class ArrayField extends Component { renderFiles() { const {schema, uiSchema, idSchema, name, disabled, readonly, autofocus} = this.props; const title = schema.title || name; - const {items} = this.state; + const {items} = this.state.formData; const {widgets} = this.props.registry; const {widget="files", ...options} = getUiOptions(uiSchema); const Widget = getWidget(schema, widget, widgets); @@ -276,13 +327,19 @@ class ArrayField extends Component { autofocus, } = this.props; const title = schema.title || name; - let {items} = this.state; + let {items} = this.state.formData; + const {selectValues} = this.state; const {definitions, fields} = this.props.registry; const {TitleField} = fields; const itemSchemas = schema.items.map(item => retrieveSchema(item, definitions)); - const additionalSchema = allowAdditionalItems(schema) ? + let additionalSchema = allowAdditionalItems(schema) ? retrieveSchema(schema.additionalItems, definitions) : null; + const anyOfItems = this.anyOfItems1(); + + // if (!additionalSchema) { + // additionalSchema = anyOfItems ? anyOfItems[0] : null; + // } const {addable=true} = getUiOptions(uiSchema); const canAdd = addable && additionalSchema; @@ -304,7 +361,7 @@ class ArrayField extends Component {
{ items.map((item, index) => { const additional = index >= itemSchemas.length; - const itemSchema = additional ? + let itemSchema = additional ? additionalSchema : itemSchemas[index]; const itemIdPrefix = idSchema.$id + "_" + index; const itemIdSchema = toIdSchema(itemSchema, itemIdPrefix, definitions); @@ -324,7 +381,9 @@ class ArrayField extends Component { itemUiSchema, itemIdSchema, itemErrorSchema, - autofocus: autofocus && index === 0 + autofocus: autofocus && index === 0, + anyOfItems, + value: selectValues[index] || "" }); }) }
@@ -347,7 +406,9 @@ class ArrayField extends Component { itemUiSchema, itemIdSchema, itemErrorSchema, - autofocus + autofocus, + anyOfItems, + value }) { const {SchemaField} = this.props.registry.fields; const {disabled, readonly, uiSchema} = this.props; @@ -363,10 +424,22 @@ class ArrayField extends Component { }; has.toolbar = Object.keys(has).some(key => has[key]); const btnStyle = {flex: 1, paddingLeft: 6, paddingRight: 6, fontWeight: "bold"}; + const {SelectWidget} = this.props.registry.widgets; return (
+ + {anyOfItems ? ( + this.setType(index, value)}/> + ) : null} + { this.props.onChange(this.state, options); @@ -97,6 +103,7 @@ class ObjectField extends Component { const {SchemaField, TitleField, DescriptionField} = fields; const schema = retrieveSchema(this.props.schema, definitions); const title = (schema.title === undefined) ? name : schema.title; + let orderedProperties; try { const properties = Object.keys(schema.properties); @@ -112,6 +119,7 @@ class ObjectField extends Component {
); } + return (
{title ? + readonly={readonly} /> ); }) }
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js index 266e183ad6..7ec029c4f1 100644 --- a/src/components/fields/SchemaField.js +++ b/src/components/fields/SchemaField.js @@ -18,7 +18,7 @@ const COMPONENT_TYPES = { string: "StringField", }; -function getFieldComponent(schema, uiSchema, fields) { +function getFieldComponent(schema, name, uiSchema, fields) { const field = uiSchema["ui:field"]; if (typeof field === "function") { return field; @@ -26,7 +26,10 @@ function getFieldComponent(schema, uiSchema, fields) { if (typeof field === "string" && field in fields) { return fields[field]; } - const componentName = COMPONENT_TYPES[schema.type]; + + + const type = name === "anyOf" ? "array" : schema.type; + let componentName = COMPONENT_TYPES[type]; return componentName in fields ? fields[componentName] : UnsupportedField; } @@ -131,8 +134,16 @@ DefaultTemplate.defaultProps = { function SchemaField(props) { const {uiSchema, errorSchema, idSchema, name, required, registry} = props; const {definitions, fields, formContext, FieldTemplate = DefaultTemplate} = registry; - const schema = retrieveSchema(props.schema, definitions); - const FieldComponent = getFieldComponent(schema, uiSchema, fields); + const schema1 = retrieveSchema(props.schema, definitions); + const FieldComponent = getFieldComponent(schema1, name, uiSchema, fields); + let schema2 = schema1; + if (name === "anyOf") { + schema2 = { + "items": [{"type": "number"}] + } + } + + const schema = name === "anyOf" ? schema2 : schema1; const {DescriptionField} = fields; const disabled = Boolean(props.disabled || uiSchema["ui:disabled"]); const readonly = Boolean(props.readonly || uiSchema["ui:readonly"]); @@ -221,7 +232,10 @@ SchemaField.defaultProps = { if (process.env.NODE_ENV !== "production") { SchemaField.propTypes = { - schema: PropTypes.object.isRequired, + schema: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.array, + ]).isRequired, uiSchema: PropTypes.object, idSchema: PropTypes.object, formData: PropTypes.any, From 4d11758eab0086685fb2c401ae0063650e9d28ed Mon Sep 17 00:00:00 2001 From: Stathis Date: Tue, 22 Nov 2016 16:11:37 +0000 Subject: [PATCH 2/9] Added tests --- playground/app.js | 6 +- playground/samples/index.js | 4 +- src/components/fields/ArrayField.js | 108 ++++++++++++++++++-------- src/components/fields/ObjectField.js | 10 +-- src/components/fields/SchemaField.js | 12 +-- test/ArrayField_test.js | 111 ++++++++++++++++++++++++++- 6 files changed, 197 insertions(+), 54 deletions(-) diff --git a/playground/app.js b/playground/app.js index c792554327..cc8283cdbe 100644 --- a/playground/app.js +++ b/playground/app.js @@ -209,7 +209,7 @@ class Editor extends Component { class Selector extends Component { constructor(props) { super(props); - this.state = {current: "Simple"}; + this.state = {current: "AnyOf"}; } shouldComponentUpdate(nextProps, nextState) { @@ -261,7 +261,7 @@ class App extends Component { constructor(props) { super(props); // initialize state with Simple data sample - const {schema, uiSchema, formData, validate} = samples.Simple; + const {schema, uiSchema, formData, validate} = samples.AnyOf; this.state = { form: false, schema, @@ -275,7 +275,7 @@ class App extends Component { } componentDidMount() { - this.load(samples.Simple); + this.load(samples.AnyOf); } shouldComponentUpdate(nextProps, nextState) { diff --git a/playground/samples/index.js b/playground/samples/index.js index 5f6d280dcc..97063d4a28 100644 --- a/playground/samples/index.js +++ b/playground/samples/index.js @@ -12,6 +12,7 @@ import date from "./date"; import validation from "./validation"; import files from "./files"; import single from "./single"; +import anyOf from "./anyOf"; export const samples = { Simple: simple, @@ -27,5 +28,6 @@ export const samples = { "Date & time": date, Validation: validation, Files: files, - Single: single + Single: single, + AnyOf: anyOf }; diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 955b6526e2..7debf04531 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -60,7 +60,7 @@ class ArrayField extends Component { } componentWillReceiveProps(nextProps) { - const newState = Object.assign(this.state, {formData: this.getStateFromProps(nextProps)}); + const newState = Object.assign({}, this.state, {formData: this.getStateFromProps(nextProps)}); this.setState(newState); } @@ -91,7 +91,7 @@ class ArrayField extends Component { }); } - anyOfItems1() { + getAnyOfItems() { const schema = this.props.schema; return schema.items.anyOf; } @@ -102,31 +102,53 @@ class ArrayField extends Component { const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.anyOfItems1(); + const anyOfItems = this.getAnyOfItems(); if (isFixedItems(schema) && allowAdditionalItems(schema)) { itemSchema = schema.additionalItems; } + + let anyOfState = {} + let selectValues = [] if (anyOfItems) { - itemSchema = anyOfItems ? anyOfItems[0] : null; + itemSchema = anyOfItems[0]; + + selectValues = [ + ...this.state.selectValues, + itemSchema.type + ]; + + anyOfState = {anyOfItems: [ + ...this.state.anyOfItems, + itemSchema + ]}; + console.log(anyOfState); } - const items1 = { + const newItems = { items: items.concat([ getDefaultFormState(itemSchema, undefined, definitions) ]) }; - const anyOfItems1 = this.state.anyOfItems.concat([anyOfItems[0]]); - const newState = Object.assign(this.state, {formData: items1}, {anyOfItems: anyOfItems1}); + const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState, {selectValues: selectValues}); this.asyncSetState(newState); }; onDropIndexClick = (index) => { return (event) => { event.preventDefault(); - const items1 = { - items: this.state.formData.items.filter((_, i) => i !== index) + const { + formData: { + items + }, + anyOfItems, + selectValues + } = this.state; + const newItems = { + items: items.filter((_, i) => i !== index) }; - const newState = Object.assign(this.state, {formData: items1}); + const newAnyOfItems = anyOfItems.filter((_, i) => i !== index); + const newSelectValues = selectValues.filter((_, i) => i !== index); + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -136,7 +158,11 @@ class ArrayField extends Component { event.preventDefault(); event.target.blur(); const {items} = this.state.formData; - this.asyncSetState({ + const { + anyOfItems, + selectValues + } = this.state; + const newItems = { items: items.map((item, i) => { if (i === newIndex) { return items[index]; @@ -146,24 +172,47 @@ class ArrayField extends Component { return item; } }) - }, {validate: true}); + }; + const newAnyOfItems = anyOfItems.map((item, i) => { + if (i === newIndex) { + return anyOfItems[index]; + } else if (i === index) { + return anyOfItems[newIndex]; + } else { + return item; + } + }); + const newSelectValues = + selectValues.map((item, i) => { + if (i === newIndex) { + return selectValues[index]; + } else if (i === index) { + return selectValues[newIndex]; + } else { + return item; + } + }); + + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); + this.asyncSetState(newState, {validate: true}); }; }; onChangeForIndex = (index) => { return (value) => { - const items1 = { + const items = { items: this.state.formData.items.map((item, i) => { return index === i ? value : item; }) } - const newState = Object.assign(this.state, {formData: items1}); - this.asyncSetState(items1); + const newState = Object.assign({}, this.state, {formData: items}); + this.asyncSetState(newState); }; }; onSelectChange = (value) => { - this.asyncSetState({items: value}); + const newState = Object.assign({}, this.state, {formData: {items: value}}); + this.asyncSetState(newState); }; anyOfOptions(anyOfItems) { @@ -176,18 +225,18 @@ class ArrayField extends Component { const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.anyOfItems1(); + const anyOfItems = this.getAnyOfItems(); const newItems = items.slice(); const foundItem = anyOfItems.find((element) => { return element.type === value }); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); - const anyOfItems1 = this.state.anyOfItems; - anyOfItems1[index] = foundItem; + const newAnyOfItems = [...this.state.anyOfItems]; + newAnyOfItems[index] = foundItem; - const selectValues = this.state.selectValues; - selectValues[index] = value; + const newSelectValues = [...this.state.selectValues]; + newSelectValues[index] = value; - const newState = Object.assign(this.state, {formData: {items: newItems}}, {anyOfItems: anyOfItems1}, {selectValues: selectValues}); + const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); this.asyncSetState(newState); @@ -221,13 +270,15 @@ class ArrayField extends Component { } = this.props; const title = (schema.title === undefined) ? name : schema.title; const {items} = this.state.formData; + const {selectValues} = this.state; const {definitions, fields} = this.props.registry; const {TitleField, DescriptionField} = fields; let itemsSchema = retrieveSchema(schema.items, definitions); const {addable=true} = getUiOptions(uiSchema); - const anyOfItems = this.anyOfItems1(); + const anyOfItems = this.getAnyOfItems(); const newItems = items; + console.log(selectValues, this.state.anyOfItems) return (
@@ -259,7 +310,8 @@ class ArrayField extends Component { itemData: newItems[index], itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, - anyOfItems: anyOfItems + anyOfItems: anyOfItems, + value: selectValues[index] || "" }); }) }
@@ -328,14 +380,13 @@ class ArrayField extends Component { } = this.props; const title = schema.title || name; let {items} = this.state.formData; - const {selectValues} = this.state; const {definitions, fields} = this.props.registry; const {TitleField} = fields; const itemSchemas = schema.items.map(item => retrieveSchema(item, definitions)); let additionalSchema = allowAdditionalItems(schema) ? retrieveSchema(schema.additionalItems, definitions) : null; - const anyOfItems = this.anyOfItems1(); + const anyOfItems = this.getAnyOfItems(); // if (!additionalSchema) { // additionalSchema = anyOfItems ? anyOfItems[0] : null; @@ -348,7 +399,6 @@ class ArrayField extends Component { items = items || []; items = items.concat(new Array(itemSchemas.length - items.length)); } - return (
@@ -425,7 +474,6 @@ class ArrayField extends Component { has.toolbar = Object.keys(has).some(key => has[key]); const btnStyle = {flex: 1, paddingLeft: 6, paddingRight: 6, fontWeight: "bold"}; const {SelectWidget} = this.props.registry.widgets; - return (
diff --git a/src/components/fields/ObjectField.js b/src/components/fields/ObjectField.js index 279852d312..17e3d506b5 100644 --- a/src/components/fields/ObjectField.js +++ b/src/components/fields/ObjectField.js @@ -71,12 +71,6 @@ class ObjectField extends Component { schema.required.indexOf(name) !== -1; } - // anyOfItems(name) { - // const schema = this.props.schema; - // console.log(schema); - // return schema.properties.anyOf; - // } - asyncSetState(state, options={validate: false}) { setState(this, state, () => { this.props.onChange(this.state, options); @@ -103,7 +97,6 @@ class ObjectField extends Component { const {SchemaField, TitleField, DescriptionField} = fields; const schema = retrieveSchema(this.props.schema, definitions); const title = (schema.title === undefined) ? name : schema.title; - let orderedProperties; try { const properties = Object.keys(schema.properties); @@ -119,7 +112,6 @@ class ObjectField extends Component {
); } - return (
{title ? + readonly={readonly}/> ); }) }
diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js index 7ec029c4f1..9df8a0324c 100644 --- a/src/components/fields/SchemaField.js +++ b/src/components/fields/SchemaField.js @@ -134,16 +134,8 @@ DefaultTemplate.defaultProps = { function SchemaField(props) { const {uiSchema, errorSchema, idSchema, name, required, registry} = props; const {definitions, fields, formContext, FieldTemplate = DefaultTemplate} = registry; - const schema1 = retrieveSchema(props.schema, definitions); - const FieldComponent = getFieldComponent(schema1, name, uiSchema, fields); - let schema2 = schema1; - if (name === "anyOf") { - schema2 = { - "items": [{"type": "number"}] - } - } - - const schema = name === "anyOf" ? schema2 : schema1; + const schema = retrieveSchema(props.schema, definitions); + const FieldComponent = getFieldComponent(schema, name, uiSchema, fields); const {DescriptionField} = fields; const disabled = Boolean(props.disabled || uiSchema["ui:disabled"]); const readonly = Boolean(props.readonly || uiSchema["ui:readonly"]); diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js index 4f0aaac5ef..b7a2a93cb0 100644 --- a/test/ArrayField_test.js +++ b/test/ArrayField_test.js @@ -107,7 +107,7 @@ describe("ArrayField", () => { Simulate.click(node.querySelector(".array-item-add button")); - expect(node.querySelectorAll(".field-string")) + expect(node.querySelectorAll(".array-item")) .to.have.length.of(1); }); @@ -736,4 +736,113 @@ describe("ArrayField", () => { expect(node.querySelector("#title-")).to.be.null; }); }); + describe.only("Any of", () => { + const schema = { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + }; + + let node, comp; + + beforeEach(() => { + const form = createFormComponent({schema}); + node = form.node; + comp = form.comp; + Simulate.click(node.querySelector(".array-item-add button")); + }); + + it("should render an add button", () => { + expect(node.querySelector(".array-item-add button")) + .not.eql(null); + }); + + it("should render a select along with the input field", () => { + expect(node.querySelectorAll(".array-item")) + .to.have.length.of(1); + }); + + it("should render two select and input fields", () => { + Simulate.click(node.querySelector(".array-item-add button")); + expect(node.querySelectorAll(".array-item")).to.have.length.of(2); + }); + + it("should change the type of the widget", () => { + Simulate.change(node.querySelector(".array-item select"), { + target: {value: "number"} + }); + expect(node.querySelector(".array-item select").value).eql("number"); + expect(node.querySelectorAll(".array-item .field-number")).to.have.length.of(1); + }); + + it("should have correct type at start", () => { + Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: 'asd'}}); + expect(node.querySelector("fieldset .field-string input[type=text]").value).eql("asd"); + expect(comp.state.formData).eql(['asd']); + }); + + it("should clear the value after type update", () => { + Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: "bar"}}); + Simulate.change(node.querySelector(".array-item select"), { + target: {value: "number"} + }); + + expect(node.querySelector("fieldset .field-number input[type=text]").value).eql(''); + }); + + it("should update types accordingly", () => { + Simulate.click(node.querySelector(".array-item-add button")); + const selects = node.querySelectorAll(".array-item select"); + + Simulate.change(selects[0], { + target: {value: "number"} + }); + + Simulate.change(node.querySelector("fieldset .field-number input[type=text]"), {target: {value: '123'}}); + Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: '123'}}); + expect(comp.state.formData).eql([123, '123']); + }); + + it("should delete the correct widget", () => { + Simulate.click(node.querySelector(".array-item-add button")); + + const selects = node.querySelectorAll(".array-item select"); + + Simulate.change(selects[0], { + target: {value: "number"} + }); + + const dropBtns = node.querySelectorAll(".array-item-remove"); + + Simulate.click(dropBtns[0]); + expect(node.querySelectorAll(".array-item .field-number")).to.have.length.of(0); + }); + + it("should reorder widgets correctly", () => { + Simulate.click(node.querySelector(".array-item-add button")); + + const selects = node.querySelectorAll(".array-item select"); + + Simulate.change(selects[0], { + target: {value: "number"} + }); + + const moveDownBtns = node.querySelectorAll(".array-item-move-down"); + Simulate.click(moveDownBtns[0]); + + const inputs = node.querySelectorAll(".field-string input[type=text]"); + expect(node.querySelectorAll(".array-item select")[0].value).eql('string'); + + + // expect(comp.state.formData).eql([123, '123']); + }); + }); }); From cc2b5d64f34ee63b923e885a5a795be020ec15e4 Mon Sep 17 00:00:00 2001 From: Stathis Date: Tue, 22 Nov 2016 16:12:22 +0000 Subject: [PATCH 3/9] Added anyOf file --- playground/samples/anyOf.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 playground/samples/anyOf.js diff --git a/playground/samples/anyOf.js b/playground/samples/anyOf.js new file mode 100644 index 0000000000..14aad51716 --- /dev/null +++ b/playground/samples/anyOf.js @@ -0,0 +1,23 @@ +module.exports = { + schema: { + "title": "Any of", + "type": "object", + "properties": { + "List of widgets": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + } + } + }, + uiSchema: {}, + formData: {} +}; From 140c4572e4761aecce1eac68c656d0ed0cf69b5a Mon Sep 17 00:00:00 2001 From: Stathis Date: Tue, 22 Nov 2016 16:52:29 +0000 Subject: [PATCH 4/9] Fix small issues and add tests --- src/components/fields/ArrayField.js | 90 +++++++++-------------------- test/ArrayField_test.js | 16 +++-- 2 files changed, 39 insertions(+), 67 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 7debf04531..0b2240c3e4 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -56,7 +56,7 @@ class ArrayField extends Component { constructor(props) { super(props); - this.state = { formData: this.getStateFromProps(props), anyOfItems: [], selectValues: []}; + this.state = { formData: this.getStateFromProps(props), anyOfItems: []}; } componentWillReceiveProps(nextProps) { @@ -91,7 +91,7 @@ class ArrayField extends Component { }); } - getAnyOfItems() { + getAnyOfItemsSchema() { const schema = this.props.schema; return schema.items.anyOf; } @@ -99,29 +99,22 @@ class ArrayField extends Component { onAddClick = (event) => { event.preventDefault(); const {items} = this.state.formData; - const {schema, registry } = this.props; + const {schema, registry} = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.getAnyOfItems(); + const anyOfItems = this.getAnyOfItemsSchema(); if (isFixedItems(schema) && allowAdditionalItems(schema)) { itemSchema = schema.additionalItems; } let anyOfState = {} - let selectValues = [] if (anyOfItems) { itemSchema = anyOfItems[0]; - selectValues = [ - ...this.state.selectValues, - itemSchema.type - ]; - anyOfState = {anyOfItems: [ ...this.state.anyOfItems, itemSchema ]}; - console.log(anyOfState); } const newItems = { @@ -129,26 +122,20 @@ class ArrayField extends Component { getDefaultFormState(itemSchema, undefined, definitions) ]) }; - const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState, {selectValues: selectValues}); + const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState); this.asyncSetState(newState); }; onDropIndexClick = (index) => { return (event) => { event.preventDefault(); - const { - formData: { - items - }, - anyOfItems, - selectValues - } = this.state; + const {anyOfItems} = this.state; + const {items} = this.state.formData; const newItems = { items: items.filter((_, i) => i !== index) }; const newAnyOfItems = anyOfItems.filter((_, i) => i !== index); - const newSelectValues = selectValues.filter((_, i) => i !== index); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -160,7 +147,6 @@ class ArrayField extends Component { const {items} = this.state.formData; const { anyOfItems, - selectValues } = this.state; const newItems = { items: items.map((item, i) => { @@ -182,18 +168,8 @@ class ArrayField extends Component { return item; } }); - const newSelectValues = - selectValues.map((item, i) => { - if (i === newIndex) { - return selectValues[index]; - } else if (i === index) { - return selectValues[newIndex]; - } else { - return item; - } - }); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); }; }; @@ -220,12 +196,11 @@ class ArrayField extends Component { } setType(index, value) { - // event.preventDefault(); const {items} = this.state.formData; const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.getAnyOfItems(); + const anyOfItems = this.getAnyOfItemsSchema(); const newItems = items.slice(); const foundItem = anyOfItems.find((element) => { return element.type === value }); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); @@ -233,10 +208,7 @@ class ArrayField extends Component { const newAnyOfItems = [...this.state.anyOfItems]; newAnyOfItems[index] = foundItem; - const newSelectValues = [...this.state.selectValues]; - newSelectValues[index] = value; - - const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}, {selectValues: newSelectValues}); + const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState); @@ -269,16 +241,14 @@ class ArrayField extends Component { autofocus, } = this.props; const title = (schema.title === undefined) ? name : schema.title; + const {anyOfItems} = this.state; const {items} = this.state.formData; - const {selectValues} = this.state; const {definitions, fields} = this.props.registry; const {TitleField, DescriptionField} = fields; let itemsSchema = retrieveSchema(schema.items, definitions); const {addable=true} = getUiOptions(uiSchema); - const anyOfItems = this.getAnyOfItems(); - const newItems = items; + const anyOfItemsSchema = this.getAnyOfItemsSchema(); - console.log(selectValues, this.state.anyOfItems) return (
@@ -293,8 +263,8 @@ class ArrayField extends Component { idSchema={idSchema} description={schema.description}/> : null}
{ - newItems.map((item, index) => { - if (anyOfItems) { + items.map((item, index) => { + if (anyOfItemsSchema) { itemsSchema = this.state.anyOfItems[index]; } const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; @@ -303,15 +273,15 @@ class ArrayField extends Component { return this.renderArrayFieldItem({ index, canMoveUp: index > 0, - canMoveDown: index < newItems.length - 1, + canMoveDown: index < items.length - 1, itemSchema: itemsSchema, itemIdSchema, itemErrorSchema, - itemData: newItems[index], + itemData: items[index], itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, - anyOfItems: anyOfItems, - value: selectValues[index] || "" + anyOfItemsSchema: anyOfItemsSchema, + value: anyOfItems.length > 0 ? anyOfItems[index].type : "" }); }) }
@@ -384,13 +354,8 @@ class ArrayField extends Component { const {TitleField} = fields; const itemSchemas = schema.items.map(item => retrieveSchema(item, definitions)); - let additionalSchema = allowAdditionalItems(schema) ? + const additionalSchema = allowAdditionalItems(schema) ? retrieveSchema(schema.additionalItems, definitions) : null; - const anyOfItems = this.getAnyOfItems(); - - // if (!additionalSchema) { - // additionalSchema = anyOfItems ? anyOfItems[0] : null; - // } const {addable=true} = getUiOptions(uiSchema); const canAdd = addable && additionalSchema; @@ -399,6 +364,7 @@ class ArrayField extends Component { items = items || []; items = items.concat(new Array(itemSchemas.length - items.length)); } + return (
{ items.map((item, index) => { const additional = index >= itemSchemas.length; - let itemSchema = additional ? + const itemSchema = additional ? additionalSchema : itemSchemas[index]; const itemIdPrefix = idSchema.$id + "_" + index; const itemIdSchema = toIdSchema(itemSchema, itemIdPrefix, definitions); @@ -431,8 +397,7 @@ class ArrayField extends Component { itemUiSchema, itemIdSchema, itemErrorSchema, - autofocus: autofocus && index === 0, - anyOfItems + autofocus: autofocus && index === 0 }); }) }
@@ -456,7 +421,7 @@ class ArrayField extends Component { itemIdSchema, itemErrorSchema, autofocus, - anyOfItems, + anyOfItemsSchema, value }) { const {SchemaField} = this.props.registry.fields; @@ -474,16 +439,17 @@ class ArrayField extends Component { has.toolbar = Object.keys(has).some(key => has[key]); const btnStyle = {flex: 1, paddingLeft: 6, paddingRight: 6, fontWeight: "bold"}; const {SelectWidget} = this.props.registry.widgets; + return (
- {anyOfItems ? ( + {anyOfItemsSchema ? ( this.setType(index, value)}/> ) : null} @@ -572,7 +538,7 @@ if (process.env.NODE_ENV !== "production") { ])).isRequired, fields: PropTypes.objectOf(PropTypes.func).isRequired, definitions: PropTypes.object.isRequired, - formContext: PropTypes.object.isRequired, + formContext: PropTypes.object.isRequired }), }; } diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js index b7a2a93cb0..0a91ed0d33 100644 --- a/test/ArrayField_test.js +++ b/test/ArrayField_test.js @@ -736,7 +736,7 @@ describe("ArrayField", () => { expect(node.querySelector("#title-")).to.be.null; }); }); - describe.only("Any of", () => { + describe("Any of", () => { const schema = { "type": "array", "items": { @@ -820,10 +820,15 @@ describe("ArrayField", () => { target: {value: "number"} }); + const inputs = node.querySelectorAll("input[type=text]"); + Simulate.change(inputs[0], {target: {value: 123}}) + Simulate.change(inputs[1], {target: {value: 'abc'}}) + const dropBtns = node.querySelectorAll(".array-item-remove"); Simulate.click(dropBtns[0]); expect(node.querySelectorAll(".array-item .field-number")).to.have.length.of(0); + expect(comp.state.formData).eql(['abc']); }); it("should reorder widgets correctly", () => { @@ -835,14 +840,15 @@ describe("ArrayField", () => { target: {value: "number"} }); + const inputs = node.querySelectorAll("input[type=text]"); + Simulate.change(inputs[0], {target: {value: 123}}) + Simulate.change(inputs[1], {target: {value: 'abc'}}) + const moveDownBtns = node.querySelectorAll(".array-item-move-down"); Simulate.click(moveDownBtns[0]); - const inputs = node.querySelectorAll(".field-string input[type=text]"); expect(node.querySelectorAll(".array-item select")[0].value).eql('string'); - - - // expect(comp.state.formData).eql([123, '123']); + expect(comp.state.formData).eql(['abc', 123]); }); }); }); From 9b8249d8f207c6e55c153a5d5aa9f560b5ebbb89 Mon Sep 17 00:00:00 2001 From: Stathis Date: Wed, 23 Nov 2016 10:58:57 +0000 Subject: [PATCH 5/9] Any of and one of together --- src/components/fields/ArrayField.js | 86 +++++++++++++--------------- src/components/fields/SchemaField.js | 2 +- src/utils.js | 11 ++++ 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 0b2240c3e4..ea2e95eadc 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -13,7 +13,8 @@ import { toIdSchema, shouldRender, getDefaultRegistry, - setState + setState, + getSubschemaOptions } from "../../utils"; function ArrayFieldTitle({TitleField, idSchema, title, required}) { @@ -56,7 +57,7 @@ class ArrayField extends Component { constructor(props) { super(props); - this.state = { formData: this.getStateFromProps(props), anyOfItems: []}; + this.state = { formData: this.getStateFromProps(props), subschemaItems: []}; } componentWillReceiveProps(nextProps) { @@ -91,28 +92,23 @@ class ArrayField extends Component { }); } - getAnyOfItemsSchema() { - const schema = this.props.schema; - return schema.items.anyOf; - } - onAddClick = (event) => { event.preventDefault(); const {items} = this.state.formData; const {schema, registry} = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.getAnyOfItemsSchema(); + const subschemaItems = getSubschemaOptions(schema); if (isFixedItems(schema) && allowAdditionalItems(schema)) { itemSchema = schema.additionalItems; } - let anyOfState = {} - if (anyOfItems) { - itemSchema = anyOfItems[0]; + let newSubschemaItems = {} + if (subschemaItems) { + itemSchema = subschemaItems[0]; - anyOfState = {anyOfItems: [ - ...this.state.anyOfItems, + newSubschemaItems = {subschemaItems: [ + ...this.state.subschemaItems, itemSchema ]}; } @@ -122,20 +118,19 @@ class ArrayField extends Component { getDefaultFormState(itemSchema, undefined, definitions) ]) }; - const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState); + const newState = Object.assign({}, this.state, {formData: newItems}, newSubschemaItems); this.asyncSetState(newState); }; onDropIndexClick = (index) => { return (event) => { event.preventDefault(); - const {anyOfItems} = this.state; - const {items} = this.state.formData; + const {formData: {items}, subschemaItems} = this.state; const newItems = { items: items.filter((_, i) => i !== index) }; - const newAnyOfItems = anyOfItems.filter((_, i) => i !== index); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); + const newSubschemaItems = subschemaItems.filter((_, i) => i !== index); + const newState = Object.assign({}, this.state, {formData: newItems}, {subschemaItems: newSubschemaItems}); this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -144,10 +139,7 @@ class ArrayField extends Component { return (event) => { event.preventDefault(); event.target.blur(); - const {items} = this.state.formData; - const { - anyOfItems, - } = this.state; + const {formData: {items}, subschemaItems} = this.state; const newItems = { items: items.map((item, i) => { if (i === newIndex) { @@ -159,17 +151,17 @@ class ArrayField extends Component { } }) }; - const newAnyOfItems = anyOfItems.map((item, i) => { + const newSubschemaItems = subschemaItems.map((item, i) => { if (i === newIndex) { - return anyOfItems[index]; + return subschemaItems[index]; } else if (i === index) { - return anyOfItems[newIndex]; + return subschemaItems[newIndex]; } else { return item; } }); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); + const newState = Object.assign({}, this.state, {formData: newItems}, {subschemaItems: newSubschemaItems}); this.asyncSetState(newState, {validate: true}); }; }; @@ -191,8 +183,8 @@ class ArrayField extends Component { this.asyncSetState(newState); }; - anyOfOptions(anyOfItems) { - return anyOfItems.map(item => { return {value: item.type, label: item.type}; }); + formSubschemaOptions(subschemaItems) { + return subschemaItems.map(item => { return {value: item.type, label: item.type}; }); } setType(index, value) { @@ -200,18 +192,17 @@ class ArrayField extends Component { const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; - const anyOfItems = this.getAnyOfItemsSchema(); + const subschemaItems = getSubschemaOptions(schema); const newItems = items.slice(); - const foundItem = anyOfItems.find((element) => { return element.type === value }); + const foundItem = subschemaItems.find((element) => { return element.type === value }); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); - const newAnyOfItems = [...this.state.anyOfItems]; - newAnyOfItems[index] = foundItem; + const newSubschemaItems = [...this.state.subschemaItems]; + newSubschemaItems[index] = foundItem; - const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}); + const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {subschemaItems: newSubschemaItems}); this.asyncSetState(newState); - } render() { @@ -241,13 +232,14 @@ class ArrayField extends Component { autofocus, } = this.props; const title = (schema.title === undefined) ? name : schema.title; - const {anyOfItems} = this.state; - const {items} = this.state.formData; + const {formData: {items}, subschemaItems} = this.state; const {definitions, fields} = this.props.registry; const {TitleField, DescriptionField} = fields; let itemsSchema = retrieveSchema(schema.items, definitions); const {addable=true} = getUiOptions(uiSchema); - const anyOfItemsSchema = this.getAnyOfItemsSchema(); + const subschemaOptions = getSubschemaOptions(schema); + // If this is an oneOf, it will only have one add button + const canAdd = (subschemaItems.length > 0 && addable && schema.items.oneOf) ? false : addable; return (
: null}
{ items.map((item, index) => { - if (anyOfItemsSchema) { - itemsSchema = this.state.anyOfItems[index]; + if (subschemaOptions) { + itemsSchema = this.state.subschemaItems[index]; } const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; const itemIdPrefix = idSchema.$id + "_" + index; @@ -280,12 +272,12 @@ class ArrayField extends Component { itemData: items[index], itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, - anyOfItemsSchema: anyOfItemsSchema, - value: anyOfItems.length > 0 ? anyOfItems[index].type : "" + subschemaOptions: subschemaOptions, + value: subschemaItems.length > 0 ? subschemaItems[index].type : "" }); }) }
- {addable ? : null}
@@ -421,7 +413,7 @@ class ArrayField extends Component { itemIdSchema, itemErrorSchema, autofocus, - anyOfItemsSchema, + subschemaOptions, value }) { const {SchemaField} = this.props.registry.fields; @@ -444,14 +436,14 @@ class ArrayField extends Component {
- {anyOfItemsSchema ? ( + {subschemaOptions ? ( +
this.setType(index, value)}/> + onChange={(value) => this.setType(index, value)}/>
) : null} { const label = schema.enumNames && schema.enumNames[i] || String(value); From b2295d989a3c1675e2308fbc67fef3dc9faf9859 Mon Sep 17 00:00:00 2001 From: Stathis Date: Wed, 23 Nov 2016 11:49:05 +0000 Subject: [PATCH 6/9] Remove oneOf --- src/components/fields/ArrayField.js | 93 ++++++++++++++++------------- src/utils.js | 11 ---- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index ea2e95eadc..800e0b383b 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -13,8 +13,7 @@ import { toIdSchema, shouldRender, getDefaultRegistry, - setState, - getSubschemaOptions + setState } from "../../utils"; function ArrayFieldTitle({TitleField, idSchema, title, required}) { @@ -57,7 +56,7 @@ class ArrayField extends Component { constructor(props) { super(props); - this.state = { formData: this.getStateFromProps(props), subschemaItems: []}; + this.state = { formData: this.getStateFromProps(props), anyOfItems: []}; } componentWillReceiveProps(nextProps) { @@ -92,23 +91,34 @@ class ArrayField extends Component { }); } + getAnyOfItemsSchema() { + const schema = this.props.schema; + if (schema.items.oneOf) { + return schema.items.oneOf; + } else if (schema.items.anyOf) { + return schema.items.anyOf; + } else { + return null; + } + } + onAddClick = (event) => { event.preventDefault(); const {items} = this.state.formData; const {schema, registry} = this.props; const {definitions} = registry; let itemSchema = schema.items; - const subschemaItems = getSubschemaOptions(schema); + const anyOfItems = this.getAnyOfItemsSchema(); if (isFixedItems(schema) && allowAdditionalItems(schema)) { itemSchema = schema.additionalItems; } - let newSubschemaItems = {} - if (subschemaItems) { - itemSchema = subschemaItems[0]; + let anyOfState = {} + if (anyOfItems) { + itemSchema = anyOfItems[0]; - newSubschemaItems = {subschemaItems: [ - ...this.state.subschemaItems, + anyOfState = {anyOfItems: [ + ...this.state.anyOfItems, itemSchema ]}; } @@ -118,19 +128,19 @@ class ArrayField extends Component { getDefaultFormState(itemSchema, undefined, definitions) ]) }; - const newState = Object.assign({}, this.state, {formData: newItems}, newSubschemaItems); + const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState); this.asyncSetState(newState); }; onDropIndexClick = (index) => { return (event) => { event.preventDefault(); - const {formData: {items}, subschemaItems} = this.state; + const {formData: {items}, anyOfItems} = this.state; const newItems = { items: items.filter((_, i) => i !== index) }; - const newSubschemaItems = subschemaItems.filter((_, i) => i !== index); - const newState = Object.assign({}, this.state, {formData: newItems}, {subschemaItems: newSubschemaItems}); + const newAnyOfItems = anyOfItems.filter((_, i) => i !== index); + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -139,7 +149,7 @@ class ArrayField extends Component { return (event) => { event.preventDefault(); event.target.blur(); - const {formData: {items}, subschemaItems} = this.state; + const {formData: {items}, anyOfItems} = this.state; const newItems = { items: items.map((item, i) => { if (i === newIndex) { @@ -151,17 +161,17 @@ class ArrayField extends Component { } }) }; - const newSubschemaItems = subschemaItems.map((item, i) => { + const newAnyOfItems = anyOfItems.map((item, i) => { if (i === newIndex) { - return subschemaItems[index]; + return anyOfItems[index]; } else if (i === index) { - return subschemaItems[newIndex]; + return anyOfItems[newIndex]; } else { return item; } }); - const newState = Object.assign({}, this.state, {formData: newItems}, {subschemaItems: newSubschemaItems}); + const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); }; }; @@ -183,8 +193,8 @@ class ArrayField extends Component { this.asyncSetState(newState); }; - formSubschemaOptions(subschemaItems) { - return subschemaItems.map(item => { return {value: item.type, label: item.type}; }); + anyOfOptions(anyOfItems) { + return anyOfItems.map(item => { return {value: item.type, label: item.type}; }); } setType(index, value) { @@ -192,15 +202,15 @@ class ArrayField extends Component { const {schema, registry } = this.props; const {definitions} = registry; let itemSchema = schema.items; - const subschemaItems = getSubschemaOptions(schema); + const anyOfItems = this.getAnyOfItemsSchema(); const newItems = items.slice(); - const foundItem = subschemaItems.find((element) => { return element.type === value }); + const foundItem = anyOfItems.find((element) => { return element.type === value }); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); - const newSubschemaItems = [...this.state.subschemaItems]; - newSubschemaItems[index] = foundItem; + const newAnyOfItems = [...this.state.anyOfItems]; + newAnyOfItems[index] = foundItem; - const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {subschemaItems: newSubschemaItems}); + const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState); } @@ -232,14 +242,12 @@ class ArrayField extends Component { autofocus, } = this.props; const title = (schema.title === undefined) ? name : schema.title; - const {formData: {items}, subschemaItems} = this.state; + const {formData: {items}, anyOfItems} = this.state; const {definitions, fields} = this.props.registry; const {TitleField, DescriptionField} = fields; let itemsSchema = retrieveSchema(schema.items, definitions); const {addable=true} = getUiOptions(uiSchema); - const subschemaOptions = getSubschemaOptions(schema); - // If this is an oneOf, it will only have one add button - const canAdd = (subschemaItems.length > 0 && addable && schema.items.oneOf) ? false : addable; + const anyOfItemsSchema = this.getAnyOfItemsSchema(); return (
: null}
{ items.map((item, index) => { - if (subschemaOptions) { - itemsSchema = this.state.subschemaItems[index]; + if (anyOfItemsSchema) { + itemsSchema = anyOfItems[index]; } const itemErrorSchema = errorSchema ? errorSchema[index] : undefined; const itemIdPrefix = idSchema.$id + "_" + index; @@ -272,12 +280,12 @@ class ArrayField extends Component { itemData: items[index], itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, - subschemaOptions: subschemaOptions, - value: subschemaItems.length > 0 ? subschemaItems[index].type : "" + anyOfItemsSchema: anyOfItemsSchema, + value: anyOfItems.length > 0 ? anyOfItems[index].type : "" }); }) }
- {canAdd ? : null}
@@ -413,7 +421,7 @@ class ArrayField extends Component { itemIdSchema, itemErrorSchema, autofocus, - subschemaOptions, + anyOfItemsSchema, value }) { const {SchemaField} = this.props.registry.fields; @@ -436,14 +444,15 @@ class ArrayField extends Component {
- {subschemaOptions ? ( + {anyOfItemsSchema ? (
- this.setType(index, value)}/>
+ this.setType(index, value)}/> +
) : null} { const label = schema.enumNames && schema.enumNames[i] || String(value); From 2c0dc9f68323aa9a70965c57b2d49b8d421aac63 Mon Sep 17 00:00:00 2001 From: Stathis Date: Wed, 23 Nov 2016 17:25:33 +0000 Subject: [PATCH 7/9] Add support for anyOf --- playground/app.js | 6 +- playground/samples/anyOf.js | 31 +++++++- playground/samples/index.js | 2 +- src/components/fields/ArrayField.js | 105 ++++++++++++++++----------- src/components/fields/SchemaField.js | 10 +-- test/ArrayField_test.js | 30 ++++---- 6 files changed, 115 insertions(+), 69 deletions(-) diff --git a/playground/app.js b/playground/app.js index cc8283cdbe..c792554327 100644 --- a/playground/app.js +++ b/playground/app.js @@ -209,7 +209,7 @@ class Editor extends Component { class Selector extends Component { constructor(props) { super(props); - this.state = {current: "AnyOf"}; + this.state = {current: "Simple"}; } shouldComponentUpdate(nextProps, nextState) { @@ -261,7 +261,7 @@ class App extends Component { constructor(props) { super(props); // initialize state with Simple data sample - const {schema, uiSchema, formData, validate} = samples.AnyOf; + const {schema, uiSchema, formData, validate} = samples.Simple; this.state = { form: false, schema, @@ -275,7 +275,7 @@ class App extends Component { } componentDidMount() { - this.load(samples.AnyOf); + this.load(samples.Simple); } shouldComponentUpdate(nextProps, nextState) { diff --git a/playground/samples/anyOf.js b/playground/samples/anyOf.js index 14aad51716..47bd662eb9 100644 --- a/playground/samples/anyOf.js +++ b/playground/samples/anyOf.js @@ -8,10 +8,28 @@ module.exports = { "items": { "anyOf": [ { + "title": "string", "type": "string" }, { - "type": "number" + "title": "integer", + "type": "integer" + }, + { + "title": "array", + "type": "array", + "items": { + "anyOf": [ + { + "title": "string", + "type": "string" + }, + { + "title": "integer", + "type": "integer" + } + ] + } } ] } @@ -19,5 +37,14 @@ module.exports = { } }, uiSchema: {}, - formData: {} + formData: { + "List of widgets": [ + 27, + "Batman", + [ + "Bruce", + "Wayne" + ] + ] + } }; diff --git a/playground/samples/index.js b/playground/samples/index.js index 97063d4a28..bd6f9f5c21 100644 --- a/playground/samples/index.js +++ b/playground/samples/index.js @@ -29,5 +29,5 @@ export const samples = { Validation: validation, Files: files, Single: single, - AnyOf: anyOf + "Any of": anyOf }; diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 800e0b383b..784cdc717c 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -56,7 +56,14 @@ class ArrayField extends Component { constructor(props) { super(props); - this.state = { formData: this.getStateFromProps(props), anyOfItems: []}; + const formData = this.getStateFromProps(props); + let anyOfItems = []; + if (this.getAnyOfItemsSchema()) { + // We need to contruct the initial anyOfItems state, by searching for the props anyOf items + // in the available anyOf schema items + anyOfItems = this.getAnyOfItemsFromProps(formData.items, props.schema.items.anyOf); + } + this.state = {formData: formData, anyOfItems: anyOfItems}; } componentWillReceiveProps(nextProps) { @@ -76,6 +83,29 @@ class ArrayField extends Component { return shouldRender(this, nextProps, nextState); } + getAnyOfItemsFromProps(formDataItems, anyOfSchema) { + return formDataItems.map((item) => { + const type = typeof item; + const itemType = (type === "object" && Array.isArray(item)) ? "array" : type; + console.log(type, itemType); + const schema = this.getAnyOfItemSchema(anyOfSchema, itemType); + + // If this schema is an array, we need to recursively add its contents + if (schema.type === "array") { + this.getAnyOfItemsFromProps(item, schema.items.anyOf); + } + + return schema; + }); + } + + getAnyOfItemSchema(anyOfSchema, type) { + return anyOfSchema.find((schemaElement) => { + const schemaElementType = schemaElement.type === "integer" ? "number" : schemaElement.type; + return schemaElementType === type; + }); + } + get itemTitle() { const {schema} = this.props; return schema.items.title || schema.items.description || "Item"; @@ -92,14 +122,8 @@ class ArrayField extends Component { } getAnyOfItemsSchema() { - const schema = this.props.schema; - if (schema.items.oneOf) { - return schema.items.oneOf; - } else if (schema.items.anyOf) { - return schema.items.anyOf; - } else { - return null; - } + const {schema} = this.props; + return schema.items.anyOf; } onAddClick = (event) => { @@ -113,14 +137,15 @@ class ArrayField extends Component { itemSchema = schema.additionalItems; } - let anyOfState = {} + let newAnyOfItems = []; if (anyOfItems) { + // We pick the first anyOf item by default itemSchema = anyOfItems[0]; - anyOfState = {anyOfItems: [ + newAnyOfItems = [ ...this.state.anyOfItems, itemSchema - ]}; + ]; } const newItems = { @@ -128,7 +153,7 @@ class ArrayField extends Component { getDefaultFormState(itemSchema, undefined, definitions) ]) }; - const newState = Object.assign({}, this.state, {formData: newItems}, anyOfState); + const newState = Object.assign({}, this.state, {formData: newItems, anyOfItems: newAnyOfItems}); this.asyncSetState(newState); }; @@ -140,7 +165,8 @@ class ArrayField extends Component { items: items.filter((_, i) => i !== index) }; const newAnyOfItems = anyOfItems.filter((_, i) => i !== index); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); + const newState = Object.assign({}, this.state, + {formData: newItems, anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); // refs #195 }; }; @@ -150,8 +176,9 @@ class ArrayField extends Component { event.preventDefault(); event.target.blur(); const {formData: {items}, anyOfItems} = this.state; - const newItems = { - items: items.map((item, i) => { + + const reorder = (items, newIndex) => + items.map((item, i) => { if (i === newIndex) { return items[index]; } else if (i === index) { @@ -159,19 +186,15 @@ class ArrayField extends Component { } else { return item; } - }) - }; - const newAnyOfItems = anyOfItems.map((item, i) => { - if (i === newIndex) { - return anyOfItems[index]; - } else if (i === index) { - return anyOfItems[newIndex]; - } else { - return item; - } }); - const newState = Object.assign({}, this.state, {formData: newItems}, {anyOfItems: newAnyOfItems}); + const newItems = { + items: reorder(items, newIndex) + }; + const newAnyOfItems = reorder(anyOfItems, newIndex); + + const newState = Object.assign({}, this.state, + {formData: newItems}, {anyOfItems: newAnyOfItems}); this.asyncSetState(newState, {validate: true}); }; }; @@ -182,7 +205,7 @@ class ArrayField extends Component { items: this.state.formData.items.map((item, i) => { return index === i ? value : item; }) - } + }; const newState = Object.assign({}, this.state, {formData: items}); this.asyncSetState(newState); }; @@ -194,23 +217,23 @@ class ArrayField extends Component { }; anyOfOptions(anyOfItems) { - return anyOfItems.map(item => { return {value: item.type, label: item.type}; }); + return anyOfItems.map(item => ({value: item.type, label: item.type})); } - setType(index, value) { + setWidgetType(index, value) { const {items} = this.state.formData; - const {schema, registry } = this.props; + const {registry} = this.props; const {definitions} = registry; - let itemSchema = schema.items; - const anyOfItems = this.getAnyOfItemsSchema(); + const anyOfItemsSchema = this.getAnyOfItemsSchema(); const newItems = items.slice(); - const foundItem = anyOfItems.find((element) => { return element.type === value }); + const foundItem = anyOfItemsSchema.find((element) => element.type === value); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); const newAnyOfItems = [...this.state.anyOfItems]; newAnyOfItems[index] = foundItem; - const newState = Object.assign({}, this.state, {formData: {items: newItems}}, {anyOfItems: newAnyOfItems}); + const newState = Object.assign({}, this.state, + {formData: {items: newItems}, anyOfItems: newAnyOfItems}); this.asyncSetState(newState); } @@ -281,7 +304,7 @@ class ArrayField extends Component { itemUiSchema: uiSchema.items, autofocus: autofocus && index === 0, anyOfItemsSchema: anyOfItemsSchema, - value: anyOfItems.length > 0 ? anyOfItems[index].type : "" + selectWidgetValue: anyOfItems.length > 0 ? anyOfItems[index].type : "" }); }) }
@@ -422,7 +445,7 @@ class ArrayField extends Component { itemErrorSchema, autofocus, anyOfItemsSchema, - value + selectWidgetValue }) { const {SchemaField} = this.props.registry.fields; const {disabled, readonly, uiSchema} = this.props; @@ -443,18 +466,16 @@ class ArrayField extends Component { return (
- {anyOfItemsSchema ? ( -
+
this.setType(index, value)}/> + value={selectWidgetValue} + onChange={(value) => this.setWidgetType(index, value)}/>
) : null} - { Simulate.click(node.querySelector(".array-item-add button")); - expect(node.querySelectorAll(".array-item")) + expect(node.querySelectorAll(".field-string")) .to.have.length.of(1); }); @@ -783,10 +783,10 @@ describe("ArrayField", () => { expect(node.querySelectorAll(".array-item .field-number")).to.have.length.of(1); }); - it("should have correct type at start", () => { - Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: 'asd'}}); + it("should be created with correct type", () => { + Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: "asd"}}); expect(node.querySelector("fieldset .field-string input[type=text]").value).eql("asd"); - expect(comp.state.formData).eql(['asd']); + expect(comp.state.formData).eql(["asd"]); }); it("should clear the value after type update", () => { @@ -795,7 +795,7 @@ describe("ArrayField", () => { target: {value: "number"} }); - expect(node.querySelector("fieldset .field-number input[type=text]").value).eql(''); + expect(node.querySelector("fieldset .field-number input[type=text]").value).eql(""); }); it("should update types accordingly", () => { @@ -806,9 +806,9 @@ describe("ArrayField", () => { target: {value: "number"} }); - Simulate.change(node.querySelector("fieldset .field-number input[type=text]"), {target: {value: '123'}}); - Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: '123'}}); - expect(comp.state.formData).eql([123, '123']); + Simulate.change(node.querySelector("fieldset .field-number input[type=text]"), {target: {value: "123"}}); + Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: "123"}}); + expect(comp.state.formData).eql([123, "123"]); }); it("should delete the correct widget", () => { @@ -821,14 +821,14 @@ describe("ArrayField", () => { }); const inputs = node.querySelectorAll("input[type=text]"); - Simulate.change(inputs[0], {target: {value: 123}}) - Simulate.change(inputs[1], {target: {value: 'abc'}}) + Simulate.change(inputs[0], {target: {value: 123}}); + Simulate.change(inputs[1], {target: {value: "abc"}}); const dropBtns = node.querySelectorAll(".array-item-remove"); Simulate.click(dropBtns[0]); expect(node.querySelectorAll(".array-item .field-number")).to.have.length.of(0); - expect(comp.state.formData).eql(['abc']); + expect(comp.state.formData).eql(["abc"]); }); it("should reorder widgets correctly", () => { @@ -841,14 +841,14 @@ describe("ArrayField", () => { }); const inputs = node.querySelectorAll("input[type=text]"); - Simulate.change(inputs[0], {target: {value: 123}}) - Simulate.change(inputs[1], {target: {value: 'abc'}}) + Simulate.change(inputs[0], {target: {value: 123}}); + Simulate.change(inputs[1], {target: {value: "abc"}}); const moveDownBtns = node.querySelectorAll(".array-item-move-down"); Simulate.click(moveDownBtns[0]); - expect(node.querySelectorAll(".array-item select")[0].value).eql('string'); - expect(comp.state.formData).eql(['abc', 123]); + expect(node.querySelectorAll(".array-item select")[0].value).eql("string"); + expect(comp.state.formData).eql(["abc", 123]); }); }); }); From 00a96ac363a2649c3f0618d5726742e98b73fef9 Mon Sep 17 00:00:00 2001 From: Stathis Date: Wed, 23 Nov 2016 17:30:27 +0000 Subject: [PATCH 8/9] Remove whitespace --- src/components/fields/ArrayField.js | 2 +- test/ArrayField_test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index 784cdc717c..45519b0a12 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -228,7 +228,7 @@ class ArrayField extends Component { const newItems = items.slice(); const foundItem = anyOfItemsSchema.find((element) => element.type === value); newItems[index] = getDefaultFormState(foundItem, undefined, definitions); - + const newAnyOfItems = [...this.state.anyOfItems]; newAnyOfItems[index] = foundItem; diff --git a/test/ArrayField_test.js b/test/ArrayField_test.js index 3bed06849f..f41faea8c1 100644 --- a/test/ArrayField_test.js +++ b/test/ArrayField_test.js @@ -760,7 +760,7 @@ describe("ArrayField", () => { Simulate.click(node.querySelector(".array-item-add button")); }); - it("should render an add button", () => { + it("should render an add button", () => { expect(node.querySelector(".array-item-add button")) .not.eql(null); }); @@ -775,7 +775,7 @@ describe("ArrayField", () => { expect(node.querySelectorAll(".array-item")).to.have.length.of(2); }); - it("should change the type of the widget", () => { + it("should change the type of the widget", () => { Simulate.change(node.querySelector(".array-item select"), { target: {value: "number"} }); @@ -805,7 +805,7 @@ describe("ArrayField", () => { Simulate.change(selects[0], { target: {value: "number"} }); - + Simulate.change(node.querySelector("fieldset .field-number input[type=text]"), {target: {value: "123"}}); Simulate.change(node.querySelector("fieldset .field-string input[type=text]"), {target: {value: "123"}}); expect(comp.state.formData).eql([123, "123"]); From 3460ec6991d3c0a1ecb27270c3ff57faa9324720 Mon Sep 17 00:00:00 2001 From: jManji Date: Tue, 31 Jan 2017 16:03:44 +0000 Subject: [PATCH 9/9] Remove debug statements --- src/components/fields/ArrayField.js | 1 - src/components/fields/SchemaField.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/fields/ArrayField.js b/src/components/fields/ArrayField.js index e78d760b4e..28ef75f709 100644 --- a/src/components/fields/ArrayField.js +++ b/src/components/fields/ArrayField.js @@ -193,7 +193,6 @@ class ArrayField extends Component { return formDataItems.map((item) => { const type = typeof item; const itemType = (type === "object" && Array.isArray(item)) ? "array" : type; - console.log(type, itemType); const schema = this.getAnyOfItemSchema(anyOfSchema, itemType); // If this schema is an array, we need to recursively add its contents diff --git a/src/components/fields/SchemaField.js b/src/components/fields/SchemaField.js index 87235640d9..a56b3e5203 100644 --- a/src/components/fields/SchemaField.js +++ b/src/components/fields/SchemaField.js @@ -162,7 +162,6 @@ function SchemaField(props) { } const {__errors, ...fieldErrorSchema} = errorSchema; - console.log(props); const field = (