Skip to content

Commit 1a24616

Browse files
committed
all chapters pass done
1 parent 892de08 commit 1a24616

File tree

11 files changed

+2956
-212
lines changed

11 files changed

+2956
-212
lines changed

Chapters/08-Tasks/tasks.md

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
1-
## Tasks@cha:tasks% +Embedding a ContactView into another component.>file://figures/iAddress-ListAndEditor.png|width=80|label=ref:listAndItem+In Seaside, it is possible to define components whose responsibility is to represent the flow of control between existing components. These components are called tasks. In this chapter, we explain how you can define a task. We also show how Seaside supports application control flow by isolating certain paths from others. We will start by presenting a little game, the number guessing game. Then, we will implement two small hotel registration applications using the calendar component to illustrate tasks.### Sequencing ComponentsTasks are used to encapsulate a process or control flow. They do not directly render XHTML, but may do so via the components that they call. Tasks are defined as subclasses of `WATask`, which implements the key method `WATask>>go`, which is invoked as soon as a task is displayed and can call other components.Let's start by building our first example: a number guessing game \(which was one of the first Seaside tutorials\). In this game, the computer selects a random number between 1 and 100 and then proceeds to repeatedly prompt the user for a guess. The computer reports whether the guess is too high or too low. The game ends when the user guesses the number.!!note Those of you who remember learning to program in BASIC will recognise this as one of the common exercises to demonstrate simple user interaction. As you will see below, in Seaside it remains a simple exercise, despite the addition of the web layer. This comes as a stark contrast to other web development frameworks, which would require pages of boilerplate code to deliver such straightforward functionality.We create a subclass of `WATask` and implement the `go` method:```WATask subclass: #GuessingGameTask
2-
instanceVariableNames: ''
3-
classVariableNames: ''
4-
package: 'SeasideBook'``````GuessingGameTask >> go
1+
## Tasks
2+
3+
@cha:tasks
4+
5+
% +Embedding a ContactView into another component.>file://figures/iAddress-ListAndEditor.png|width=80|anchor=ref:listAndItem+
6+
7+
In Seaside, it is possible to define components whose responsibility is to represent the flow of control between existing components. These components are called tasks. In this chapter, we explain how you can define a task. We also show how Seaside supports application control flow by isolating certain paths from others. We will start by presenting a little game, the number guessing game. Then, we will implement two small hotel registration applications using the calendar component to illustrate tasks.
8+
9+
### Sequencing Components
10+
11+
12+
Tasks are used to encapsulate a process or control flow. They do not directly render XHTML, but may do so via the components that they call. Tasks are defined as subclasses of `WATask`, which implements the key method `WATask>>go`, which is invoked as soon as a task is displayed and can call other components.
13+
14+
Let's start by building our first example: a number guessing game \(which was one of the first Seaside tutorials\). In this game, the computer selects a random number between 1 and 100 and then proceeds to repeatedly prompt the user for a guess. The computer reports whether the guess is too high or too low. The game ends when the user guesses the number.
15+
16+
!!note Those of you who remember learning to program in BASIC will recognise this as one of the common exercises to demonstrate simple user interaction. As you will see below, in Seaside it remains a simple exercise, despite the addition of the web layer. This comes as a stark contrast to other web development frameworks, which would require pages of boilerplate code to deliver such straightforward functionality.
17+
18+
We create a subclass of `WATask` and implement the `go` method:
19+
20+
```
21+
WATask << #GuessingGameTask
22+
package: 'SeasideBook'
23+
```
24+
25+
26+
```
27+
GuessingGameTask >> go
528
| number guess |
629
number := 100 atRandom.
730
[ guess := (self request: 'Enter your guess') asNumber.
@@ -10,17 +33,63 @@
1033
guess > number
1134
ifTrue: [ self inform: 'Your guess was too high' ].
1235
guess = number ] whileFalse.
13-
self inform: 'You got it!'```The method `go` randomly draws a number. Then, it asks the user to guess a number and gives feedback depending on the input number. The methods `request:` and `inform:` create components \(`WAInputDialog` and `WAFormDialog`\) on the fly, which are then displayed by the task. Note that unlike the components we've developed previously, this class has no `renderContentOn:` method, just the method `go`. Its purpose is to drive the user through a sequence of steps. Register the application \(as 'guessinggame'\) and give it a go. Figure *@ref:game-interaction@* shows a typical execution. ![Guessing Game interaction.](figures/GuessingGame.png width=80&label=ref:game-interaction)Why not try modifying the game to count the number of guesses that were needed?This example demonstrates that with Seaside you can use plain Smalltalk code \(conditionals, loops, etc.,\) to define the control flow of your application. You do not have to use yet another language or build a scary XML state-machine, as required in other frameworks. In some sense, tasks are simply components that start their life in a callback. Because tasks are indeed components \(`WATask` is a subclass of `WAComponent`\), all of the facilities available to components, such as `call:` and `answer:` messages, are available to tasks as well. This allows you to combine components and tasks, so your `LoginUserComponent` can call a `RegisterNewUserTask`, and so on.!!note Important Tasks do not render themselves. Don't override `renderContentOn:` in your tasks. Their purpose is simply to sequence through other views.!!note Important If you are reusing components in a task -- that is, you store them in instance variables instead of creating new instances in the `go` method -- be sure to return these instances in the #children method so that they are backtracked properly and you get the correct control flow.### Hotel Reservation: Tasks vs. ComponentsTo compare when to use a task or a component, let's build a minimal hotel reservation application using a task and a component with children. Using a task, it is easy to reuse components and build a flow. Here is a small application that illustrates how to do this. We want to ask the user to specify starting and ending reservation dates. We will define a new subclass of `WATask` with two instance variables `startDate` and `endDate` of the selected period.```WATask subclass: #HotelTask
14-
instanceVariableNames: 'startDate endDate'
15-
classVariableNames: ''
16-
package: 'Calendars'```We define a method `go` that will first create a calendar with selectable dates after today, then create a second calendar with selectable days after the one selected during the first interaction, and finally we will display the dates selected as shown in *@ref:hotel@*.```HotelTask >> go
36+
self inform: 'You got it!'
37+
```
38+
39+
40+
41+
The method `go` randomly draws a number. Then, it asks the user to guess a number and gives feedback depending on the input number. The methods `request:` and `inform:` create components \(`WAInputDialog` and `WAFormDialog`\) on the fly, which are then displayed by the task. Note that unlike the components we've developed previously, this class has no `renderContentOn:` method, just the method `go`. Its purpose is to drive the user through a sequence of steps.
42+
43+
Register the application \(as 'guessinggame'\) and give it a go. Figure *@ref:game-interaction@* shows a typical execution.
44+
45+
![Guessing Game interaction. %width=80&anchor=ref:game-interaction](figures/GuessingGame.png )
46+
47+
Why not try modifying the game to count the number of guesses that were needed?
48+
49+
This example demonstrates that with Seaside you can use plain Smalltalk code \(conditionals, loops, etc.,\) to define the control flow of your application. You do not have to use yet another language or build a scary XML state-machine, as required in other frameworks. In some sense, tasks are simply components that start their life in a callback.
50+
51+
Because tasks are indeed components \(`WATask` is a subclass of `WAComponent`\), all of the facilities available to components, such as `call:` and `answer:` messages, are available to tasks as well. This allows you to combine components and tasks, so your `LoginUserComponent` can call a `RegisterNewUserTask`, and so on.
52+
53+
!!note Important Tasks do not render themselves. Don't override `renderContentOn:` in your tasks. Their purpose is simply to sequence through other views.
54+
55+
!!note Important If you are reusing components in a task -- that is, you store them in instance variables instead of creating new instances in the `go` method -- be sure to return these instances in the #children method so that they are backtracked properly and you get the correct control flow.
56+
57+
58+
### Hotel Reservation: Tasks vs. Components
59+
60+
61+
To compare when to use a task or a component, let's build a minimal hotel reservation application using a task and a component with children. Using a task, it is easy to reuse components and build a flow. Here is a small application that illustrates how to do this. We want to ask the user to specify starting and ending reservation dates. We will define a new subclass of `WATask` with two instance variables `startDate` and `endDate` of the selected period.
62+
63+
```
64+
WATask << #HotelTask
65+
slots: { #startDate . #endDate};
66+
package: 'Calendars'
67+
```
68+
69+
70+
We define a method `go` that will first create a calendar with selectable dates after today, then create a second calendar with selectable days after the one selected during the first interaction, and finally we will display the dates selected as shown in Figure *@ref:hotel@*.
71+
72+
```
73+
HotelTask >> go
1774
startDate := self call: (WAMiniCalendar new
1875
canSelectBlock: [ :date | date > Date today ]).
1976
endDate := self call: (WAMiniCalendar new
2077
canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]).
2178
self inform: 'from ' , startDate asString , ' to ' ,
2279
endDate asString , ' ' , (endDate - startDate) days asString ,
23-
' days'```![ A simple reservation based on task.](figures/hotel.png width=80&label=ref:hotel)Note that you could add a confirmation step and loop until the user is OK with his reservation.Now this solution is not satisfying because the user cannot see both calendars while making his selection. Since we can't render components in our task, it's not easy to remedy the situation. We could use the message `addMessage: aString` to add a message to a component but this is still not good enough. This example demonstrates that tasks are about flow and not about presentation.```HotelTask >> go
80+
' days'
81+
```
82+
83+
84+
![ A simple reservation based on task. % width=80&anchor=ref:hotel](figures/hotel.png)
85+
86+
87+
Note that you could add a confirmation step and loop until the user is OK with his reservation.
88+
89+
Now this solution is not satisfying because the user cannot see both calendars while making his selection. Since we can't render components in our task, it's not easy to remedy the situation. We could use the message `addMessage: aString` to add a message to a component but this is still not good enough. This example demonstrates that tasks are about flow and not about presentation.
90+
91+
```
92+
HotelTask >> go
2493
startDate := self call: (WAMiniCalendar new
2594
canSelectBlock: [ :date | date > Date today ];
2695
addMessage: 'Select your starting date';
@@ -30,11 +99,34 @@
3099
addMessage: 'Select your leaving date';
31100
yourself).
32101
self inform: (endDate - startDate) days asString , ' days: from ' ,
33-
startDate asString , ' to ' , endDate asString , ' '```### Mini Inn: Embedding ComponentsLet's solve the same problem using component embedding. We define a component with two calendars and two dates. The idea is that we want to always have the two mini-calendars visible on the same page and provide some feedback to the user as shown by *@ref:resa@*.```WAComponent subclass: #MiniInn
34-
instanceVariableNames: 'calendar1 calendar2 startDate endDate'
35-
classVariableNames: ''
36-
package: 'Calendars'```Since we want to show the two calendars on the same page we return them as children.```MiniInn >> children
37-
^ Array with: calendar1 with: calendar2```We initialize the calendars and make sure that we store the results of their answers.```MiniInn >> initialize
102+
startDate asString , ' to ' , endDate asString , ' '
103+
```
104+
105+
106+
### Mini Inn: Embedding Components
107+
108+
109+
Let's solve the same problem using component embedding. We define a component with two calendars and two dates. The idea is that we want to always have the two mini-calendars visible on the same page and provide some feedback to the user as shown by *@ref:resa@*.
110+
111+
```
112+
WAComponent << #MiniInn
113+
slots: { #calendar1 . #calendar2 . #startDate . #endDate};
114+
package: 'Calendars'
115+
```
116+
117+
118+
Since we want to show the two calendars on the same page we return them as children.
119+
120+
```
121+
MiniInn >> children
122+
^ Array with: calendar1 with: calendar2
123+
```
124+
125+
126+
We initialize the calendars and make sure that we store the results of their answers.
127+
128+
```
129+
MiniInn >> initialize
38130
super initialize.
39131
calendar1 := WAMiniCalendar new.
40132
calendar1
@@ -43,7 +135,14 @@
43135
calendar2 := WAMiniCalendar new.
44136
calendar2
45137
canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ];
46-
onAnswer: [ :date | endDate := date ]```Finally, we render the application, and this time we can provide some simple feedback to the user. The feedback is simple but this is just to illustrate our point.```MiniInn >> renderContentOn: html
138+
onAnswer: [ :date | endDate := date ]
139+
```
140+
141+
142+
Finally, we render the application, and this time we can provide some simple feedback to the user. The feedback is simple but this is just to illustrate our point.
143+
144+
```
145+
MiniInn >> renderContentOn: html
47146
html heading: 'Starting date'.
48147
html render: calendar1.
49148
startDate isNil
@@ -53,4 +152,17 @@
53152
(startDate isNil not and: [ endDate isNil not ]) ifTrue: [
54153
html text: (endDate - startDate) days asString ,
55154
' days from ' , startDate asString , ' to ' ,
56-
endDate asString , ' ' ]```![A simple reservation with feedback.](figures/resa.png width=80&label=ref:resa)### SummaryIn this chapter, we presented tasks, subclasses of Task. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo.
155+
endDate asString , ' ' ]
156+
```
157+
158+
159+
![A simple reservation with feedback. % width=80&anchor=ref:resa](figures/resa.png)
160+
161+
### Summary
162+
163+
In this chapter, we presented tasks, subclasses of `Task`. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo.
164+
165+
166+
167+
168+

0 commit comments

Comments
 (0)