Seaside: small tutorial

HOWTO: Build a simple Seaside website in 30 minutes

Guest author: Bernat Romagosa Carrasquer

So you have 30 minutes to spare and a website to code. Don't panic, keep reading!

This is a very quick guide to get your first Seaside website running, I'm guessing you've already read both A walk on the Seaside and (at least some of) An Introduction to Seaside, so I'm gonna omit some basic concepts you should already be familiar with by now. Oh, and yes, it's really gonna take 30 minutes.

Our first website is going to have a couple of tabs, a header and some random contents. Just what you need.

FIRST STEP: A root component and some empty "tabs"

Open the class browser, create a new category, name it whatever.

Create a new subclass of WAComponent, name it WhateverRoot:

WAComponent subclass: #WhateverRoot 
instanceVariableNames: 'aCoupleOfTabs selectedTab'
classVariableNames: ''
poolDictionaries: ''
category: 'Whatever'

Now get to the class side and add the following methods:

WhateverRoot class >> canBeRoot

WhateverRoot class >> initialize
(self registerAsApplication: 'whatever')

WhateverRoot class >> description
^ 'A website about whatever'

I'm guessing you already know what the first method is for, the second one just tells your Seaside configuration this is an App [1]. I think the third one is quite self-explanatory.

Alright, we're done with the class side, let's get back to instance and add the following methods:

WhateverRoot >> title
^ 'Whatever Website'

WhateverRoot >> updateRoot: aHtmlRoot
super updateRoot: aHtmlRoot.
aHtmlRoot title: self title

Again, the first method is obvious. The role of the second one is -in a nutshell- to allow you to use CSS styles, JavaScript and other fancy stuff in each one of your components. Actually, I'm afraid it's a little bit more complex than this, but we don't care right now. Let's go on:

WhateverRoot >> states
^ Array with: self

"states" is the method that allows you to use the famous back-button feature. It's up to you to implement it, but you might need it later on, and since we still have a minute to spare we can add it now ;)

Adding the next method is going to ask you what do you want to do with some selectors we haven't defined yet, just tell Seaside to create new classes and click "Yes" to whatever it asks you for.

WhateverRoot >> initialize
super initialize.
aCoupleOfTabs := OrderedCollection new
add: 'Welcome' -> (Array
with: 'Hey this is my website' -> WhateverHey new
with: 'What is this all about' -> WhateverWhat new);
add: 'Nice frameworks' -> (Array
with: 'Seaside' -> WhateverSeaside new);
selectedTab := aCoupleOfTabs first value

You probably guessed it right, we initialize the website by creating an OrderedCollection of tabs, which we assign to our aCoupleOfTabs instance variable. Step by step:

  • This collection contains the names of the tabs we will need in our website.
  • Each of those tabs contain an array of assignations.
  • Each of those assignations link a string (which will be displayed later) to a component (which we will create later on).

How long did that take? 10 minutes? Your duin it purrfect!

SECOND STEP: Actually printing something

We go on adding instance methods to our Root component (WhateverRoot):

WhateverRoot >> renderContentOn: html
self renderHeaderOn: html.
html div id: 'tabs'; with: [self renderTabsOn: html].
self renderChildrenOn: html

This method, as you may know, is called every time we access the root component (that is... pretty much all the time). We've added three renderers, each of which is going to print out a different section of our root page, namely: a header, some tabs, some content.

But wait! We should tell Seaside how to actually render all this:

WhateverRoot >> renderHeaderOn: html
html div id: 'header';
with: [html heading: self title]

WhateverRoot >> renderTabsOn: html
html unorderedList id: 'tabs'; with: [
aCoupleOfTabs do: [ :each |
html listItem: [
html anchor
class: (selectedTab = each value
ifTrue: [ 'active' ]);
callback: [ selectedTab := each value ];
with: each key ] ] ]

WhateverRoot >> renderChildrenOn: html
html div id: 'content'; with: [
selectedTab do: [ :each |
html heading: each key.
html paragraph; render: each value.
html paragraph ] ]

The first method is too short to deserve an explanation. Let's take a quick look at the second one: We are creating an unorderedList (Pay attention! An HTML unorderedList, not a Smalltalk one...) of links (anchors) each of which corresponds to an item of our aCoupleOfTabs collection and setting it to link the selected tab with the instance variable we named after that purpose.

About the third method, you're probably wondering what a child is (seasidely speaking, of course). To put it simple, a child is nothing but a branch of the root component. Our website has a main structure defined in our root component, and as we go navigating through anchors, some parts of the website keep changing. Those changing parts are called children. So, when we select a tab, selectedTab will become a reference to that tab, and renderChildrenOn will print out a heading with that tab's name, its contents and an empty paragraph. It probably looks complicated. It's not. Just wait until the end of the tutorial.

Believe me, there's only 10 minutes left and that's more than enough.

THIRD STEP: Filling the website with info

This is the easiest task of them all, we just need to create a new component for each piece of information (remember WhateverHey, WhateverWhat, WhateverSeaside?). You might have asked Seaside to create them automatically when you accepted the "initialize" method of the Root Component, in that case you'll just need to change their superclasses. Let's go!

WAComponent subclass: #WhateverHey
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Whatever'

WAComponent subclass: #WhateverWhat
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Whatever'

WAComponent subclass: #WhateverSeaside
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Whatever'

And now we should type something into the RenderContentOn method of these new classes:

WhateverHey >> renderContentOn: html
html paragraph: 'Hi, this is my website. It only took me 30 minutes to build it. Cool huh?'

WhateverWhat >> renderContentOn: html
html paragraph: 'This website has no useful content whatsoever...'.
html paragraph: 'I just needed to type some nonsense to see it working :)'.

WhateverSeaside >> renderContentOn: html
html paragraph: 'This framework is very nice because:'.
html orderedList with: [html listItem: 'It is fast';
listItem: 'It is easy';
listItem: 'It is Smalltalk'].


We're done! Go to http://localhost:8080/seaside/config and follow the next steps:

  1. In the "Add entry point" form, "Name" inputfield, type "whatever".
  2. Click "Add".
  3. In the "Configuration" form, choose "WhateverRoot" from the "Root Component" dropdown menu.
  4. Click "Save".
  5. Go to http://localhost:8080/seaside/whatever

Okay, I cheated. I said we were gonna build a website in 30 minutes and we actually did, but we're gonna need like 10 more minutes for an acceptable CSS, which I'm not going to explain because it's pretty easy to read, I know as much CSS or less than you do, and it's definitely out of the matter anyway.

FOURTH STEP: Making it less ugly

CSS can be edited directly online on the fly by toggling halos and clicking on the RootComponent's CSS icon, or by coding it inside a method called style. We're choosing the second way.

WhateverRoot >> style
^ '
background-color: #aeeeee;

width: 100%;
height: 40px;
padding-left: 5px;
background-color: #d7eeee;

font-family: arial;
text-align: justify;
margin-left: 5px;
margin-right: 5px;
padding-left: 5px;
padding-right: 5px;
padding-top: 5px;
padding-bottom: 5px;
background-color: #f7eeee;

list-style-type: none;

#tabs li{
display: inline;

#tabs li a{
text-align: center;
width: 100%;
height: 25px;
color: #000;
background-color: #d7eeee;
margin: 0.5em;
padding: 0.5em;
font-size: 12px;
text-decoration: none
; }

#tabs li a:hover{
font-weight: bold;


Bernat Romagosa Carrasquer

Smalltalk WorkGroup 2009

[1] This method is deprecated in Seaside 2.9, instead we'd use:

WhateverRoot class >> initialize
WAAdmin register: self asApplicationAt: 'whatever'

Thanks to Torsten Bergmann for pointing this out and for other details.

Posted by Bernat Romagosa Carrasquer at 2 April 2009, 10:49 am link