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"
]
(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"
]
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"
]
and when you submit
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"
]
But you'll still need to bind the submission function to the button (maybe by copying the JS of a deployed FormFunction
page).
Form-Control|Object|Function
's play nicely withWeb-Row|Col|Item
s... $\endgroup$WebItem
and friends for this. Do you want to use them explicitly? $\endgroup$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 inBlock[{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$