Custom application configuration parameters

C. David Shaffer
Shaffer Consulting

Getting started

You should begin by reading Avi's overview of Seaside's configuration system. I'll also assume that you are familiar with using existing configurations, at the very least making simple changes to an application's configuration to set the root component etc. So, the goal of this document is to show you how to add your own application-specific configuration parameters.

With the background out of the way, a quick example will show you most of what you need to use this framework. Generally the framework seems designed to help encourage reuse of Seaside components in multiple applications (throughout these examples I use the term "application" to mean an instance of WAApplication, typically created through the Seaside web-based tool). From the point of view of a component, the configuration can be thought of as a Dictionary-like collection of associations which may be customized by the system administrator. In our first example we have a component SCFrontDoor, used as a generic store front:

WAComponent subclass: #SCFrontDoor
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: SCSeasideTutorial'
This component renders itself with:
renderContentOn: html
	html heading: self storeName.
	html hr.
	html text: 'Welcome to our awesome store!'.

We start with an implementation of storeName which simply returns a fixed string:

storeName
	^'Sir Save-some-dough'

We also need the class-side canBeRoot method:

canBeRoot
	^true

We create a seaside application with a path /seaside/save-some-dough and with SCFrontDoor as its root component, bring it up in our browser and everything looks hunky dory. Our application goes into production and months later we get a request to develop a similar shopping application for a similar business. So similar, in fact, that we can parameterize the difference by a handful of values (in this case the value of a single string: the store name). So, rather than develop a separate store front class for each store, we will create a new configuration and allow the system administrator to create as many store fronts as s/he likes. We begin by creating a subclass of WASystemConfiguration called SCExampleConfiguration:

WASystemConfiguration subclass: #SCExampleConfiguration
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'SCSeasideTutorial'
In this new class we need only override #attibutes as follows:
attributes
	^ Array with: (WAStringAttribute key: #storeName group: #store)
which obviously specifies that our configuration has a single attribute of type String named storeName. The group property is used to group attributes in the web-based editor. You can see the full list of existing attribute types by browsing the Seaside-Configuration category...more on that later. Here we have indicated that each application which "uses" our configuration will have a value of the property storeName. We can specify a default value by implementing an accessor in SCExampleConfiguration:
storeName
	^'Default store name...you forgot to set it!'
although a default value isn't required. Now, in our browser we open the Seaside configuration tool and configure the /seaside/save-some-dough application adding SCExampleConfiguration to the "Configuration Ancestors" section. Your configuration page should have a section looking like this:

Once we do this we see the configuration section called "Store" with our Store Name property listed. We can now edit the value of this property but changing it will have no effect on our shopping application since it doesn't make reference to it. We modify SCFrontDoor>>storeName as follows:
storeName
	^self session application preferenceAt: #storeName

Now our component fetches the value of the store name from the configuration. We have attained our goal: we can have several Seaside applications which use our SCFrontDoor and the system administrator can configure each of them to have different store names. Try this by creating another seaside application at /seaside/other-store which uses our same root component but with a different value for the store name (remember to add SCExampleConfiguration to the Configuration Ancestors list).

Finally, it is relatively common practice to provide a mechanism for a root component to install itself into a Seaside server. The most common one I've seen is a class side initialize method:

initialize
	self registerAsApplication: 'blah'
If you want your application to include your configuration you need to change this to something like:
initialize
	| app |
	app := self registerAsApplication: 'blah'.
	app configuration addAncestor: SCExampleConfiguration localConfiguration.

A more common example

It is quite common to use configurations to set database connection information for an application. When connecting to GOODS, for example, we need to know the host and port number that the database serving is running on. I use the following configuration to that end:
WASystemConfiguration subclass: #GoodsAppConfiguration
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'SC-GoodsTools'

attributes
	^ Array
		with: (WAStringAttribute key: #goodsHost group: #Goods)
		with: (WANumberAttribute key: #goodsPortNumber group: #Goods)

More complex configuration attributes

Most applications have more complex configuration needs than simple string values taken from text fields. Seaside includes support for (see the Seaside-Configuration class category):

There are plenty of examples of each of these in the Seaside tools...just search for references to each of the attribute classes. Extending the system to support more value types and editors is quite simple as well. Suppose we wanted to add a property to control the background color of the title region of our store. There are two new players in attribute customization: WAConfigurationEditor which is a Seaside component used in the Seaside "config" application and WAConfigurationAttribute whose subclasses are adapters used to convert to and from string representations as well as for double dispatching to display an editor for an attribute.

Let's add color to our company name:

renderContentOn: html 
	html style:  'background-color:' , self backgroundColor.
	html div: [html text: self storeName].
	html hr.
	html text: 'Welcome to our awesome store!'
    
backgroundColor
	^self session application preferenceAt: #titleBackgroundColor
Since there no pre-defined attribute type for color values we subclass WAConfigurationAttribute
WAConfigurationAttribute subclass: #SCColorAttribute
	instanceVariableNames: ''
	classVariableNames: 'WebSafeColors'
	poolDictionaries: ''
	category: 'SCSeasideTutorial'
and are now obliged to implement three methods in that class:
accept: aVisitor with: anObject 
	^ aVisitor visitColorAttribute: self with: anObject

stringForValue: aColor 
	^ aColor

valueFromString: aString 
	^ aString
On the class side we add method to access the standard "web safe" colors:
"put these on the CLASS side"
webSafeColors
	^WebSafeColors ifNil: [self initWebSafeColors]

initWebSafeColors
	"self initWebSafeColors"
	WebSafeColors := OrderedCollection new.
	0 to: 16rFF by: 16r33 do: [:r |
		0 to: 16rFF by: 16r33 do: [:g |
			0 to: 16rFF by: 16r33 do: [:b |
				WebSafeColors add: '#' , (self twoDigitHex: r) ,
					(self twoDigitHex: g) ,
					(self twoDigitHex: b)]]].
	^WebSafeColors

twoDigitHex: value
	| result |
	result := value radix: 16.
	(result beginsWith: '16r') ifTrue: [result := result allButFirst: 3].
	^result size < 2 ifTrue: ['0' , result] ifFalse: [result]
Note the use of the visitor pattern in #accept:with:. When a configuration attribute's value is being edited, #accept:with: is invoked with a WAConfigurationEditor, and a WAHtmlRenderer as arguments. (While I haven't tried it it seems that this makes the configuration framework quite reusable -- could be used to support user application preferences etc.) The method #visitColorAttribute:with: doesn't exist in the class WAConfigurationEditor so we must add it (to WAConfigurationEditor, not our SCColorAttribute!):
visitColorAttribute: anAttribute with: html 
   | group current |
   group _ html radioGroup.
   current _ configuration valueForAttribute: anAttribute.
   html
      table: [1
         to: SCColorAttribute webSafeColors size // 20
         do: [:major | html
            tableRow: [1
                to:  20
                do: [:minor | 
                   | theColor | 
                      theColor _ SCColorAttribute webSafeColors at: major - 1 * 20 + minor.
                      html attributeAt: 'bgcolor' put: theColor.
                      html
                         tableData: [
                            html space.
                            html radioButtonInGroup: group
                                 selected: theColor = current
                                 callback: [:v | configuration takeValue: theColor
                                                               forAttribute: anAttribute].
                            html space]]]]]
(Note: adding methods to classes in Smalltalk is OK). I appologize for this mess on two accounts: the messy code obfuscates the simplicity of the job of this method and when rendered it looks ugly. Basically we need to render a component on the argument html which, when edited, sends #takeValue:forAttribute: to the editor's current configuration (in the i-var configuration). Look at the other editors for simpler examples. Anyway, there it is. Now we need to modify our configuration subclass to include a color attribute, modify SCExampleConfiguration>>attributes:
attributes
	^ Array
	        with: (WAStringAttribute key: #storeName group: #store)
	        with: (SCColorAttribute key: #titleBackgroundColor group: #store)
and add a default value:
titleBackgroundColor
	^ '#ff0000'
Now, we configure one of our store fronts and we are presented with a section that looks like this:

We wish to override this setting so we click on the "override" link for our color setting and we are shown this component:

Notice that the radio button corresponding to the attribute's current value is selected. Selecting a new color and clicking "Done" will install the new attribute setting in this application. Again, each application can have its own value for this attribute, that's the whole idea.

Configuration templates: User configurations

Seaside provides the ability to configure your own instances of WAUserConfiguration, independent of an application, and then add them as ancestors to an application's configuration. I'm shooting from the hip here but I think this is to make it simpler to create multiple applications which have the same configuration layout and/or start with similar "default" values which are different from the defaults supplied by the configuration classes themselves. Suppose, for example, that you have five unrelated (not ancestors of each other) subclasses of WASystemConfiguration which you use in all of your applications. In addition the default values provided by your classes are not the ones you typically use when building a new application. This might be a motivation to create a "free standing" user configuration. In the Seaside "config" app, click the "Edit Configurations" link and then enter a new configuration name, maybe "standard store" in the New Configuration text box and click "Add". You are presented with a configuration editor but you are not configuring any application in particular, just building a template. So, for our applications you would add SCExampleConfiguration to the ancestors of this user configuration and possibly cusomize the color to default to some other color, instead of the existing programmatic default of red. Then, when building your store applications, instead of adding SCExampleConfiguration to your applications you'd add standard store. Note: as of this writing, due to a bug in Seaside your pre-built configurations will not show up as choices in the ancestor's menu. To fix this bug, replace WASystemConfigurationPool>>configurations with:

configurations
	^ configurations copy
	      addAll: (WASystemConfiguration allSubclasses
	              collect: [:ea | ea localConfiguration]);
	yourself
Here is what your configuration page would look like for an application configured in this way:

Notice that this application is getting the default values for the properties from standard store instead of from SCExampleConfiguration.

Footnotes

1. There are two reasons that it is generally OK to add methods to classes in Smalltalk. First, adding a method to a class is generally as safe as subclassing since the added method simply adds functionality to the class, without changing its existing functionality. Second, adding a method is a maintainable practice since the Monticello source code management system keeps track of these class "Extension" versions. Just make sure to put them in a class category whose name is your package name with an asterisks (*) in front of it. For example, I keep this tutorial source code in a Monticello package called SCSeasideTutorial so I put the method (WAConfigurationEditor>>visitColorAttribute:with:) in a method category named *SCSeasideTutorial. Just be sure to choose method names which won't clash with future extensions to this class. Also, keep in mind that you don't have to add a method to WAConfigurationEditor if you don't like the idea. You can create your own class to house the editor code (or even put the method in the attribute class) with some modification. In this tutorial I was simply following the pattern of the existing attributes.