User interaction¶
Forms and Inputs¶
Forms get input from users. Build up the Form by adding Input
Widgets to it (or one of its children), via an appropriate FormLayout.
To group some TextInputs visually, add them to FieldSet via a
FormLayout. The FormLayout arranges the TextInputs 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.
A rough design sketch¶
Fields 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 Fields 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 Transitions are
defined. ‘save’ is added to the @exposed decorator of Address.events
to be able to reference Address.events.save instead.
