Rendering basics

David Shaffer
Shaffer Consulting

Rendering plain text: Hello world

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:

Grossly simplified: When we request this URL Seaside creates a new instance of our class for us and then sends it 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'

Rendering lists and tables

Let's modify our component to display a list of the site contents. Here's the relevant code:
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.

Exercises

  1. Figure out how to render HTML "table heading" tags. Modify the last example to include a heading for each row.
  2. Look at the 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.

Renderable objects

Many of the renderer's methods that generate tags accept any type of renderable object, an object which implements 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.

Tag attributes

Our table example above looks ugly in most browsers since the default table has no borders. We know that the HTML 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.

The halo

If you tried to use your web browser to view the HTML source for the document above you'll find that it is not particularly readable since it is not formatted for human readers (no linefeeds, indentation etc). Not to worry, Seaside has a great tool called the halo that can be used to get to a display of nicely formatted HTML source code and do much more as well. At the bottom of your web browser's window you should see a tool bar:


Click the link "Toggle Halos" and notice that a halo appears around your component's visual representation:


The "R" and "S" links stand for "Render" and "Source" respectively. Right now you're looking at the rendered result. To see the HTML for this component switch to the source view by clicking the "S" link. The icons on the left side of the halo are very useful as well. The first one (the one that looks like a wrench) opens a class browser on that component in your web browser. Try it. The second one (the eye) opens an inspector on your component. Again, try it. The third (the paint can) opens a style editor on your component. We'll use this one in a couple minutes. DESCRIBE HALO ELEMENTS IN A TABLE FOR QUICKER READING.

Style sheets

There are two standard ways to generate CSS style sheets in Seaside: through the component's 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:

Other renderer methods

Here are some other methods in the renderer which have fairly obvious results: Just browse the code to find more. Also, when the renderer doesn't understand a zero or one argument message it generates tags based on the message selector. For example there is no implementation of br 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.

Exercises

  1. Create a new Seaside component (new subclass of 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

Further reading

Most of what you've learned here is about generating HTML/CSS from Seaside. If you're not an experienced web developer the next step is to figure out how to make things look the way you want (or even to learn how to think about color and layout in a page). I have found the following documents to be helpful in sorting out HTML/CSS problems: There are many CSS resources on the web. I've accumlated a long list but here are a few that stand out as sites that I repeatedly visited:

Footnotes

1. Seaside applications can also refer to external style sheets. I generally prefer this method when I deploy while using "style libraries" during development.
C. David Shaffer
Last modified: Sat Jul 9 14:12:51 EDT 2005