Moving between Views

The user interface of the address book application can be split up into two Views : one that lists all the addresses, and another on which you can add a new address.

A diagram showing two Views, and how one can move between them.

The address book application with two UrlBoundViews.

Deriving similar looking pages

The simplest way to have many pages look similar is to create a common page from which you derive pages, each with different extra content. Add AddressBookPanel to HomePage, and AddressForm to AddAddressPage:

class HomePage(AddressBookPage):
    def __init__(self, view, main_bookmarks):
        super().__init__(view, main_bookmarks)
        self.body.add_child(AddressBookPanel(view))
class AddAddressPage(AddressBookPage):
    def __init__(self, view, main_bookmarks):
        super().__init__(view, main_bookmarks)
        self.body.add_child(AddressForm(view))

Bookmarks and Navs

A Bookmark marks a particular UrlBoundView. A Nav is a menu created from a list of Bookmarks. It is a means for the user to move around in the application.

Create the common AddressBookPage with a Navbar just like before, but add a Nav to it:

class AddressBookPage(HTML5Page):
    def __init__(self, view, bookmarks):
        super().__init__(view)
        self.body.use_layout(Container())

        layout = ResponsiveLayout('md', colour_theme='dark', bg_scheme='primary')
        navbar = Navbar(view, css_id='my_nav').use_layout(layout)
        navbar.layout.set_brand_text('Address book')
        navbar.layout.add(Nav(view).with_bookmarks(bookmarks))

        self.body.add_child(navbar)

Bookmarks are created for the UrlBoundViews in AddressBookUI.assemble, and passed to the WidgetFactory of each page. The WidgetFactory arguments match those of the __init__ method of each page.

class AddressBookUI(UserInterface):
    def assemble(self):
        home = self.define_view('/', title='Show')
        add = self.define_view('/add', title='Add')

        bookmarks = [v.as_bookmark(self) for v in [home, add]]
        home.set_page(HomePage.factory(bookmarks))
        add.set_page(AddAddressPage.factory(bookmarks))

        self.define_transition(Address.events.save, add, home)

The call to define_transition() is changed to take the user back to the home page after adding a new Address.

AddressBookForm is unchanged in this example. AddressBookPanel though contains a list of AddressBoxes, but no AddressBookForm. The AddressBookForm is now on the other UrlBoundView.

class AddressBookPanel(Div):
    def __init__(self, view):
        super().__init__(view)

        self.add_child(H(view, 1, text='Addresses'))

        for address in Session.query(Address).all():
            self.add_child(AddressBox(view, address))

Guards

A guard is an Action passed to define_transition(). If there is more than one Transition for the same Event, guards are used to decide on the fly which Transition to follow.

The features.pageflow example is a simple application that contains guards to distinguish between two Transitions:

A diagram showing how Guards datermine which transition should be followed.

After input data is set on the model object, the guard of each applicable Transition is called. The first Transition whose guard returns True is followed.

class PageFlowExampleUI(UserInterface):
    def assemble(self):
        comment = Comment()

        home = self.define_view('/', title='Page flow demo')
        home.set_slot('main', CommentForm.factory(comment))

        thanks = self.define_view('/thanks', title='Thank you!')
        thanks_text = 'Thanks for submitting your comment'
        thanks.set_slot('main', P.factory(text=thanks_text))

        none_submitted = self.define_view('/none', title='Nothing to say?')
        none_text = 'Mmm, you submitted an empty comment??'
        none_submitted.set_slot('main', P.factory(text=none_text))

        self.define_transition(comment.events.submit, home, thanks, 
                               guard=Action(comment.contains_text))
        self.define_transition(comment.events.submit, home, none_submitted, 
                               guard=Not(Action(comment.contains_text)))
class Comment:
    @exposed
    def fields(self, fields):
        fields.email_address = EmailField(label='Email address', required=True)
        fields.text = Field(label='Comment')

    @exposed
    def events(self, events):
        events.submit = Event(label='Submit', action=Action(self.submit))

    def submit(self):
        print('%s submitted a comment:' % self.email_address)
        print(self.text)

    def contains_text(self):
        return self.text and self.text.strip() != ''