Archive for the 'Seaside' Category

Seaside 2.8 Released

Quoted from the Seaside mailing list…

After a beta phase of two months we release the final version of Seaside 2.8. Most bugs fixed during this period were either long standing (already in 2.7), minor or portability related, Together with the dozens of Seaside 2.8 applications already in production today this gives a pretty good feeling about this version. A special mention deserves Roger Whitney, thanks to him we went from 99 commented classes to 144.

We have a list of new features [1] and a migration guide [2] on our homepage.

Squeak users can get it either from SqueakMap, Universes or directly via Monticello (Seaside2.8a1-lr.518). A special note for Squeak users, do not load Seaside 2.8 into an image that has already Seaside 2.7 in it. If you use Squeak 3.7 you will have to load SeasideSqueak37 as well.

VisualWorks users can get it form Store (2.8a1-lr.518,tkogan).

GemStone/S users can load Seaside2.8g1-dkh.522.

[1] http://www.seaside.st/community/development/seaside28a1
[2] http://www.seaside.st/documentation/migration

Cheers
The Seaside Team

I’ve been using 2.8 for a while now in development and for several weeks in production, it’s solid and very easy to port to for 2.7 users. Upgrade as soon as you can, it’s quite a bit snappier and uses much less memory.

Seaside Tutorial By Software Architecture Group

A new Seaside tutorial has been released by the Software Architecture Group at the Hasso-Plattner-Institut (University of Potsdam). They walk you through the setup of your image and the building of a ToDo list application including such things as encryption, external resources, various persistence options, and Ajax. It’s a good tutorial and more importantly an up to date tutorial using the latest versions of Seaside and Squeak. It should prove to be a good resource for beginners looking to attempt their first Seaside applications, go check it out!

A Simple File Based Wiki in Seaside

There can never be enough simple sample apps to help beginners learn Seaside. In that spirit, here’s a simple file based Wiki written in pure Seaside (i.e. no Magritte and not overly abstracted to the point you can’t figure out what’s going on).

It has bookmarkable URLs, uses regex (regex package found on SqueakSource) to make WikiWords into links, keeps line breaks, and accepts raw HTML. Pages are stored on the file system under your image directory based upon the app name.

For a production quality Wiki, use Pier, this one is super simple and only intended for learning. It was written in about two hours (not counting some changes made during the writing of this article) as a single Seaside component.

OK, here we go, broken up into code sections by method category, first declare the class…

WAComponent subclass: #WikiPage
    instanceVariableNames: 'isEditing currentContent currentPage'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SimpleFileWiki'

Setup the app on the class side…

canBeRoot
    ^ true  

initialize
    self registerAsApplication: #wiki

Initialize instances of the class…

initialize
    super initialize.
    currentPage := ''.
    isEditing := false

Create some accessing methods we’ll need…

currentContent
    ^ currentContent ifNil: [currentContent := '']

currentContent: aString
    currentContent := aString   

style
    ^ ‘ textarea {width:90%;height:500px;}’

Then some fancier accessors that ensure our file system is setup and reads pages from it…

pageDirectory
    ^ (FileDirectory default
        directoryNamed: self session application name , #Pages) assureExistence 

pageAt: aPage
	isEditing := (self pageExists: aPage) not.
	isEditing ifTrue: [^ ''].
	^ FileStream readOnlyFileNamed: (self pageDirectory fullNameFor: aPage)
		do: [:file | file contentsOfEntireFile]

If a page doesn’t exist, the Wiki kicks into editing mode to create it. A testing method use by the above…

pageExists: match
    ^ self pageDirectory fileExists: match

A couple of actions (our controller methods)…

loadPage: aPage
    isEditing := false.
    currentPage := aPage.
    self currentContent: (self pageAt: aPage)   

savePage
	self currentContent
		ifEmpty:
			[ (self pageExists: currentPage) ifTrue:
				[ self pageDirectory deleteFileNamed: currentPage.
				self loadPage: #FrontPage ] ]
		ifNotEmpty:
			[ FileStream
				forceNewFileNamed: (self pageDirectory fullNameFor: currentPage)
				do: [ :file | file nextPutAll: self currentContent ].
			isEditing := false ]    

cancel
	isEditing := false

Deleting the contents of a page, deletes the page as well.

Now we’re ready for rendering. Let’s start with page title in the head…

updateRoot: aRoot
    super updateRoot: aRoot.
    aRoot title: currentPage

And setting up the url…

updateUrl: aUrl
    super updateUrl: aUrl.
    aUrl addToPath: currentPage withFirstCharacterDownshifted

Now that the URL looks valid, lets make it work by parsing new requests, those that don’t include the session key (_s) or includes an expired session key. Once the session key and the continuation key (_k) are present, the URL is no longer necessary and will be ignored. Should this URL be bookmarked and returned to later, after the session has expired, #initialRequest: will be invoked, a new session started, and the correct page served…

initialRequest: aRequest
	| page url |
	url := aRequest url stringAfter: self application basePath.
	page := (url beginsWith: '/')
		ifTrue: [ url allButFirst copyAfterLast: $/ ]
		ifFalse: [ url copyAfterLast: $/ ].
	self loadPage: (page ifEmpty: [ 'FrontPage' ] ifNotEmpty: [ page ])

This uses an extension method #stringAfter that I have loaded in all my images, and it relies on another #split that is also in my images. Here they are…

String>>stringAfter: aDelim
    | list |
    list := self split: aDelim.
    ^ list isEmpty ifTrue: [self] ifFalse: [list last withBlanksTrimmed]

String>>split: aString
    | index lastIndex |
    index := lastIndex := 1.
    ^ Array streamContents:
            [:stream |
            [index <= self size] whileTrue:
                    [index := self findString: aString startingAt: lastIndex.
                    index = 0 ifTrue: [index := self size + 1].
                    stream nextPut: (self copyFrom: lastIndex to: index - 1).
                    lastIndex := index + aString size]]

Now our main render method which decides which mode the Wiki is in…

renderContentOn: html
    isEditing
        ifTrue: [self renderEditorOn: html]
        ifFalse: [self renderViewerOn: html]

And either edits the Wiki page…

renderEditorOn: html
	(html heading)
		level1;
		with: ((self pageExists: currentPage)
					ifFalse: ['Page ' , currentPage , ' hasn''t been created yet, go for it!']
					ifTrue: ['Editing ' , currentPage]).
	html form:
			[html textArea on: #currentContent of: self.
			html break.
			html submitButton on: #savePage of: self.
			html text: ' or '.
			html anchor on: #cancel of: self]

Or renders the viewer which also parses the text for WikiWords and line breaks…

renderViewerOn: html
    self withLineBreaks: (self currentContent
                copyWithRegex: '[A-Z][a-z]+([A-Z][a-z]+)+’
                matchesTranslatedUsing:
                    [:match |
                    (self pageExists: match)
                        ifTrue: ['<a href="' , (html urlForAction: [self loadPage: match]) displayString, ‘”>’, match , ‘</a>’]
                        ifFalse: [match , '<a href="' , (html urlForAction: [self loadPage: match]) displayString, ‘”>?</a>’]])
        on: html.
    html paragraph:
            [(html anchor)
                callback: [isEditing := true];
                text: ‘Edit’.
            html space.
            (html anchor)
                callback: [self loadPage: #FrontPage];
                text: ‘FrontPage’]

The editor and viewer could have been separate components, but I’m going for simple here, one class. And finally, the method for breaking lines…

withLineBreaks: aString on: html
    | stream |
    stream := aString readStream.
    [stream atEnd] whileFalse:
            [html html: stream nextLine.
            stream atEnd ifFalse: [html break]]

And there we have it, a simple file based Wiki that covers quite a few things you’d want to do in a web app and should be easily digestible for the Seaside beginner. There are probably bugs, I didn’t do a ton of testing and its only intended use is this blog post.

According to the message “WikiPage linesOfCode”, that’s 90 lines of code total (and that’s including the HTML and CSS). Here’s a file out of the code for anyone interested. Make sure to manually add the two extension methods to String for this to work.

9 July 2007 Upgrading Seaside

Here’s some very valuable info posted by Philippe Marschall (core Seaside developer) in the mailing list…

This is a short list of things to do when migrating for an older to a newer version of Seaside in the wiki at:
http://www.squeaksource.com/Seaside.html

2.6 to 2.7 must:

  • every component that uses the old default WAHtmlBuilder must do so explicity by returning it in #rendererClass
  • evaluate WADispatcher resetAll
  • use WATagBrush >> #title: instead of WATagBrush >> #tooltip:
  • move to new image map api, check all senders of #imageMap, see WAImageMapTag for details

2.6 to 2.7 should:

  • move to new header api, modify implementors of #updateRoot:
  • remove the implemtors of #rendererClass that return WARenderCanvas
  • migrate to WARenderCanvas
  • migrate from WAScriptLibrary and WAStyleLibrary to WAFileLibrary
  • use WAAnchorTag >> #with: instead of WAAnchorTag >> #text:
  • check for deprecated message sends
  • replace users of WAChangePassword, WAEditDialog, WAEmailConfirmation, WAGridDialog, WALoginDialog, WANoteDialog

2.7 to 2.8 must:

  • migrate to WARenderCanvas
  • backtrack state by implementing #states (check all senders of #registerObjectForBacktracking: and #registerForBacktracking)
  • move to new header api, modify #updateRoot:. #linkToStyle becomes #stylesheet #url, #linkToScript becomes #javascript #url:, check all implementors of #updateRoot:
  • use WAAnchorTag >> #with: instead of WAAnchorTag >> #text:
  • replace users of WAChangePassword, WAEditDialog, WAEmailConfirmation, WAGridDialog, WALoginDialog, WANoteDialog
  • every component that implements #initialize must send this message also to super (use SLint)
  • if you use Squeak 3.7 load SeasideSqueak37 after Seaside
  • if you use Squeak do not load into an image that has an earlier version of Seaside already loaded

2.7 to 2.8 should:

  • check for deprecated message sends
  • migrate from WAScriptLibrary and WAStyleLibrary to WAFileLibrary
  • remove the implementors of #rendererClass that return WAHtmlCanvas

I’m sure this list is not complete and I forgot stuff. So if you spot something that is missing send a message to me or the list or fix it directly. This is not on the Seaside homepage so anyone can edit it.
It will probably be moved to the homepage at a future point.

Cheers, Philippe

« Previous PageNext Page »