smalltalk programming for the web


16 July 2017

I have started a booklet on Pharo, hopefully the first of, um, more than one. It is entitled RedditSt20, on my fork and extension of Sven Van Caekenberghe's excellent " in 10 elegant classes", to cover the following in another 10 or so classes:

  • GlorpSQLite
  • Seaside-MDL
  • username/password authentication
  • logging
  • 2-factor authentication

The book is hosted on Github. Source code is on Smalltalkhub.

The book is being written using Pillar, of course. Note that the Pharo 5 version of Pillar that I downloaded from InriaCI doesn't work - the supporting makefiles aren't able to obtain the output of "./pillar introspect <something>". Use the Pharo 6 version.

Seaside and Pharo 5

21 May 2017

I just tried loading Seaside into the latest Pharo 6 pre-release image from the Catalog Browser. It loads cleanly. However, the Seaside control panel doesn't start because it uses NewListModel which does not exist in this image:


  self instantiateModels: #(
    listModel NewListModel "<== Exists In Pharo 5, not in Pharo 6"
    textModel TextModel
    toolbarModel WAServerAdapterToolbar).

Ok, how about Pharo 5? Using Pharo 50772, which is currently the latest Pharo 5 image, I loaded Seaside 3.1.5 programmatically after visually inspecting ConfigurationOfSeaside3:

Gofer it 
  smalltalkhubUser: 'Seaside' project: 'Seaside31';
  package: 'ConfigurationOfSeaside3';
(#ConfigurationOfSeaside3 asClass project version: '3.1.5') load.

The load sequence includes messages to String>>#subStrings: which is deprecated. The senders are GRStringTest>>#testSubStrings, JQAjax>>#callback:passengers:, WAAdmin class>>#register:at:in: and WAAdmin class>>#unregister:in:.

Otherwise Seaside 3.1.5 loads cleanly. Test Runner reports 1173 run, 1171 passes and 2 expected failures.

Seaside jQuery Plugin Recipe

15 August 2015

Here's how to create a Seaside wrapper using, in this case, the Rainbow Text jQuery plugin as the example.

0. Download the source into, say, '/home/pierce/src/jq/Rainbow-Text'.

1. Create a file library.

WAFileLibrary subclass: #JQRainbowTextDevelopmentLibrary
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

2. Load the Javascript (and CSS, if any) into the file library:

JQRainbowTextDevelopmentLibrary addFileAt: '/home/pierce/src/jq/Rainbow-Text/rainbow.js'.

3. Subclass JQWidget.

JQWidget subclass: #JQRainbowText
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

4. Add instance methods.

    ^ 'rainbow'
    super initialize.
    self animate: true;
        animateInterval: 100;
        colors: ('#ff0000' '#f26522' '#fff200' '#00a651' '#28abe2' '#2e3192' '#6868ff')
animate: aBooleanOrStringOrNumber
    self optionAt: 'animate' put: aBooleanOrStringOrNumber
animateInterval: aNumber
    self optionAt: 'animateInterval' put: aNumber
colors: anArray
    self optionAt: 'colors' put: anArray

5. Create the example component.

WAComponent subclass: #JQRainbowTextExample
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

6. Add class-side methods.

    ^ true
    ^ 'jQuery Rainbow Text'
    | app |
    app := WAAdmin register: self asApplicationAt: '/jqrainbowtext'.
    app addLibrary: JQDevelopmentLibrary;
       addLibrary: JQRainbowTextDevelopmentLibrary.

7. Add instance methods.

updateRoot: aRoot
    super updateRoot: aRoot.
    aRoot script url: (JQRainbowTextDevelopmentLibrary urlOf: #rainbowJs).
renderContentOn: html
    | id |
    id := html nextId.
    html div id: id; with: 'Because Rainbow Text Rules!'.
    html script: (html jQuery id: id) rainbowText

8. In a workspace, initialize JQRainbowTextExample.

JQRainbowTextExample initialize

9. Create a method in JQueryInstance.

    ^ self create: JQRainbowText

10. In the browser, visit

The above is the standalone way. Alternatively, preferably, integrate the plugin into JQueryWidgetBox.

Pattern Language for Relational Databases and Smalltalk

13 April 2014

As I develop NBSQLite3 (tag, repository), I've been surfing the web for material on using Smalltalk with SQL databases effectively. This article, A Pattern Language for Relational Databases and Smalltalk by Kyle Brown and Bruce Whitenack, looks like it is from the late 90s but remains an interesting read.

The Seaside book is a bit light on persistency as is. The Hasso-Platter-Institut Seaside tutorial does present an abstract database wrapper class with these words:

   In order to decouple your application from the chosen database system, it
   is a wise decision to encapsulate the required functionality within a
   separate database wrapper class. This class provides an interface for the
   required persistence functionality of the application but leaves the
   concrete implementation of those functions to its subclasses.

Now Has Dynamic Content

6 April 2012

This blog began life as a set of static pages, generated by a home-grown content management system written in Smalltalk, imaginatively called SmallCMS1.

I've now rewritten SmallCMS1 to serve content dynamically, to support tag linking, like this: SQLite.

Each blog post page now has forward and backward navigational links just above the blog post title.

Rendering code now uses Seaside. More than a year ago, I blogged on that. Seaside now has a cleaner way to render static HTML, or maybe that previous blog post got it wrong. Anyhow, here's how SmallCMS1 uses Seaside's HTML rendering engine:

^ WAHtmlCanvas builder
    fullDocument: true;
    rootBlock: [ :root | self renderSiteRootOn: root ];
    render: [ :html | self renderContentOn: html ].

Similarly, RSS is rendered thusly:

^ RRRssRenderCanvas builder
    fullDocument: true;
    render: [ :rss | self renderRssOn: rss ]).

Seaside and static files with Comanche

10 July 2011

I've had this for a while. Putting it up here in case this is helpful to others. To use, create "htdocs" in "FileDirectory default". Web-wise, "htdocs" is known as "/static". I serve my CSS and JS files from there.

WAComancheAdaptor subclass: #WAComancheStaticAdaptor
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Seaside-Adaptors-Comanche'

    | contentPath dirPath  svc ma |

    contentPath := 'htdocs'.
    dirPath := FileDirectory default fullNameFor: contentPath.
    svc := (HttpService on: self port)
        name: 'seaside-' , self port greaseString.
    ma := ModuleAssembly core.
    ma alias: '/static' to: [
        ma serverRoot: dirPath.
        ma documentRoot: dirPath.
        ma directoryIndex: 'index.html index.htm'.
        ma serveFiles ].
    ma addPlug: self.
    svc plug: ma rootModule.
    ^ svc

Static RSS Rendering with Seaside

7 February 2011

Back to Seaside, it also provides an API to generate RSS. Indeed, that is how this blog's feed is generated:

| doc ctx root rss |
String streamContents: [ :stream |
    doc := WAXmlDocument new initializeWithStream: stream codec: nil.
    ctx := WARenderContext new document: doc.
    root := RRRssRoot new openOn: doc.
    rss title: self siteTitle.
    rss description: self siteSlogan.
    self blog do: [ :ea |
        rss item: [
            rss title: ea title.
            rss author: self siteAuthor.
            rss link: self baseUrl, ea blogUrlPath.
            rss guid: self baseUrl, ea blogUrlPath.
            rss publicationDate: ea timestamp printHttpFormat.
            rss description: ea outputHtml ]].
    root closeOn: doc ]

Static HTML Rendering with Seaside

15 January 2011

One of Seaside's distinctive features is that it generates HTML in Smalltalk, i.e., Seaside provides an API to produce HTML using Smalltalk code. This API can be used for generation of static web pages too:

| doc ctx root html |
String streamContents: [ :stream |
    doc := WAHtmlDocument on: stream.
    ctx := WARenderContext new document: doc.
    root := WAHtmlRoot new.
    root beXhtml11.
    root title: 'Static HTML Generation using Seaside'.
    root writeHeadOn: doc.
    html := WARenderCanvas new initializeWithContext: ctx.		
    html html: '<p>Seaside!</p>'.
    root writeFootOn: doc ]