This document discusses the Topher F3 demo based on the original Topher's Breakfast Cereal Character Guide web page as well as on its Simile application version. It's assumed that the reader is familiar with the Java programming language and has read the F3 Programming Language document.
It's also assumed that the reader has run the F3 Topher Webstart application.
The application's F3 source code files are:
Content in the original web page is static. This page has limited query capabalities as it allows link-based selection by brand only:
The Simile version is a purely client-side application written in a combination of Javascript and CSS. This version improves significantly over the original webpage by providing sophisticated query capabilities based on the notion of selecting one or more elements of four filters: brand, country, decade and character form. It also provides for user-defined ordering and grouping and boasts a pop-up window displaying the character's description as well as a timeline graph:
The F3 version mimics the Simile version but uses a richer Swing GUI that makes better use of window real estate by using tabs for filter criteria and combo boxes for sort criteria. Because this version is meant to illustrate F3 capabilities rather than fully supporting the guide, though, some features present in the Simile version (such as result grouping and description pop-up) have been omitted.
This application is the subject of this document and is analyzed from both the application user and the F3 programmer perspectives.
In this section the Topher F3 application is presented in terms of its behavior and the tasks required for its use.
Four visual and functional areas are identified that make up the application window:
This section constitutes the bulk of this document and is meant to dissect and provide a detailed analysis of the F3 implementation. Among others, the following F3 features are emphasized as they're especially exploited in the code:
foreach templates.
A common construct used in conjunction with binding and object
literal variables.
This section begins by examining the simple CerealCharacter
class and its relationships with the four enumerations used as
filter criteria.
The DB class is then examined in detail as the
application's workhorse providing support for generic queries on
enumerations and dynamic ordering. This class illustrates the
use of:
Finally, class ExhibitView is analyzed as the
core class of the Topher application GUI.
This class illustrates the use of:
foreachThis section presents the Topher F3 application from the end user's perspective:
The Topher demo is an F3 application that displays characters used on cereal boxes for promotional purposes. The Topher website maintains over 900 images and detailed descriptions of such characters discriminated by:
The application's window is divided into four main areas:
Of these four areas only the filter and sort ones have application functionality.
Filtering is the act of restricting the number of characters displayed by specifying what values to select for any combination of the brand, country, decade and form properties.
The filter area is composed of four tabs -one per filter attribute- each of which displays the distinct values taken by the corresponding property:
When a filter criteria value is selected, the display area will show only the characters whose corresponding property value match that of the value selected:
When more than one filter criteria values are selected the results displayed correspond to the set union of such values:
It is possible to specify criteria for more than one property at a time. In this case, the result area will show only those characters that satisfy simultaneously the filter criteria specified for each property. The results displayed in this case correspond to the set intersection of the values returned by each individual query.
By convention, when no property values are selected then all characters for the corresponding property are returned. Thus, leaving all values unselected is equivalent to selecting them all.
In addition to selecting what characters to display based on their property values it's also possible to order the results by up to five criteria:
where label corresponds to the character's name.
Thus, the above query can be ordered as shown to look like:
There are no provision to sort in descending order or to group results.
Note that filtering and sorting take place immediately after being specified. Thus, for example, when a brand is selected the display area will be automatically updated to show only characters in that brand without any intervening "execute button".
This section presents the Topher application from the perspective of the F3 programmer. Emphasis is placed in illustrating features of the F3 language the are peculiar to it and especially those for which there is no Java equivalent.
From a programming perspective, the Topher application has two layers:
Both layers are examined in turn by presenting a detailed analysis of the language features used by the implementation.
This is a code-intense section that requires basic familiarity with the concepts and syntax of F3.
CerealCharacter class
The subject of our example application is the
CerealCharacter class:

The F3 definition for this class is:
public class CerealCharacter { public attribute type: String; public attribute label: String; public attribute cereal: String; public attribute brand: String; public attribute decade: String; public attribute country: String; public attribute form: String; public attribute image: String; public attribute url: String; public attribute text: String; public attribute thumbnail: String; }
This is a rather trivial class dealt with primarily by the GUI portion
of the application. Its attribute names are self-explanatory.
The remainder of this section will cover the DB class
used by the application to access the collection of
CerealCharacters but which is totally agnostic with
respect to the structure and semantics of any particular database
relation.
DB Class: An In-memory Database
Class DB is the workhorse of Topher application.
This class contains data, filters, orderings and results of
user-defined queries.

The definition of the DB class is:
public class DB { public attribute extent: **; public attribute filter: AttributeValueAssertion*; public attribute sortCriteria: Attribute*; public attribute selection: **; private function applyFilter(obj):Boolean; }
The above attributes are defined as follows:
extent: An in-memory database relation
implemented as an untyped array that can hold instances of any F3
class. For our particular application, the extent is an array of
instances of the CerealCharacter class.
filter:
An array of filter criteria that determine what subset of the
extent to return as query results. Filter criteria are of type
AttributeValueAssertion
which consist of a reflective attribute of the extent class plus
zero or more values of such attribute. These values are matched
against the extent to select query result elements. In our example
application, there are filters defined for the brand,
country, decade and form
enumerations.
sortCriteria:
An array of reflective attributes used as sort criteria. This
array determines how the results returned by a query are to be
ordered. In our example case, there are sort criteria for each of
the CerealCharacter enumerations used for filtering
plus the character's label (name) attribute.
selection:
An untyped array that contains the results of the current query.
This array is the subset of extent elements that match the filters
in effect at any given time.
The applyFilter() private function is used to check
whether a particular instance of the extent matches the current set
of filter criteria.
The above defines a generic mechanism that can perform
queries on any collection of F3 class instances. The use of untyped
arrays and reflective attributes allows for any F3 class to be used
as an extent. It also allows for the formulation of arbitrary query
filters and sort criteria. There's a restriction, though, in that
filters operate only on enumerated attributes such as
brand or country; there are no provisions
for queries based on comparisons, wildcards or their combinations.
Note that both extent and selection are
arrays of the wildcard type frequently referred to as
"any." The wildcard type is akin to Java's
Object in that an attribute of this type can hold any
value regardless of its object's type. Unlike Java's
Object, however, the wildcard type is not an F3
class. The wildcard type is better thought of as "untyped." Its main
use is to store values whose runtime type is not known in advance. It's
especially well suited to implement collection-type classes dealing
with their contents in a generic fashion that is independent of their
class. The wildcard type is specified in F3 with an asterisk in place
of the attribute type.
public attribute extent: **; // ** means "zero or more of any" // the first asterisk is the "wildcard" type, // the second is the cardinality specifier public attribute selection: **;
Arrays of the wildcard type can contain elements of heterogeneous types.
var any:**; insert 'Hi!' into any; insert 0 into any; insert new <<java.util.Date>>() into any;
In the DB class, however, extents are required to
hold elements of a single class. This class is referred to as the
extent class.
The extent of a DB is untyped and can
store in it collections of instances of any F3 class.
Due to this generality, accessing attributes of objects stored in
an extent requires reflection.
Like in Java, an object can refer to its class through its
class property, as in:
var greeting = "Hi there!"; var clazz = greeting.class; println(clazz.Name); // prints 'String'
Retrieving attribute metadata is also straightforward:
class Person { attribute name: String; attribute age: Number; } var nameAttr = Person.class.Attributes[Name == 'name']; var ageAttr = Person.class.Attributes[Name == 'age'];
In the above snippet, nameAttr and ageAttr
are of type Attribute.
Variables can be indexed with expressions of type
Attribbute (as long as such expressions correspond to
actual attributes of the variable). Thus, it's possible to
retrieve attributes reflectively like in:
var alex = Person { name: 'Alex', age: 7 }; println(alex[nameAttr]); // Prints 'Alex' println(alex[ageAttr]); // Prints '7' println(alex[nameAttr] == alex.name); // Prints true println(alex[ageAttr] == alex.age); // Prints true
The Topher F3 application makes extensive use of reflective attribute access.
DB has a filter array attribute of type
AttributeValueAssertion where user selections for
each extent filter attribute are stored.
For any given extent the filter array
contains as many elements as extent properties are being used for
filtering. Thus, in the Topher application, the
filter array will contain four elements
corresponding to brand, country,
decade and form.
Class AttributeValueAssertion is defined as:
public class AttributeValueAssertion { public attribute attr: Attribute; public attribute values: **; public function evaluate(obj):Boolean; } function AttributeValueAssertion.evaluate(obj) = sizeof values == 0 or obj[attr] in values;
This class embodies the notion of filter criteria.
attr is a reflective attribute corresponding to an
enumerated extent class attribute.
values is an untyped array containing the values
currently selected by the user for filtering.
evaluate() function asserts whether a given object
(of type attr) is among the filter values.
To illustrate the above, lets consider the case where the user wants to filter characters to display only those of brands Kellog's or Nestlé and decade 1990. The following object literals correspond to these filters:
AttributeValueAssertion { attr: CerealCharacter.class.Attribute[Name == 'brand'] values: [ 'Kellog\'s', 'Nestlé' ] } AttributeValueAssertion { attr: CerealCharacter.class.Attribute[Name == 'decade'] values: [ '1990' ] }
Function evaluate() is straightforward: an object
matches the assertion if either there are no values specified or if
its corresponding attribute value is among the assertion's
values:
function AttributeValueAssertion.evaluate(obj) = sizeof values == 0 or obj[attr] in values;
Unconditionally matching an object if the user has specified no
filter values (sizeof values == 0) implements the
convention that selecting no filter values is equivalent to selecting
them all.
If the user has specified at least one filter value then
the attr attribute of the object is looked up in the
values array (obj[attr] in values).
Elaborating on the above, let's consider the following extent instances:
var trixRabbit = CerealCharacter { label: 'Trix Rabbit' brand: 'General Mills' decade: '1960' country: 'USA' form: 'rabbit' }; var batman = CerealCharacter { label: 'Batman' brand: 'Ralston' decade: '1980' country: 'USA' form: 'person' }; var winnie = CerealCharacter { label: 'Winnie the Pooh' brand: 'Nabisco' decade: '1970' country: 'USA' form: 'bear' };
And the following assertion:
var brandAttr = CerealCharacter.class.Attributes[Name == 'brand']; var brandAssertion = AttributeValueAssertion { attr: brandAttr values: [ 'Nabisco', 'Ralston' ] };
We can now formulate filter queries like:
// Prints true: batman is of brand Ralston println(batman[brandAttr] in brandAssertion.values); // Prints true: winnie is of brand Nabisco println(winnie[brandAttr] in brandAssertion.values); // Prints false: trixRabbit is of brand General Mills, not Nabisco or Ralston println(trixRabbit[brandAttr] in brandAssertion.values);
Note, by the way, that in this example expressions like
winnie[brandAttr] could have been written as
winnie.brand. We use the reflective notation instead
to illustrate how generic filtering works.
So far, we've considered the case in which only one filter is applied
over the extent. Class DB has a selection
attribute that holds the results of the current user-defined query.
This attribute contains the intersection of the sets returned
for each individual filter assertion element.
To understand how this works, let's assume we have the following four user-defined filters:
var brandAttr = CerealCharacter.class.Attributes[Name == 'brand']; var brandAssertion = AttributeValueAssertion { attr: brandAttr values: [ 'Nabisco', 'Ralston' ] }; var countryAttr = CerealCharacter.class.Attributes[Name == 'country']; var brandAssertion = AttributeValueAssertion { attr: countryAttr values: [ 'USA', 'France' ] }; var formAttr = CerealCharacter.class.Attributes[Name == 'form']; var formAssertion = AttributeValueAssertion { attr: formAttr values: [ 'person', 'rabbit', 'bear' ] }; var decadeAttr = CerealCharacter.class.Attributes[Name == 'decade']; var decadeAssertion = AttributeValueAssertion { attr: decadeAttr values: [ '1960', '1970', '1980' ] }; filter = [ brandAssertion, countryAssertion, formAssertion, decadeAssertion ];
Let's revisit the previously defined extent instance batman:
var batman = CerealCharacter { label: 'Batman' brand: 'Ralston' decade: '1980' country: 'USA' form: 'person' };
If we evaluate the expression:
select f.evaluate(obj) from f in filter
we'll get the following array:
[ true, // 'Ralston' is among {Nabisco, Ralston} true, // 'USA' is among {USA, France} true, // '1980' is among {1960, 1970, 1980} true, // 'person' is among {people, rabbit, bear} ]
Because all conditions are true batman will be selected by the query because it holds that:
not (false in (select f.evaluate(obj) from f in filter));
that is, false does not appear among the boolean
array returned by the above select statement.
If we instead revisit the extent instance trixRabbit:
var trixRabbit = CerealCharacter { label: 'Trix Rabbit' brand: 'General Mills' decade: '1960' country: 'USA' form: 'rabbit' };
The resulting boolean array is:
[ false, // 'General Mills' is not among {Nabisco, Ralston} true, // 'USA' is among {USA, France} true, // '1960' is among {1960, 1970, 1980} true, // 'rabbit' is among {people, rabbit, bear} ]
Because false does appear in the boolean array returned by
the select statement, trixRabbit will not be
selected by the query.
After seeing these examples we can take a look at the
DB.applyFilter() function declaration:
function DB.applyFilter(obj) = not (false in (select f.evaluate(obj) from f in filter));
We can now introduce an expression that yields the array of extent
elements selected by the combination of DB
filters:
extent[elm | this.applyFilter(elm) == true]
The above is an array predicate that reads:
each elm in extent such that
applyFilter(elm) yields true.
This expression generates an array containing all elements in the
extent array for which function applyFilter
returns true (the result set).
Since function applyFilter has a Boolean
return type, the above can be shortened to:
extent[elm | this.applyFilter(elm)]
An even more succint form of the above expression -à la XPath- is:
extent[this.applyFilter(.)]
Note, incidentally, how calculating the result set is possible without any intervening procedural logic. Thanks to F3's functional programming features it's simple to derive non-trivial results through the use of array predicates. All this without loops, conditional statements or intermediate variables.
The F3 order by operator is a binary operator. The
left-hand side is any list of objects. The right-hand side is an
expression that must return a list of Comparable objects.
The right-hand expression is evaluated in the context of each element
of the left hand side. The current element can be accessed with the .
(dot) operator (as in XPath).
var winnie = CerealCharacter { label: 'Winnie the Pooh' brand: 'Nabisco' decade: '1970' country: 'USA' form: 'bear' }; var leRequinKix = CerealCharacter { label: 'Le Requin Kix' brand: 'Nestlé' decade: '1980' country: 'France' form: 'shark' }; var pico = CerealCharacter { label: 'Pico' brand: 'Nestlé' decade: '1980' country: 'Spain' form: 'dog' }; var characters = [winnie, leRequinKix, pico]; var orderedCharacters = select c from c in characters order by [decade, country]; for (c in orderedCharacters) { println("{c.label} ({c.decade}): {c.brand}, {c.country}"); } /* prints: Winnie the Pooh (1970): Nabisco, USA Le Requin Kix (1980): Nestlé, France Pico (1980): Nestlé, Spain */
The right-hand side of the order by clause is not
limited to a list of attribute values. It can also contain arbitrary
expressions as in:
var orderedCharacters = select c from c in characters order by [if country == 'France' then 0 else 1, decade]; for (c in orderedCharacters) { println("{c.label} ({c.decade}): {c.brand}, {c.country}"); } /* prints: Le Requin Kix (1980): Nestlé, France Winnie the Pooh (1970): Nabisco, USA Pico (1980): Nestlé, Spain */
order by clauses can be combined with attribute
reflection to provide even more general ordering capabilities.
Let's recall attribute sortCriteria of class
DB is an array of reflective type Attribute
that holds the sort criteria specified by the user.
Also, attribute selection of class DB holds
the subset of the extent that matches the current
user-defined filter.
This attribute must be ordered in accordance to the sort criteria
specified by the user and is defined as follows:
attribute DB.selection = bind extent[elm|this.applyFilter(elm)] order by select obj[a] from obj in ., a in sortCriteria;
Here, the right-hand side of the order by expression
is a list of extent attribute values dynamically built from the
sortCriteria attribute.
Since the form obj[attr] is reflectively equivalent
to obj.attr this dynamic ordering works as if
the given sort attributes had been specified explicitly.
The right-hand select statement uses an
alias (obj) for the current element (.).
This is done for readability only as the expression could also be
written as:
select .[a] from a in sortCriteria;
Finally, note the use of bind in the attribute definition.
Thanks to it, F3 keeps track of all direct and indirect dependencies
of this attribute. Thus, any change in the filter or
sortCriteria attributes results in the immediate
-and efficient- recalculation of the result set stored in
selection. This is referred to as
incremental evaluation.
Again, notice how it's possible to keep the result set always up-to-date without any intervening procedural logic. The combination of array predicates and -especially- binding provide an almost "magical" declarative solution.
This section discusses the GUI of the Topher F3 application as follows:
foreach. The
foreach is briefly discussed as an introduction to
its use in subsequent sections.
ExhibitView class.
The ExhibitView class is introduced as the context
for subsequent sections.
CompositeWidgets generally compose their GUI as an
object literal whose containment structure mirrors that of the
GUI's visual appearance. The following snippet shows the
skeletal object literal making up the ExhibitView
widget:
BorderPanel { top: Label // Section #1: top title (html) center: BorderPanel { left: TabbedPane { // Section #2: filter area Tab { // One per filter criterion. BorderPanel { ScrollPane { GroupPanel { // One per filter value SimpleLabel // Filter value count CheckBox // Filter value } } } } } center: BorderPanel { // Section #3: result area top: Label // Result count (html) center: ScrollPane { // Result display area Label // One per result (html). } bottom: GroupPanel { // Section #4: sort area Label // "order by" ComboBox // One per sort criterion. } } } };
A container widget is a type of widget that provides an area inside which other widgets (including, possibly, other containers) are displayed and laid out. The Topher application makes use of the following F3-provided containers:
We use the term visible widgets to refer to GUI components capable of displaying visible content and possibly capturing user input. Our application uses the following visible widgets:
We now examine briefly each of the above F3 widgets.
BorderPanel Container
The BorderPanel is a container that can place its contents
in one of five areas: top, left,
bottom, right and center.
From the Swing programming perspective BorderPanel is a
convenience container whose peer component is a JPanel
configured with a BorderLayout. Note how the terms
NORTH, WEST, etc. have been renamed to
make it more intuitive for developers not familiar with Swing.
It's not necessary to specify all five regions in order to be able
to use a BorderPanel. Thus, if only left
and center are specified the effect is to divide the
area in two vertical regions. Likewise, if only top
and bottom are specified the effect is to divide the
area in two horizontal regions. Region sizes are adjusted
automatically as necessary.
BorderPanel { top: Label { ... } // The Top Title area left: TabbedPane { ... } // The Filter area center: BorderPanel { ... } The Result Display area bottom: GroupPanel { ... } The Sort area }
TabbedPane Container
A TabbedPane container is a multiscreen container
that has Tab handlers to switch among screens.
Only a Tab (the selected tab) is displayed
at a time.
TabbedPane { tabs: [ Tab { title: 'brand' ... }, Tab { title: 'decade' ... }, Tab { title: 'country' ... }, Tab { title: 'form' ... }, ] }
Tabs within a TabbedPabe are containers in
their own right and thus capable of containing visible widgets as well
as other containers.
Tab { content: BorderPanel { top: Label { ... } // Filter criteria name center: BorderPanel { ... } // Filter value checkboxes } }
From the Swing programming perspective TabbedPane wraps
a JTabbedPane component. Unlike Swing's
JTabbedPane, though, F3's TabbedPane makes
the notion of Tab explicit, through a class that can
be populated separatedly. The Tab component is a
JPanel that is the actual container of the
Tab contents.
ScrollPane Container
The ScrollPane container adds vertical and horizontal
scrolling bars to its view content, which is frequently
another container.
ScrollPane { view: Label { ... } // The Result Display area }
From the Swing programming perspective TabbedPane wraps
a JScrollPane as well as a JViewPort. F3's
TabbedPane simplifies scrolling by limiting it to just
populating a view attribute.
GroupPanel Container
The GroupPanel container organizes its contents in rows
and columns so that they align properly. For a widget to be placed
inside a GroupPanel it must initialize its
row and column attributes. Remarkably,
All F3 widgets have these attributes which are simply omitted
they're not contained inside a GroupPanel.
GroupPanel { var row1 = Row {} var row2 = Row {} ... var countColumn = Column {alignment: LEADING} var checkBoxColumn = Column {alignment: LEADING, resizable: true} rows: [row1, row2, ...] columns: [countColumn, checkBoxColumn] content: [ SimpleLabel { text: '3' row: row1 column: countColumn }, CheckBox { text: 'General Mills' row: row1 column: checkBoxColumn }, SimpleLabel { text: '9' row: row2 column: countColumn }, CheckBox { text: 'Kellog\'s' row: row2 column: checkBoxColumn }, ... ] }
Where Row and Column are auxiliary classes
that control alignment and resizability.
The attributes of GroupPanel are:
rows: An array of Rows with as
many elements as rows there are in the content.
columns: An array of Columns with
sa many elements as columns there are in the content.
content: An array of widgets each of which
specifies at what row and column it must be displayed.
GroupPanel provides a very precise control over the
placement of contained items when these are laid out as a grid.
From the Swing programming perspective GroupPanel
encapsulates a JPanel and a
JDesktop
GroupLayout.
SimpleLabel Widget
The SimpleLabel F3 widget is a component that displays
simple text optionally accompanied by an icon or image. It's typically
used for prompts and boilerplate text.
SimpleLabel { text: 'Country' }
From the Swing programming perspective SimpleLabel
encapsulates a JLabel.
Label Widget
Label is a powerful F3 widget that
supports rich text using HTML 3.2 syntax. Because it can display
arbitrary HTML, Label can be used to display graphics,
style text, show and follow links as F3 operations and also produce
complex text layouts through the use of HTML tables.
Label { text: "<html> <table id='header'> <tr valign='middle'> <td><img src='http://simile.mit.edu/exhibit/examples/cereals/banner.png' style='float: left; margin-right: 2em;'/></td> <td> <p>The information within this page, including all images, is ALL copyrighted by Topher.<br/> The original web page can be found at <a href='http://www.lavasurfer.com/cereal-guide.html' target='_blank'> http://www.lavasurfer.com/cereal-guide.html </a>. </p> <p>We are grateful to Topher for letting us host this data on our site. </p> </p> </td> </tr> </table> </html>" }
The above snippet results in:
From the Swing programming perspective Label wraps an
F3 XLabel which is a simple specialization of
JEditorPane. This pane is configured with an
HTMLToolKit when the text starts with the
<html> directive.
CheckBox Widget
The CheckBox widget wraps a JCheckBox and
behaves like it. Unlike JCheckBox though, it implements
an onChange() operation that makes replaces the
implementation of the ActionListener interface and can be
inlined in object literals.
CheckBox { selected: true text: 'General Mills' onChange: operation(newValue: Boolean) { if (newValue) { println('General Mills has been selected'); } else { println('General Mills has been deselected'); } } }
ComboBox Widget
The ComboBox widget works in conjunction with the
ComboBoxCell class to provide a simple way to
create drop-down lists with selectable items.
ComboBox { cells: [ ComboBoxCell { text: 'Brand' } ComboBoxCell { text: 'Country' } ComboBoxCell { text: 'Decade' } ComboBoxCell { text: 'Form' } ComboBoxCell { text: 'Label' } ] }
From the Swing programming perspective, ComboBox
encapsulates a JComboBox but makes the notion
of drop-down list element explicit through class
ComboBoxCell.
Used in assignments and attribute initializers, the bind
operator drives the F3 interpreter to keep track of all variables and
attributes referenced in the right-hand side of the assignment.
Thus, whenever a variable used in the right-hand side of an assignment
changes its value, F3 recursively recalculates the value of all
dependent bound variables. This is comparable to the way spreadsheets
recalculate formula values whenever the cells they depend upon change
their value.
When used in conjunction with array predicates, bind
can recalculate values that would otherwise require complex
procedural code. Let's revisit the definition of attribute
DB.selection used to store the result set of a filter
query:
attribute DB.selection = bind extent[elm|this.applyFilter(elm)] order by select obj[a] from obj in ., a in sortCriteria;
Here, selection depends on
extent array attribute. Recalculation takes
place whenever an element in this array changes its value as
well as when elements are inserted or deleted.
applyFilter(). Recalculation
takes place whenever variables and attributes external to the
function and referenced inside its body change their value.
sortCriteria array attribute. Recalculation
takes places whenever an element in this array changes its value
as well as when elements are inserted or deleted.
In our Topher example elements of the sortCriteria
change their value depending on the selection of a set of combo boxes.
Thus, when the user changes a sort combo box selection the result set
stored in model.selection will be automatically reordered
to reflect the change. As usual, this takes place without any
intervening procedural logic. It's all based on dependencies.
Function applyFilter() depends on the value of array
attribute filter as well as the application of function
AttributeValueAssertion.evaluate() for each filter
element. This function, in turn, depends on the entries selected by the
user as filter values. Thus, whenever the user selects a checkbox
corresponding to a filter value, the selection is
recalculated to include the newly selected value.
As can be seen, bind promotes a declarative programming
style that minimizes or eliminates the need for procedural state
management and dependency tracking.
It could be argued that the main difference between F3 and regular
Swing programming is that in Swing it's the programmer who statically
wires bindings through procedural listeners and event handlers. In
F3 bindings area automatically taken care of by declaring dependencies
through the bind operator.
foreach Operator
The foreach operator iterates over an array and yields
another array built from repeatedly expanding its control variable
within its template body. The template body can contain object literals.
Likewise, the resulting array can be inserted into object literals
where it's interpreted as if defined statically.
ComboBox { cells: foreach (name in [ 'brand', 'country','decade', 'form', 'label' ]) ComboBoxCell { text: name } }
// Generates: ComboBox { cells: [ ComboBoxCell { text: 'brand' } ComboBoxCell { text: 'country' } ComboBoxCell { text: 'decade' } ComboBoxCell { text: 'form' } ComboBoxCell { text: 'label' } ] }
foreach is extensively used in F3 and in particular in
GUI programming. Thanks to its use, it's possible to create highly
dynamic user interfaces when used in conjunction with binding. This
is so because a bound foreach is re-executed whenever the
expressions it depends upon change their values. As a result, entire
regions of the GUI can change, appear or disappear in response to
model changes without any intervening procedural logic.
ComboBox { cells: bind foreach (j in sortBy) ComboBoxCell { text: j.Name } }
In the above snippet, the combo box will automatically have its cell
array change, grow or shrink depending on whether elements in the
sortBy array are updated, inserted or deleted
(programmatically or in response to user input).
F3 GUIS's are built from object literals inside which widgets are declared and their attributes initialized. The visual composition of the GUI is reflected in the containment structure of widgets defined in its object literal.
// Skeletal containment structure of the Topher GUI BorderPanel { top: Label center: BorderPanel { left: TabbedPane { Tab { BorderPanel { ScrollPane { GroupPanel { SimpleLabel { ... } CheckBox { ... } } } } } } center: BorderPanel { top: Label center: ScrollPane { Label { ... } } bottom: GroupPanel { Label { ... } ComboBox { ComboBoxCell { ... } } } } } };
In addition to attribute initializers, object literals also can contain embedded variables, functions, operations and triggers. These constructs are legal only before or after attribute initializers. They're visible only after their declaration and only inside their enclosing object literal block.
Object literal variables are variables declared between attribute initializers and must be assigned a value in their declaration. These variables can be referenced later in subsequent attribute initializers as well as in function and operation code inside their scope. Code can be embedded in object literals when attributes being initialized are of type function or operation (e.g. event handlers) or in object literal-level functions or operations (discussed below).
BorderPanel { top: Label { ... } var extent = bind (CerealCharacter*)model.extent var selection = bind (CerealCharacter*)model.selection center: BorderPanel { left: TabbedPane { background: white preferredSize: {height: 300, width: 200} tabs: foreach (name in ["brand", "decade", "country", "form"]) Tab { var at = CerealCharacter.class.Attributes[a|a.Name == name] var allValuesOfThisAttribute = bind extent[at] var values = bind (select unique s from s in allValuesOfThisAttribute) order by . var filter = bind model.filter[f|f.attr == at] title: bind "{name} { if sizeof filter.values > 0 then "({sizeof filter.values}/{sizeof values})" else "(all)"}" ... onChange: operation(newValue:Boolean) { updating = true; if (newValue) { if (filter <> null) { insert value into filter.values; } else { insert AttributeValueAssertion { attr: at values: value } into model.filter; } } else { delete filter.values[. == value]; } updating = false; } ... } } } ... }
Note that control variables in foreach expressions behave
as object literal variables.
In addition to variables, stand-alone functions and operations can be embedded inside object literals. These functions and operations can be referenced later in attribute initializers as well as in other code.
... BorderPanel { center: BorderPanel { operation reset() { updating = true; delete model.filter.values; updating = false; } top: Label { text: bind "<html> <b style='font-size:18;'>{sizeof selection}</b> Characters <font color='gray'> {if sizeof selection == sizeof extent then "total</font>" else "filtered from {sizeof extent}</font> (<a href='{#reset}'>reset</a>)" } </html>" } ... } ... } ...
It's possible to declare change triggers on attributes of objects declared inside object literals.
... ComboBox { var: box horizontal: {pref: 100} row: r1 column: otherCols[i] cells: bind foreach (j in sortBy) ComboBoxCell { text: j.Name } selection: bind select indexof x from x in sortBy where x == model.sortCriteria[i] trigger on (k = box.selection) { updating = true; model.sortCriteria[i] = sortBy[k]; updating = false; } }
The var pseudo-attribute is used to introduce a variable
pointing to the context object. Thus, in the above snippet
box is used to refer to the ComboBox
instance being populated. This is comparable to a this
qualifier on the current object.
There are many uses for the var pseudo-attribute.
One of them is the definition of change triggers on attributes
of the context object. In the above snippet, a change
trigger is defined on the combo box's selection
attribute. This trigger alters the value of the model's
sortCriteria attribute. Since the model's
selection attribute is bound to an expression
involving sortCriteria the result set will be
automatically recalculated (in this case reordered.)
ExhibitView GUI class
Class ExhibitView implements the application's GUI
and is defined as:

public class ExhibitView extends CompositeWidget { public attribute model: DB; private attribute updating: Boolean; }
The above attributes are defined as follows:
model: An instance of DB that
constitutes the "backend" of the GUI and encapsulates all
data management-related functionality.
updating: A boolean attribute indicating whether
the result set (selection) is currently being updated
so the display area should be temporarily blank.
Class ExhibitView extends CompositeWidget,
an F3-provided base class used to create new widgets out of the
composition of existing ones. CompositeWidget subclasses
must implement the composeWidget operation which has
the following signature:
protected operation composeWidget(): Widget
In the Topher application the entire GUI is composed by the
ExhibitView.composeWidget() function. This function
returns an object literal whose containment structure mirrors that
of the GUI's visual appearance.
function ExhibitView.composeWidget() = BorderPanel { top: Label // Section #1: top title (html) center: BorderPanel { left: TabbedPane { // Section #2: filter area Tab { // One per filter criterion. BorderPanel { ScrollPane { GroupPanel { // One per filter value SimpleLabel // Filter value count CheckBox // Filter value } } } } } center: BorderPanel { // Section #3: result area top: Label // Result count (html) center: ScrollPane { // Result display area Label // One per result (html). } bottom: GroupPanel { // Section #4: sort area Label // "order by" ComboBox // One per sort criterion. } } } };
Here, the composeWidget returns an object literal
which uses attributes model and updating.
A detailed analysis of how these two attributes are used to provide
application functionality follows below.
Once a new widget has been created by this composition mechanism it can be used wherever a language-provided widget could be legally used.
Frame { onClose: operation() {System.exit(0);} title: "Cereal Characters" height: 500 width: 800 visible: true content: ExhibitView { border: EmptyBorder {top: 10, bottom: 10, left: 10, right: 10} background: white model: main } }
We now proceed to dissect the object literal making up the
ExhibitView widget where all the GUI application
functionality is defined. Unlike the DB class,
the ExhibitView class is "Topher-aware".
The top-level container of the widget is a BorderPanel
for which only the top and center sections
are defined:
BorderPanel { var extent = bind (CerealCharacter*)model.extent var selection = bind (CerealCharacter*)model.selection top: Label { ... } // Top-level title area center: BorderPanel { // Application area ... } }
Here, the object literal variables extent and
selection are declared at the top level because they're
used throughout the ExhibitView widget composition.
Note the casts to CerealCharacter *. These are necessary
because the corresponding model attributes are untyped but the
application code needs to reference them as arrays of type
CerealCharacter.
The top-level title is a static Label containing HTML:
top: Label { text: "<html> <table id='header'> <tr valign='middle'> <td><img src='http://simile.mit.edu/exhibit/examples/cereals/banner.png' style='float: left; margin-right: 2em;'/></td> <td> <p>The information within this page, including all images, is ALL copyrighted by Topher.<br/> The original web page can be found at <a href='http://www.lavasurfer.com/cereal-guide.html' target='_blank'> http://www.lavasurfer.com/cereal-guide.html </a>. </p> <p>We are grateful to Topher for letting us host this data on our site. </p> </p> </td> </tr> </table> </html>" }
The application container contains the windows areas that present application functionality:
center: BorderPanel { left: TabbedPane { // Section #2: filter area } center: BorderPanel { // Section #3: result and sort areas } }
The filter area is enclosed in a TabbedPane featuring
one tab per filter criterion:
left: TabbedPane { background: white preferredSize: {height: 300, width: 200} tabs: foreach (name in ["brand", "decade", "country", "form"]) Tab { ... } }
The following object literal variables are declared inside each
Tab:
Tab { var at = CerealCharacter.class.Attributes[a|a.Name == name] var allValuesOfThisAttribute = extent[at] var values = bind (select unique s from s in allValuesOfThisAttribute) order by . var filter = bind model.filter[f|f.attr == at] title: bind "{name} {if sizeof filter.values > 0 then "({sizeof filter.values}/{sizeof values})" else "(all)"}" ... }
Note that the above variables will hold different values for each
iteration of the foreach loop.
at contains the reflective attribute corresponding
to the current filter name.
allValuesOfThisAttribute is an array whose
elements hold the values of the corresponding attribute in the
extent. Note that -despite it scalar appearance-
the expression extent[at] actually returns an array.
values is an array containing the ordered
list of distinct values occurring in the current extent
attribute. This is actually the enumeration corresponding to the
current filter.
filter holds the filter associated
with the current filter attribute. Recall that this filter (of
type AttributeValueAssertion) contains the list of
values selected by the user for each filter criterion.
Notice how the initializer for the title attribute of the
current Tab references some of above object literal
variables.
Each Tab encloses a border panel that holds the filter
criterion name and the checkbox area:
Tab { ... content: BorderPanel { top: SimpleLabel {text: "<html><b>{name}</b></html>"} center: ScrollPane { view: GroupPanel { ... // Checkbox area } } } }
As can be seen, the actual filter area is enclosed in the
ScrollPane. Its contents are:
GroupPanel { var rows = bind foreach (i in [1..sizeof values]) Row {} var col1 = Column {alignment: LEADING} var col2 = Column {alignment: LEADING, resizable: true} rows: bind rows columns: [col1, col2] content: bind foreach (value in values) [SimpleLabel { row: rows[indexof value] column: col1 text: bind "{sizeof allValuesOfThisAttribute[. == value]}" }, CheckBox { row: rows[indexof value] column: col2 selected: bind value in filter.values text: value onChange: operation(newValue:Boolean) { updating = true; if (newValue) { if (filter <> null) { insert value into filter.values; } else { insert AttributeValueAssertion { attr: at // From outer scope values: value } into model.filter; } } else { delete filter.values[. == value]; } updating = false; } }] }
Recall that attribute rows of container
GroupPanel requires an array of Rows
containing as many elements as rows will be displayed. This
array is normally specified statically as it's generally the
case the programmer knows in advance how many rows and columns
will be used to lay contents out.
In this case, though, rows will contain as many elements
as distinct values there are for the current filter attribute. This
is an example of the use of foreach to generate
dynamic object literals where static ones would be expected.
Variables col1 and col2 correspond to the
columns used to display the number of values and the
filter checkboxes respectively.
The GroupPanel content is populated by a
foreach that iterates over the distinct values of the
current filter attribute. The foreach template body
contains a SimpleLabel and a CheckBox.
The SimpleLabel displays the number of times
the current value occurs in the extent for the current filter
attribute. The CheckBox displays the current value
and has a onChange operation that modifies the
current model's filter attribute depending on
whether the checkbox has been selected or deselected.
Recall that model.filter is an attribute
model.selection depends upon. Insertions and
deletions on the model filter taking place inside the
onChange event handler will result in the automatic
recalculation of the selection. Thus, each time the
user selects or deselects a filter value the result display area
will be immediately updated to reflect the change.
Note how attribute model.updating is used. When it's
set to true, the display area will be blanked out. When set to false,
the recalculated selection will be shown on screen.
This avoids a flickering effect. The code that controls how the
display area is populated follows immediately below.
The result display area is actually a long Label that
formats query results as HTML:
center: BorderPanel { operation reset() { updating = true; delete model.filter.values; updating = false; } top: Label { var location = 'http://simile.mit.edu/exhibit/examples/cereals' text: bind "<html> <b style='font-size:18;'>{sizeof selection}</b> Characters <font color='gray'> {if sizeof selection == sizeof extent then "total</font>" else "filtered from {sizeof extent}</font> (<a href='{#reset}'>reset</a>)"} </html>"} center: ScrollPane { view: Label { var columnCount = 2 var rowCount = bind (sizeof selection / columnCount).intValue() + 1 text: bind if updating then // Blank display area out "<html><body/></html>" else "<html> <table> { foreach (i in [1..rowCount]) "<tr> { foreach (j in [1..columnCount]) "<td> <table cellspacing='5'> <tr> { select "<td><img src='{location}/{c.thumbnail}"></img></td> <td> <table> <tr> <td colspan='2'><b>{c.label}</b></td> </tr> <tr> <td>Brand:</td><td>{c.brand}</td> </tr> <tr> <td>Year:</td><td>{c.decade}</td> </tr> <tr> <td>Country:</td><td>{c.country}</td> </tr> </table> </td>" from c in selection[(i-1)*columnCount + j-1] } </tr> </table> </td>" } </tr>" } </table> </html>" } } }
Operation reset() deletes all filters currently in effect.
Because of the checkbox bindings, this will result in the immediate
deselection of all checkboxes in all tabs.
In the expression delete model.filter.values both
filter and values are arrays. When an
array within another array is referenced without qualification
the entire nested tree of elements is returned. In the particular
case of operation reset() the net effect is nullifying
all filter elements.
Note that the HTML content of the Label making up the
result display area includes an
<a href="#reset">reset</a>
hyperlink. Clicking on this link will result in the invocation of
operation reset(). Thus, a Label doesn't
only format text but can enable user input in a button-like fashion
too. A Label can also mimic a container by laying out
contents inside HTML tables.
Attribute updating is manipulated by both the checkbox
onChange event handler and the reset()
operation. This variable controls what is displayed on the result
display area. If updating is true because filters are
being modified then the display area will be blank. Otherwise, the
display area will contain the extent elements selected by the current
combination of filter values.
The sort area displays a number of ordering criteria as a set of combo boxes as follows:
bottom: GroupPanel { var clazz = CerealCharacter.class var sortBy = select clazz.Attributes[Name == n] from n in ["brand", "country", "decade", "form", "label"] var r1 = Row {} var c1 = Column {alignment: TRAILING} var otherCols = foreach (i in [1..sizeof sortBy]) Column { } rows: r1 columns: [c1, otherCols] content: [SimpleLabel { text: "Order by:" row: r1 column: c1 }, foreach (i in [0..sizeof sortBy-1]) ComboBox { var: box horizontal: {pref: 100} row: r1 column: otherCols[i] cells: bind foreach (j in sortBy) ComboBoxCell { text: j.Name } selection: bind select indexof x from x in sortBy where x == model.sortCriteria[i] trigger on (k = box.selection) { updating = true; model.sortCriteria[i] = sortBy[k]; updating = false; } }] }
sortBy is an array of reflective Attributes
corresponding to the CerealCharacter attributes
used for ordering. This includes all of the filter
criteria plus the label attribute. sortBy
is used in conjunction to the model's sortCriteria
attribute which is also of type Attribute.
The GroupPanel container requires that the arrays of
rows and columns be specified. The number of columns is dynamic
as more (or less) attributes could plausibly be used for ordering.
Thus, the array otherCols of type Column
is built by means of foreach.
The expression [c1, otherCols] would appear to be an
array of two elements. However, because otherCols is
itself an array the expression array is flattened and will contain
as many elements as sizeof otherCols plus one
(corresponding to the leading element c1.)
The selection attribute of each combo box is bound
to the position of the currently selected element within the model's
sortCriteria array attribute.
The change trigger defined on the ComboBox
selection fires each time the user changes the
selection. The action is to set the value of the corresponding
element of the model's sortCriteria attribute.
The model's selection attribute is bound to an expression
involving the sortCriteria attribute. Because of this its
value will be automatically reordered every time the user selects an
option in any of the sort combo boxes.
As can be seen, F3 features powerful constructs that promote a declarative programming style quite different and more productive than regular Swing programming. The main three such constructs are:
foreach
These constructs are not limited to GUI programming but are especially well-suited for it.
F3 is very similar to Java in that it's strongly typed and fully object-oriented. In this regard, most of the differences between the two languages are merely syntactic.
F3, however, borrows from functional programming, XPath and XQuery. In F3, functions and operations are first class citizens that can be declared as class attributes or passed as arguments (including closures.) Likewise, F3 array predicates allow for list access in powerful declarative ways.
Most importantly, F3 features incremental evaluation. In conventional GUI programming much of procedural code is devoted to binding: keeping track of changes and synchronizing dependent state in response to value-changing events. In F3, on the contrary, binding is implemented in a purely declarative way.