seaBreeze Control Flow and Composition

Components

seaBreeze uses Seaside as a basis. Seaside unlike other web frameworks introduced the concept of Components. A website is no longer described in a template but instead composed of separate parts. A normal webpage could contain components to show a list of articles, an advertisement banner, a login component and a footer component that renders the copyright and contact information. Each component is implemented interdependently and can be replaced by another component. If for example the user logs in using the login-component it could be replaced by a component that allows the user to log out.

#call: and #answer: 

The example illustrates not only the Component concept but also the concept of replacing a component with another one. There are two way of doing that. The obvious way is to simply replace a component in its wrapping component. In the example the outer component would be responsible for replacing the login component with a logout-component. If that's a desirable design decision or not depends on the problem that needs to be solved. The other way replacing the login with the logout component is by making the login component call the logout component.

The term "calling" refers to the method that's being used for the task: #call:. The #call: method takes the new component as parameter and replaces the receiver with the new component. The return value of #call: depends on the component that's being called. The execution of the method where #call: is used, is suspended until the return value of #call: is provided. To provide a return value and to return control to the calling component, the new component has to call the method #answer: and pass the return value as an argument. The parameter is then returned by the #call: method in the calling component and execution of the method, where #call: was used is resumed.

In the example with the login component, the login component could call the logout component and when the user chooses to log out, control is given back to the login component. The return value in this case is irrelevant so it's probably nil.

 

Examples

To illustrate the described concepts, the following example provides information on how to implement both scenarios.

Lets assume the following Components:

MainComponent

This component is the wrapping component, it has an aspect returning the login component called #loginComponent

LoginComponent

This component is used for logging in. It shows an input field for the username and the password and a button to login.

LogoutComponent

This component is used for logging out. It shows the name of the logged in user and has a button to logout.

 

Example for replacing Components

When the login component authenticates the user, it tells its parent component that it successfully logged in a user and the MainComponent then replaces the LoginComponent with an instance of LogoutComponent.

LoginComponent>>login

self authenticate ifTrue:[self parent loggedInUser: self user].

All application models that are subclasses of SBSubApplicationModel have the instance variable parent, which is set by instantiating the Component using #from:.

MainComponent>>loggedInUser: aUser

self loginComponent value: ((LogoutComponent from: self)
                                             user: self user;
                                             yourself)

Replacing the loginComponent's value will replace the component that is rendered.

The LogoutComponent then implements logout in the same spirit as the login component and tells its parent to logout.

LogoutComponent>>logout

self parent userLoggedOut.

The MainComponent then again replaces the components

MainComponent>>userLoggedOut

self loginComponent value: ((LoginComponent from: self)
                                displayMessage:'Thank you for your visit, see you soon.';
                                yourself)

 

Example for using #call: and #answer:

When the login component authenticates the user, it calls the logout component directly. The MainComponent may not need to know if the user is logged in or not, lets assume that's dealt with in the background and not in the user interface.

LoginComponent>>login

self authenticate ifTrue:[self call: ((LogoutComponent from: self parent)
                                             user: self user;
                                             yourself).
                          self displayMessage:'Thank you for your visit, see you soon.'
].

The Logout component then implements the logout method by simply answering back to the login component.

LogoutComponent>>logout

self answer: nil.

The MainComponent doesn't have to implement anything because it is affected at all.

 

Summary

While the #call: and #answer: method may seem the better choise it can be better to have the parent component manage its sub-components. 

It is also important to mention that sending #call: will suspend the execution of the method LoginComponent>>login and after #anwser: was sent, it will continue the execution. That's why the Thank you string can be used directly in the login method, because when the login method finishes, the user is logged out again.

 

 

Copyright 2013 by Georg Heeg eK, released under MIT License