Use jsonary to build form data for EntityEditForm
authorFrank Bessou <frank.bessou@logilab.fr>
Thu, 30 Mar 2017 16:47:17 +0200
changeset 58 31a55e924862
parent 57 b0ecee629533
child 59 51b18c27cc76
Use jsonary to build form data for EntityEditForm
src/components/Entity.js
src/utils.js
test/index.js
--- a/src/components/Entity.js	Thu Mar 30 17:21:14 2017 +0200
+++ b/src/components/Entity.js	Thu Mar 30 16:47:17 2017 +0200
@@ -180,7 +180,7 @@
         super(props);
         this.etype = props.params.etype;
         this.eid = props.params.eid;
-        this.state = {entity: null, schema: null};
+        this.state = {schema: null};
         this.getEditionSchema = this.getEditionSchema.bind(this);
         this.updateEntity = this.updateEntity.bind(this);
     }
@@ -191,13 +191,14 @@
             .then(
                 ([schema, entity]) => {
                     const wrappedData = wrapEntityData(entity, schema);
-                    this.setState({entity: entity, schema: schema, data: wrappedData});
+                    this.setState({schema: schema, data: wrappedData});
                 }
             );
     }
 
-    updateEntity(data) {
-        this.setState({entity: data});
+    updateEntity(entity) {
+        const wrappedData = wrapEntityData(entity, this.state.schema);
+        this.setState({data: wrappedData});
     }
 
     getEditionSchema() {
@@ -205,13 +206,16 @@
     }
 
     render() {
-        if (this.state.entity === null || this.state.schema === null) {
+        if (this.state.schema === null) {
             return <div>loading...</div>;
         }
         if (this.props.params.view === 'edit') {
             const redirectPath = `/${this.etype}/${this.eid}`;
             return (
-                <EntityEditForm {...this.props} entity={this.state.entity}
+                <EntityEditForm {...this.props}
+                    etype={this.etype}
+                    eid={this.eid}
+                    data={this.state.data}
                     getSchema={this.getEditionSchema}
                     updateEntity={this.updateEntity}
                     redirectPath={ redirectPath }
@@ -431,18 +435,19 @@
         this.props.getSchema()
             .then(
                 (schema) => {
-                    const {entity} = this.props;
-                    const formData = buildFormData(entity, schema);
+                    const {data} = this.props;
+                    const wrappedEntity = wrapEntityData(data.value(), schema);
+                    const formData = buildFormData(wrappedEntity);
                     this.setState({schema: schema, formData: formData});
                 }
             );
     }
 
     onSubmit({formData}) {
-        const {entity} = this.props;
-        Api.updateEntity(entity.cw_etype, entity.eid, formData)
-            .then(data => {
-                this.props.updateEntity(data);
+        const {etype, eid} = this.props;
+        Api.updateEntity(etype, eid, formData)
+            .then(entity => {
+                this.props.updateEntity(entity);
                 this.context.router.push(this.props.redirectPath);
             });
     }
@@ -450,7 +455,9 @@
 }
 
 EntityEditForm.propTypes = {
-    entity: React.PropTypes.object.isRequired,
+    etype: React.PropTypes.string.isRequired,
+    eid: React.PropTypes.number.isRequired,
+    data: PropTypeJsonaryWrapper.isRequired,
     redirectPath: React.PropTypes.string.isRequired,
     getSchema: React.PropTypes.func.isRequired,
     updateEntity: React.PropTypes.func.isRequired,
--- a/src/utils.js	Thu Mar 30 17:21:14 2017 +0200
+++ b/src/utils.js	Thu Mar 30 16:47:17 2017 +0200
@@ -1,4 +1,4 @@
-import {forOwn, get, has} from 'lodash/object';
+import {get} from 'lodash/object';
 
 export function refSchema(schema) {
     // parse $ref and remove leading #
@@ -6,42 +6,59 @@
     return get(schema, definitionPath);
 }
 
-export function buildFormData(entity, schema) {
+export function buildFormData(wrappedData) {
     // Return a formData object suitable for usage in a
     // react-jsonschema-form's Form with `schema`. This is built by filter out
     // `entity` JSON document from fields that do no not appear in
     // `schema`.
-    const eschema = refSchema(schema);
-    const formData = {};
 
-    function iteratee(value, key) {
-        const target = get(entity, key, null);
-        if (target === null) {
-            return;
-        }
-        if (value.type === 'array') {
-            if (!has(value, 'items.$ref')) {
-                console.warn('unhandled items kind', value, `in ${key} property`);
-                return;
-            }
-            const ref = get(value, 'items.$ref');
-            const prefix = '#/definitions/'
-            if (!ref.startsWith(prefix)) {
-                throw new Error(`unhandled reference kind ${ref}`);
-            }
-            const targetType = ref.slice(prefix.length);
-            if (!has(schema, 'definitions', targetType)) {
-                throw new Error(`missing definition for ${targetType} referenced in ${key}`);
-            }
-            formData[key] = target.map(tgt => buildFormData(tgt, schema));
-        } else {
-            formData[key] = target;
-        }
+    // If there is no schema associated to this data return the data value.
+    const schemas = wrappedData.schemas();
+    if (schemas.length === 0) {
+        return wrappedData.value();
     }
 
-    if (eschema === undefined) {
-        return entity;
+    const actualPropertyType = wrappedData.basicType();
+    const type = schemas.basicTypes()[0];
+
+    // Compare the type expected by the schema with the type of the data.
+    if (type === 'array' && actualPropertyType === 'array') {
+
+        // The data represents an array, apply this function to its items.
+        const formData = [];
+        for (let i = 0; i < wrappedData.length(); i++) {
+            const itemValue = buildFormData(wrappedData.index(i));
+            if (itemValue !== null) {
+                formData.push(itemValue);
+            }
+        }
+        return formData;
+
+    } else if (type === 'object' &&  actualPropertyType === 'object') {
+
+        // The data represents an object, remove all the properties which are
+        // not present in the schema.
+        const formData = {};
+        const propertiesNames = wrappedData.schemas().definedProperties();
+        const properties = propertiesNames.map( propertyName => {
+            return wrappedData.property(propertyName);
+        });
+
+        const filteredProperties = properties.filter( property => {
+            return property.defined() && property.value() !== null
+        });
+
+        for (let property of filteredProperties) {
+            const propertyValue = buildFormData(property);
+            formData[property.parentKey()] = propertyValue;
+        }
+        return formData;
     }
-    forOwn(eschema.properties, iteratee);
-    return formData;
+
+    // Handle two cases:
+    // - The data is an atomic type (string, number, bool) return its value
+    // - The data is a composite type (object, array) but does not validate
+    //   against its schema. As this is no breaking the tests, let assume
+    //   returning the data value is a correct behaviour.
+    return wrappedData.value();
 }
--- a/test/index.js	Thu Mar 30 17:21:14 2017 +0200
+++ b/test/index.js	Thu Mar 30 16:47:17 2017 +0200
@@ -81,7 +81,8 @@
         const expected = {
             "login": "admin",
         };
-        const formData = buildFormData(entity, userEditionSchema);
+        const data = wrapEntityData(entity, userEditionSchema);
+        const formData = buildFormData(data);
         expect(formData).to.deep.equal(expected);
     });
 
@@ -113,7 +114,8 @@
                 },
             ],
         };
-        const formData = buildFormData(entity, userEditionSchema);
+        const data = wrapEntityData(entity, userEditionSchema);
+        const formData = buildFormData(data)
         expect(formData).to.deep.equal(expected);
     });