Multiple input elements (form) into one message

Hi all,

We are in the process of building a new user interface where we'd like to use more of the custom ui elements (ui-slider, ui-text-input, ui-dropdown, etc.) for user input. Currently, we are using a join node to create a single message from all the inputs when the user presses a Submit button (which sends a msg.complete to the join node).
However, this is not without it's problems. If input data is not cleared after a Submit, the input field retains the data (visually) but that information is not sent to the join node. This can of course be corrected by sending the output info back to the input nodes. And having a Clear button that sends empty payloads to all input nodes.
So while we do have something that works, this is all kind of elaborate to set up. Especially with big forms. What I'm asking here if there is a simpler way to go about achieving this.

What would be the "correct" or most accepted way to build an input form using individual input nodes, resulting in a single message containing all correct (i.e. on-screen) input values after a Submit button press?

I'd got for a pattern of utilising flow. context variables. So, each element in your "form" has a change node after it which sets flow.<something>.

Then when you click your submit button, you retrieve the relevant values from flow. context variables, form a single object, and clear the flow. for the next submission.

This does however breakdown if you're having multiple people submit a form at the same time, but you could utilise the _client.socketId and map accordingly here instead, or, if on FlowFuse, use the msg._client.user object.

Thanks Joe!
I'll try it out and see if that works better.

Hi Joe,

The method using flow. works better, but I'm having trouble setting a msg variable as a key for the flow. object. Could you please give an example on how to set a flow. variable using msg._client.user as a key in a change node?

Thanks!

So long as it is a sub property, you can use square bracket notation.

image

Top one is OK, second one is not.

Top version is better anyhow as you only have one object to maintain instead of hundreds* of top level properties.

Thank you Steve!
But I'm clearly doing something wrong.

I am setting flow.object[msg._client.user.username].ticket to the value of msg.payload in a Change node.
In a subsequent Change node I am setting setting msg.data.ticket (after first creating a msg.data object with JSON) to the value of flow.{username}.ticket (where I've hard-coded the username for testing purposes).

The msg.data.ticket property is not being set and I don't see why not. Any thoughts?

Here's a functioning flow.json:

[
    {
        "id": "91b2fc6183819936",
        "type": "ui-radio-group",
        "z": "da7bf783aac88231",
        "group": "07e6fb7315e56445",
        "name": "Radio Group",
        "label": "Select Option:",
        "order": 2,
        "width": 0,
        "height": 0,
        "columns": 1,
        "passthru": false,
        "options": [
            {
                "label": "Option A",
                "value": "a",
                "type": "str"
            },
            {
                "label": "Option B",
                "value": "b",
                "type": "str"
            }
        ],
        "payload": "",
        "topic": "radio-group",
        "topicType": "str",
        "className": "",
        "x": 190,
        "y": 140,
        "wires": [
            [
                "a61aac88d51a335e"
            ]
        ]
    },
    {
        "id": "69d760689847ef18",
        "type": "ui-text-input",
        "z": "da7bf783aac88231",
        "group": "07e6fb7315e56445",
        "name": "",
        "label": "text",
        "order": 1,
        "width": 0,
        "height": 0,
        "topic": "text-input",
        "topicType": "str",
        "mode": "text",
        "delay": 300,
        "passthru": true,
        "sendOnDelay": false,
        "sendOnBlur": true,
        "sendOnEnter": true,
        "className": "",
        "x": 210,
        "y": 100,
        "wires": [
            [
                "a61aac88d51a335e"
            ]
        ]
    },
    {
        "id": "43436045a697228e",
        "type": "ui-button",
        "z": "da7bf783aac88231",
        "group": "07e6fb7315e56445",
        "name": "",
        "label": "Submit",
        "order": 3,
        "width": 0,
        "height": 0,
        "passthru": false,
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "",
        "payload": "",
        "payloadType": "str",
        "topic": "topic",
        "topicType": "msg",
        "x": 200,
        "y": 180,
        "wires": [
            [
                "0082a28d21feeddc"
            ]
        ]
    },
    {
        "id": "0082a28d21feeddc",
        "type": "change",
        "z": "da7bf783aac88231",
        "name": "Build Single Form Object",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "formSubmissions[msg._client.user.username]",
                "tot": "flow"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 390,
        "y": 180,
        "wires": [
            [
                "f7aec72923931f45"
            ]
        ]
    },
    {
        "id": "a61aac88d51a335e",
        "type": "change",
        "z": "da7bf783aac88231",
        "name": "Store Temporary Context",
        "rules": [
            {
                "t": "set",
                "p": "formSubmissions[msg._client.user.username][msg.topic]",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 410,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "f7aec72923931f45",
        "type": "ui-template",
        "z": "da7bf783aac88231",
        "group": "07e6fb7315e56445",
        "page": "",
        "ui": "",
        "name": "",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n    <h3>Form Submission:</h3>\n    <pre>{{ msg?.payload }}</pre>\n    <label>Submitted by {{ msg._client?.user?.username }}</label>\n</template>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 580,
        "y": 180,
        "wires": [
            []
        ]
    },
    {
        "id": "07e6fb7315e56445",
        "type": "ui-group",
        "name": "Form",
        "page": "3bceecc44f91d26b",
        "width": "6",
        "height": "1",
        "order": -1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "3bceecc44f91d26b",
        "type": "ui-page",
        "name": "Custom Form Submission",
        "ui": "c851adb9b2a29c9e",
        "path": "/form-submission",
        "icon": "home",
        "layout": "grid",
        "theme": "35ee7753b5b3599b",
        "order": -1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "c851adb9b2a29c9e",
        "type": "ui-base",
        "name": "Multi User Dashboard",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control",
            "ui-template"
        ]
    },
    {
        "id": "35ee7753b5b3599b",
        "type": "ui-theme",
        "name": "Theme Name",
        "colors": {
            "surface": "#16234b",
            "primary": "#1d44b9",
            "bgPage": "#ecf2f8",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        }
    }
]

It uses the msg.topic on the different inputs to define the type, then in the change node, uses @Steve-Mcl's pattern by which to save this to a flow variable with formSubmissions[msg._client.user.username][msg.topic] - building out a single object per user.

Then, on submission, retrieves that form for the user

You could also then clear the flow variable and inputs on submission too for completeness, but I've not done that here.

The flow ends up building:

Just to say this could also be done with an Auto join and msg.parts.
e.g.

[{"id":"91b2fc6183819936","type":"ui-radio-group","z":"d1395164b4eec73e","group":"07e6fb7315e56445","name":"Radio Group","label":"Select Option:","order":2,"width":0,"height":0,"columns":1,"passthru":false,"options":[{"label":"Option A","value":"a","type":"str"},{"label":"Option B","value":"b","type":"str"}],"payload":"","topic":"radio_group","topicType":"str","className":"","x":470,"y":5840,"wires":[["73b65db425be8fa3"]]},{"id":"69d760689847ef18","type":"ui-text-input","z":"d1395164b4eec73e","group":"07e6fb7315e56445","name":"","label":"text","order":1,"width":0,"height":0,"topic":"text_input","topicType":"str","mode":"text","delay":300,"passthru":true,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","x":470,"y":5800,"wires":[["6e97049c966c8c09"]]},{"id":"43436045a697228e","type":"ui-button","z":"d1395164b4eec73e","group":"07e6fb7315e56445","name":"","label":"Submit","order":3,"width":0,"height":0,"passthru":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"submit","payloadType":"str","topic":"submit","topicType":"str","x":460,"y":5880,"wires":[["65534118a1626935"]]},{"id":"6238d86895290052","type":"switch","z":"d1395164b4eec73e","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"submit","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":910,"y":5840,"wires":[["f7aec72923931f45"]]},{"id":"6ec4fe70f4c5f29b","type":"debug","z":"d1395164b4eec73e","name":"debug 2485","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":850,"y":5940,"wires":[]},{"id":"6e97049c966c8c09","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"parts","pt":"msg","to":"{\"key\": \"text_input\",\t\"count\": 3,\t\"index\": 0,\t\"type\": \"object\",\t\"id\": $$._client.socketId\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":5800,"wires":[["442b2978bcb47527","6ec4fe70f4c5f29b"]]},{"id":"73b65db425be8fa3","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"parts","pt":"msg","to":"{\"key\": \"radio_input\",\t\"count\": 3,\t\"index\": 1,\t\"type\": \"object\",\t\"id\": $$._client.socketId\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":5840,"wires":[["442b2978bcb47527","6ec4fe70f4c5f29b"]]},{"id":"65534118a1626935","type":"change","z":"d1395164b4eec73e","name":"","rules":[{"t":"set","p":"parts","pt":"msg","to":"{\"key\": \"submit\",\t\"count\": 3,\t\"index\": 2,\t\"type\": \"object\",\t\"id\": $$._client.socketId\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":5880,"wires":[["442b2978bcb47527","6ec4fe70f4c5f29b"]]},{"id":"442b2978bcb47527","type":"join","z":"d1395164b4eec73e","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":810,"y":5800,"wires":[["6238d86895290052"]]},{"id":"f7aec72923931f45","type":"ui-template","z":"d1395164b4eec73e","group":"07e6fb7315e56445","page":"","ui":"","name":"","order":0,"width":0,"height":0,"head":"","format":"<template>\n    <h3>Form Submission:</h3>\n    <pre>{{ msg?.payload }}</pre>\n    <label>Submitted by {{ msg?._client?.socketId }}</label>\n</template>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":960,"y":5880,"wires":[["6ec4fe70f4c5f29b"]]},{"id":"07e6fb7315e56445","type":"ui-group","name":"Form","page":"3bceecc44f91d26b","width":"6","height":"1","order":-1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"3bceecc44f91d26b","type":"ui-page","name":"Custom Form Submission","ui":"1805777f90e92057","path":"/form-submission","icon":"home","layout":"grid","theme":"35ee7753b5b3599b","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"1805777f90e92057","type":"ui-base","name":"dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false},{"id":"35ee7753b5b3599b","type":"ui-theme","name":"Theme Name","colors":{"surface":"#16234b","primary":"#1d44b9","bgPage":"#ecf2f8","groupBg":"#ffffff","groupOutline":"#cccccc"}}]

How would this cope with multiple people submitting at the same time though? Or is that what the id field is controlling?

Yes the msg.parts.id would be set to the socket or username.

Thanks Joe and E1cid,

Both solutions are working beautifully..
Never would have come up with these, so.. still a lot to learn.. :slight_smile:
Thanks very much, both of you!

Joe's solution looks a little bit cleaner and easier to expand upon.
But great to see 2 different approaches and techniques.