User interaction¶
Forms and Inputs¶
Form
s get input from users. Build up the Form
by adding Input
Widget
s to it (or one of its children), via an appropriate FormLayout
.
To group some TextInput
s visually, add them to FieldSet
via a
FormLayout
. The FormLayout
arranges the TextInput
s with their
associated labels and validation error messages. Call
add_child()
to add the FieldSet
to the
Form
.
class AddressForm(Form):
def __init__(self, view):
super().__init__(view, 'address_form')
inputs = self.add_child(FieldSet(view, legend_text='Add an address'))
inputs.use_layout(FormLayout())
new_address = Address()
inputs.layout.add_input(TextInput(self, new_address.fields.name))
inputs.layout.add_input(TextInput(self, new_address.fields.email_address))
Fields provide metadata¶
Each Input
is wired to an associated Field
. A Field
holds more information about the similarly named attribute of the
Address. EmailField
constrains the input to be a valid email
address. Invalid input is blocked by the EmailField
and the
FormLayout
displays an error message underneath its TextInput
.
Field
s are defined on Address using a method decorated with
exposed
:
class Address(Base):
__tablename__ = 'addressbook2_address'
id = Column(Integer, primary_key=True)
email_address = Column(UnicodeText)
name = Column(UnicodeText)
@exposed
def fields(self, fields):
fields.name = Field(label='Name', required=True)
fields.email_address = EmailField(label='Email', required=True)
Note
Don’t confuse Field
s with SQLAlchemy’s Columns.
The email_address Field
sets the email_address attribute of an
Address when a Form
is submitted (and after validating the
input). A Column on that same email_address takes another
step—to persist the attribute to the database. They are
independent and do not have to be used together.
Buttons and Events¶
To save an Address to the database, create a save() method on it. Expose
an Event
for save() so that it can be tied to a Button
.
class Address(Base):
__tablename__ = 'addressbook2_address'
id = Column(Integer, primary_key=True)
email_address = Column(UnicodeText)
name = Column(UnicodeText)
@exposed
def fields(self, fields):
fields.name = Field(label='Name', required=True)
fields.email_address = EmailField(label='Email', required=True)
def save(self):
Session.add(self)
@exposed('save')
def events(self, events):
events.save = Event(label='Save', action=Action(self.save))
Add a Button
, linked to the save Event
. When the Button
is
clicked, its Event
occurs, and the Action
is executed.
Note
You don’t have to process input data
Before the Action
is executed, each Field
processes its input data
and sets it on the object the Field
is bound to.
class AddressForm(Form):
def __init__(self, view):
super().__init__(view, 'address_form')
inputs = self.add_child(FieldSet(view, legend_text='Add an address'))
inputs.use_layout(FormLayout())
new_address = Address()
inputs.layout.add_input(TextInput(self, new_address.fields.name))
inputs.layout.add_input(TextInput(self, new_address.fields.email_address))
inputs.add_child(Button(self, new_address.events.save, style='primary'))
A Transition
is defined in AddressBookUI.assemble to say that, if
the save Event
occurs on the home UrlBoundView
, the user
should just stay on the home UrlBoundView
.
class AddressBookUI(UserInterface):
def assemble(self):
home = self.define_view('/', title='Address book', page=AddressBookPage.factory())
self.define_transition(Address.events.save, home, home)
No instance of Address is available at the time Transition
s are
defined. ‘save’ is added to the @exposed decorator of Address.events
to be able to reference Address.events.save instead.