jsonform / jsonform

Build forms from JSON Schema. Easily template-able. Compatible with Bootstrap 3 out of the box.
https://jsonform.github.io/jsonform/playground/index.html
MIT License
2.72k stars 553 forks source link

change the 'previously submitted values' from a variable. #425

Closed backnext321 closed 1 year ago

backnext321 commented 1 year ago

First of all: thank you for sharing this beautiful system. I'm looking for a way to change the 'previously submitted values'. I want to download an 'array' from the server and put it in the 'previously submitted values' first. The only way I've found so far is to replace in jsonform.js:

formTree.prototype.computeInitialValues = function () {
    this.root.computeInitialValues(this.formDesc.value);
   };

with

formTree.prototype.computeInitialValues = function () {
    this.root.computeInitialValues(previouslySubmitted);
 };

Where previouslySubmitted is a variable ( in my html) from server: var previouslySubmitted = ({"fotos":[{"foto":["foto1"],"tekst":["text1"]},{"foto":["foto2"],"tekst":["text2"]}]}); But I'm sure there is an easier way to achieve this. Any suggestion?

sdetweil commented 1 year ago

in the json you submit to json form there are three sections

schema:{} form:[] value:{}

see https://github.com/jsonform/jsonform/wiki#previous

so you put the previous data in the value section , no code changes required

from one of my uses, not the complete jsonform document, but a section from each for a data element (config)

{
  "schema": {
    "config": {
      "type": "object",
      "title": "properties for MagicMirror base",
      "properties": {
        "address": {
          "type": "string",
          "title": "address",
          "enum": [
            "localhost",
            "0.0.0.0",
            "192.168.2.106",
            "172.17.0.1",
            "169.254.86.97",
            "192.168.152.1",
            "172.16.141.1"
          ]
        },
        "port": {
          "type": "number",
          "title": "port"
        },
        "basePath": {
          "type": "string",
          "title": "basePath"
        },
        "language": {
          "type": "string",
          "title": "language",
          "enum": [
            "af",
            "bg",
            "ca",
            "cs",
            "cv",
            "cy",
            "da",
            "de",
            "el",
            "en",
            "es",
            "et",
            "fi",
            "fr",
            "fy",
            "gl",
            "gu",
            "he",
            "hi",
            "hr",
            "hu",
            "id",
            "is",
            "it",
            "ja",
            "ko",
            "lt",
            "ms-my",
            "nb",
            "nl",
            "nn",
            "pl",
            "ps",
            "pt-br",
            "pt",
            "ro",
            "ru",
            "sk",
            "sv",
            "tlh",
            "tr",
            "translations",
            "uk",
            "zh-cn",
            "zh-tw"
          ]
        },
        "locale": {
          "type": "string",
          "title": "locale"
        },
        "logLevel": {
          "type": "array",
          "title": "logLevel",
          "items": {
            "type": "string",
            "enum": [
              "INFO",
              "LOG",
              "WARN",
              "ERROR",
              "DEBUG"
            ]
          }
        },
        "timeFormat": {
          "type": "number",
          "title": "timeFormat",
          "enum": [
            12,
            24
          ]
        },
        "units": {
          "type": "string",
          "title": "units",
          "enum": [
            "imperial",
            "metric"
          ]
        },
        "useHttps": {
          "type": "boolean",
          "title": "useHttps"
        },
        "ipWhitelist": {
          "type": "array",
          "title": "ipWhitelist",
          "items": {
            "type": "string"
          }
        }
      }
    },
},
"form":[
....
         "type": "fieldset",
          "title": "Base",
          "expandable": true,
          "items": [
            {
              "key": "config.address",
              "titleMap": {
                "localhost": "localhost - application access only from same machine",
                "0.0.0.0": "0.0.0.0 - application access from any machine that can access network",
                "192.168.2.106": "192.168.2.106 - application access only from machine on same network",
                "172.17.0.1": "172.17.0.1 - application access only from machine on same network",
                "169.254.86.97": "169.254.86.97 - application access only from machine on same network",
                "192.168.152.1": "192.168.152.1 - application access only from machine on same network",
                "172.16.141.1": "172.16.141.1 - application access only from machine on same network"
              }
            },
            "config.port",
            "config.basePath",
            "config.language",
            "config.locale",
            {
              "key": "config.logLevel",
              "type": "checkboxes"
            },
            "config.timeFormat",
            "config.units",
            "config.useHttps",
            {
              "type": "array",
              "deleteCurrent": false,
              "title": "ipWhitelist",
              "items": [
                {
                  "key": "config.ipWhitelist[]",
                  "title": "ipWhitelist {{idx}}"
                }
              ]
            }
          ]
        },
],
"value":{
    "config": {
      "address": "0.0.0.0",
      "port": 8090,
      "basePath": "/",
      "language": "en",
      "locale": "en-IN",
      "logLevel": [
        "INFO",
        "LOG",
        "WARN",
        "ERROR"
      ],
      "timeFormat": 24,
      "units": "metric",
      "useHttps": false,
      "ipWhitelist": []
    }
}
}
backnext321 commented 1 year ago

My problem is I have javascript code inside json "form". So the "schema", "form" and "value" stays in the html file like this:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Form</title>
    <link rel="stylesheet" type="text/css" href="deps/opt/bootstrap.css" />
</head>
<body>
    <div class="container">
        <h2>Form</h2>
        <div><img id="x" src="fotos/kleintjes/geenAfbeelding.jpg"></div>
        <form></form>
    </div>
    <div id="res" class="alert"></div>
    <script type="text/javascript" src="deps/jquery.min.js"></script>
    <script type="text/javascript" src="deps/underscore.js"></script>
    <script type="text/javascript" src="deps/opt/jsv.js"></script>
    <script type="text/javascript" src="lib/jsonform.js"></script>
    <!--<script type="text/javascript" src="deps/opt/jquery-ui.js"></script>--><!--dit toevoegen om zinnen van plaats te wisselen-->
    <script type="text/javascript">
        var x = 0;
        var lengte = 0;
        var resultArray = [];
        const editedText = "";
        var value = "";
        var previouslySubmitted = ({ "fotos": [{ "foto": ["foto1"], "tekst": ["text1"] }, { "foto": ["foto2"], "tekst": ["text2"] }] });        $('form').jsonForm({
            schema: {
                fotos: {
                    type: 'array',
                    maxItems: 4,
                    items: {
                        type: 'object',
                        title: 'Foto',
                        properties: {
                            foto: {
                                type: 'string',
                                description: 'Bestandsnaam van de foto zonder extensie. (.jpg)',
                                title: "Foto nr {{idx}}",
                                maxItems: 1,
                            },
                            tekst: {
                                type: 'array',
                                maxItems: 3,
                                items: {
                                    type: 'string',
                                    title: 'tekst'
                                }
                            }
                        }
                    }
                }
            },
            form: [
                {
                    type: 'tabarray',
                    items: {
                        type: 'section',
                        title: '{{idx}}',
                        items: [
                            'fotos[].foto',
                            {
                                type: 'array',
                                items: [
                                    'fotos[].tekst[]'
                                ]
                            },
                        ]
                    },
                    onClick: function (evt) {
                        var value = $(evt.target).val();
                        document.getElementById("x").src = 'fotos/kleintjes/' + value + '.jpg';
                        var fotonaam = 'fotos/kleintjes/' + value + '.jpg';
                        checkImage(fotonaam,
                            function () {
                              // exists
                            },
                            function () {
                                document.getElementById('x').src = 'fotos/kleintjes/geenAfbeelding.jpg';
                            });

                        function checkImage(imageSrc, good, bad) {
                            var img = new Image();
                            img.onload = good;
                            img.onerror = bad;
                            img.src = imageSrc;
                        };
                    }
                },
                {
                    type: 'submit',
                    title: 'Verstuur'
                }
            ],
            value: {
                fotos: [
                    { "foto": ["FirstFoto"], "tekst": ["First Text"] }
                ]
            },
            onSubmit: function (errors, values) {
                if (errors) {
                    $('#res').html('<p>Wrong?</p>');
                } else {
                    const JSONbasis = values.fotos;
                    lengte = JSONbasis.length;
                    for (let i = 0; i < lengte; i++) {
                        const propertyValues = Object.values(values.fotos[i]);//values
                        const jsonStringpropertyValues1 = ('{"foto":' + JSON.stringify(propertyValues[0]));// foto
                        const jsonStringpropertyValues2 = JSON.stringify(Object.assign({}, propertyValues[1]));// tekst
                        const result = jsonStringpropertyValues1.concat(jsonStringpropertyValues2);
                        const resultB = result.replace('"{"', '","');
                        const resultC = resultB.replaceAll('","', '","zz');
                        resultArray.push([resultC]);
                    }
                    console.log("resultArray= " + resultArray);
                    console.log("JSON.stringify(values) = " + JSON.stringify(values));
                }
            }
        });

    </script>
</body>
</html>
sdetweil commented 1 year ago

and? just make your jsonform a variable (as it is an unnamed one now) and then change the value section

const  formvar= {
            schema: {
                fotos: {
                    type: 'array',
                    maxItems: 4,
                    items: {
                        type: 'object',
                        title: 'Foto',
                        properties: {
                            foto: {
                                type: 'string',
                                description: 'Bestandsnaam van de foto zonder extensie. (.jpg)',
                                title: "Foto nr {{idx}}",
                                maxItems: 1,
                            },
                            tekst: {
                                type: 'array',
                                maxItems: 3,
                                items: {
                                    type: 'string',
                                    title: 'tekst'
                                }
                            }
                        }
                    }
                }
            },
            form: [
                {
                    type: 'tabarray',
                    items: {
                        type: 'section',
                        title: '{{idx}}',
                        items: [
                            'fotos[].foto',
                            {
                                type: 'array',
                                items: [
                                    'fotos[].tekst[]'
                                ]
                            },
                        ]
                    },
                    onClick: function (evt) {
                        var value = $(evt.target).val();
                        document.getElementById("x").src = 'fotos/kleintjes/' + value + '.jpg';
                        var fotonaam = 'fotos/kleintjes/' + value + '.jpg';
                        checkImage(fotonaam,
                            function () {
                              // exists
                            },
                            function () {
                                document.getElementById('x').src = 'fotos/kleintjes/geenAfbeelding.jpg';
                            });

                        function checkImage(imageSrc, good, bad) {
                            var img = new Image();
                            img.onload = good;
                            img.onerror = bad;
                            img.src = imageSrc;
                        };
                    }
                },
                {
                    type: 'submit',
                    title: 'Verstuur'
                }
            ],
            value: {
                fotos: [
                    { "foto": ["FirstFoto"], "tekst": ["First Text"] }
                ]
            },
            onSubmit: function (errors, values) {
                if (errors) {
                    $('#res').html('<p>Wrong?</p>');
                } else {
                    const JSONbasis = values.fotos;
                    lengte = JSONbasis.length;
                    for (let i = 0; i < lengte; i++) {
                        const propertyValues = Object.values(values.fotos[i]);//values
                        const jsonStringpropertyValues1 = ('{"foto":' + JSON.stringify(propertyValues[0]));// foto
                        const jsonStringpropertyValues2 = JSON.stringify(Object.assign({}, propertyValues[1]));// tekst
                        const result = jsonStringpropertyValues1.concat(jsonStringpropertyValues2);
                        const resultB = result.replace('"{"', '","');
                        const resultC = resultB.replaceAll('","', '","zz');
                        resultArray.push([resultC]);
                    }
                    console.log("resultArray= " + resultArray);
                    console.log("JSON.stringify(values) = " + JSON.stringify(values));
                }
            }
        };
        formvar.value=previouslySubmitted //(take off the parens, not needed)
        $(form).jsonForm(formvar)  // generate the form

then if u need to adjust the form, fix it and regen to the same div.

I send my jsonform document to my web client over socket.io, make some corrections, and then generate it. I do like this

 // receive the form text from host
 activesocket.on("json", function (incoming_json) {

    // convert to object 
    let data = parseData(incoming_json.slice(1, -1));
    // free the memory
    incoming_json = null;

    // save pointers to data sent with the form object
    let pairs = data.pairs;
    let arrays = data.arrays;
    let objects = data.objects;
    let mangled_names = data.mangled_names;
    let convertedObjects = data.convertedObjects;

    // clear any text left over in the output error field 
    $("#outmessage").text("");

      // add the submit handlers to the jsonform  object
      // submitValid

      data.onSubmitValid = function (values) {
        // restore the fixup data from the incoming
        values["pairs"] = pairs;
        values["arrays"] = arrays;
        values["objects"] = objects;
        values["mangled_names"] = mangled_names;
        values["convertedObjects"] = convertedObjects;

        activesocket.emit("saveConfig", values);
        $("#outmessage").html(
          "<p><strong>Your Configuration has been submitted.</strong></p>"
        );
      };

      // submit
      data.onSubmit = function (errors, values) {
        if (errors) {
          console.log("Validation errors 1", errors, values);
          let buildInner = "";
          errors.forEach(function (errItem) {
            let errSchemaUri = errItem.schemaUri
              .replace(/.+\/properties\//, "")
              .replace("/", " >> ");
            buildInner +=
              `<p><strong style="font-color:red">Error: ` +
              errItem.message +
              "</strong></br>Location: " +
              errSchemaUri +
              "</p>";
          });
          $("#outMsg").html(buildInner);
          console.log("Validation errors 2", values);
          return false;
        }
        return true;
      };

      // replace any form from last connection
      $("#result").html('<form id="result-form" class="form-vertical"></form>');

      // generate the new form to the element id (using the id of the element injected in prior statement)
      $("#result-form").jsonForm(data);
backnext321 commented 1 year ago

Thanx. This helps me a lot. Just one more thing: I would like to put an image at the top of the form that corresponds to the first item of the tabarray. I try something like this:

  onClick: function (evt) {
                        var value = $(evt.target).val();
                        var fotonaam = 'fotos/kleintjes/' + value + '.jpg';
                        checkImage(fotonaam,
                            function () {
                                //exists
                                document.getElementById('x').src = fotonaam;

                            },
                            function () {

                                // not
                            });

                        function checkImage(imageSrc, good, bad) {
                            var img = new Image();
                            img.onload = good;
                            img.onerror = bad;
                            img.src = imageSrc;
                        };
                    }

But now one has to click the inputfield each time. This is not a good solution. Any idea?

sdetweil commented 1 year ago

what do you mean first? when the form is generated? or when the tab is selected?

if when form is generated, you know what the 1st tab will be, so you can inject that info to another element in the html above the form element.

if when the tab is selected you can add an on change handler to the tabarray. you are clicking that already

backnext321 commented 1 year ago

Thank you for viewing this. I'm almost there but not quite. When form is generated, the image is showing up. When the tab is selected I have still a strange problem: If I use onChange nothing happens when I click on the tab. If I use onClick -as in the example here- it works but I have to click twice on the tab every time before the image shows up. Searched for hours but no solution found.

<head>
    <meta charset="utf-8" />
    <title>Form</title>
    <link rel="stylesheet" type="text/css" href="deps/opt/bootstrap.css" />
</head>
<body>
    <div class="container">
        <h2>Form</h2>
        <div><img id="x" src="fotos/kleintjes/geenAfbeelding.jpg"></div>
        <form></form>
        <p id="demo"></p>
    </div>
    <div id="res" class="alert"></div>
    <script type="text/javascript" src="deps/jquery.min.js"></script>
    <script type="text/javascript" src="deps/underscore.js"></script>
    <script type="text/javascript" src="deps/opt/jsv.js"></script>
    <script type="text/javascript" src="lib/jsonform.js"></script>
    <!--<script type="text/javascript" src="deps/opt/jquery-ui.js"></script>--><!--dit toevoegen om zinnen van plaats te wisselen-->
    <script type="text/javascript">
        function tekstOpmaak(antwoord) {
        var x = 0;
        var lengte = 0;
        var resultArray = [];
        const formvar = {
            schema: {
                fotos: {
                    type: 'array',
                    maxItems: 4,
                    items: {
                        type: 'object',
                        title: 'Foto',
                        properties: {
                            foto: {
                                type: 'string',
                                title: 'Bestandsnaam van de foto zonder ".jpg"',
                                maxItems: 1,
                                required: true
                            },
                            tekst: {
                                type: 'array',
                                maxItems: 3,
                                items: {
                                    type: 'string',
                                    title: 'tekst'
                                }
                            }
                        }
                    }
                }
            },
            form: [
                {
                    key: "fotos",
                    type: 'tabarray',
                    onClick: function () {
                        let elements = document.getElementsByClassName('active');
                        let data = [].map.call(elements, elem => elem.textContent);
                        let data_0 = data[0] - 1;
                        console.log("data_0 = " + data_0);
                        let fotoData_0 = JSON.stringify(antwoord.fotos[data_0].foto[0]);
                        var fotoData_0_Sliced = fotoData_0.slice(1, -1);
                        console.log("fotoData_0_Sliced = " + fotoData_0_Sliced);
                        var fotonaam = 'fotos/kleintjes/' + fotoData_0_Sliced + '.jpg';
                        checkImage(fotonaam,

                            function () {
                                //exists
                                document.getElementById('x').src = fotonaam;
                            },

                            function () {
                               // exists not
                                document.getElementById('x').src = 'fotos/kleintjes/geenAfbeelding.jpg';
                            });

                        function checkImage(imageSrc, good, bad) {
                            var img = new Image();
                            img.onload = good;
                            img.onerror = bad;
                            img.src = imageSrc;
                        };
                    },
                    items: {
                        type: 'section',
                        title: '{{idx}}',
                        items: [
                            'fotos[].foto',
                            {
                                type: 'array',
                                notitle: true,
                                items: [
                                    'fotos[].tekst[]'
                                ]
                            },
                        ]
                    },
                },
                {
                    type: 'submit',
                    title: 'Verstuur'
                }
            ],
            onSubmit: function (errors, values) {
                if (errors) {
                    $('#res').html('<p>Is er iets fout gegaan?</p>');
                } else {
                    const JSONbasis = values.fotos;
                    lengte = JSONbasis.length;
                    for (let i = 0; i < lengte; i++) {
                        const propertyValues = Object.values(values.fotos[i]);//values
                        const jsonStringpropertyValues1 = ('{"foto":' + JSON.stringify(propertyValues[0]));// foto
                        const jsonStringpropertyValues2 = JSON.stringify(Object.assign({}, propertyValues[1]));// tekst
                        const result = jsonStringpropertyValues1.concat(jsonStringpropertyValues2);
                        const resultB = result.replace('"{"', '","');
                        const resultC = resultB.replaceAll('","', '","zz');
                        resultArray.push([resultC]);
                    }
                }
            }
        };
        formvar.value = antwoord;
        var showFoto = JSON.stringify(formvar.value.fotos[0].foto[0]);// voorlaatste [0] vervangen door variabele...
        var showFoto = showFoto.slice(1, -1);
        console.log('showFoto = ' + showFoto);
        console.log('showFoto = ' + showFoto);
        document.getElementById("x").src = 'fotos/kleintjes/' + showFoto + '.jpg';
            $('form').jsonForm(formvar);  // generate the form
        }
        const xmlhttp = new XMLHttpRequest();
        xmlhttp.onload = function () {
            //if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //}
            var myObj = JSON.parse(this.responseText);
            tekstOpmaak(myObj);
        }
        xmlhttp.open("GET", "hoofdstukken/JSONformtest2.txt");
        xmlhttp.send();
    </script>
</body>
sdetweil commented 1 year ago

click fires BEFORE the tag focus change change fires AFTER ( I believe)

you can debug this in the browser developers console, ctrl-shift-i, select the sources tab.. (chrome, firefox and edge) find your web page code in the left nav/

backnext321 commented 1 year ago

I think I found a solution. The function where I get the value of the 'tabarray' with "document.getElementsByClassName('active');" should be slightly delayed with 'setTimeout'. In this function I ask for the 'value' of the input field of the photo as below:

     let elements = document.getElementsByClassName('active');
     let data = [].map.call(elements, elem => elem.textContent);
     let data_0 = data[0] - 1;
     let data_0_Id = 'jsonform-1-elt-fotos[' + data_0 + '].foto';
     var inputfield = document.getElementById(data_0_Id).value;
     console.log("inputfield = " + inputfield);
     var fotonaam = 'fotos/kleintjes/' + inputfield + '.jpg';
sdetweil commented 1 year ago

cool!. all this is not really a jsonform issue, but app design. I have two independent sections in my firm, but they have to stay in sync. JavaScript functions are the only way!

backnext321 commented 1 year ago

Uhm. One more little question: How can I place the glyphicon-minus-sign of an array at the left of the input field instead of under this field? This to save space if there are more input fields in the form.

sdetweil commented 1 year ago

sorry got a pic of that? you mean the +/- add remove buttons? currently no way to move them

see https://github.com/jsonform/jsonform/issues/417

backnext321 commented 1 year ago

I mean the remove button under the input field. form When in Chrome Devtools I put ... in <div class="form-group jsonform-error-... and before <label for="jsonform-1-elt-... Like this,

... the 'remove button' goes behind the label text which takes less space too. (see screenshot form2.jpg) ![form2](https://user-images.githubusercontent.com/33583601/203946423-499d8d95-eac5-46e2-8e6a-568f728f27dc.jpg)
backnext321 commented 1 year ago

Here the screenshot form2.jpg I meant. form2

sdetweil commented 1 year ago

yes see #417 that asks the same.. no way to do that currently

backnext321 commented 1 year ago

I see. Now I have a form a bit like 'Navigation Tabs' in the playground. I need to change the "enum" values from outside the Json. I have some code for this but the onsubmit doesn't work anymore. Any suggestion?

sdetweil commented 1 year ago

sorry, unclear what you mean ..

sdetweil commented 1 year ago

maybe you have the jsonform js object again

{
    "schema": {
        "favorite_movie":{
          "type":"string",
          "title":"Favorite Movie",
          "enum":[
            "Field of Dreams",
            "Braveheart",
            "A League of Their Own"
          ]
        },

then

object.schema.favorite_movie.enum=[...... new list ] or
object.schema['favorite_movie'].enum = [ ..... new list ]

then regenerate the form.. you cannot change them WHILE the form is being executed... submit, change, regen...

backnext321 commented 1 year ago

I don't get it. I've done this so far. When I stringifyand then parse the formvar, the onsubmit part is no longer there...

`<!DOCTYPE html>

Form

`
sdetweil commented 1 year ago

correct, the sumbitted form only has data in it, not any of the form construct

if u add the on submits to the form after gen vs part of the form json.
then u have to add it again after form gen

backnext321 commented 1 year ago

I begin to understand the logic of these forms. It works this way. Thank you for quick help.

backnext321 commented 1 year ago

In #417 you mentioned "in #419 I added a form setting to allow removing the delete current item button..": "deleteCurrent: false," But where should I place "deleteCurrent: false". I tried in the form but nothing changes...

sdetweil commented 1 year ago

in the form section, for the array element.

last year I needed to disable the drag, so I added draggable:false

this deleteCurrent is only in that PR, not in the current released code

backnext321 commented 1 year ago

In jsonform.js after: var $nodeid = $(node.el).find('#' + escapeSelector(node.id)); I added: $nodeid.find('a._jsonform-array-deletecurrent').addClass('hide'); also after places where 'a._jsonform-array-deletecurrent'. Now "deletecurrent" is hidden.

sdetweil commented 1 year ago

@backnext321 just be careful updating the code, if u take any updates...

sdetweil commented 1 year ago

can we close this?

backnext321 commented 1 year ago

Yes, thank you for helping me and best wishes.