|
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 |
5 | 28 | | number guess | |
6 | 29 | number := 100 atRandom. |
7 | 30 | [ guess := (self request: 'Enter your guess') asNumber. |
|
10 | 33 | guess > number |
11 | 34 | ifTrue: [ self inform: 'Your guess was too high' ]. |
12 | 35 | 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. 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 | + |
| 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 |
17 | 74 | startDate := self call: (WAMiniCalendar new |
18 | 75 | canSelectBlock: [ :date | date > Date today ]). |
19 | 76 | endDate := self call: (WAMiniCalendar new |
20 | 77 | canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]). |
21 | 78 | self inform: 'from ' , startDate asString , ' to ' , |
22 | 79 | endDate asString , ' ' , (endDate - startDate) days asString , |
23 | | - ' days'```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 | + |
| 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 |
24 | 93 | startDate := self call: (WAMiniCalendar new |
25 | 94 | canSelectBlock: [ :date | date > Date today ]; |
26 | 95 | addMessage: 'Select your starting date'; |
|
30 | 99 | addMessage: 'Select your leaving date'; |
31 | 100 | yourself). |
32 | 101 | 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 |
38 | 130 | super initialize. |
39 | 131 | calendar1 := WAMiniCalendar new. |
40 | 132 | calendar1 |
|
43 | 135 | calendar2 := WAMiniCalendar new. |
44 | 136 | calendar2 |
45 | 137 | 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 |
47 | 146 | html heading: 'Starting date'. |
48 | 147 | html render: calendar1. |
49 | 148 | startDate isNil |
|
53 | 152 | (startDate isNil not and: [ endDate isNil not ]) ifTrue: [ |
54 | 153 | html text: (endDate - startDate) days asString , |
55 | 154 | ' days from ' , startDate asString , ' to ' , |
56 | | - endDate asString , ' ' ]```### 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 | + |
| 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