Programmer's Guide
General
Programming Æjaks is similar to programming Tcl/Tk windowing applications. Tcl/Tk programs are not directly compatible with Æjaks, as Æjaks uses class names more closely associated with the Echo2 toolkit, on which it is based.
The Æjaks classes are built using the Incr Tcl object system, and each class is named the same as the underlying Echo2 class. Each Incr Tcl object proxies the Java Echo2 object, and adds some additional functionality to make programming easier.
Note that we'll use the terms object and widget somewhat interchangably in the documentation. We generally say widget when referring to the object as it is used in a windowing context.
Consult the Aejaks Manual page for detailed information about Æjaks components and commands.
Object Construction
Æjaks objects are constructed using Incr Tcl notation:
TextArea .textarea
Where TextArea is the class name, and .textarea is the name of the object to be constructed. Optional properties can also be set during construction:
TextArea .textarea -font $fontObj -foreground $colorObj
The .textarea is also created as a Tcl command, and responds to the TextArea class methods. In addition, all other Incr Tcl builtin methods also work as usual, such as configure and cget.
.textarea setText "Hello World"or
.textarea configure -text "Hello World"
Incr Tcl also allows anonymous object to be constructed, i.e., objects whose name we don't care about. For this case, use #auto as the name of the object to construct. Incr Tcl will construct the object with a unique name, and return it as the result of the object construction:
set whiteColorObj [Color #auto 255 255 255]
Since object constructors always return the name of the object being constructed, we can further simplify usage in some cases:
Pack [Label .label_hw -text "Hello World"] -insets {10 10}
is equivalent to:
Label .label_hw -text "Hello World"
Pack .label_hw -insets {10 10}
Incr Tcl objects are not garbage collected and should be deleted when no longer used. Be sure to Pack forget .widget before deleting the object if the object has been placed into a container object.
itcl::delete object .textarea
Æjaks can also unpack and delete objects, including all child objects in one command. This is most useful when an entire widget hierarchy is named (see below for Widget Hierarchy.) Pack destroy .row1.column2 will unpack and delete all of the widgets and containers at .row1.column2 and below.
Incr Tcl provides a builtin procedure for defining local objects, those that are deleted when they go out of scope of the procedure in which they are defined. To use this feature prefix the class constructor with itcl::local:
set green [itcl::local Color #auto 0 255 0]
All widget components and containers inherit from the Component object. Be sure to review the manual page for Component to learn of methods inherited from that object.
Properties
Æjaks objects also have Tk-style properties that correspond to Echo2 objects' getXxxx() and setXxxx() methods. For those methods that have either corresponding pairs of setXxxx() / getXxxx() or setXxxx() / isXxxx(), Æjaks provides a property named -xxxx.
For example, most component objects implement setFont() and getFont() to set and retrieve the font property for that object. Æjaks provides both methods, but also includes a property that can be configured using Incr Tcl notation of $obj configure -font $value to set a font value, and $obj cget -font to retrieve the font property value. This shortcut is further enhanced by Incr Tcl's ability to set multiple properties at once:
$obj configure -font $fontObj -border $borderObj
The cget method returns a property value from an object.
set t [$obj cget -text]
One property, id, is automatically set to the name of the widget when widgets are added to the widget hierarchy if not previously specified. Assigning an id property to a widget may aid development when using certain testing tools.
Attribute Object Construction
Attribute objects (e.g., Color, Font, Border, etc.) have another unique ability. Anywhere that an attribute object is expected, you can code a valid constructor to that object:
TextArea .id -foreground yellow -backgound {0 0 0} -font {sans plain 12}
Æjaks will construct a Java object (but not an Incr Tcl object) and use it in place of the constructor. Also note that Æjaks can use any of the following where attribute objects are required:
-
Incr Tcl object
set myFont [Font #auto sans plain 12]
-
Constructor arguments
set myFont {sans plain 12}
-
Java object
set myFont [java::new nextapp.echo2.app.Font ...args... ]
Then you can use the any of the font values as:
TextField .name -foreground $myFont
If you find yourself using the same attribute constructor, you are better off constructing an Æjaks object and using it, rather than constructing the similar objects over and over.
Widget Hierarchy
Æjaks uses a strict hierarchy path naming convention in which component widgets are placed into their parent container widgets. This helps containers easily keep track of their child widgets, and the Pack command to construct the proper LayoutData object.
The root window is named . (dot), and is pre-constructed upon execution of your program. The root window in Æjaks comes pre-constructed with a ContentPane object. There is no need to create another ContentPane for the root Window object.
Components and containers that are to be added to any parent container must prefix their names with the parent widget path. For example:
Column .main ;# outtermost container Row .main.row1 ;# first row TextArea .main.row1.text ;# text area in row1 Row .main.row2 ;# second row ListBox .main.row2.list ;# listbox in row2
Widgets names can get rather long using this approach, so Æjaks programmers can use a common idiom from Tk. Instead of specifing the complete object path literally for each widget, set a variable name and use it for the parent portion of the widget. This also helps when code changes are needed to re-parent widgets, or building up libraries of useful widgets:
set w .main Column $w Row $w.row1 TextArea $w.row1.text Row $w.row2 ListBox $w.row2.list
The Pack command places component objects into their parent containers (and thus makes the components visible in the browser window.) Note that containers are also components, in that a container can be placed into another container where appropriate.
Pack $w ;# put .main inside of . (root window) Pack $w.row1 ;# put .main.row1 inside of .main Pack $w.row1.text ;# put .main.row1.text inside of .main.row1 Pack $w.row2 ;# put .main.row2 inside of .main Pack $w.row2.list ;# put .main.row2.list inside of .main.row2
Pack ensures that all widgets specified have the same parent widget, and that the parent has room to fit the children. Pack currently has the following sub-commands. If the sub-command is missing, it is assumed to be configure:
- configure - pack children into the parent with optional layout
- forget - remove children from its parent
- children - return the list of children
- slaves - return the list of children currently packed
- destroy - remove children from its parent and delete all objects
-
parent - return the parent container name
Pack also makes sure that any specified layout properties are valid for the parent.
Widget hierarchy naming conventions only apply to component objects, i.e., widgets. You should avoid widget path names for attribute objects.
Font smallFontObj sans plain 8 Font largeFontObj sans bold 18 Label .fineprint -text $fineprint -font $smallFontObj Label .warning -text $warning -font $largeFontObj
Styles
Each widget may have a styleName property set. If no styleName is set for any widget, a default styleName of Default is set when the widget is packed into a parent. The style is then determined by the root window setDefaultStyle method.
The current set of pre-defined styles are:
- slateblue (default)
- glassblue
- transgreen
- bisque
- lightwhite
-
greenscreen
To change the default style:
. setDefaultStyle $name
where $name is one of the styles listed above.
Echo2 Constants
Æjaks provides easy access to constants (Java static class fields) defined in the Echo2 objects by defining Incr Tcl static proc's to return the field value.
For example, the SplitPane Echo2 object defines a number of constants to specify the orientation of the SplitPane, whether it should be vertical or horizontal. An example Java static field is SplitPane.ORIENTATION_HORIZONTAL
You can access the constant as:
SplitPane .split -orientation [SplitPane::ORIENTATION_HORIZONTAL]
For attribute objects that require constants as part of the constructor, the name can simply be used, in upper or lower case:
Border myBorder 4px black style_inset
Note that Echo2 properties that are prefixed with the value PROPTERY_ are not proxied in the Æjaks objects.
argv and argv0
Several arguments are set for the script to use if needed. These are available in the global variable argv, and are in the form of -argname value -argname value .... If an argument is not available, it will not be added to the argv list. The argv list is always an even number of elements, so it can be easily loaded into a array.
- -remoteIpAddr ip-address
- -encoding charset-name
- -userAgent user-agent
- -userApp user-application
- -userAppVer user-application-version
- -language user-preferred-language
- -locales locales-list
- -platform user-platform
- -utcOffsetMinutes integer-offset-minutes
- -cookies name-value-list
- -contextpath context-path
- -requestURI request-uri
- -servletName servlet-name
-
-requestParameters name-value-list
The global variable argv0 will contain the full pathname of the script that is executed. You can use the directory name component to source other script files, find image resources, etc.
Any additional request parameters found on the URI are made available in the -requestParameters argument. The request parametes are a flat list of name value pairs. The request parameter AEJAKS_SCRIPT_NAME is removed from the list if used to designate the script. Request paramenter values are URI-decoded.
Any cookies that the browser may forward on application startup are made available in the -cookies argument. The cookies are a flat list of name value pairs. Cookie values are URI-decoded.
Attributes
Attribute objects are generally immutable, the constructor specifies all object attributes, and cannot be altered after construction. DerivedMutableStyle and MutableStyleSheet are exceptions.
The Char object is not an Echo2 object, but rather an Incr Tcl class of static procs to return the character value for common HTML and Unicode characters.
set msg "[Char::getChar AElig]jaks is really cool!"
set msg "The card you picked is the Ace of [Char::getChar hearts]"
Here is the list of attribute objects:
- Alignment
- AwtImageReference
- Border
- Char
- Color
- DerivedMutableStyle
- Extent
- FillImageBorder
- FillImage
- Font
- HttpImageReference
- ImageReference
- Insets
- LayoutDirection
- MutableStyleSheet
- MutableStyle
- ResourceImageReference
-
StreamImageReference
Layout Data Objects
Layout Data Objects are implicitly used with the Pack command. Pack will construct the appropriate LayoutData object for use by the parent container and set values specified as Pack arguments.
Here is the list of layout objects:
- AccordionPaneLayoutData
- ColumnLayoutData
- DisplayLayoutData
- GridLayoutData
- LayoutData
- RowLayoutData
- ScrollableDisplayLayoutData
- SplitPaneLayoutData
- TableLayoutData
- TabPaneLayoutData
Containers
Container objects can hold other container or component objects. Some containers can only have a limited number of child objects that are not WindowPane objects.
WindowPane objects are floating windows, and multiple WindowPane's can be packed into one parent. WindowPane objects may be modal, requiring action before any other widgets can be manipulated (non-modal is the default.) WindowPane and Window objects also have a -onclose property that specifies a block of Tcl code to be run when the window is closed. When an application is terminated, either from user action or session time out, the Window object's -onclose code is evaluated.
The Window object is pre-constructed upon application startup, and contains a child ContentPane by default. It has a method to exit the application gracefully and navigate to another web page:
. exitApp /index.html
Window, WindowPane, and ContentPane objects may only accomodate a single child component. A SplitPane may only hold two components, top and bottom in the case of horizontal orientation, or left and right in the case of vertical orientation.
Row and Column objects can contain any number of children. These are the containers used most often to layout an application.
Grid objects are specified as either horizontal or vertical orientation, with a specific size. As components are packed into a grid, they occupy the next column cell (horizontal orientation) or row cell (vertical orientation) until the size is reached. Then, the next component to be packed advances to the next row or column.
DragSource enables its child to be the source of a Drag and Drop operation. Target widgets must be specified to the DragSource container, along with a command to be executed when the drop operation is complete. When a drop event is fired, the name of the drop target widget is appended to the callback.
AccordionPane and TabPane are similar containers which arrange their children as tabs. Child objects are typically ContentPane containers. When child containers are packed into an AccordionPane or TabPane, the Pack option -title xxx assigns a title to that tab.
TransitionPane applies visual eye-candy when the child container is replaced.
GroupBox draws a border around its contents, and provides a title for the box.
ExpandableSection allows its contents to be collapsed and expanded, with title and control icons.
Two containers, ContentPaneEx and ContainerEx have additional capabilities in that they allow pixel-level control on positioning child components. Also, both of these containers may contain any number of child components. ContentPaneEx can be used anywhere a Pane container is required; ContainerEx can be used anywhere a non-Pane container required.
Here is the list of container objects
- AccordionPane
- BorderPane
- Column
- ContainerEx
- ContentPane
- ContentPaneEx
- DragSource
- ExpandableSection
- Grid
- GroupBox
- MenuBarPane
- Panel
- Row
- SplitPane
- TabPane
- TransitionPane
- WindowPane
-
Window
Components
Component objects are the primary visible widgets. Many of the components also have a -command property that specifies a block to Tcl code to be executed for some action event. For example, in the case of the Button object, the -command specifies Tcl code to run when the Button is pushed. All widgets inherit from the Component object.
Button .exit -text "Exit Application" -command { . exitApp /index.html }
ListBox and SelectField objects have underlying Render, Model, and Selection Model objects. These are defaulted in Æjaks. Appropriate methods of these objects are exposed as ListBox and SelectField methods.
For example, the ListModel method add is exposed as the ListBox insert method.
ListBox .cars .cars insert end Chevy Dodge Ford
Likewise, the Table object exposes its default TableModel as ordinary methods.
Here is the list of widget components:
- AbleProperties
- Button
- CalendarSelect
- ChartDisplay
- CheckBox
- ColorSelect
- ComboBox
- DirectHtml
- DropDownMenu
- Label
- ListBox
- Menu
- PasswordField
- ProgressBar
- RadioButton
- RichTextArea
- SelectField
- Slider
- Strut
- Table
- TextArea
- TextField
- TextFieldEx
-
UploadSelect
Commands
Æjaks also has a number of commands to control or initiate various functions.
Pack has already been mentioned above, as it is integral to arrange widgets inside of parent containers.
BrowserCommand can initiate a number of browser related functions, such as opening a new browser window, setting cookies, print dialogs, and evaluating arbitrary Javascript code:
BrowserCommand openwindow http://aejaks.sf.net/ newwindow ""
BrowserCommand setcookie userid $userid -maxAge 3600
BrowserCommand print
Download initiates a file download to the browser. The content to download can either be a simple string, a file, or a Java byte array object. Note that a content-type and filename are required. The content-type allows the browser to select a suitable helper application or plug-in, and the filename provides a hint to the browser if saving the file:
Download application/pdf report.pdf -file /tmp/report.pdf -delete
Log allows access to the Log4j logging system. Log is a shortcut for the Window method . log .
Log $logLevel $msg
where logLevel is debug, error, fatal, info, trace, or warn. Actual output to the log file and/or console is dependent on the Log4j settings.
TaskQueue manages the Echo2 polling feature, allowing asynchronous-like updating of widgets. In reality, a TaskQueue instructs the browser to begin polling on a regular interval, so that widgets can be updated without user interaction. Care should be taken to only use TaskQueue when required, and to set the polling interval to an appropriate amount.
TaskQueue is typically used with the Tcl after command. Care should be taken to construct the TaskQueue add command, since it will be evaluated once during the after command, and once again when executed. The Tcl list command is useful to ensure the TaskQueue command is quoted properly:
TaskQueue create
TaskQueue setpollinginterval 5000
after 5000 [list TaskQueue add [list .alert_label configure -foreground red]]
Here is the list of commands:
- BrowserCommand
- Download
- Log
- Pack
-
TaskQueue
Tables
The Table component presents data in tabular form. Tables can be used with default presentation, or can have programmer specified rendering for headers and cells.
Table Basics
Conceptually, tables consist of a 2D array of data elements and a presentation. Tables can also have a behavior by specifing single or multiple row selection, and a command to execute when a row or rows are selected.
Table Configuration
The two basic configuration options are -rows and -columns to specify the dimensions of the table. Table dimensions may be altered after the table is created. Null data ("") is inserted for additional row and column cells beyond the current dimensions.
Also, rows may be inserted or deleted with table instance methods. Using insert or delete row methods will update the number of rows in the table.
Table Data
Table data is specified in one of two ways: 1) upon inserting a row with a list of column data, or 2) setting a specific cell data.
Using the insertRow method:
Table .tab -rows 0 -columns 2 .tab insertRow 0 [list "first" "second"] .tab insertRow 1 [list "third" "fourth"]
Using the setValueAt method:
Table .tab -rows 2 -columns 2 .tab setValueAt "first" 0 0 .tab setValueAt "second" 1 0 .tab setValueAt "third" 0 1 .tab setValueAt "fourth" 1 1
Note that setValueAt has arguments of value column row.
Table Rendering
Without specifing custom header or cell renderers, table data is displayed as Label components. There are two ways to alter Table cell components.
First is the setCellLableAttrs method, which allows modification of the cell Label component.
Table .tab -columns 2 .tab insertRow 0 {Smoke Mirrars} .tab setCellLabelAttrs 0 0 -foreground grey .tab setCellLabelAttrs 1 0 -text Mirrors -foreground red # TableLayoutData to change the cell background TableLayoutData greenCell -background green .tab setCellLabelAttrs 0 0 -layoutData greenCell
Note: any changes to a Table's row or column values, such as additional insertRow methods will cause the Table to be re-rendered, thus creating new Label components. You should invoke setCellLabelAttrs methods after all insertRow commands have been executed.
To change the actual component rendered, header and cell renderers allow specific Tcl procedures to be called to create and populate column headers and cells with any widget.
Table .tab -rows 2 -columns 2 -headerRenderer tableHeaderRenderer -cellRenderer tableCellRenderer
A -headerRenderer procedure must be defined as:
proc procName {tableWidget headerWidget value columnIdx} { .. body.. }
Example:
proc tableHeaderRenderer {tableWidget headerWidget value columnIdx} { if {! [itcl::is object $headerWidget] } { Label $headerWidget -foreground blue } $headerWidget configure -text $value }
When called, the procedure is passed the tableWidget name, a headerWidget name, a header value, and a column number. The procedure must create the headerWidget if it does not already exists, and populate it with the value parameter.
A -cellRenderer procedure must defined as:
proc procName {tableWidget cellWidget value columnIdx rowNum} { .. body .. }
Example:
proc tableCellRenderer {tableWidget cellWidget value columnIdx rowNum} { if {$columnIdx == 0} { if {! [itcl::is object $cellWidget] } { SelectField $cellWidget -width 6em $cellWidget insert end Red Green Blue } $cellWidget setSelectedItem $value } else { if {! [itcl::is object $cellWidget] } { Label $cellWidget } $cellWidget configure -text $value } }
When called, the procedure is passed the tableWidget name, a headerWidget name, a header value, a column number, and a row number. Same as in the case of the -headerRenderer, the -cellRenderer must create the cellWidget if it does not already exist, and populate it with the value parameter.
For both the -headerRenderer and -cellRenderer, any valid component can be created. Additionally, TableLayoutData can be created to control how the component is placed within the table header or cell. Use the component's -layoutData property to specify the TableLayoutData object.
If either procedure raises an error, or if the headerWidget or cellWidget was not created, a simple Label component will be created and assigned with the header or cell value.
Menus
Menus require two widgets: a Menu widget which describes the menu structure and a MenuBarPane container widget to dispplay the menu.
Menu Basics
Make room for a MenuBarPane container by first creating a SplitPane to contain the MenuBarPane and main content windows.
SplitPane .top -orientation [SplitPane::ORIENTATION_VERTICAL] -separatorPosition 25 Pack .top MenuBarPane .top.mb ContentPane .top.content Pack .top.mb .top.content
Menu objects can exists outside of the normal widget packging heirarchy. Each menu object or item requries an id to distinguish itself. If a -text option is not specified, the -id also becomes the name of the menu item. Sub-menus are added to its parent by adding it as a cascade option.
Menu main # a cascading child menu Menu main.file -id File -text File Menu main.edit -id Edit -test Edit Menu main.help -id Help -test Help main add cascade main.file main add cascade main.edit main add cascade main.help # attach the Menu object to the MenuBarPane widget .top.mb menu main
Menu Item Types
Menus consist of some number or option commands, cascade sub-menus, radiobutton buttons, checkbutton buttons, or separator items.
option specifies the most common option: a menu item that invokes a command.
main.file add options -id New -text New -command newFileCommand main.file add options -id Save -text Save -command saveFileCommand main.file add options -id Exit -text Exit -command exitProgram
cascade specifies sub-menus to be added to the menu. See above as and example of adding a sub-menu to a parent.
radiobutton specifies a group of buttons, one of which is selected at any one time. -group should be specifies if there is more than one group of radiobuttons, otherwise, the radiobutton will be a member of the default group.
main.edit add radiobutton -id Plain -text Plain -group style main.edit add radiobutton -id Bold -text Bold -group style main.edit add radiobutton -id Italic -text Italic -group style
The state of a radiobutton menu item can be determined by quering the MenuBarPane object (not the Menu), using the -id of each item:
foreach item {Plain Bold Italic} { if {.top.mb isSelected $item} {   set fontStyle $item } }
checkbutton is similar to radiobutton, but each option has its own state:
main.edit add checkbutton -id Arhive -text Archive main.edit add checkbutton -id Binary -text Binary # check the menuBarPane by item set archive 0 set binary 0 if {[.top.mb isSelected Arhive]} { set archived 1 } if {[.top.mb isSelected Binary]} { set binary 1 }
separator items merely draw a line to separate groups of meny items:
main.edit add radiobutton -id Plain -text Plain -group style main.edit add radiobutton -id Bold -text Bold -group style main.edit add radiobutton -id Italic -text Italic -group style main.edit add separator main.edit add checkbutton -id Arhive -text Archive main.edit add checkbutton -id Binary -text Binary
Build Notes
Æjaks requires Java 1.5 or higher to build the Java-based components. An ant build.xml file compiles the Java files and builds the .war.
To rebuild, you should have ant installed (website). Simply invoke ant from top level directory.
You can also rebuild using Eclipse. Right-click on the build.xml file, and select Run As -> Ant Build.
Most of the Tcl files are machine generated. After starting off writing initial Incr Tcl proxy classes by hand, it quickly became apparent that this process could be automated and provide more accurate results.
The JTcl program tools/itclproxy.tcl generates the Incr Tcl proxy class files by inspecting Java objects, and adding custom code as necessary. The shell script tools/mkObjs.sh kicks off the process. It invokes tools/mkAejak.tcl which controls the generation process. The file tools/mkAejakCustom.tcl specifies additional custom code that is injected for certain objects. This is where custom constructors and methods are added.
For the attribute objects (e.g., Color, Font, etc.), the custom constructor instead calls a _newObj_ static procedure that takes the constructor argument list and returns a Java object. When another component object configures one of its properties using constructor arguments, the _newObj_ for the attribute object is invoked and returned.
Finally, tools/mkObjs.sh builds a tclIndex file to autoload source files when their classes are referenced. The few hand built Tcl files (Pack, Char, etc.) are copied from the tools directory into the library directory before the tclIndex is built.
Windows users who wish to develop will probably need to use Cygwin or MSYS Bash shell and utilities to rebuild the Incr Tcl classes.