Skip to content

Commit

Permalink
Merge pull request #96 from SquareBracketAssociates/finish-dual-aspects
Browse files Browse the repository at this point in the history
Finish chapter "The dual aspects of presenters: Domain and interaction model"
  • Loading branch information
Ducasse committed May 20, 2024
2 parents 05a183e + 6160ad0 commit 1cb95de
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 40 deletions.
83 changes: 43 additions & 40 deletions Chapters/ThreePillarsOfSpec/ThreePillarsOfSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ In this chapter, we visit the key aspects of Spec and put the important customiz
### About presenters on a model


It is frequent that you want to open a presenter on a given object such as your list of todo items.
In that case, you would like the subpresenters (list, text,..) get initialized based on the object that you passed. For example, you may want to get all the items in your basket.
Frequently you want to open a presenter on a given object such as your list of to-do items.
In that case, you would like the subpresenters (list, text,..) to be initialized based on the object that you passed. For example, you may want to get all the items in your basket.


However, simply instantiating a presenter using the message `new` and passing the object will not work because the messages such as `initializePresenters` will be already sent.
However, simply instantiating a presenter using the message `new` and passing the object will not work because messages such as `initializePresenters` will be already sent.

There are two ways to address this situation in Spec and in particular, Spec offers a special presenter called `SpPresenterWithModel`. Let us explain how to take advantage of it.

We will build the simplest example to show the way to do it. We will implement a presenter that lists the method signatures of a class, first using a presenter and second using a presenter (subclass of `SpPresenterWithModel`) dedicated to handling a model.
We will build the simplest example to show how to do it. We will implement a presenter that lists the method signatures of a class, first using a presenter and second using a presenter (subclass of `SpPresenterWithModel`) dedicated to handling a model.


### Example with SpPresenter
Expand All @@ -33,36 +33,38 @@ First, we create a new presenter class.

```
SpPresenter << #MethodLister
slots: { #sourceClass . #list};
package: 'Spec2Book'
slots: { #sourceClass . #list};
package: 'Spec2Book'
```

We define a list presenter and populate it.

```
MethodLister >> initializePresenters
list := self newList.
list items: sourceClass selectors sorted
list := self newList.
list items: sourceClass selectors sorted
```

Specializing the method `setModelBeforeInitialization:`, we assign its argument coming from the `on:` message to the instance variable `sourceClass` for future use.

```
MethodLister >> setModelBeforeInitialization: aModel
sourceClass := aModel
sourceClass := aModel
```

We define a basic layout for the list presenter.

```
MethodLister >> defaultLayout
^ SpBoxLayout newTopToBottom add: #list; yourself
^ SpBoxLayout newTopToBottom
add: #list;
yourself
```

The following snippet creates a window with the list of methods of the class `Point` as shown in Figure *@pointselectors@*.
The following snippet opens a window with the list of methods of the class `Point` as shown in Figure *@pointselectors@*.

```
(MethodLister on: Point) open.
Expand All @@ -75,7 +77,7 @@ The following snippet creates a window with the list of methods of the class `Po

### SpPresenter vs. SpPresenterWithModel

The key difference between using `SpPresenter` and `SpPresenterWithModel` is if you need to react to changes of the model. We mean that while the presenter is open, an event changes the model that was used to build the UI. In our example, this means that when you change the class, the method list displays its selectors. If you need this behavior, then you should use `SpPresenterWithModel`.
The key difference between using `SpPresenter` and `SpPresenterWithModel` is if you need to react to changes of the model. We mean that while the presenter is open, an event changes the model that was used to build the UI. In our example, that means that when you change the class, the method list displays its selectors. If you need this behavior, then you should use `SpPresenterWithModel`.

The following snippet shows that the change of model is not taken into account in the sense that the list is not refreshed and still displays methods of the class `Point`, while the methods of the class `Rectangle should be displayed.

Expand All @@ -89,37 +91,36 @@ lister class: Rectangle

### Example with SpPresenterWithModel

A presenter may also have a model that is a domain object you need to interact with to display or update data. In this case, you should inherit from `SpPresenterWithModel` so that the presenter keeps a reference to the domain object and manages its changes. As a client of this presenter, we use the message `model:` to change the model.
A presenter may also have a model that is a domain object you need to interact with to display or update data. In that case, you should inherit from `SpPresenterWithModel` so that the presenter keeps a reference to the domain object and manages its changes. As a client of this presenter, we use the message `model:` to change the model.

The corresponding method is inherited from the superclass.
This `model:` method implements the following behavior:
- If the domain object is an instance of `Model`, it is stored as is in the presenter,
- else a value holder is created to hold the domain object so that you can be notified when the domain object used by the presenter changes.
The method is inherited from the superclass. This `model:` method implements the following behavior:
- If the domain object is an instance of `Model`, it is stored as is in the presenter.
- Else a value holder is created to hold the domain object so that you can be notified when the domain object used by the presenter changes.

You do not need to define the method `setModelBeforeInitialization:` as we previously showed.

Let us revisit our little example. First, we inherit from `SpPresenterWithModel`.

```
SpPresenterWithModel << #MethodListerWithModel
slots: { #list };
package: 'Spec2Book'
slots: { #list };
package: 'Spec2Book'
```

Second, we define `initializePresenters`.

```
MethodListerWithModel >> initializePresenters
list := self newList
list := self newList
```

You can then implement the `modelChanged` method to refresh your UI when the model changes.

```
MethodListerWithModel >> modelChanged
list items: announcingObject selectors sorted
list items: self model selectors sorted
```


Expand All @@ -128,7 +129,9 @@ We define the same layout method as before:
```
MethodListerWithModel >> defaultLayout
^ SpBoxLayout newTopToBottom add: #list; yourself
^ SpBoxLayout newTopToBottom
add: #list;
yourself
```


Expand All @@ -153,7 +156,7 @@ app := SpApplication new
lister := MethodListerWithModel newApplication: app.
```

Then we have is a problem because we want to specify the model too. The correct and idiomatic way is to use the method `newApplication:model:` and the final code version is:
Then we have a problem because we want to specify the model too. The correct and idiomatic way is to use the method `newApplication:model:` so the final code version is:

```
| lister |
Expand All @@ -178,9 +181,9 @@ In the end, it is the presentation model and the UI elements that make up the re
To define a new user interface, the developer should create a subclass of `SpPresenter`.

Fundamentally, it is built around three concerns that materialize themselves as the following three methods in `SpPresenter`:
- the method `initializePresenters` treats the widgets themselves,
- the method `connectPresenters` treats the interactions between widgets, and
- the method `defaultLayout` treats the layout of the widgets.
- The method `initializePresenters` treats the subpresenters themselves.
- The method `connectPresenters` treats the interactions between the subpresenters.
- The method `defaultLayout` treats the layout of the subpresenters.

Hence, these methods are typically found in the model of each user interface.
You can read the code of the small interface presented in Chapter *@chaSmallExample@* to get examples of each of the points we will present now.
Expand All @@ -190,17 +193,17 @@ In this chapter, we describe the finer points of each method and how these three
### The _initializePresenters_ method
@sec_initializeWidgets

The method `initializePresenters` instantiates, holds in instance variables and partially configures the different widgets that will be part of the UI.
The method `initializePresenters` instantiates, holds in instance variables, and partially configures the different widgets that will be part of the UI.

The instantiation of the presentation models will cause the instantiation and initialization of the different lower-level user interface components, constructing the UI that is shown to the user. The first part of the configuration of each widget is specified in `initializePresenters` as well.

The focus of this method is to specify what the widgets will look like and what their self-contained behavior is. The behavior to update the model state, e.g., when pressing a `Save` button, is described in this method as well. It is explicitly _not_ the responsibility of this method to define the interactions _between_ the widgets.

In general, the `initializePresenters` method should follow the pattern:

- widget instantiation
- widget configuration
- specification of focus order
- Widget instantiation
- Widget configuration
- Specification of focus order

The last step is not mandatory since the focus order is by default given by the order of declaration of the subpresenters.

Expand All @@ -211,30 +214,30 @@ The last step is not mandatory since the focus order is by default given by the

The instantiation of a subpresenter (i.e., the model for a widget composing the UI) can be done in two ways: through the use of a creation method or through the use of the `instantiate:` method.

- Considering the first option, the framework provides unary messages for the creation of all basic widgets. The format of these messages is `new[Widget]`, for example, `newButton` creates a button widget, and `newList` creates a list widget. The complete list of available widget creation methods can be found in the class `SpPresenter` in the protocol `widgets`.
- Considering the first option, the framework provides unary messages for the creation of all basic widgets. The format of these messages is `new[Widget]`, for example, `newButton` creates a button widget, and `newList` creates a list widget. The complete list of available widget creation methods can be found in the class `SpPresenter` in the protocol `scripting - widgets`.

- The second option is more general: to reuse a `SpPresenter` subclass (other than the ones handled by the first option) the widget needs to be instantiated using the `instantiate:` method. For example, to reuse a `MessageBrowser` presenter, the code is `self instantiate: MessageBrowser`. The `instantiate:` method has the responsibility to build an internal parent presenter tree.
- The second option is more general: to reuse a `SpPresenter` subclass (other than the ones handled by the first option), the widget needs to be instantiated using the `instantiate:` method. For example, to reuse a `MessageBrowser` presenter, the code is `self instantiate: MessageBrowser`. The `instantiate:` method has the responsibility to build an internal parent presenter tree.


### The _connectPresenters_ method
@sec_connectPresenter

The method `connectPresenters` defines the interactions between the different widgets. By connecting the behaviors of the different widgets it specifies the overall presentation, i.e., how the overall UI responds to interactions by the user. Usually, this method consists of specifications of actions to perform when a certain event is received by a widget. The whole interaction flow of the UI then emerges from the propagation of those events.
The method `connectPresenters` defines the interactions between the different widgets. By connecting the behaviors of the different widgets, it specifies the overall presentation, i.e., how the overall UI responds to interactions by the user. Usually, this method consists of specifications of actions to perform when a certain event is received by a widget. The whole interaction flow of the UI then emerges from the propagation of those events.

**Note.** The method `connectPresenters` is an optional method for a Spec UI, but we recommend to separate this behavior clearly.

In Spec, the different UI models are contained in value holders, and the event mechanism relies on the announcements of these value holders to manage the interactions between widgets.
In Spec, the different UI models are contained in value holders, and the event mechanism relies on the announcements from these value holders to manage the interactions between widgets.

Value holders provide the method `whenChangedDo:` that is used to register a block to perform on change and the method `whenChangedSend: aSelector to: aReceiver` to send a message to a given object. In addition to these primitive methods, the basic widgets provide more specific hooks, e.g., when an item in a list is selected (`whenSelectionChangedDo:`).
Value holders provide the method `whenChangedDo:` that is used to register a block to perform on change, and the method `whenChangedSend: aSelector to: aReceiver` to send a message to a given object. In addition to these primitive methods, the basic widgets provide more specific hooks, e.g., when an item in a list is selected (`whenSelectionChangedDo:`).

### Defining UI Layouts
### The _defaultLayout_ method

@sec_layoutmethod

Widget layout is defined by specifying methods that state how the different widgets that compose a UI are placed. In addition, it also specifies how a widget reacts when the window is resized. As we will see later, these methods can have different names.
Widget layout is defined by specifying methods that state how the different widgets are placed in the UI. In addition, it also specifies how a widget reacts when the window is resized. As we will see later, these methods can have different names.


The method `defaultLayout` is an instance method, but it can be also defined at the class level. Put differently, typically all the instances of the same user interface have the same layout but a layout can be specific to one instance and be dynamic.
The method `defaultLayout` is an instance method, but it can be also defined at the class level. Put differently, typically all the instances of the same user interface have the same layout, but a layout can be specific to one instance and be dynamic.

**Note.** Specifying a layout is mandatory, as without it the UI would show no widgets to the user.

Expand All @@ -245,12 +248,12 @@ We recommend to clearly separate presenter initialization (`initializePresenters

#### Multiple layouts for a widget

For the same UI, multiple layouts can be described, and when the UI is built the use of a specific layout can be indicated. To do this, instead of calling `open` (as we have done until now), use the `openWithLayout:` message with a layout as an argument.
For the same UI, multiple layouts can be described, and when the UI is built, the use of a specific layout can be indicated. To do this, instead of calling `open` (as we have done until now), use the `openWithLayout:` message with a layout as an argument.



### Conclusion

In this chapter, we have given a more detailed description of how the three fundamental methods of Spec: `initializePresenters`, `defaultLayout`, and `connectPresenters` are each responsible for a different aspect of the user interface building process.
In this chapter, we have given a more detailed description of how the three fundamental methods of Spec, `initializePresenters`, `defaultLayout`, and `connectPresenters`, are each responsible for a different aspect of the user interface building process.

Although reuse is fundamental in Spec, we did not explicitly treat it in this chapter. Instead, we refer to the next chapter for more information.
Binary file removed Chapters/ThreePillarsOfSpec/figures/Interactive.png
Binary file not shown.
Binary file modified Chapters/ThreePillarsOfSpec/figures/PointSelectors.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Chapters/ThreePillarsOfSpec/figures/SuperWidget.png
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit 1cb95de

Please sign in to comment.