On this page:
formular
~error
~errors
~all-errors
formular-field?
formular-autofill
input-list
input-number
input-range
checkbox
input-text
textarea
input-date
input-time
input-file
make-checkboxes
make-radios
make-radios-with-other
make-sliders
select/  inline
map-result
map-result*
6.2.2.1 Form tools
submit-button
submit-button/  label

6.2.2 Formular: forms and fields🔗

 (require congame/components/formular)
  package: congame-core

A field is a special value that, when used inside a study form, renders as an input element. When the user submits the form, the field yields whatever value the user has entered as a Racket value.

syntax

(formular maybe-bot maybe-fields form-body maybe-action)

 
maybe-bot = 
  | #:bot ([bot-id (field-kwd bot-value) ...] ...)
     
maybe-fields = 
  | #:fields ([field-id field-expr] ...)
     
form-body = xexpr-with-fields
     
maybe-action = 
  | action-expr
Creates a form from an X-expression template containing embedded field declarations.

Fields can be declared in two ways within form-body:

  • Keyword syntax: (#:field-name field-expr) where field-expr produces a field. The field’s value will be passed to action-expr as a keyword argument.

  • Set! syntax: (set! var-id field-expr) where var-id is a study variable. When the form is submitted, the variable is automatically updated with the field’s value. This syntax cannot be combined with a custom action-expr.

Optional default values can be specified using (#:field-name field-expr {#:default value}).

The #:bot clause defines autofill values for bots. Each bot-id names a bot configuration, and the keyword/value pairs specify what values to fill in for each field. Use formular-autofill in the step’s bot handler to trigger the autofill.

The #:fields clause allows defining fields dynamically, useful when field order needs to be randomized or fields are computed at runtime.

Within form-body, you can use these special forms to display validation errors:

syntax

(~error field-ref)

 
field-ref = field-id
  | #:field-name
Displays validation errors for a specific field. Use the field’s identifier or keyword name to specify which field’s errors to display.

syntax

(~errors field-ref ...)

Displays validation errors for multiple fields at once.

syntax

(~all-errors)

Displays all validation errors from the form in a single list.

(formular
 #:bot
 ([ok (#:name "Alice")
      (#:age 30)])
 (haml
  (:div
   (:p "Name: " (#:name (input-text)))
   (:p "Age: " (#:age (input-number #:min 0 #:max 120)))
   ,@(~all-errors)
   (:button ([:type "submit"]) "Submit"))))

Using set! syntax with study variables:

(defvar participant-name)
(defvar participant-age)
 
(formular
 (haml
  (:div
   (:p "Name: " (set! participant-name (input-text)))
   (:p "Age: " (set! participant-age (input-number)))
   (:button ([:type "submit"]) "Submit"))))

procedure

(formular-field? v)  boolean?

  v : any/c
Returns #t if v is a field, #f otherwise.

procedure

(formular-autofill bot-id)  void?

  bot-id : symbol?
Automatically fills in and submits a form created with formular when running as a bot.

The bot-id argument must match one of the bot identifiers declared in the #:bot clause of the formular macro. The function reads autofill metadata embedded in the page by formular, fills in each form field with the corresponding value, and clicks the submit button.

For example, given a form defined with:

(formular
 #:bot
 ([ok (#:emissions 42)
      (#:location "Cluj-Napoca")])
 (haml
  (:div
   (#:emissions (input-number))
   (#:location (input-text))
   (:button ([:type "submit"]) "Submit"))))

The bot handler can use (formular-autofill 'ok) to fill in 42 for the emissions field, "Cluj-Napoca" for the location field, and submit the form.

This function handles different input types appropriately: text and number fields are filled by typing, checkboxes and radio buttons are clicked, and select dropdowns have their values set directly.

procedure

(input-list fields)  formular-field?

  fields : (listof formular-field?)
Returns a field that is a collection of fields, and which will provide all values in fields as a single list when the form is submitted.

For example, the following code creates two input-number fields:

(set! myvals (input-list (list (input-number) (input-number))))

When the user enters two numeric values (say, -3 and 9) and submits the form, myvals will contain '(-3 9).

procedure

(input-number [label    
  #:min min    
  #:max max    
  #:step step    
  #:required? req]    
  #:validators validators    
  [#:attributes attribs])  formular-field?
  label : (or/c #f string?) = #f
  min : real? = -inf.0
  max : real? = +inf.0
  step : integer? = 1
  req : (or/c string? boolean?) = #t
  validators : '()
  attribs : (listof (list/c symbol? string?)) = '()

procedure

(input-range [label    
  #:min min    
  #:max max    
  #:step step    
  #:required? req]    
  #:validators validators    
  [#:attributes attribs])  formular-field?
  label : (or/c #f string?) = #f
  min : real? = -inf.0
  max : real? = +inf.0
  step : integer? = 1
  req : (or/c string? boolean?) = #t
  validators : '()
  attribs : (listof (list/c symbol? string?)) = '()
Returns a field that ensures its input is numeric and falls between min and max (inclusive). Use input-number field to get a textbox with spinner arrow buttons for incrementing/decrementing the value, or input-range to get a horizontal slider.

If step is provided, then using the stepper arrows or slider will increase or decrease the value by that amount (though the value is not guaranteed to be a multiple of step).

If required? is not #f, the field must contain data before the form can be submitted.

procedure

(checkbox label    
  [#:required? required?    
  #:attributes attrs])  formular-field?
  label : (or/c #f string?)
  required? : any/c = #t
  attrs : (listof (list/c symbol? string?)) = null
Returns a field that renders as a single checkbox. If required? is not #f, the checkbox must be selected before the form can be submitted. Any attrs will be used as HTML attributes in the checkbox’s <input> tag.

procedure

(input-text [label    
  #:required? req]    
  #:validators validators    
  [#:attributes attrs])  formular-field?
  label : (or/c #f string?) = #f
  req : (or/c string? boolean?) = #t
  validators : '()
  attrs : (listof (list/c symbol? string?)) = '()
Returns a field that renders as a single-line text input. If required? is not #f, the checkbox must be selected before the form can be submitted. Any attrs will be used as HTML attributes in the text box’s <input> tag.

procedure

(textarea label    
  [#:required? req]    
  #:validators validators    
  [#:attributes attrs])  formular-field?
  label : (or/c #f string?)
  req : (or/c string? boolean?) = #t
  validators : '()
  attrs : (listof (list/c symbol? string?)) = '()
Returns a field that renders as a multi-line text input area. If required? is not #f, the text box must contain text before the form can be submitted. Any attrs will be used as HTML attributes in the field’s <input> tag.

Use attrs to specify the size of the text box in rows and columns:

(textarea "Label" #:attributes '((rows "5") (cols "33")))

procedure

(input-date label    
  [#:required? req]    
  #:validators validators    
  [#:attributes attrs])  formular-field?
  label : (or/c #f string?)
  req : (or/c string? boolean?) = #t
  validators : '()
  attrs : (listof (list/c symbol? string?)) = '()

procedure

(input-time label    
  [#:required? req]    
  #:validators validators    
  [#:attributes attrs])  formular-field?
  label : (or/c #f string?)
  req : (or/c string? boolean?) = #t
  validators : '()
  attrs : (listof (list/c symbol? string?)) = '()
Return fields for entering date and time values, respectively.

Date values are returned in strings of the form "yyyy-mm-dd". Time values are returned as strings of the form "hh:mm" (24-hour format).

If required? is not #f, the field must contain text before the form can be submitted. Any attrs will be used as HTML attributes in the field’s <input> tag.

procedure

(input-file [label    
  #:required? required?    
  #:validators validators    
  #:attributes attributes])  formular-field?
  label : (or/c #f string?) = #f
  required? : (or/c string? boolean?) = #t
  validators : (listof procedure?) = null
  attributes : (listof (list/c symbol? string?)) = null
Returns a field that renders as a file upload input.

When the form is submitted, the field’s value is a binding:file struct containing the uploaded file’s name, headers, and content.

If label is provided, the file input is wrapped in a label element displaying label. If required? is not #f, the user must select a file before the form can be submitted.

procedure

(make-checkboxes options    
  render-proc    
  [#:n n    
  #:message message    
  #:validators validators    
  #:attributes attributes])  formular-field?
  options : (listof (cons/c symbol? any/c))
  render-proc : 
(-> (listof (cons/c symbol? any/c))
    (-> symbol? string? xexpr?)
    xexpr?)
  n : exact-nonnegative-integer? = 0
  message : (or/c #f string?) = #f
  validators : (listof procedure?) = null
  attributes : (listof (list/c symbol? string?)) = null
Returns a field containing multiple checkboxes with custom rendering.

The options argument is a list of pairs, where each pair contains a symbol (the checkbox value) and associated data (typically a label string, but can be any value your render procedure uses).

The render-proc argument is a procedure that controls how the checkboxes are rendered. It receives the options list and a make-checkbox function. The make-checkbox function takes a symbol (the checkbox value) and an optional label string, and returns an X-expression for a labeled checkbox input.

When n is greater than 0, the user must check at least n boxes before the form can be submitted. If message is provided, it is shown when this validation fails.

(make-checkboxes
 '((apple . "Apple")
   (banana . "Banana")
   (cherry . "Cherry"))
 (lambda (options make-checkbox)
   (haml
    (:ul
     ,@(for/list ([opt (in-list options)])
         (haml (:li (make-checkbox (car opt) (cdr opt))))))))
 #:n 1
 #:message "Please select at least one fruit.")

procedure

(make-radios options    
  render-proc    
  [#:required? required?    
  #:validators validators    
  #:attributes attributes])  formular-field?
  options : (listof (cons/c symbol? any/c))
  render-proc : 
(-> (listof (cons/c symbol? any/c))
    (-> symbol? string? xexpr?)
    xexpr?)
  required? : (or/c string? boolean?) = #t
  validators : (listof procedure?) = null
  attributes : (listof (list/c symbol? string?)) = null
Returns a field containing radio buttons with custom rendering.

The options argument is a list of pairs, where each pair contains a symbol (the radio button value) and associated data (which can be any value your render procedure uses).

The render-proc argument is a procedure that controls how the radio buttons are rendered. It receives the options list and a make-radio function. The make-radio function takes a symbol (the radio value) and an optional label string, and returns an X-expression for a labeled radio input.

If required? is not #f, the user must select one of the radio options before the form can be submitted.

This example renders radio buttons in a table format:

(make-radios
 '((mac1    "Apple Mac" "White")
   (mac2    "Apple Mac" "Gray")
   (dell1    "Dell" "Blue"))
 (lambda (options make-radio)
   (haml
    (:table
     (:thead
      (:tr (:th "") (:th "Brand") (:th "Color")))
     (:tbody
      ,@(for/list ([opt (in-list options)])
          (haml
           (:tr
            (:td (make-radio (car opt)))
            (:td (car (cdr opt)))
            (:td (cadr (cdr opt)))))))))))

procedure

(make-radios-with-other options 
  [#:required? required? 
  #:validators validators]) 
  formular-field?
  options : (listof (cons/c symbol? string?))
  required? : (or/c string? boolean?) = #t
  validators : (listof procedure?) = null
Returns a field containing radio buttons with an additional "Other" option that includes a text input field.

The options argument is a list of pairs where each pair contains a symbol (the radio value) and a string (the displayed label).

When the user selects one of the predefined options, the field’s value is that option’s symbol (as a string). When the user types text in the "Other" field, the field’s value is whatever they typed.

If required? is not #f, the user must either select one of the radio options or provide text in the "Other" field before the form can be submitted.

(make-radios-with-other
 '((often . "Often")
   (sometimes . "Sometimes")
   (rarely . "Rarely")))

procedure

(make-sliders n    
  render-proc    
  [#:message message    
  #:validators validators])  formular-field?
  n : exact-positive-integer?
  render-proc : (-> exact-nonnegative-integer? string? (or/c number? #f) xexpr?)
  message : (or/c #f string?) = #f
  validators : (listof procedure?) = null
Returns a field containing n slider inputs with custom rendering.

The render-proc argument is a procedure that renders a single slider. It receives three arguments: the slider’s index (starting from 0), the field name (a string), and the current value (a number, or #f if no value is set). It should return an X-expression containing an <input> element with type="range" and name set to the provided field name.

When the form is submitted, the field’s value is a list of numbers corresponding to each slider’s value.

(make-sliders
 3
 (lambda (idx name current-value)
   (haml
    (:div
     (:label (format "Slider ~a: " (add1 idx))
             (:input ([:type "range"]
                      [:name name]
                      [:min "0"]
                      [:max "100"]
                      [:value (or current-value "50")])))))))

procedure

(select/inline options    
  [#:required? required?    
  #:validators validators    
  #:attributes attributes])  formular-field?
  options : (listof (cons/c string? string?))
  required? : (or/c string? boolean?) = #t
  validators : (listof procedure?) = null
  attributes : (listof (list/c symbol? string?)) = null
Returns a field that renders as a dropdown select element without a label or wrapper, suitable for embedding inline within text or other custom layouts.

The options argument is a list of pairs where each pair contains a value string and a display label string.

(haml
 (:p "I prefer "
     (#:color (select/inline '(("red" . "Red")
                               ("blue" . "Blue")
                               ("green" . "Green"))))
     " as my favorite color."))

procedure

(map-result field proc)  formular-field?

  field : formular-field?
  proc : (-> any/c any/c)
Transforms the value of field by applying proc after validation succeeds.

When the form is submitted and field passes validation, proc is called with the validated value. The result of proc becomes the field’s final value.

; Convert a text input to a symbol
(map-result (input-text) string->symbol)
 
; Square a number input
(map-result (input-number) (λ (n) (* n n)))

procedure

(map-result* field    
  proc    
  [#:exn-predicate exn-predicate    
  #:exn-handler exn-handler])  formular-field?
  field : formular-field?
  proc : (-> any/c any/c)
  exn-predicate : (-> any/c boolean?) = exn:fail?
  exn-handler : (-> exn? (cons/c 'err string?))
   = (λ (e) (cons 'err (exn-message e)))
Like map-result, but catches exceptions raised by proc and converts them to validation errors.

If proc raises an exception matching exn-predicate, the exception is caught and exn-handler is called. The handler should return a pair (cons 'err message) where message is the error string to display to the user.

This is useful when the transformation might fail on certain inputs:

; Parse a number, showing a friendly error on failure
(map-result* (input-text)
             string->number
             #:exn-handler (λ (_) (cons 'err "Please enter a valid number")))
6.2.2.1 Form tools🔗

 (require (submod congame/components/formular tools))

value

submit-button : xexpr?

procedure

(submit-button/label label)  xexpr?

  label : string?
Returns a representation of an HTML button that submits a form. The label argument is used as the button’s label.