Terse Guide to Seaside UPDATED: 7.4.08 Learning Seaside Seaside isn't as hard as it might initially seem, it's just different. There are only a few essential classes you need to learn to use most of it so here's a quick lesson. First, make sure you're using the latest stable release, currently 2.8. There's no reason to be using 2.6 or 2.7 if you're just learning and you probably wouldn't be reading this if you already knew how. If you are using an older version, porting from 2.6 or 2.7 is not that difficult, so bite the bullet and just do it. To get going, you'll need to understand WAComponent, WARenderCanvas, WATask, and WASession. You'll also need to understand that Seaside is a framework, not an API, so you'll work with it by subclassing and extending these core classes. WAComponent WAComponent is the main class you'll be working with in Seaside. A component represents both the concept of "page" and "user control". If you're coming from another framework, consider the word "page" and "component" to be indistinguishable. To allow a component to be configured as the root of an application in the configuration UI (/seaside/config), you'll need to override #canBeRoot on the components class side. FooComponent class>>canBeRoot ^true This will make the component show up as an option in the root component drop down in the configuration editor. A more direct, and I'd say preferable route, would be to create your site programatically by creating an #initialize method on your root class like so... FooComponent class>>initialize "self initialize" | app | app := self registerAsApplication: #foo. app libraries add: SULibrary. app preferenceAt: #sessionClass put: FooSession This sets up a dispatcher at /seaside/foo with the Scriptaculous library (SULibrary) and a custom session class that might contain things like the current user or current database session. You can then highlight the comment "self initialize" and run it to create your site. This has the additional advantage of automatically setting up your site in any new image when you load your package into it and also allowing you to programatically recreate your site on demand. This comes in very handy when upgrading to newer versions of Seaside which sometimes require recreating your sites. Rendering Seaside can render two kinds of things, views, WAComponent subclasses via the overridden #renderContentOn:, or any other non UI object via the overridden #renderOn: method. Both #renderContentOn: and #renderOn: are framework methods, you override them so the framework can call them. Never call these methods yourself in an attempt to render an object and never just add a #renderContentOn: to any random object thinking it'll just work, it won't. #renderContentOn: only works in WAComponent subclasses. Views For those who like the model view controller style, you'll want to keep all of your rendering code in WAComponent subclasses representing your views. To create a view in Seaside, you subclass WAComponent, override the #renderContentOn: method, and start writing HTML using the render canvas, which is passed in as an argument to #renderContentOn: renderContentOn: html html div: 'Hello World' WAComponents have a collection, called #children, consisting of other WAComponents. This serves as the user interface's control tree. A component is built from itself and optionally, nested subcomponents or models, allowing component composition. This is where people start running into trouble and meet the dreaded "Components not found while processing callbacks" error. You must return all visible subcomponents in a collection from the #children accessor that you have to override... children ^ { header. currentBody. footer } renderContentOn: html html div id: #header; with: [html render: header]. html div id: #body; with: [html render: currentBody]. html div id: #footer; with: [html render: footer] Models For those who just want their models to render themselves and don't need multiple visual representations of the same model, you can skip the WAComponent and just override #renderOn: in your model. Rendering Mistakes People often overlook this and do it wrong leading to all sorts of weird errors. You must know how to properly render subcomponents, don't do this... renderContentOn: html foo renderOn: html And don't do this... renderContentOn: html foo renderContentOn: html There is only one correct way to render another object, regardless of what kind of object it is... renderContentOn: html html render: foo It's a good idea to create a subclass of WAComponent once at the project level, and write the rest of the components in that project, using your custom component as the superclass. This gives you a place to push up component type things you want to apply project wide, and override defaults project wide, like a different render canvas. Using #call: and #answer: In traditional web frameworks, you move between pages either by building forms the user posts, or anchor tags and server side redirects containing parameters that can be parsed from the request by the other page. This means any page is a potential entry point and that no parameters passed to it can be trusted and must be validated and parsed sanely. It also means you can only pass simple parameters like strings and numbers that can fit in a URL. Seaside works differently. In Seaside components are real objects and they can #call: and #answer: to each other allowing you to pass any object as a parameter or result between them. Both methods are meant to be used during the callback phase, i.e. your controller methods. For example, you may click and edit link in a row of results that allows you to edit some object... editFoo: aFoo self call: (FooEditor on: aFoo) renderContentOn: html foos do: [:each | html div class: #fooRow; with: [html anchor callback:[ self editFoo: each ]; text: 'Edit'. html text: foo description ]] This is a simple case, a more complex case might include some sort of workflow where components return results... orderFoo: aFoo | customer address order | customer := (self call: CustomerForm new) ifNil: [ ^self ]. address := (self call: AddressForm new) ifNil: [ ^self ]. order := (self call: (FooOrder foo: aFoo customer: customer shipTo: address)) ifNil: [ ^self ]. self sendEmailConfirmationFor: order In each case here, each component calls #answer: with either a result or nil if the user presses cancel. The entire workflow for a multi page order process is represented here, with each components result being used later in the other steps or bailing if the user cancelled. By attaching callbacks directly to user actions, you never have to worry about how to represent your state in the url, or parsing and validating the input and re-fetching your models from the database on subsequent steps of the work flow. Every page in a Seaside application is not a valid entry point so users can't arbitrarily hack the URL to navigate to a page you didn't intend for them to see. WARenderCanvas WARenderCanvas contains the API for creating HTML. It's currently the default canvas, but you can override it to return your own customized subclass if you like. rendererClass ^ MyCoolRenderCanvas The WARenderCanvas is a starting point for understanding how to write code in #renderContentOn:, when you get confused, just look at the canvas and find the method you want, see which tag object it creates, then look on the tag class to see what attributes are valid for it. I've learned just about everything I know about Seaside using exactly this method. Documentation is nice, bit it isn't always available and you really can easily find what you need just by looking at the tag classes directly. WATask WATask is a special subclass of WAComponent used to do work flow. WATask is used just like a component, except you override #go instead of #renderConentOn: and you must #call: another component, because a task has no UI. Tasks essentially coordinate the display of other components, allowing you to write very simple and elegant code by calling, displaying, and getting answers from components, which you can use to determine what step comes next. When one component calls another component, the callee replaces the caller in the UI. If the caller was a subcomponent of another component, then the callee appears to takes its place in the composite component. All components continue to exist, the caller is simply not displayed until the callee answers. This allows you to easily setup a parent component to mediate the display of it's children, this comes in very handy when complex work flow is involved. WASession WASession is optional, you don't strictly need to subclass it and use it, but if you have data you want available globally within the scope of the current session, it is often convenient to create a custom session class with accessors for that data. This is where you'd put a database connection, or a current user. A Seaside session is inherently single threaded, so you don't have to worry about concurrency or locking unless you explicitly start forking stuff. Whatever session object you use, it's available on every component via the #session accessor. From within any component you can say "self session" and have access to your session. WASession is also where you can find other things you'll eventually want access to, like the current request. You can read in query string values like so... self fieldsAt: #someKey WASession is also where redirectTo: is located, something you'll likely need. self session redirectTo: 'http://www.google.com'



Danger Will Robinson! Seaside uses thread local variables to store the current session, so if during processing you #fork a block to do some work on another thread, you'd better pass that block the data it needs when called because once launched on the other thread, it will no longer have access to the current session.

Unless you're a masochist, don't try creating UI components on a background thread because Seaside expects to have access to the current session when WAComponent subclasses are instantiated.

Don't put workflow logic in render methods, a render method should be able to be called many times without affecting the component or changing its state. Simple logic like deciding if something should be rendered or not, or rendering something in a loop is fine.

Don't call components directly from the render methods of other components, always make sure any #call:'s are inside #callback: blocks, this bites every newbie for some reason.

When a page posts back, any form data will be used to update the components state, prior to the processing of any callbacks. You don't need to access the request directly, you just use your instance variables which Seaside has kindly updated for you.

Bind form controls directly to accessors either on the component, or on the components model, using the #on:of: shortcut, this saves much typing and works with most controls, and helps keep command code factored into separate methods rather than in line in some render method where it can't be easily found or reused.

If a component has child controls, you must override #children, and return a collection containing all current child controls or you will have issues.

If the list of child controls is dynamic or you want the back button to actually revert your server side state to match what's in the users browser cache, be sure to override #states and return a collection of objects that need backtracked. Is that all? No, there's much more to learn about Seaside, I left out plenty, but these are the basics, and should get you going and productive fairly quickly and within a short time you should understand why Seaside is game changing; this isn't your last web framework, but it just might be your last web framework. Simple Example of a Login Process and Component I cheat a little by calling #inform:, which is a built in generic dialog for displaying a message to the user and getting an OK. SeasideLoginTask class>>initialize "sets up app at http://localhost/seaside/loginSample" self registerAsApplication: #loginSample SeasideLoginTask>>go | user | user := self call: SeasideLogin new. user ifNil: [self inform: 'Unknown user or password, please try again!'] ifNotNil: [self inform: 'Congratulations, you are in!'. self session redirectTo: 'http://onsmalltalk.com'] and... SeasideLogin>>userName ^ userName SeasideLogin>>userName: aName userName := aName SeasideLogin>>password ^ password SeasideLogin>>password: aPassword password := aPassword SeasideLogin>>login (userName = 'seaside' and: [password = 'rocks']) ifTrue: [ self answer ] ifFalse: [ self answer: nil ] SeasideLogin>>renderContentOn: html html form: [html heading level3; with: 'User Name:'. html textInput on: #userName of: self. html heading level3; with: 'Password:'. html passwordInput on: #password of: self. html break; submitButton on: #login of: self] Related Posts Squeak Smalltalk and Databases

Mapping Seaside Blog to PostgreSQL with Glorp

05 Dec 2006 > Squeak Image Updated

Fixing Squeak's Sesame Street Looking Windows

Screencast: How to Build a Blog in 15 Minutes with Seaside Comments (automatically disabled after 1 year)

Topics Ajax (6)

Apache (5)

Databases (12)

Gemstone (2)

Linux (6)

Lisp (9)

Magritte (10)

Performance (8)

Profiling (1)

Programming (39)

Ruby (25)

Seaside (64)

Smalltalk (82)

Sql (13)

Squeak (6)

Updates (9)