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(AddressForm, self).__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 after validating input, etc. 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.
class AddressForm(Form):
def __init__(self, view):
super(AddressForm, self).__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))
button = inputs.add_child(Button(self, new_address.events.save))
button.use_layout(ButtonLayout(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.