brutusin / json-forms

JSON Schema to HTML form generator, supporting dynamic subschemas (on the fly resolution). Extensible and customizable library with zero dependencies. Bootstrap add-ons provided
http://brutusin.org/json-forms
Apache License 2.0
607 stars 168 forks source link

What's the correct use of a resolver? #79

Closed joguiMultimedia closed 7 years ago

joguiMultimedia commented 7 years ago

Hi, I want to have a IMC field automatically computed with the value of two other fields POIDS and TAILLE, and I want this field to be read only.

For that sake I use the following (simplified) schema:

{
  "$schema": "http://json-schema.org/draft-03/schema#",
  "type": "object",
  "properties": {
    "TAILLE_CLI":{
      "title": "Taille",
      "type": "number",
      "description": "Entrez la taille en mètre. par exemple: 1.75"
    },
    "POIDS_CLI":{
      "title": "Poids",
      "type": "number",
      "description": "Entrez le poids en Kg. par exemple: 75.5"
    },
    "IMC_CLI":{
      "title": "IMC",
      "readOnly": true,
      "type": "number",
      "description": "Indice de masse corporelle = poids/taille²",
      "dependsOn": ["POIDS_CLI", "TAILLE_CLI"]
    }
  }
}

and the following (simplified) resolver:

    // return the resolved schema given the specific business rule
    resolver : function(names, data, cb) {
        var schemas = [];
        var sch = new Object();
        var current = currentSchema.properties;
        switch(names[0]) {
            case "$.IMC_CLI":
                sch = JSON.parse(JSON.stringify(current.IMC_CLI));
                if(data.POIDS_CLI !== undefined && data.TAILLE_CLI !== undefined) {
                    sch.enum = [data.POIDS_CLI / (data.TAILLE_CLI * data.TAILLE_CLI),""];
                    sch.readOnly = true;
                }
                break;
        }
        schemas[names] = sch;
        cb(schemas);
    }

If I entirely clone 'current.IMC_CLI' into 'sch' with sch = JSON.parse(JSON.stringify(current.IMC_CLI)); I get the following error: Uncaught TypeError: Cannot read property 'type' of undefined at render (brutusin-json-forms.js:1210) at renderers.object (brutusin-json-forms.js:598) at render (brutusin-json-forms.js:1224) at Object.obj.render (brutusin-json-forms.js:831) at client.html:25 If I comment out sch = JSON.parse(JSON.stringify(current.IMC_CLI)); My IMC_CLI field is never visible

The only way I found to have my IMC_CLI field behaving correctly is to copy member by member 'current' into 'sch' like this:

            case "$.IMC_CLI":
                sch.type = current.IMC_CLI.type;
                sch.title = current.IMC_CLI.title;
                sch.description = current.IMC_CLI.description;
                sch.required = current.IMC_CLI.required;
                sch.readOnly = current.IMC_CLI.readOnly;
                if(data.POIDS_CLI !== undefined && data.TAILLE_CLI !== undefined) {
                    sch.required = true;
                    sch.enum = [data.POIDS_CLI / (data.TAILLE_CLI * data.TAILLE_CLI),""];
                    sch.readOnly = true;
                }
                break;

Is there a simpler way to return a fully filled object from resolver without having to copy each member, which is error prone?

Note: when cloning the object with the JSON stringify/parse method I see a strange behaviour in the last for loop of populateSchemaMap() function. After a few series of calls, the 'name' parameter of the function becomes "$.IMC_CLI,$.IMC_CLI" instead of "$.IMC_CLI". I assume this is due to the returned value of my resolver.

idelvall commented 7 years ago

can you post the actual value of currentSchema?

by the way, schemas has to be an associative array {} rather than an array []. The keys in that map has to be the queried names so in your case it hsould be something like schemas[names[0]] = sch

joguiMultimedia commented 7 years ago

OK, I updated the two things you said (sch being an Object and setting names[0] as a key instead of names) but nothing has changed.

currentSchema is the very same schema as described above stored in a global variable.

joguiMultimedia commented 7 years ago

BTW, I am using a ugly hack to store the computed value in IMC into a 2 sized 'enum' table with the second value being empty. Is there a better way to store a single value?

idelvall commented 7 years ago

ok, you need to remove the "dependsOn" property in the cloned schema. the previous changes, should fix the "$.IMC_CLI,$.IMC_CLI" issue

joguiMultimedia commented 7 years ago

Sorry for the previous answer. It does work and solve the issue. I was setting dependsOn to null when I had to delete it. BTW: a simple assignment was enough instead of the JSON stringify/parse deep copy.

Thanks a lot.

joguiMultimedia commented 7 years ago

summarizing in that case, the simplest resolver should look like:

    resolver : function(names, data, cb) {
        var schemas = new Object();
        var sch = new Object();
        var current = currentSchema.properties;
        for(var i = 0; i < names.length; ++i) {
            switch (names[i]) {
                case "$.DEPENDENT": {
                   sch = JSON.parse(JSON.stringify(current.IMC_CLI));
                    if (data && data.DEPENDEE) {
                        sch.required = true;
                        sch.enum = [compute(data.DEPENDEE), ""];
                    }
                    break;
                }
            }
            delete sch.dependsOn;
            schemas[names[i]] = sch;
        }
        cb(schemas);
    }

with: 1) removing the "dependsOn" in any case 2) the sch.required = true is required (!) to display the value of the enum

idelvall commented 7 years ago

data is null first time is called try something like:

var schemas = {};
        var sch = new Object();
        var current = currentSchema.properties;
        switch(names[0]) {
            case "$.IMC_CLI":
                sch = JSON.parse(JSON.stringify(current.IMC_CLI));
            delete sch.dependsOn;
            if(data && data.POIDS_CLI && data.TAILLE_CLI ) {
                    sch.default = data.POIDS_CLI / (data.TAILLE_CLI * data.TAILLE_CLI);
                    sch.readOnly = true;
                }
                break;
        }
        schemas[names[0]] = sch;
        cb(schemas);
joguiMultimedia commented 7 years ago

Ok much simpler test. Updating simple example.

idelvall commented 7 years ago

be aware that by using the simple assignment your are modifying the original schema, since you have two references to the same instance