Seaside is an open source web application framework written in Smalltalk.
As of the Seaside 2.5 release (which was beta as of 2004 Jan 08), Seaside has been under the MIT license. This means that you can use it to build commercial apps, royalty free, with no restrictions. Note that, besides Squeak, this also applies to commercial Smalltalks such as VisualWorks and Dolphin Smalltalk.
See more details on the Seaside license page.
The Seaside Download Pagedescribes the installation procedure and links to prebuilt images.
Starting with version 7.2.1, Seaside is shipped on the VisualWorks distribution CD.
Seaside can be loaded in the VisualWorks image using the Parcel Manager, section "Application Server" where you should be able to see the SeasideForWebToolkit parcel. There is some installation documentation in the parcel comments.
Seaside can also be loaded from the public Store repository where either of the following bundles can be loaded:
Usually, loading from the repository may give you a more recent version of Seaside, also it gives the choice of loading older versions of Seaside. Since the repository is used to build the Seaside ports, it is possible to get an unstable version, therefore it is recommended to load Seaside from your hard disk using the Parcel Manager, unless you want to experiment with newer versions of Seaside or if you are using a version of VisualWorks older than 7.2.1.
After the VisualWorks installation, Seaside is located in the goodies/contributed/Seaside directory. More items can be found in this directory using the File Browser or the Parcel Manager, like an HTML Seaside introductory tutorial and the original Squeak code.
Since VisualWorks 7.3 Seaside comes with even more items that can be found in this directory:
See the original maling list post .
Normally, the transition from one page to another is split into two request- response cycles. The first triggers the callbacks associated with the link or form elements, and Seaside sends back a 302 response, redirecting the browser to another url. The second request triggers no callbacks and is a "pure" HTTP GET. Seaside sends back an html representation of the page in its new state. That page can be reloaded as much as you like with no side effects.
This is the default case for anchors and form in GET mode. For anchors this can be changed to a single GET request that triggers the callbacks and renders the response by sending #navigation to the anchor tag.
See original post in the mailinglist.
The basic problem that Canvas is trying to solve is that of combinatorial explosion in the protocol of WAHtmlRenderer. Consider something like a text input. The original, basic method for rendering a text input was #textInputWithValue:callback:
html textInputWithValue: person name callback: [:v | person name: v]
But there's also a variation using the ...On:of: pattern:
html textInputOn: #name of: person
And a variation with a liveUpdate callback:
html
textInputWithValue: person name
callback: [:v | person name: v]
liveCallback: [:r :v | ...]
Now what if we want to use both of these variations at once? Do we also need #textInputOn:of:liveCallback:? What about #passwordInputOn:of:liveCallback:? And so on. It's relatively easy to add new convenience methods, but it's a huge pain to combine them, even when (as in this case) they're totally orthogonal.
The Canvas renderer is just a slight change to the API style that makes this kind of combination much easier. It divides what would have been a single method on HtmlRenderer into three steps: first, you tell the canvas what kind of "brush" (tag, usually) you want to use. For example, the method #textInput will return a new instance of WATextInputTag. Then, you use protocol specific to that brush to configure it: for example, you might send it some combination of #value:, #callback:, #liveCallback:, #on:of:, etc. You almost never need to use temps when doing this, but can do it with cascades instead:
html textInput
value: person name;
callback: [:v | person name: v]
or
html textInput on: #name of: person
or
html textInput
on: #name of: person;
liveCallback: [:r :n | ....]
The third step is to render any contents or children (if any) of this tag. You do this by passing some renderable (often a block) to the method #with:. A text input wouldn't have any, but consider something like a table:
html tableRow
rowSpan: 3;
with:
[html tableData with: [html bold with: person name].
(html tableData) colSpan: 2; with: [....]]
Note that instead of using #attributeAt:put: before the element, as in HtmlRenderer, attributes are set as part of the configuration step - and so classes like WATableRow can implement convenience methods for common attributes like #rowSpan: as needed.
If no configuration is needed, you can combine the first and third steps by using a keyword message instead of a unary message to specify the brush type, which is more compact:
html tableRow
rowSpan: 3;
with:
[html tableData: [html bold: person name].
html tableData ....]
This style of API still allows streaming - the HTML for the open tag is generated as soon as #with: is sent. If #with: is never sent, it"s triggered automatically (with an empty block) whenever the next tag is started. That means #with: does have to be the last thing you send, any configuration done afterwards will have no effect.
Sometimes a temp is handy:
myDiv := html div.
self useSpecialId ifTrue: [myDiv id: "specialId"].
myDiv with: [html text: ....]
But bear in mind that these are basically temporary objects - it won't do you any good to stash them in an ivar and try to use them later, for example.
The Canvas implementation in recent 2.6a versions is relatively complete (we're using it on one of the projects I'm working on), but there are no doubt still lots of holes to patch up and conveniences to add, so feel free to pitch in.
Avi Bryant
Mon Jun 20 17:18:45 CEST 2005
See original post in the mailing list
Hi
Now that both Seaside 2.7a1 and 2.6b1 have the "new" header API i think its time to write something short about it so people don't have to ask.
The old header API was suffering from the same problems as the old renderer API. Just to add an stylesheet with media and title you had to copy and paste about 4 methods. Addional 4 for alternate stylesheets. If you wanted a new conditional import you needed to copy and paste a 3 LOC method. And so on. So we followed the same path like the Canvas API and made use of cascades.
The new API offers four base methods to create elements
Most of the time will want to use one of these two convenience methods:
See the classes WABaseElement, WAConditionalComment, WALinkElement, WAMetaElement and WAScriptElement for details
examples:
aRoot stylesheet
url: 'styles/blue.css'
aRoot stylesheet
document: 'a { color:red; }'
aRoot javascript
url: 'scripts/scriptaculous.css
aRoot if lessThan; ie7; do: [
aRoot javascripts
url: 'iehacks.js' ]
stuff that was just awkward to do with the old api:
aRoot link beAlternateStylesheet; beCss;
addProjection; addPrint;
title: 'Blue';
url: 'styles/blue.css'
Contrary to the Canvas renderer API the new header API in the background still used the old implementation. So you will have to implement no methods like #rendererClass.
If you use the old API on Seaside 2.7a1 and have the development toolbar on you will get a warning pointing to where you use the old API. These methods include the code that you should replace yours with.
Philippe
For many different reasons Seaside is broken into a complex and cumbersome bundle/package structure.
At a higher level, Seaside is broken into three bundles :
The first bundle Seaside-VW contains pre-requisites and is actually preparing VisualWorks for Seaside. This bundle also has a complex structure. See below for further details.
The second bundle Seaside contains the original Seaside code unaltered from Squeak. The structure of this bundle is derived from the corresponding Seaside Monticello package. No further details are provided here.
The third bundle Seaside-WebToolKit contains the integration code for running Seaside inside the WebToolkit framework, along with some patches to both Seaside and WebToolkit. See below for more information.
Note: There exists also a Seaside-Swazoo bundle that contains the integration code for running Seaside inside the Swazoo framework. This is described below in more details.
One-click loading: Loading Seaside means loading the three bundles in the order above, making sure the bundle versions are compatible. However to provide one-click loading, there is another bundle named SeasideForWebToolkit that contains a script that loads the three bundles programmatically.
The contents of the Seaside-VW bundle are :
The contents of the Seaside-WebToolKit bundle are :
The contents of the Seaside-Swazoo bundle are :
Set the value of "Deployment Mode" to false using the dispatcher editor ("config").
This can be also done programatically in the #initialize method of the application's root component.
initialize
| app |
app := self registerAsApplication: 'app-name'.
app preferenceAt: #deploymentMode put: true.
The most convenient way to serve files like pictures or stylesheets from a Seaside application is WAFileLibrary. See the class comment of WAFileLibrary for more information about how to use it.
The following assumes that you have defined MyFileLibrary as explained in the class comment.
First you will need to setup the root of your library. Typically this can be returned by a method.
library
^MyFileLibrary
To display an image
html image url:
"myPicturePng is the method in which the picture is stored"
(self library urlOf: #myPicturePng).
To display an image button (within a form)
html imageButton
url: (self library urlOf: #myButton.gif);
callback: [self commit].
To add a stylesheet and a javascript
updateRoot: anHtmlRoot
super updateRoot: anHtmlRoot.
anHtmlRoot stylesheet url:
(self library urlOf: #myStyle.css).
anHtmlRoot javascript url:
(self library urlOf: #myJavascript.js).
Advanced users might want to use Apache 2.
When a Seaside session is not used during a certain duration, it expires. After a session has expired, it cannot be used any more.
By default, this duration is 10 minutes (600 seconds) but it can be set to any other desired value using the "config" application ("Session Expiry" field). It can also be set programmatically
app preferenceAt: #sessionExpirySeconds put: 1200
From time to time, Seaside processes expired sessions (see #unregisterExpiredHandlers). Expired sessions are removed from Seaside, making them candidates for future garbage collections, then they are sent #unregistered. The standard implementation of #unregistered in WASession is empty.
If it is necessary to release resources held by an expired Seaside session, this can be done by subclassing WASession and by reimplementing the #unregistered method.
It is sometimes necessary to subclass WASession. Typical situations for subclassing WASession.
Seaside applications are assigned their session class using the "config" application ("Session Class" field). It can also be set programmatically
app preferenceAt: #sessionClass put: MySession
It is sometimes necessary to subclass WAApplication, for instance when it is needed to display a session expiration page.
When you subclass WAApplication, make sure that the subclass reimplements #description on the class side, this will ensure that you can differentiate the WAApplication subclasses in the "config" application. Once the WAApplication is in place you can use the "config" application to create your Seaside application.
New Seaside applications can also be created programmatically by carefully reimplementing #applicationWithPath: on the class side of your root component.
It is sometimes necessary to know the IP address of the requester. For instance you may want to produce a different behavior when the request comes from "localhost".
The IP address is normally available from the native request object. The native request object is obtained by sending #nativeRequest to the current Seaside request object, like this:
renderContentOn: html
nativeRequest := self session currentRequest nativeRequest.
The native request object will be a Kom request, a Swazoo request or a WebToolkit request depending on the exact environment.
For instance, with Squeak
ipAddress := nativeRequest remoteAddress.
When the user wants to reuse an expired Seaside session, Seaside silently starts a new session. It is sometimes necessary to change this default behavior and notify the user that the session has expired.
First thing to do is to subclass WAApplication. Next thing is to reimplement either #handleExpiredRequest: or #expiryPathFor:
In #handleExpiredRequest: you are given a chance to build a redirect response to another Seaside application that will display the expiration component.
Below is a very simple example based on reimplementing #expiryPathFor:
expiryPathFor: aRequest
^ self basePath , '?expired=true'
The expired=true field can be obtained by implementing #initialRequest: in the application root component, for instance
initialRequest: aRequest
(aRequest fields includesKey: 'expired') ifPresent: [restartedAfterExpiration := true].
The rendering can then take expiration into consideration, for instance
renderContentOn: html restartedAfterExpiration ifTrue: [ self renderExpiredOn: html ] ifFalse: [ self renderNormalOn: html ]
If you would like to make the VisualWorks Seaside application server manage all the path-information on its own and to let it appear in the root like:
then you need to load the SeasideShortPath parcel that can be found in the contributed/other/Seaside/BonusPack directory starting with VisualWorks 7.3
To view
(WADispatcher default entryPoints at: 'config') preferenceAt: #login.
(WADispatcher default entryPoints at: 'config') preferenceAt: #password.
and to change:
(WADispatcher default entryPoints at: 'config')
preferenceAt: #login put: 'new id'.
(WADispatcher default entryPoints at: 'config')
preferenceAt: #password put: 'new password'.
WARegistry clearAllHandlers.
Smalltalk garbageCollect.
With the code shown below the MIME document is created at render time, but the data I want sent is dependent upon the fields on the form.
html anchor
document: anObject mimeType: mimeTypeString fileName: fileNameString;
text: 'Export (csv)'.
Is there a convenient way to make the document be created after the link is clicked?
Yes, this is a common problem. One solution is to create an anchor and redirect:
html anchor
callback: [ self session returnResponse:
(WAResponse
document: anObject
mimeType: mimeTypeString
fileName: fileNameString) ];
with: 'Export'.
Per default all output is escaped for HTML unless especially reqested by using the #html: method.
When the callback attached to an anchor is not evaluated, make sure you check that #with: or #text: is the last message sent to the anchor brush.
html anchor
callback: [ self close ];
with: [
html image
fileName: 'closeButton.png';
mimeType: 'image/png'].
When a Seaside session is not used during a certain duration, it expires. After a session has expired, it cannot be used any more.
From time to time, Seaside processes expired sessions and sends them #unregistered. This is not immediate and it may take some time before Seaside enters this expiry process. With its default algorithm, Seaside expiry process occurs only when starting a new session, once every 10 sessions in average (see #registerRequestHandler: and #shouldCollectHandlers). If no new session are started, the #unregistered message is not sent. The more new sessions are created, the more the #unregistered message is sent.
Beware, a common mistake is to name the method #unregister rather than #unregistered
One of the things I added in the recent 2.5b5 release was an ExpiryTest application which let me do some ad-hoc testing of the expiration and unregistration mechanism. As far as I can tell, things are actually working, although it's fairly erratic when exactly #unregistered will get sent - usually quite a while after the session actually expires, since the cleanup is only triggered by (on average) every 10th new session creation.
If you want to play with this yourself, execute "WAExpiryTest install" and then go to /seaside/expiry. Hopefully the links/stats are fairly self explanatory.
I think what I will probably do is start a background process that does the cleanup every few minutes, instead. The only small concern I have about this is that it's one more thing to break - you might not notice that the cleanup process had died somehow, and end up with a lot of expired sessions sitting around in memory. The way it is now, it's tied directly to the web server process, and if that goes down you'll hear about it :).
Avi
When you see unexpected text in your rendered page (typically html tag names), you may have to verify that you are using the correct renderer class.
When you are using the old rendering API, you must tell Seaside to use the corresponding renderer class WAHtmlRenderer. Your component must implement #rendererClass like this
rendererClass
^WAHtmlRenderer
This is especially important when you upgrade to Seaside 2.7 from an older version. In older versions of Seaside the default renderer was WAHtmlRenderer. Starting with Seaside 2.7, the default renderer is WARenderCanvas. Starting with Seaside 2.8, the old WAHtmlRenderer is no longer available.
Before deploying for production, it is not a bad idea to remove from the Seaside dispatcher all those applications that are not strictly needed. Otherwise, users may accidentally enter the "counter" or the "store" demo applications.
First of all, you will have to explicitely designate those Seaside applications that are deployed. This is achieved by re-implementing the #isDeployed class method in the root component of the deployed applications. By default, this class method answers false, only the dispatcher editor ("config") and the dispatcher navigator ("browse") answer true.
Then ,to remove applications from the Seaside dispatcher, evaluate the following:
WADispatcher default trimForDeployment.
This will remove all those applications that answer false to message #isDeployed.
You can run Seaside under VisualWorks behind Apache using either the the CGI gateway or the CGI Perl Interface.
There is however some potential difficulties to take into consideration. By default, Seaside is designed to use only one WADispatcher object and the WADispatcher object has a hard-coded base URI. Therefore, if you start your image, access Seaside through TinyHttpServer, then access Seaside through Apache, the access through Apache should fail because the WADispatcher object has been initialized with the TinyHttpServer URI and does not recognize the Apache URI.
For instance if the first Seaside visitor comes in through TinyHttpServer with a request for 'seaside/go/counter', the WADispatcher base path will be 'seaside/go'. When another visitor comes in through Apache with a request for 'cgi-bin/visualworks.pl/seaside/go/counter', Seaside uses the REQUEST_URI environment variable and the WADispatcher does not find any match.
The problem would be solved if the Apache request would come with REQUEST_URI equals to 'seaside/go/counter'. This might be possible by modifying the perl script so that it modifies the REQUEST_URI variable before transfering the HTTP request to VisualWorks.
I (Michel Bany) recently made an attempt to solve this issue differently by implementing the concept of alternate servlet. See the Seaside mailing list post.
With this concept there are two SeasideServlet instances rather than just one. Each instance has its own separate WADispatcher object. As a result you may serve Seaside applications through two different base URIs.
The alternate servlet support has been included in VW 7.3.
The parcel for alternate servlet support is inside the BonusPack folder and is named SeasideShortPath. You will need to use a different URI when coming through Apache, something like for '/cgi-bin/visualworks.pl/counter' rather than for 'cgi-bin/visualworks.pl/seaside/go/counter'. With the later, Seaside would attempt to use the default servlet and would fail. With the former, it will drop into the alternate servlet and initialize a new WADispatcher with the right base path. Having explained all this, I believe that the best solution will be to have multiple WADispatcher objects available for the SeasideServlet providing support for multiple channels into Seaside.
Use Firebug to get more information.