8
$\begingroup$

I'd like to make a contact form exactly like this page:

enter image description here

Of course changing the icon and where it gets sent to, but otherwise exactly the same look.

Key features:

  • Submit sends an email
  • Field are validated
  • Drag-n-drop attachment

There must be a hello world example with address validations and multiple input fields that I'm missing.... It should be easy to format this with the new WebItem, WebColumn, WebRow symbols in v13 right?

CloudDeploy@
 WebColumn[{WebItem[Rasterize["My Icon"], {200}], 
   WebItem["Please leave a detailed comment in the space below:"], 
   FormControl[Dynamic[message], 
    FormObject[{"message", 
       "Please leave a detailed comment in the space below:"} -> 
      "String"]], 
   FormControl[Dynamic[address], 
    FormObject[{"email", "Email:*"} -> "String", 
     AppearanceRules -> <|"ItemLayout" -> "Vertical"|>]], 
   WebItem["Attachment:"], 
   WebRow[{WebItem[Checkbox[]], WebItem["I agree..."]}], 
   WebItem@
    Button["Submit", SendMail[address, message], Background -> Red]}, 
  Alignment -> Center]

Response to @b3m2a1

The first piece of code makes the form page as you show, but I don't see a "send to" address, it doesn't send results to my wolfram cloud email account either...

banner = URLExecute@"https://picsum.photos/300/150";
CloudDeploy[ (*where is the "to" email address?*)
 FormFunction[{"Name" -> <|"Interpreter" -> "String", "Label" -> None|>,
    "Email" -> <|"Interpreter" -> "EmailAddress", "Label" -> None|>, 
   "Message" -> <|"Interpreter" -> "TextArea", 
     "Hint" -> "Please leave a more detailed comment", 
     "Label" -> None|>, 
   "Agreed" -> <|"Interpreter" -> "Boolean", 
     "Label" -> "I agree to the resale of my data on the dark web"|>},
   TemplateApply[
    "Thanks ``! Your submission went through.", {#Name}] &, 
  FormLayoutFunction -> 
   With[{r = banner}, 
    Function[
     Column[{Item[r, Alignment -> Left], "Name", #["Name", "Control"],
        "Email", #["Email", "Control"], 
       Spacer[{10, 5}], #["Message", "Control"], 
       Row@{#["Agreed", "Control"], 
         Spacer[10], #["Agreed", "Label"]}}, 
      Alignment -> Left]]]], "testControl", Permissions -> "Public"]

Discrepancies:

  • There is no drag and drop field. It should upload the payloads and add them as attachments to the email (or dump it to a Databin and include a link if more than 25mb?)
  • The email and name fields are not enforced (you can click submit without filling them out and the page reloads but doesn't highlight the incomplete fields)
  • The background isn't white (and there's an extra Panel[] it looks like)
  • The spacing and fonts are off (probably easy to fix this one)
  • The gray bar at the bottom "made with wolfram" shouldn't be there... (I'm guessing that impossible to remove?)

The second piece of code (the fully-fleshed-out snippet) spews errors for me in desktop version 13.0 (and there are no controls in the generated page):

enter image description here

$\endgroup$
6
  • $\begingroup$ I'm hope it's the case that Form-Control|Object|Function's play nicely with Web-Row|Col|Items... $\endgroup$
    – M.R.
    Commented Feb 1, 2022 at 19:48
  • $\begingroup$ You don't need WebItem and friends for this. Do you want to use them explicitly? $\endgroup$
    – b3m2a1
    Commented Feb 1, 2022 at 20:09
  • $\begingroup$ @b3m2a1 As long as the page looks the same and the code is intuitive I'm happy! $\endgroup$
    – M.R.
    Commented Feb 1, 2022 at 21:04
  • $\begingroup$ See my most recent edit for a fully functioning form embedded in a page $\endgroup$
    – b3m2a1
    Commented Feb 2, 2022 at 22:54
  • $\begingroup$ the first nits are all stuff that's in the docs or can only be customized by using a custom XML template in the Appearance option and you'll need to spelunk to figure out how that's handled. The errors in the second part are from Mathematica's XML parser shitting the bed. Maybe the XML generated for you won't be mangled if you don't wrap the parse step in Block[{StringReplace=...}, ...] so give that a go. Most of your issues are things that you can figure out with from the docs/with a bit of Javascript and spelunking $\endgroup$
    – b3m2a1
    Commented Feb 4, 2022 at 7:52

1 Answer 1

5
$\begingroup$

Here's the simple way using FormFunction with a custom FormLayoutFunction:

CloudDeploy[
 FormFunction[
  {
   "Name" -> <|
     "Interpreter" -> "String", 
     "Label" -> None
     |>,
   "Email" -> <|
     "Interpreter" -> "EmailAddress", 
     "Label" -> None
     |>,
   "Message" -> <|
     "Interpreter" -> "TextArea",
     "Hint" -> "Please leave a more detailed comment",
     "Label" -> None
     |>,
   "Agreed" -> <|
     "Interpreter" -> "Boolean",
     "Label" -> "I agree to the resale of my data on the dark web"
     |>
   },
  TemplateApply[
    "Thanks ``! Your submission went through.",
    {#Name}
    ] &,
  FormLayoutFunction -> With[{r = Rasterize["My Icon"]},
    Function[
     Column[
      {
       Item[r, Alignment -> Center],
       "Name",
       #["Name", "Control"],
       "Email",
       #["Email", "Control"],
       Spacer[{10, 5}],
       #["Message", "Control"],
       Row@{#["Agreed", "Control"], Spacer[10], #["Agreed", "Label"]}
       },
      Alignment -> Left
      ]
     ]
    ]
  ],
 "testControl",
 Permissions -> "Public"
 ]

enter image description here

(the Checkbox layout is currently broken but I've submitted a report so hopefully it's fixed soon)


Form-in-external-page setup might never "just work"

After much spelunking I realized I could get almost the perfect setup by first Export-ing a FormFunction and then injecting that into a WebColumn layout:

myForm =
  FormFunction[
   {
    "Name" -> <|
      "Interpreter" -> "String", 
      "Label" -> None
      |>,
    "Email" -> <|
      "Interpreter" -> "EmailAddress", 
      "Label" -> None
      |>,
    "Message" -> <|
      "Interpreter" -> "TextArea",
      "Hint" -> "Please leave a more detailed comment",
      "Label" -> None
      |>,
    "Agreed" -> <|
      "Interpreter" -> "Boolean",
      "Label" -> "I agree to the resale of my data on the dark web"
      |>
    },
   TemplateApply[
     "Thanks ``! Your submission went through.",
     {#Name}
     ] &,
   FormLayoutFunction ->
    Function[
     WebColumn[{
       WebColumn[
        {
         "Name",
         #["Name", "Control"],
         "Email",
         #["Email", "Control"],
         Spacer[{10, 5}],
         #["Message", "Control"],
         Row@{#["Agreed", "Control"], Spacer[10], #["Agreed", "Label"]}
         },
        Alignment -> Left
        ]
       },
      Alignment -> Right
      ]
     ]
   ];
test = CloudDeploy[myForm, "testControl2"];
formXML = Block[{body = URLRead[test]["Body"], xml},
   xml = ImportString[body, {"HTML", "XMLObject"}];(*first pass to initialize*)
   Block[{StringReplace = # &},
    xml = ImportString[body, {"HTML", "XMLObject"}];
    ];
   FirstCase[xml, 
    XMLElement["form", a_, e_] :> 
     XMLElement["form", 
      Append[DeleteCases[a, "action" -> _], "action" -> api[[1]]], e], None, 
    Infinity
    ]
   ];
CloudDeploy[
 ExportForm[
  WebColumn[Flatten@{
     WebItem[Rasterize["My Icon"], 200],
     formXML
     },
   600,
   Alignment -> Center,
   Padding -> 10,
   "margin" -> {50, Scaled[1/3]}
   ],
  "Form"
  ],
 "testControl2",
 Permissions -> "Public"
 ]

enter image description here

and that Submit button really does submit the form.

Unfortunately behind the scenes it's clear that FormFunction has something on the server side set up so that a POST request feeds into the function we supply and then reloads the page with the proper response. Similarly, a FormPage will reload but with the result in a box.

We're not going to be able to do that on our WebColumn page.

There is a viable workaround, though. You can create an APIFunction/FormObject pair and have the FormObject submit to the APIFunction

Here's a fully fleshed out example of that:

formSpec =
  {
   "Name" -> <|
     "Interpreter" -> "String", 
     "Label" -> None
     |>,
   "Email" -> <|
     "Interpreter" -> "EmailAddress", 
     "Label" -> None
     |>,
   "Message" -> <|
     "Interpreter" -> "TextArea",
     "Hint" -> "Please leave a more detailed comment",
     "Label" -> None
     |>,
   "Agreed" -> <|
     "Interpreter" -> "Boolean",
     "Label" -> "I agree to the resale of my data on the dark web"
     |>
   };
api = CloudDeploy[
   APIFunction[
    formSpec,
    TemplateApply[
      "Thanks ``! Your submission went through.",
      {#Name}
      ] &
    ],
   "testControl2_submit",
   Permissions -> "Public"
   ];
myForm =
  FormFunction[
   formSpec,
   Null,
   FormLayoutFunction ->
    Function[
     WebColumn[{
       WebColumn[
        {
         "Name",
         #["Name", "Control"],
         "Email",
         #["Email", "Control"],
         Spacer[{10, 5}],
         #["Message", "Control"],
         Row@{#["Agreed", "Control"], Spacer[10], #["Agreed", "Label"]}
         },
        Alignment -> Left
        ]
       },
      Alignment -> Right
      ]
     ]
   ];
test = CloudDeploy[myForm, "testControl2"];
formXML = Block[{body = URLRead[test]["Body"], xml},
   xml = ImportString[body, {"HTML", "XMLObject"}];(*first pass to initialize*)
   Block[{StringReplace = # &},
    xml = ImportString[body, {"HTML", "XMLObject"}];
    ];
   FirstCase[xml, 
    XMLElement["form", a_, e_] :> 
     XMLElement["form", 
      Append[DeleteCases[a, "action" -> _], "action" -> api[[1]]], e], None, 
    Infinity
    ]
   ];
myPage =
  WebColumn[{
    WebRow@{
      ImageResize[ExampleData[{"TestImage", "Mandrill"}], {600, 300}],
      WebItem[StringTake[ExampleData[{"Text", "LoremIpsum"}], 1000], 400]
      },
    WebItem[
     WebColumn[
      Flatten@{
        WebItem[Rasterize["My Icon"], 200],
        formXML
        },
      600
      ],
     1000,
     Alignment -> Center,
     Padding -> {50, 200, 50, 200},
     "border-top" -> GrayLevel[0],
     Background -> GrayLevel[.95]
     ]
    },
   1000
   ];
CloudDeploy[
 ExportForm[myPage, "Form"],
 "testControl2",
 Permissions -> "Public"
 ]

enter image description here

and when you submit

enter image description here

You can of course customize this more, but that requires more Javascript manipulation that I think is worth putting in this already long post.

Original

If you really want to add this to a page you're building with WebColumn it'll take more work. Here's what I currently have

CloudDeploy[
 WebColumn[{
   WebItem[Rasterize["My Icon"], 200],
   FormFunction[
    {
     "Name" -> <|
       "Interpreter" -> "String", 
       "Label" -> None
       |>,
     "Email" -> <|
       "Interpreter" -> "EmailAddress", 
       "Label" -> None
       |>,
     "Message" -> <|
       "Interpreter" -> "TextArea",
       "Hint" -> "Please leave a more detailed comment",
       "Label" -> None
       |>,
     "Agreed" -> <|
       "Interpreter" -> "Boolean",
       "Label" -> "I agree to the resale of my data on the dark web"
       |>
     },
    TemplateApply[
      "Thanks ``! Your submission went through.",
      {#Name}
      ] &,
    FormLayoutFunction ->
     Function[
      WebColumn[{
        WebColumn[
         {
          "Name",
          #["Name", "Control"],
          "Email",
          #["Email", "Control"],
          Spacer[{10, 5}],
          #["Message", "Control"],
          Row@{#["Agreed", "Control"], Spacer[10], #["Agreed", "Label"]}
          },
         Alignment -> Left
         ],
        WebItem[
         XMLElement["button", 
          {"type" -> "submit", "class" -> "btn btn-primary form-submit"},
          {"Submit"}
          ]
         ]
        },
       Alignment -> Right
       ]
      ]
    ]
   },
  600,
  Alignment -> Center,
  Padding -> 10,
  "margin" -> {50, Scaled[1/3]}
  ],
 "testControl2",
 Permissions -> "Public"
 ]

enter image description here

But you'll still need to bind the submission function to the button (maybe by copying the JS of a deployed FormFunction page).

$\endgroup$
1
  • $\begingroup$ Thanks for looking into this, I tried your code and had some issues, I'm positing a response in the question now if you can take a look, thanks! $\endgroup$
    – M.R.
    Commented Feb 4, 2022 at 3:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.