4
$\begingroup$

I would like to create a form with FormFunction[] that contains random elements in description. As an example I would like to create a form that generates two random numbers, a and b, and asks a user to choose the larger number.

Creating a form with description is relatively simple, say I have the following code.

FormFunction[
 {
  "<h1 style=\"color:black; font-family:verdana\">Play a little \
game</h1>",
  "<p style=\"color:black; font-family:verdana\">There are two \
numbers a = " <> ToString[RandomReal[]] <> " and b = " <> 
   ToString[RandomReal[]] <> ". Which one is larger?",
  "x" -> <|"Interpreter" -> {"A" -> 1, "B" -> 2},
    "Control" -> (RadioButtonBar[##, Appearance -> "Vertical"] &),
    "Label" -> "Select number",
    "Help" -> "Select the larger number"|>,
  },
 Identity,
 PageTheme -> "Blue"
 ]

The above code results in the following static form.

enter image description here

The form is, however, static, that is it does not change when reloaded.

Question

How can I make it dynamic, that is, every time the page is reloaded the numbers are generated anew. I have experimented with Delayed[] but to no avail. (Edit). I would also like to know the randomly generated numbers in the submit function.

Alternative answer

One answer is provided below. In the mean time, based on the same idea, I came up with a slightly different code. The solution below creates an API function that is deployed to a directory testDir. Every time the function is called it produces a different form (random), deploys to a subdirectory RandomForms under a different name and redirects to it. Such solution produces a new form every time a function is called. This can be solved either by manually cleaning the subdirectory RandomForms or by adding such statement in the submit function of a random form.

CloudDeploy[
 APIFunction[
  {},
  Function[
   a = RandomReal[];
   b = RandomReal[];
   dir = CreateUUID[];
   HTTPRedirect@CloudDeploy[FormFunction[
      {
       Style["Little form", "Title"],
       Style["First number: " <> ToString[a], "Text"],
       Style["Second number: " <> ToString[b], "Text"],
       "x" -> <|"Label" -> "Selection", 
         "Interpreter" -> {"A" -> 1, "B" -> 2}, 
         "Control" -> RadioButtonBar|>
       },
      ({a, b, #x} &)
      ], FileNameJoin[{$CloudRootDirectory, "testDir", "RandomForms", 
       dir}]]
   ]
  ],
 FileNameJoin[{$CloudRootDirectory, "testDir", "fun1"}],
 Permissions -> "Private"
 ]

For public use the obvious changes need to be introduced.

$\endgroup$
14
  • $\begingroup$ Always try to reduce an example to a minimum. Here the theme and html styling is not relevant at all. $\endgroup$
    – Kuba
    Aug 21 '17 at 13:20
  • 1
    $\begingroup$ FWIW, I think this is a very well-written question... $\endgroup$
    – C. E.
    Aug 21 '17 at 13:25
  • $\begingroup$ Also, don't you want to know those numbers in the submit function? Because this may be trickier. $\endgroup$
    – Kuba
    Aug 21 '17 at 13:26
  • $\begingroup$ @Kuba This is an excellent point, yes, I would like to know these numbers in the submit function. Also, I agree that the styling is not necessary but here it provides a context of the question: what I'm trying to do is to generate a simple task to check if the user understands the question. In fact, this is already very simpilified since if it is possible I will use it to generate a random example of a simple two-player normal-form game and ask for a payoff associated with randomly generated strategies. $\endgroup$
    – Michal
    Aug 21 '17 at 13:29
  • 1
    $\begingroup$ @Kuba Your solution works perfectly. I will post an answer based on your solution. As a side note I have to say that the documentation distributed with Mathematica and available on Wolfram pages is severely lacking. I really appreciate your help. Can you point me to any tutorial on how to write an APIFunction that creates a page with a form and on submit redirects to another APIFunction that creates another page like this so I can chain forms but a form need to take an argument (I need to identify a person so I need to pass a user id along). $\endgroup$
    – Michal
    Aug 22 '17 at 9:05
2
$\begingroup$

Don't know why Delayed fires again on 'submit' event so here is an example with an APIFunction that will generated and redirect to a FormFunction each time it is called:

 formTemplate = TemplateExpression @ TemplateWith[{"a" -> RandomReal[], "b" -> RandomReal[]
    },
   FormFunction[{"Play a little game", 
     StringTemplate@
      "There are two numbers a = `a` and b = `b`. Which one is \
larger?", 
     "x" -> <|"Interpreter" -> {"A" -> 1, "B" -> 2}, 
       "Control" -> RadioButtonBar|>}, #x == 
      If[TemplateSlot["a"] > TemplateSlot["b"], 1, 2] &, 
    PageTheme -> "Blue"]];


With[{formTemplate = formTemplate},
  APIFunction[{}, HTTPRedirect@CloudDeploy[
      TemplateApply@formTemplate
      , Permissions -> "Public"] &
   ]
] // CloudDeploy[#, Permissions -> "Public"] &
$\endgroup$
8
  • $\begingroup$ Thanks a lot. Am I wrong or does this solution deploy an unnamed cloud object every time it is called and redirects to it? If so, aren't we going to end up with a lot of random unnamed objects? On the other hand, this has the advantage that I can pass arguments like user id. $\endgroup$
    – Michal
    Aug 22 '17 at 12:08
  • $\begingroup$ @Michal yep, unfortunately t does this. You can use CloudDeploy[(*form*), CreateUUID["randomForms/"], ...] and clean this directory from time to time. $\endgroup$
    – Kuba
    Aug 22 '17 at 12:10
  • $\begingroup$ OK, taking into account expected number of uses this will not be a problem. I can also use DeleteFile[CloudObjects[None]] to remove all unnamed objects (I always use named objects) or maybe set up a scheduled task. That's fine I will accept the answer. $\endgroup$
    – Michal
    Aug 22 '17 at 12:14
  • $\begingroup$ One more thing. If an initial value of an input is defined as RuleDelayed[] the value is also lost in the submit function (is generated anew). Otherwise I could use disabled Input[] to catch the value. Now it needs to be enabled so even if I do not show it on a page a user can still focus on a field and delete the value or change it. $\endgroup$
    – Michal
    Aug 22 '17 at 15:40
  • $\begingroup$ I've just tested the answer. If used the way it is it produces a random form and redirect to it. However, the function is fixed, so when called next time it gives the same form with the same numbers. Those numbers are visible to the submit function in the form. If in the template code "a"->RandomReal[] is changed to "a":>RandomReal[] then the function generates random form every time it is called but the number are not available to the submit function in the form. $\endgroup$
    – Michal
    Aug 23 '17 at 15:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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