Our first Seaside component will simply display "Hello
world". Begin by subclassing WAComponent:
WAComponent subclass: #HelloWorldComponent instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'SCSeasideTutorial'We will talk about
WAComponent throughout this tutorial.
For now, just think of subclasses of WAComponent as
"visual components". When its time for a component to be
displayed Seaside sends it the message renderContentOn:
with an WAHtmlRenderer
(the "renderer") as the argument. Think of the renderer as
the canvas on which you will paint your component. It provides a
rather transparent interface to XHTML while making it easy to produce
text, anchors, forms etc in a modular way. We simply want to render
some text so we'll use the renderers text: method:
renderContentOn: html html text: 'Hello world'Great, we have a component but how do we get Seaside to serve it? For now let's just use a class side method in our superclass...evaluate the following code in a workspace:
HelloWorldComponent registerAsApplication: 'hello'This tells Seaside to register our component with a URI path of
/seaside/hello. Point your web browser at http://localhost:9090/seaside/hello.
Your browser should display this no frills greeting:
renderContentOn:. The HTML painted onto the renderer is
then returned to the web browser to be displayed. You will probably
never need to send your component the message
renderContentOn: since the Seaside framework takes care
of that for you. When its time to paint your component, Seaside sends
it renderContentOn:. This is very similar to models used
in most GUI frameworks where a component (or window) is told to paint
itself whenever the windowing systems deems necessary. Keep this in
mind as you work with Seaside: your rendering method is just for
painting the current state of your component, it shouldn't be
concerned with changing that state.
Modify your renderContentOn: to include some html such
as html text: '<h1>Hello
world</h1>'. Refresh your browser and note that this did
not have the desired effect. This is actually a good thing. The
text: method takes care of translating any characters
that are considered special in HTML so that your strings are displayed
correctly. If you really want to generate HTML directly you
can use the html: method instead of text:.
I recommend against that practice. The renderer's API for html
generation is much easier to use and refactor than manually coded
HTML.
OK, so how do we get headings? We can generate the various heading
tags (H1, H2 etc) using
heading:level:. Let's also add a paragraph:
renderContentOn: html html heading: 'Hello world' level: 1. html paragraph: 'Welcome to my Seaside web site. In the future you will find all sorts of applications here such as: calendars, todo lists, shopping carts etc'
renderContentOn: html html heading: 'Hello world' level: 1. html paragraph: 'Welcome to my Seaside web site. In the future you will find all sorts of applications here such as:'. html orderedList: [html listItem: 'Calendars'. html listItem: 'Todo lists'. html listItem: 'Shopping carts'. html listItem: 'And lots more...'].Note that
orderedList: takes a block (actually, a Renderable). It places HTML <OL> and
</OL> markup around whatever HTML is generated in the block.
Each send of listItem: takes a String (again, actually a
Renderable) and places that String inside
<LI> and </LI> markup. We could also have used
unorderedList:. How about a table of "expected
delivery dates":
renderContentOn: html html heading: 'Hello world' level: 1. html paragraph: 'Welcome to my Seaside web site. In the future you will find all sorts of applications here such as:'. html table: [html tableRow: [html tableData: 'Calendars'. html tableData: '1/1/2006'. html tableData: 'Track events, holidays etc']. html tableRow: [html tableData: 'Todo lists'. html tableData: '5/1/2006'. html tableData: 'Keep track of all the things you have to remember to do.']. html tableRow: [html tableData: 'Shopping carts'. html tableData: '8/1/2006'. html tableData: 'Enable your customers to shop online.']]This is very similar to the list example in terms of how it marks up the text.
orderedList:do: and
unorderedList:do: methods in
WAAbstractHtmlBuilder. For examples just browse the
senders of those selectors. Modify our example to use one of
these methods.renderOn:,
as an argument. In response to messages like
orderedList: the renderer generates a start tag, renders
the argument and generates an end tag. In the case where all you want
is text between the tags you can use a String as your renderable
component. Quite often you want to render other objects inside the
tags (like the list items in our orderedList: example
above). In those cases you can use a zero or one argument block as
the renderable. The following two rendering methods produce the same
result:
renderContentOn: html html paragraph: 'Hello'.
renderContentOn: html html paragraph: [html text: 'Hello'].The first form is shorter but you can't generate other HTML inside the paragraph. The second form gives us the ability to generate additional HTML between the paragraph's start and end tags:
renderContentOn: html html paragraph: [html text: 'Hello'. html space. html bold: 'users'].Browse implementers of the
renderOn: method to see what
renderable objects are already in your image.
TABLE tag
accepts a parameter called border whose value is the
width of the border in pixels. We specify tag attributes by sending
attributeAt:put: to the renderer. It will store all
attributes and use them when it renders the next tag. After they are
used to render a tag, all attributes are discarded. Modify the method
above to read:
renderContentOn: html html heading: 'Hello world' level: 1. html paragraph: 'Welcome to my Seaside web site. In the future you will find all sorts of applications here such as:'. html attributeAt: 'border' put: 1. html table: [html tableRow: [html tableData: 'Calendars'. html tableData: '1/1/2006'. html tableData: 'Track events, holidays etc']. html tableRow: [html tableData: 'Todo lists'. html tableData: '5/1/2006'. html tableData: 'Keep track of all the things you have to remember to do.']. html tableRow: [html tableData: 'Shopping carts'. html tableData: '8/1/2006'. html tableData: 'Enable your customers to shop online.']]Notice the
border tag attribute is only applied to the
table tag generated. Setting too many attributes is
tedius and obscures code. There are several simple practices to keep
your renderContentOn: method readble. We will see these
as we progress to more realistic examples but basically it boils down
to using CSS style information as much as possible.


style method and using style
libraries.1 We will look at the former and
save the latter for, well, later. Let's start by adding a
style method to center the heading:
style
^'h1 {text-align: center;}'
Now, refresh your browser and you should see a centered "Hello
world". (If your browser is not CSS2 compliant you may not see
centered text.) Bring up the halo on this component and click the
paint can. Notice that you can edit the component's style method
here. If you save your changes then the component's style method will
be updated. This might be useful if you have graphic designers who
control the layout and look of your site but don't know anything about
Smalltalk or Squeak. I personally use a static CSS file for my sites'
static and dynamic content.
If you've used CSS regularly then you're already familiar with using div (block elements) and span (inline elements) with the class attribute to help you select specific parts of a document for stylization. Here's how one would, for example, give a red background to error text:
renderContentOn: html
.... existing code (from above) ....
html spanClass: 'error' with: 'This site doesn''t work yet'
style
^'
h1 {text-align: center;}
span.error {background-color: red;}
'
There is also a divClass:with: method for producing HTML
DIV's.
This tutorial is not about XHTML or CSS so I'll leave it to you to figure out how to obtain the layout and design of a component. I provide some references at the bottom of this page. I do have a few hints:
SPAN and DIV with CSS classes or
IDs to mark content generated by your components.
Quite often components render themselves entirely within a
DIV whose CSS class is the name of the component's
Smalltalk class. This makes it easier for CSS developers to locate
the HTML elements that they want to stylize. Come up with
conventions that work for you and stick to them!renderContentOn: method. This will make it difficult
to customize the way in which your component is displayed.style methods like the one above only
when the style elements are very specific to that component.
Otherwise use style libraries (discussed later).break -- generates brhorizontalRule -- generates hrdiv:span:bold:italics:emphasis:image: -- takes a String URI as an argumentbr but sending the renderer the message br
will cause it to generate the <br /> tag. In the case of the
one argument method, the argument must be a renderable object and it
will be rendered between the open and close tag as usual.
WAComponent) to display a web page of your own design.
Don't worry about anchors etc...just stick to simple text and images
stylized with CSS. Be sure to register your component with Seaside
so that you can view it. Remember we configured your server to
serve static content from the FileRoot directory so an
image URI like /myPicture.jpg would be found in
FileRoot/myPicture.jpg