AIDA/Web Tutorial
1. Introduction
To understand how to create web applications with Aida and to start programming on your own, here is a short tutorial which guides you through the main steps in creating an on-line address book.
This tutorial is prepared on VisualWorks but it can be used on Squeak and Dolphin Smalltalk (only class declarations are different).
Let we start preparing a domain model for it.
2. Preparing a domain model
Aida uses a strict separation of domain objects from their visual represenation in a MVC like way. Therefore we need to create some domain model objects first. In our example we'll make a domain model for our example address book, instantiate it and fill it with a few example addresses:
- in package (none) create a new class AddressBook with instance variable addresses:
Smalltalk defineClass: #'AddressBook'
superclass: #
indexedType: #'none'
private: false
instanceVariableNames: 'addresses '
classInstanceVariableNames: ''
imports: ''
category: '(none)'
- create new class Address with instance variables: name surname phone email
Smalltalk defineClass: #'Address'
superclass: #
indexedType: #'none'
private: false
instanceVariableNames: name surname phone email '
classInstanceVariableNames: ''
imports: ''
category: '(none)'
- create an initialize method and accessors for the AddressBook instvar addresses:
initAddresses
addresses := OrderedCollection new
addresses
addresses isNil ifTrue: self initAddresses.
^addresses
- create accessors and mutators for all instvars in class Address
- add a class method for fast Address creation:
newName: aName surname: aSurname phone: aPhone email: anEmail
^super new
name: aName; surname: aSurname; phone: aPhone; email: anEmail
- In a workplace make an instance of AddressBook and fill it with a few example Addresses:
book := AddressBook new.
book addresses
add: (Address newName: 'Sebastjan' surname: 'Dormir' phone: '01/514 33 66' email: 'sebastjan@something.si');
add: (Address newName: 'John' surname: 'Newton' phone: '05/555 77 66' email: 'john@something.si');
add: (Address newName: 'Elizabeth' surname: 'Schneider' phone: '03/561 23 12' email: 'elizabeth@something.at').
3. Registering a root domain object
After our AddressBook is prepared, we register its url in Aida, so that it is accessible from web. In our workspace we evaluate:
(AIDASite named: 'aidademo') urlResolver defaultURL: '/addressbook.html' forObject: book
You need to register only a root object of your domain model. Url links to all objects reachable from the root will be automatic.
4. Creating presentation classes (Apps)
To represent our domain objects as web pages we need to create a web application (presentation) class for every domain object we would like to show. In our case the domain classes are AddressBook and Address. This is simple; just subclass WebApplication with a new class named according to the formula: 'domain class name'+App. In our case we need two Apps: AddressBookApp and AddressApp:
Smalltalk defineClass: #'AddressBookApp'
superclass: #
indexedType: #'none'
private: false
instanceVariableNames: ''
classInstanceVariableNames: ''
imports: ''
category: '(none)'
Smalltalk defineClass: #'AddressApp'
superclass: #
indexedType: #'none'
private: false
instanceVariableNames: ''
classInstanceVariableNames: ''
imports: ''
category: '(none)'
4.1. View methods
We start by making our first 'view' method, that is, a method which builds a web page to represent one of the views of our objects. View methods are named according to the formula: view+'View name'. For instance: for view 'summary' we make a method #viewSummary. However every App should also have a default view 'main' and therefore a view method #viewMain. Let's write one for our AddressBookApp using the protocol 'printing':
viewMain
| e |
e := WebElement new.
e addTextH1: 'Address book'.
self pageFrameWith: e title: 'Address book'
In this case we created a local instance of WebElement, which is both a subclass of all other elements of a web page (such as WebText, WebLink etc.) and a composite, so that we can add another WebElements to it. We can also do this in a longer way by instantiating a new element and then adding to it, for example:
e add: (WebText text: 'Address book' attributes: #bold)
The shorter, more direct conveniance methods can all be found in the WebElement 'adding' protocols (adding text, images, links, form elements, ajax components, other).
The line of our example adds our element into a page and frames it with a standard header and navigation bar on the left. Try to change that to add directly to self (self add: e)!
Now we can open our first page in browser: http://localhost:8888/addressbook.html .
Wou will first need to login as admin with password: 'password', otherwise you wont be able to see your page. As admin you have the right to see all pages by default, while as a guest you cannot (although there are security settings which allow this).
4.2. Text
Now lets write out all the addresses in our address book main view:
viewMain
| e |
e := WebElement new.
e addTextH1: 'Address book'.
self observee addresses do: :each |
.
self pageFrameWith: e title: 'Address book'
Notice the message self observee . This way we ask your App to return a reference to the domain object it represents (do you remember the observer pattern and MVC?). The method addText: is used to add text to our page, which can include HTML tags too. Finally, the method addBreak creates a new line.
But this page is ugly! Let's put data into a nice table:
4.3. Tables
Tables in Aida are built in a convenient way, which shortens the work otherwise needed for web tables:
viewMain
| e |
e := WebElement new.
e addTextH1: 'Address book'.
e table border: 1.
self observee addresses do: :each |
.
self pageFrameWith: e title: 'Address book'
Every element has a dormant table which is activated when we first start to use table accessing/manipulation methods (table, newTable, cell, newCell, row, newRow). By simply sequencing those methods we can do everything with tables. We can even nest tables that way (remember, a table is also a web element and therefore can have a table inside!). Let's put name and surname in a nested table:
e cell cell addText: each name. e cell newCell addText: each surname.
4.4. CSS styles
Let's make this table even nicer. A better way is to use CSS (Cascaded Style Sheet) to separate design from content. There are already a lot of styling methods included in the class WebStyle (see methods css in the protocol styles-screen). We will reuse already defined styles in a method css24WebGrid for tables. Let's change our table line (e table border: 1) to:
e table class: #webGrid.
Now our table has a colored background and the font is smaller. Style methods in WebStyle are composed together by alphabetic order into one CSS page, which can be seen at http://localhost:8888/screen.css . Alphabetic order is important for CSS and that's the reason that css methods have numbers in their names to maintain order. Every method which starts with 'css' will be included in our CSS declaration. We can make a separate CSS for printing too, by naming methods as cssPrint .
To make your own style (skin), just subclass WebStyle and add or override css methods. Then change the style in AIDASite:
(AIDASite named: 'test') styleClass: 'MyWebStyle'; style: nil
Note: changing the style to nil will lazily instantiate it to a new style class at the first use.
5. Linking objects together
Because our domain objects are "linked" together with object references, our web pages should be too. For examle, our AddressBook holds references to all Addresses, and because we will represent each address with a separated web page, we need to make an url link from the AddressBook page to it.
First let's start by preparing a main view for addresses in AddressApp:
viewMain
| e |
e := WebElement new.
e addTextH1: 'Address'.
e table class: #webGrid.
e cell addText: 'name: '. e newCell addTextBold: self observee name. e newRow.
e cell addText: 'surname: '. e newCell addTextBold: self observee surname. e newRow.
e cell addText: 'phone: '. e newCell addTextBold: self observee phone. e newRow.
e cell addText: 'email: '. e newCell addTextBold: self observee email.
self pageFrameWith: e title: 'Address'
5.1. Prefered urls
We would like to have nice looking urls for our addresses, something like /address/newton.html and not automatically generated ones, like /object/o4899057.html, which is what is created for you as a default by Aida. To suggest to Aida what is a better url, implement in your domain class the method #preferedUrl. Let's do that for Address :
preferedUrl
^'/address/', self surname, '.html'
Url suggesting will be accepted if the url is unique, otherwise it will degrade to an automatically generated one.
Now we are ready to make links to our addresses:
5.2. Url links
Url links are made automatically by simply pointing to a domain object we'd like to show. In our case we will point to each of the addresses in address book. We will use the #addLinkTo:text: method from WebElement to change surname into a link, for example:
e addLinkTo: address text: address surname.
Let's update our viewMain in the AddressBookApp:
viewMain
| e |
e := WebElement new.
e addTextH1: 'Address book'.
e table class: #webGrid.
self observee addresses do: :each |
.
self pageFrameWith: e title: 'Address book'
Now all the surnames will be changed to url links. Check out how they look in your browser. Our suggestions obviously work. If we click on one of them, we can check if each address is properly shown as a web page as we wrote in the AddressApp viewMain.
More later ...