Ron and I have been doing some Smalltalk lately. He has been writing about coding the bowling game as an exercise to help get our chops back, so I thought I should do something to help us catch up with the current state of Smalltalk development.

Since one of the applications we are looking at doing will have a web interface, I thought I would look at Seaside. Seaside is is a set of Smalltalk classes that turns your image into a stateful web server. Seaside hides the icky HTML request-response cycle and lets us view user interaction much as we would in a fat client application. In addition to handling all the parallelism and backtracking expected by a modern web browser, Seaside will generate the html from a simple Smalltalk description. It even supports CSS.

Just to make things easy, I asked Ron to send me the latest version of the bowling game. He sent me a couple of versions which I brought into my Smalltalk image and after running the unit tests (they were green) I am now ready to go to work.

I guess the first thing to do is gather some requirements. I could build something that looks like a bowling scoresheet, but to do that I would have to know what a bowling scoresheet looks like. Frankly, that is a lot of work for a Sunday afternoon. How about if we start with an input box for the scores, a button to calculate, and a box for the result. Ugly, but simple.

As you learned from Ron’s articles, I will first have to create a new package, I think I will call it #SeasideExperiment. After looking at the Seaside example, I see that in order to make things work our app will need to be subclassed from #WAComponent. I think I will call it #BowlingScoreCalculator. I will most likely need some instance variables, but I don’t know that those are yet so I think I will burn the 3/5 of a second it will take to complile. Here is the code so far.

Smalltalk defineClass: #BowlingScoreCalculator
    superclass: #{Seaside.WAComponent}
    indexedType: #none
    private: false
    instanceVariableNames: ''
    classInstanceVariableNames: ''
    imports: ''
    category: 'SeasideExperiment'

#WAComponent will give our new class the ability to render itself as html and to have that html served by Seaside. The first thing I will need to do is implement a class-side #initialize method.

initialize
    (self registerAsApplication: 'bowling')

With this in place, Seaside now knows to fire up our class when someone asks for ‘bowling’. I guess the next thing to do is teach our class how to render itself into html. That is done in the #renderContentsOn: method. This method reminds me of the #printOn: method. In his artcles, Ron made often made use of the ‘Print It’ feature of the VisualWorks IDE. When you run ‘Print It’ on an object the text that is displayed is determined by that object’s #printOn: method. The #printOn: method is passed a Stream object, which it can write on and even pass to other objects’ #printOn: methods. #renderContentsOn: is passed a WAHtmlRenderer which we will use to describe how we want our web page to look.

To start, I am going to make sure things work.

renderContentOn: html
    html heading: 'Bowling Game'.
    html horizontalRule.
    html text: 'Hello World'

I am guessing that #heading: will wrap ‘Bowling Game’ in H1 tags, #text: should put out ‘Hello World’ in normal text, and I would be very surprised it #horizontalRule did anything but draw a line across the page.

The URL for our app should be ‘https://localhost:8008/seaside/go/bowling’.

Well that didn’t work. The exception said that it could not find my page. I had better look around and see what I have missed.

Ahha, I need to register the app with Seaside at https://localhost:8008/seaside/go/config. Pretty simple (I hope). Let’s try now.

image

Success.

That’s good, but not very interesting or useful. I now need to add a place to enter the scores and a button to trigger the calculation. And, a place for the answer. Back to #renderContentOn:.

After poking around in the #WAHtmlRenderer class, I hit on #textInputWithCallback: as the most likely suspect for getting the scores. It takes a block as an argument that I will use to set the received input into an instance variable called ‘scores’. When I accept the method I am asked about the unknown ‘scores’, so I tell Smalltalk to make it an instance variable. Here is what we have so far.

renderContentOn: html
    html heading: 'Bowling Game'.
    html horizontalRule.
    html textInputWithCallback: [:v | scores := v].

Let’s see what happens.

image

Looks like a text box all right.

Now for the button. I am going to try #submitButtonWithAction:text:. The first argument is a block containing what the button should do and the second is the button’s label. I am going to have the button call #calculateScore.

renderContentOn: html
    html heading: 'Bowling Game'.
    html horizontalRule.
    html textInputWithCallback: [:v | scores := v].
    html submitButtonWithAction: [self calculateScore] 
        text: 'Calculate'.

image

Before I push the button I had better code up #calculateScore. I think I will display the score on another page. Seaside has a very easy way of doing that.

calculateScore
    self inform: 'The score is 200'

Nothing happens when I push the calculate button. I am guessing that the problem is the callback stuff on the input field. I will comment that out and see what happens. That wasn’t it.

I did a little poking around in the Seaside sample and added an #initialize method.

initialize
    self session registerObjectForBacktracking: self

This didn’t do the trick, then I saw that I had wrapped the stuff in the #renderContentOn: method in a #form: method. When I did that in the new code things started working.

renderContentOn: html
    html form: [
        html heading: 'Bowling Game'.
        html horizontalRule.
        html textInputWithCallback: [:v | scores := v].
        html 
            submitButtonWithAction: [self  calculateScore] 
            text: 'Calculate'.
    ]

Now when I push the the ‘Calculate’ button I get the new page.

image

I think it is finally time to hook our code up to Ron’s bowling game. The various versions Ron sent me all have the same interface. You pass the scores in one at a time using the #roll: method and return the score via the #score method. Since they all passed their tests, I am going to use the one called #BowlingGame.

Now I need to improve the #calculateScore method to pull the individual scores out of the scores instance variable, pass them one at a time to an instance of #BowlingGame and then feed the result of the #score method to our call to #inform.

calculateScore
    | game |
    game := BowlingGame new.
    self scores do: [:each | game roll: each].
    self inform: 'The score is ' , game score printString

This should work. I will create a new instance of the BowlingGame, feed in each of the scores from the web page and then put out a new page with a little text and the score. The only tricky bit will be turning the string of digits and spaces into a collection of integers. Let’s see how #scores does that.

scores
    | scoresCollection stream |
    scoresCollection := OrderedCollection new.
    stream := ReadStream on: scores.
    [stream skipSeparators.
    stream atEnd] whileFalse: [scoresCollection add:  (stream upToSeparator) asInteger].
    ^scoresCollection

After declaring a couple of temps I initialize one as an OrderedCollection. That is where I will store the integers. The other I make a ReadStream with the string from the web page. Making it a stream will allow us to do some pretty neat stuff. The result of which will be the passing of the one or two characters representing each score into the block which will convert them to an integer, which is then added to the Ordered Collection. When we have done, the collection gets passed back. Easy and neat.

Let’s see if it all works.

Here is the first page with the scores entered.

image

We push the Calculate button and voila.

image

There we are. After just a little screwing around we have been able to add a very (and I mean very) simple web interface to our bowling game. For those of us that think Smalltalk is a pretty cool language, this means we can build web-based apps without having leaving our image. We can have all of our code in one place. There are still some things to learn. We need to build some more complex pages, ones with cascading style sheets, and we need to figure out how to embed javascript. But, this was a good start.