Ask the user for information
Goal
You want the visitor to type, pick, or check something, and you want those values delivered to your code.
Before you start
You can build pages with buttons. One rule appears everywhere on this page: an input's name becomes the parameter with the same name in the route the button goes to.
The smallest version
This form asks one question and delivers one answer:
from drafter import *
from dataclasses import dataclass
@dataclass
class State:
name: str
@route
def index(state: State) -> Page:
return Page(state, [
"What should we call you?",
TextBox("name"),
"\n",
Button("Save", "save_name")
])
@route
def save_name(state: State, name: str) -> Page:
state.name = name
return Page(state, [
"Nice to meet you, " + state.name + "!\n",
Button("Change it", "index")
])
start_server(State(""))
Put a plain string right before each input as its label, and save the parameter into state if you need it after this click.
Recipe: one form, several kinds of input
TextBox collects a line of text, SelectBox offers a fixed list of
choices, and CheckBox collects a yes/no. One button delivers all three
to one route, and the tests at the bottom check it without any clicking:
from drafter import *
from dataclasses import dataclass
@dataclass
class State:
replies: int
@route
def index(state: State) -> Page:
return Page(state, [
"Party invitation form.\n",
"Your name:",
TextBox("name"),
"\nPick a meal:",
SelectBox("meal", ["pizza", "salad", "soup"]),
"\nBringing a guest?",
CheckBox("guest"),
"\n",
Button("RSVP", "rsvp")
])
@route
def rsvp(state: State, name: str, meal: str, guest: bool) -> Page:
state.replies = state.replies + 1
lines = [
name + " wants " + meal + ".\n"
]
if guest:
lines.append("They are bringing a guest.\n")
lines.append("Replies so far: " + str(state.replies) + "\n")
lines.append(Button("Next reply", "index"))
return Page(state, lines)
assert_has(rsvp(State(0), "Ada", "pizza", False), "Ada wants pizza")
assert_has(rsvp(State(0), "Babbage", "soup", True), "bringing a guest")
assert_state(rsvp(State(4), "Domino", "salad", False), State(5))
start_server(State(0))
The CheckBox arrives as a bool because the parameter is annotated
guest: bool; checked is True, unchecked is False.
Recipe: numbers instead of text
Annotate the parameter as int (or float) and give the box a sensible
starting value, so the form cannot arrive empty:
from drafter import *
from dataclasses import dataclass
@dataclass
class State:
total: int
@route
def index(state: State) -> Page:
return Page(state, [
"Tickets cost 5 gold each.\n",
"How many tickets?",
TextBox("tickets", 2),
"\n",
Button("Get the price", "price")
])
@route
def price(state: State, tickets: int) -> Page:
state.total = tickets * 5
return Page(state, [
str(tickets) + " tickets cost " + str(state.total) + " gold.\n",
Button("Change the order", "index")
])
assert_state(price(State(0), 3), State(15))
start_server(State(0))
If the visitor types something that is not a whole number, Drafter shows a friendly error naming the parameter and the value, and they can go back and fix it.
Recipe: longer text
TextArea works like TextBox but gives the visitor room to type
several lines. The name-to-parameter contract is the same:
from drafter import *
from dataclasses import dataclass
@dataclass
class State:
review: str
@route
def index(state: State) -> Page:
return Page(state, [
"Review our arcade:",
"\n",
TextArea("review", state.review),
"\n",
Button("Submit review", "thank_you")
])
@route
def thank_you(state: State, review: str) -> Page:
state.review = review
return Page(state, [
"Thanks! You wrote:\n",
state.review + "\n",
Button("Edit your review", "index")
])
start_server(State(""))
Handing state.review back to the TextArea as its starting value gives
the visitor an edit loop instead of a blank box.
Common problems
- A missing-parameter error: an input name and a parameter name do not match exactly. The error names the expected parameter; compare spelling and underscores.
- A number arrives as text: the parameter has no annotation. Write
tickets: int. - The value vanishes after the next click: a parameter's value only
lasts for that one route call. Save it into a
statefield, as every recipe above does. - Two buttons, one form: both target routes receive the same inputs, so both need the matching parameters.
Understand it
Forms and input explains the name-to-parameter contract and includes the type conversion table.
See another example
Big form shows every core input in one place, and the story maker shows forms driving a whole app.
Look it up
TextBox, TextArea, CheckBox, and SelectBox.
Fix a problem
Route is missing a parameter and Could not convert text to an int.