One of the cool features back when Seaside saw the light of day were Continuations and the great things you could do with them in a web Application. The most prominent methods in this context were call: and answer: . Being a VA Smalltalk user, I still cannot use Continuations. Over time this has shown to be far less of a serious limitation as it might seem. Continuations can be very handy, but the way early Seaside adoptions used them were more or less cool but felt strange in comparison to mainstream web technologies (you can read more about this on an older post on this blog).
A false assumption in that context is that the absence of Continuation would make the use of WATask subclasses impossible. I fell hostage to that assumption for quite a while and sometimes thought: “well, this would be a great use case for Tasks. It’s such a shame I have to do it differently”. I knew how to replace call:/answer: with show:onAnswer: but it took me a while to transfer this knowledge to WATask. Today, I don’t understand why it didn’t occur to me for such a long time that the principle of replacing call:/answer: with show:onAnswer: is fully applicable to WATask. It so clear and natural, but I couldn’t see it.
So let’s take a short look at my first post back in 2011 where I showed the differences between call: and show: in detail before I continue with today’s topic.
Okay, now that we all know what I am talking about, let’s look into ways to apply that very same principle to WATask.
What is WATask good for anyways?
Well, WATask is very useful for adding logic to your application that somewhat forms the glue between your WAComponents. If you need to check some pre-Conditions before a certain Component is shown, or to make decisions based on the outcome of one component and decide which component to show next based on that, then WATask is the thing to use. Most of the times, the logic that neither fits into the previous component nor the next one belongs into a task.
So let’s say you have one component that presents a form for entering a user’s address. You may want to use it in many contexts, sometimes as an enabled form, sometimes just to display the address intonation. In some places, like if you want to generate an invoice for that person, you need to make sure the address entered is valid. In others, this is not important or would even make using your application useless (what if a disabled form complained about a missing street name? your user would have a hard time doing much about that). Then, there may be cases where you need to make sure that the address is valid just before some other component can be shown. Here is where WATask is your best candidate.
If you read the Seaside documentation about WATask, you will see lots of call: in the implementation if #go. But that doesn’t work in VA. So what can we do about it?
The answer is simple: use show:onAnswer: instead and follow the rule of thumb from my older post. The most important being that you cannot assume that show:onAnswer: dives into that other component before the current method continues. Just remember: show: just adds a decoration to caller and whatever comes after show: will simply be executed. No magic.
So how does that translate to WATask? Split the usual implementation of go into several methods and pass the “next” method to be executed after the delegate has finished in the onAnswer:-block.
Let me show you a little example that I borrowed from the Seaside Book:
HotelTask>>go startDate := self call: (WAMiniCalendar new canSelectBlock: [ :date | date > Date today ]; addMessage: 'Select your starting date'; yourself). endDate := self call: (WAMiniCalendar new canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]; addMessage: 'Select your leaving date'; yourself).
If you want to implement the same thing Continuation-Passing style, the solution might look like this:
HotelTask>>go self show:(WAMiniCalendar new canSelectBlock: [ :date | date > Date today ]; addMessage: 'Select your starting date'; yourself) onAnswer: [:strt| startDate := strt. self show: (WAMiniCalendar new canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]; addMessage: 'Select your leaving date'; yourself) onAnswer: [:end| endDate := end].
I must admit that the implementation without Continuations lacks the cleanness of the original. But with a little extraction of methods and some getting used to Continuation passing style, it’s far less ugly than you might think in the first place. Using ifTrue:ifFalse: in onAnswer: blocks and calls to the method itself again based on the answer, you can even simulate isolate: and return to the same component over and over again until either the user enters what you want her to, or she navigates somewhere else. Maybe like this:
HotelTask>>requestDate: aMessage andContinueWith: aBlock self show:(WAMiniCalendar new canSelectBlock: [ :date | date > Date today ]; addMessage: 'Select your starting date'; yourself) onAnswer: [:date| date isNil ifTrue: [^self requestDate: aMessage andContinueWith: aBlock]]. HotelTask>>go self requestDate: 'Select your starting date' andContinueWith: [:strt| startDate := strt. self requestDate: 'Select your leaving date' andContinueWith: [:end| endDate := end. self answer: ... ].
…And what about isolate:?
Well, yes, isolate: . This was a cool thing. You could force a block of code to not let the user navigate any further before they’ve entered certain info. The funny (or sad) truth is: isolate: is gone from Seaside anyways due to some internal implementation details. So forget about isolate: you couldn’t use it in plain Seaside as well 😉
Well, this post may not be special for you. Either because you use Continuations on your Smalltalk platform or because you already use this trick. In both cases: sorry for stealing your time. In all others: I hope I could help a little and give some inspiration. If you don’t understand a single word of what I am talking about here, I guess there’s no use in apologizing here, because you’ve most probably navigated to more interesting sites many, many lines before this one 😉