Building GUIs - The Basics

Introduction

This section introduces the topic of producing GUIs in Inq. GUI code has its own complexities, if different to issues raised by the demands of server-side environments. There are far fewer concurrency issues, for example, but the matter of large amounts of often repetitive code remains. This code has little to do with the application - instead it exists only to bridge the application to the GUI api. Inq's reified approach eliminates much of this and, when combined with the application's metadata, genuinely reusable components can be written.

Here we outline the main concepts of the Inq GUI environment and present a simple temperature converter. The petstore blueprint application contains many more examples including applications of the reusable components shipped with Inq - the Attribute Editor and the Item Chooser.

The Context Node - A Recap

The term Context Node, often abbreviated to just context, applies in both the client and server environments, and its definition is the node that is yielded by the path $this. The context node was introduced earlier, when discussing Inq's hierarchical data structures. When script is executing at a given context, the absolute path is given by the expression $path.

The context has greater visibility in the client because it is here that the node and its absolute node path (i.e. the path from the root node to the context node) originate. The context is important because it defines the relevant sub-division of the application for script to run in. Anything below the context can be accessed by a path of the form $this.something.useful. Any node reachable from $root remains in the node space (unless explicitly removed), so this data (and any sub-division of it) is the application's persistent state.

There is no path expression that navigates "up and out" of $this, so the context defines the universe for a given script execution. There are ways to switch between contexts while a script runs. These require the use of function pointers (defined with the data type func) that encapsulate the context they were defined in. A func is used to create create an entry point between contexts, resulting in a structured route, rather than the unstructured use and brittle nature of any kind of path expression that could do the same.

In terms of the underlying GUI components in a client, things of significance that are created explicitly in the script become nodes beneath some parent when layout is performed. Inq will create other components to implement the layout, such as vertical and horizontal boxes or scroll panes, but these do not have to become Inq nodes as well. These components participate in the component hierarchy and exist to effect the desired graphical layout and groupings.

A GUI component that is accessible as an Inq node has all its underlying Java Bean properties, and any additional ones implemented by its Inq node, accessible via the child path .properties.<property_name>. When script runs from a GUI event call-back, the context node is the nearest parent that has the property contextNode set to true. This could be the Inq node of the component that fired the event itself, though typically it is an ancestor node. The parent into which the Inq GUI nodes are placed during layout is often the one that defines itself as the context. These are typically one of

  • a top level window;
  • a container that is the immediate child of a tab (and so is the GUI hierarchy for that tab);
  • the root of any component set that has been designed for reuse within the application.

Instance Hierarchies

From the foregoing we note that there are two instance hierarchies - the graphical component hierarchy and the Inq node hierarchy. The component hierarchy exists to achieve the desired GUI layout, while the Inq hierarchy exists to propagate a context to child nodes, as well as to simply contain the GUI items for general use.

The component hierarchy is not of major concern other than at layout time, when the component parent of a batch to be laid out is specified. The Inq hierarchy is generally flat for a set of GUI nodes inheriting a particular context node parent, so that paths are simple and of the (hungarian-style) form $this.bOK, an OK button, for example. As we will see in the section covering layout detail, the Inq and component roots for a particular case can be the same or different.

The Inq hierarchy is important when the tab or card containers are used. If an Inq GUI node has either of these as its Inq parent then properties relating to its tab (such as the text and icon) and/or card (such as visibility) become active.

Basic Steps of GUI Creation

Creating GUIs in Inq involves the following steps:

  1. create the components;
  2. set any required property values;
  3. tell the component what data it is rendering, that is attach the view to the model;
  4. ask Inq to lay out the components according to a specification for their placement, geometry constraints, bordering and other parameters;
  5. establish handler functions for component events of interest.

We use the the example scripted in C2F.inq to illustrate simple GUI building. We can run the example with the invocation inq -in C2F.inq. Here is what it looks like:

C2F Window

Create The Components

Components are created explicitly in script statements or implicitly by directives and groupings specified in the layout. We cover the latter when discussing the layout function in detail.

Components are created by declaring them just like any other variables. The following data types are available:

GUI Component Data Types
Data Type Component Type Underlying Component
gWindowA top-level window that interacts with the system window managerJFrame
gDialogA modal or non-modal dialogJDialog
gIWindowAn top-level window suitable for use with a gDesktopJInternalFrame
gBoxA horizontal or vertical box for use as an intermediate, grouping containerJPanel
gCardA container that displays one of its children at any given timeJPanel
gButtonA simple buttonJButton
gArrowAn arrow buttonBasicArrowButton
gToggleA toggle buttonJToggleButton
gCheckA check boxJCheckBox
gRadioA radio button intended for use with a gButtonGroupJRadioButton
gTableA tableJTable
gListA listJList
gTreeA treeJTree
gToolBarA tool bar containerJToolBar
gMenuBarA menu bar for use as the menuBar property of a gWindow or gIWindowJMenuBar
gMenuA menu suitable for use in a gMenuBar or as a sub-menu in a gPopupMenuJMenu
gLabelA labelJLabel
gMenuButtonA simple menu buttonJMenuItem
gMenuCheckA menu check buttonJCheckBoxMenuItem
gMenuRadioA menu radio buttonJRadioButtonMenuItem
gPopupMenuA popup menu that can be placed on a component using the default or other popup eventJPopupMenu
gButtonGroupProvides access to member radio buttons as a group
gTextFieldA one-line text fieldJTextField
gPasswdFieldA one-line text field that hides its textJPasswordField
gTextAreaA multi-line, single-style text areaJTextArea
gTextPaneA multi-line, multi-style text areaJTextPane
gTabA tab container whose Inq children are the the root of the GUI under each tabJTabbedPane
gSplitA horizontal or vertical split paneJSplitPane
gComboBoxA combo-boxJComboBox
gFileChooserA file chooserJFileChooser
gSpinnerA spin button capable of supporting various underlying data typesJSpinner
gDateChooserA date chooser with popup calendarcom.toedter.calendar.JDateChooser
gProgressBarA progress barJProgressBar
gSliderA slider with a defined numeric rangeJSlider
gDesktopA desktop container to support gIWindow componentsJDesktopPane

The components our example uses are created by the following script fragment

// A top-level window.  By setting its contextNode property to true we
// are saying that events occurring at or below this point in the Inq
// hierarchy will run with $this set to "win".
gWindow win;
win.properties.contextNode = true;
win.properties.defaultCloseOperation = EXIT_ON_CLOSE;


// Create some GUI components. Labels are used to display the flag icons.
gSlider slCelsius;
gTextField tfCelsius;
gLabel lCelcius;

gSlider slFahr;
gTextField tfFahr;
gLabel lFahr;

Set The Required Property Values

Next, the script sets up any properties using each component's properties child:

slCelsius.properties.orientation =
  slFahr.properties.orientation = ORIENT_VERTICAL;

slCelsius.properties.minimum    = -273;
slCelsius.properties.maximum    = 100;
slFahr.properties.minimum = call celciusToFahrenheit(celcius = -273);
slFahr.properties.maximum = call celciusToFahrenheit(celcius = 100);

slCelsius.properties.majorTickSpacing = 13;
slFahr.properties.majorTickSpacing    = 27;
slCelsius.properties.paintTicks  = slFahr.properties.paintTicks  = true;
slCelsius.properties.paintLabels = slFahr.properties.paintLabels = true;

Provided the underlying component supports it, properties can be both set and read in this way. All the JavaBeansTM single-value properties defined by the underlying component are available. Some components define additional properties as part of their implementation in the Inq run-time. These are detailed in the components reference section [TODO].

When Is The Context Established?

The window, win has its contextNode property set to true. Although, as stated above, this means that the context node for event handlers will be win, its important to note that this will not be the context prevailing when the createGUI() function is called. While the GUI is being created the context is a higher-level part of the application, or even $root if we are creating the first window.

This temperature converter is an command line script to simply mockup a GUI. As we will see when we discuss full client-server applications, it can be inconvenient to invoke any server-side services required during GUI setup when the context is not the one that GUI will run in. For this purpose, Inq defines a component event that is fired when that component's context becomes known. This happens during GUI layout, when the component is added to the Inq hierarchy such that an ancestor node has its contextNode property set to true. We will return to this subject when we cover client-server examples.

Attaching Views to Models

All components support Inq-defined properties that are used to specify the data they are rendering. Setting these properties establishes two things:

  • tells the component where in the node space it must look to fetch the data it is displaying (and to update if the component can accept input);
  • dispatches the events Inq raises on the data to the component, so that when the data is changed by client or server-side script the component will refresh.

Using the well-known MVC paradigm, setting these properties is the way Inq binds a view to a model. Once established, the flows to implement MVC are handled by the Inq run-time. While the application may drive the GUI state from specific GUI event handlers, no scripting is required to implement MVC.

Complex components, such as tables, trees and the list forms implement more than one property for this purpose and use complex property values. Simple components rendering single values, like the ones used in this example, require only the renderInfo property to establish their MVC. Here is the script that does this for our temperature converter:

// Create two variables - the "model" data if you like. We use fixed-precision
// numbers for convenience, as the maths cannot be accurate.  Note that
// sliders can only yield integers, but Inq can handle model data of any
// non-integer type for them.
decimal:2 win.vars.celcius;
decimal:2 win.vars.fahr;

// Bind the two celcius views to the same model data....
slCelsius.properties.renderInfo = renderinfo($this.vars.celcius);
tfCelsius.properties.renderInfo = renderinfo($this.vars.celcius, editable = true);

// .... and the same for the fahrenheit views.  These statements mean
// that the GUI will update the model and changes to the model will update
// the GUI.
slFahr.properties.renderInfo = renderinfo($this.vars.fahr);
tfFahr.properties.renderInfo = renderinfo($this.vars.fahr, editable = true);

The model data is two decimal values to hold the temperatures. These are declared underneath the window, win, because that is the context node.

Note
GUI nodes support children but cannot directly contain rendered data items. Such items must be placed in a subordinate map, vars in this example.

The renderInfo Property

The renderInfo property accepts the result of a renderinfo() expression. In its simplest form, renderinfo takes a single argument of a node reference, as in these examples. However, as well as specifying where in the node space the data is, renderinfo can also represent a number of other parameters that affect how this data will be rendered. The syntax of renderinfo is as follows:

"renderinfo" "("
               ( [<expression>]
               | ( "typedef"  "=" <field_reference> | typeof = <expression> "," <expression> )
               | "format"   "=" <expression>
               | "label"    "=" <expression>
               | "width"    "=" <expression>
               | "editable" "=" <expression>
               )
               ( ","
                 ( ( "typedef"  "=" <field_reference> | typeof = <expression> "," <expression> )
                 | "format"   "=" <expression>
                 | "label"    "=" <expression>
                 | "width"    "=" <expression>
                 | "editable" "=" <expression>
                 )
               )*
             ")"

With everything appearing to be optional, examples of the minimum valid renderinfos are

renderinfo($this.vars.useBillingAddress);     // Just an expression

or

renderinfo(typedef=Order.UseBillingAddress);  // Just a typedef/field reference

The optional first argument is an expression whose result is the data to be rendered. In many cases, is a simple node reference of the form $this.vars.foo however the expression can be anything.

Note
The renderinfo's expression is evaluated when the context of its component is established, so as for event handlers, $this refers to the context node after GUI creation and not the context the script is currently running in.

If an expression is present it must be the first argument. The remaining arguments are also optional and independent of their order, so are named.

typedef =

The typedef argument is a field reference to a typedef entity or a reference to a typedef alias. From foregoing typedef examples we could specify

typedef=Entity.GlobalLimit

and

typedef=FXRate

When used with an expression argument, the effect of typedef is to apply the format, width and label from the definition of the field or alias. If no expression is specified then Inq assumes a default node path of $this.<type_name>.<field_name> where <type_name> is the name of the typedef, or its override if it defines one.

The default path is suitable when resolving children of a node set structure, such as those Inq builds when applying non-unique keys. These structures are often displayed as a table or list form, so renderinfo expressions used for these components typically do not require the expression argument.

Note
As an alternative to providing type information statically, typeof can be used when the type is not known until run-time. This is covered when we look at the reusable component, the Attribute Editor [to be written up].
format =

A string expression that specifies the format pattern. If present, the argument overrides any format implied by a typedef argument.

label =

A string expression that specifies the label. If present, the argument overrides any label implied by a typedef argument.

width =

An integer expression that specifies the width. If present, the argument overrides any format implied by a typedef argument. The width is used to dimension the component as a the character width of lower-case 'n' in the component's font. If the renderinfo applies to a table column then width dimensions the column. If neither typedef or width are present then a default width of 12 is assumed.

editable =

If the component accepts input and has an editable and non-editable state then the editable boolean expression sets this state. By default, editable is false. A component's editable state (and that of complex component elements such as table cells) is also accessible through a property. If the application needs to change editable according to current state then it does so via property access and renderinfo only provides the initial value.

The temperature converter does not use typedef declarations at all and, because the data model uses fixed precision decimal values, need not use explicit formatting. As we will see when discussing this example's layout, the text fields are allowed to take a preferred size, so no width is specified either.

The renderinfo Expression

When an event propagates upwards through the node space, the renderinfo's expression (or implied expression when only typedef is present) allows it to determine whether the event is of interest to the associated GUI component.

As stated above, A renderinfo's expression can be any Inq statement. Consider the following example of a label component that is set up to display audit information about when and by whom some data was last updated:

tfLastUpdatedByUser.properties.renderInfo = renderinfo(renderf("Last Updated {0} By {1}",
                                                               $this.vars.selected.Cpty.LastUpdated,
                                                               $this.vars.selected.Cpty.User));

In this case the expression is not a simple node reference. It is Inq's renderf function. This is similar to printf in C or Java but uses formatting information from the associated typedef, if any can be inferred from the references.

renderinfo examines the node references in its expression and responds to node events that must be of interest to it, thus binding the dependent data items to the view. Only node references relative to $this are considered because events can only arise in the persistent node space.

In general, Inq will analyse complex renderinfo expressions to determine the node events that should be dispatched to a component. Any function calls in the expression will be walked and eligible node paths in the function body considered.

renderinfo and firemodel

When a GUI component updates its model data, the MVC design states that the component will fire a model event to notify other observers of the new model state. Inq components do not, by default, raise model events in this way because

  • the component is often the only observer of the data;
  • there can be several options for component events that need to raise model events, for example a text field may update its model when the enter key is pressed, when keyboard focus is lost or on every key stroke.

As we will see when discussing component event handlers below, Inq script can configure which component events will fire model events with the firemodel argument. Note, though, that Inq can only fire model events when the renderinfo expression is a simple node path. If a complex expression is used then firemodel is ignored.

Performing The Layout

Achieving a satisfactory layout of components that also exhibits the desired behaviour when the user resizes the GUI can be a very time-consuming exercise and not especially edifying. The approach taken by Inq was originally inspired by the XMT Toolkit written by David Flanagan. It avoids the need for the programmer to master the many complex layout managers available and expresses the layout in a way that is easy to visualise and maintain.

The layout function performs a component layout given the minimum of

  • the set of components;
  • the parent into which the layout will be made;
  • the layout string specification.

It includes the layout schemes of

Row / Column

components are laid out in horizontal or vertical rows with specified geometry and alignment costraints; the underlying layout manager is the Java BoxLayout.

Table

components are placed in one or more defined cells with optional justification and gapping; the underlying layout manager is Daniel Barbalace's TableLayout. See also the Inq Mini Guide to using TableLayout.

Card

one of the child components is visible at any one time; the underlying layout manager is the Java CardLayout.

A full discussion of layout is covered in its own section [TODO] For now we will discuss those features used by our example, whose layout invocation is

layout(., win,  "Row
                {
                  Margin d:3 Caption tl $catalog.{$root.i18n}.celcius; Column
                  {
                    Geometry d:f lCelcius
                    Nofocus Geometry xy:fv slCelsius
                    Geometry xy:vf tfCelsius
                  }
                  Margin d:3 Caption tl $catalog.{$root.i18n}.fahrenheit; Column
                  {
                    Geometry d:f lFahr
                    Nofocus Geometry xy:fv slFahr
                    Geometry xy:vf tfFahr
                  }
                }");

Layout Arguments

The first argument to layout is the component set. The components we are laying out are all declared on the stack, which is yielded by the node path ".". The layout specification itself refers to the components by their name within the set. This name is a single identifier, not a node path, so all the components must reside in the same Inq map container and therefore have unique names.

The second argument is the Inq node into which Inq the components will be placed. As components are referenced in the layout specification, so they are added to this node. Remember that Inq maps operate a unique key set, so the layout will fail if there is already a child in the specified Inq node with the same name as any of the components. It is this aspect of layout processing that creates the Inq hierarchy introduced earlier and if the node, or an ancestor of it, is a context node, this node will become the context for each component as it is processed by layout. In our example the Inq root and the GUI root are the window.

The third argument is the layout specification. This is a string that is parsed to create the GUI hierarchy and apply layout constraints applicable to the scheme being used.

The Layout Specification

Rows and columns are introduced with the keywords Row and Column. Generally, keywords in the layout syntax always begin with an upper case letter. Qualifiers apply to the named component that follow. Layout artifacts, such as rows and columns are unnamed components that can also accept qualifiers. If followed by an identifer, an unnamed component becomes an Inq node in the same way as if it had been declared explicitly prior to layout.

When considering the desired layout for the temperature converter we settle on a row containing two children, each of which is a column containing the label (for the image), the slider and the text field. As each of the row children represents a temperature scale it is given the following qualifiers:

  • a margin of 3 pixels dimension;
  • a caption at the top and justified to the left in the default border.

The caption is an expression followed by a semi-colon which must evaluate to a string. In this example the expression is a path that references the system catalog with a parameterised path intended to take account of an internationalisation setting.

The Geometry qualifier states how the component it applies to will resize. When absent, as for the columns, the component is fully resizable in both the X and Y directions. For the components within the columns, the geometry constraints are set as follows:

  1. The labels are fixed in both the X and Y directions. This means that, as the window is resized, the labels will remain at their initial dimensions. There would be no consequence visually if the labels were allowed to resize in the X direction and, as they do not have any event handlers attached to them, there are no functional implications either. Fixing them in the Y direction is important because we want any resizing on this axis to affect the sliders only. To fix the label dimensions the geometry setting d:f is applied. The dimension letter d is short for both x and y. The value f means "fixed".
  2. The sliders have the geometry specifier xy:fv, meaning they can grow and shrink on the Y axis but are fixed along X. As for the labels, the same visual effect would be achieved if the sliders were fully floating (if no Geometry were specified) however constraining their width means that their mouse input area is sensible. Try the example changing the geometry value to xy:vv and (subject to any look and feel dependencies) you will see that the sliders are clickable all the way to the window extremities.
  3. Finally, for the text fields we would like them to remain a fixed height but be allowed to grow along the X axis, in other words the opposite behaviour to the sliders.

The Nofocus qualifier applied to the sliders means they will not accept the keyboard focus, leaving the only focusable components in this example as the text fields.

The Resulting Node Hierarchies

Returning to the subject of instance hierarchies, after layout is complete the component and Inq node trees are as shown in the figures below. Click on the images for a larger view.

Click to enlarge Click to enlarge
Inq Hierarchy GUI Hierarchy

The GUI hierarchy includes the necessary artifacts to achieve the desired layout. The Inq hierarchy places the components of interest under the context node, which is the window. The active components are viewing the model data rooted at $this.vars, though which all further control of the application takes place.

Initialising The Model

The next two lines of the example initialise the model data.

// Just some initialisation
win.vars.celcius = 0;
win.vars.fahr = call celciusToFahrenheit(celcius = win.vars.celcius);

In this case the model data is defined in the client itself. In client-server applications its common to make a service request to retrieve data and set up the client's server-side state. These service requests are frequently made from a handler of the context established event, set up on any suitable component. In any case, the purpose of any model data initialisation is to fire model events that, in turn, will initialise the components to which those events are dispatched. Event dispatching by the model and data resolution by the GUI components through the expression of their renderinfo property is only enabled when the context is established and so must take place after this time.

The temperature converter has two views each of $this.vars.celcius and $this.vars.fahr, the slider and the text field. Binding the view to the model has the effect of making the container vars emit events when script mutates fields within it. This is an entirely free-form way of creating active model data in the client and can be used for any client-side-only state required to drive the GUI. In our example, initialising the model as above sets up the GUI so that it shows zero celcius and 32 fahrenheit.

Input Validation

The following lines establish the validateInsert property, which must be a func whose expression is a call statement:

// Setup the "validateInsert" property on the text fields.
// $catalog.guiFuncs.numericFloat is a predefined function that allows
// only numeric characters to be entered.
// See classpath://inq/gui/verifiers.inq
tfCelsius.properties.validateInsert =
  tfFahr.properties.validateInsert = $catalog.guiFuncs.numericFloat;

The validateInsert function is called whenever text is typed or pasted into the text field or when its text property changes. This includes model events that cause the text field's content to change. The function returns null to veto the text or the string value to be inserted otherwise.

A detailed discussion of validateInsert and the companion inputVerifier property functions is covered in the detailed sections (to be written).

Establishing Component Event Handlers

An application responds to user input, or other component events, by establishing event handlers with the Inq gEvent function. Here is the syntax of gEvent

"gEvent" "(" <expression> ","
           ( [<call_statement>] ","
             [ "event" "=" "(" <event_list> ")" ] ","
             [ "gModify" "=" "(" <modifier_list> ")"] ","
             [ "gDialog" "=" ( "gDialogok" | "gDialogcancel" ) ] ","
             [ "consume" "=" "(" <boolean_literal> ")" ] ","
             [ "firemodel" "=" "(" <boolean_literal> ")" ]
           )
         ")"

The expression argument must reference a component node. To be valid, in addition gEvent requires at least a call statement or specify firemodel=true. Here are some uses of gEvent in the temperature converter:

gEvent(slCelsius, call celciusToFahrenheitCB(), firemodel=true);
gEvent(tfCelsius, call celciusToFahrenheitCB(), firemodel=true);

gEvent(slFahr, call fahrenheitToCelciusCB(), firemodel=true);
gEvent(tfFahr, call fahrenheitToCelciusCB(), firemodel=true);

The Handler Function

If a component event requires specific processing (that is it will do more than just fire a model event) then this is scripted as a function and gEvent uses the call statement argument. The temperature converter's call-backs just call helper functions to perform the conversion. These functions are entirely stack-based and do not assume any particular structure prevailing below $this. This means that functionality is separated from model data and can be used more generally.

Passing Parameters To The Event Handler

The call statement can pass parameters to the call-back function, so an alternative way to write the gEvent statements is

gEvent(slCelsius, call celciusToFahrenheitCB2(temperatures = $this.vars), firemodel=true);
gEvent(tfCelsius, call celciusToFahrenheitCB2(temperatures = $this.vars), firemodel=true);

gEvent(slFahr, call fahrenheitToCelciusCB2(temperatures = $this.vars), firemodel=true);
gEvent(tfFahr, call fahrenheitToCelciusCB2(temperatures = $this.vars), firemodel=true);

This is another pattern for reducing the dependency of call-back functions on the context. Notice that the argument passed is $this.vars and not individual fields, to avoid field ripping.

Predefined Arguments To Event Handlers

If we uncomment the line writeln($catalog.system.out, $stack); in celciusToFahrenheitCB then we can look at the predefined arguments Inq places on the stack. Here is the output we get for a call-back from the celsius slider:

{@eventId=E_CHANGE, @component={renderedValue=$this.vars.celcius}}

Inq passes the event ID, in this case E_CHANGE, and the originating component at the path @component. When single-value components are rendering a simple node path, Inq includes the indirection renderedValue. This allows the event handler to access the component's model data using the path @component.renderedValue without having to know the specific path that was used in its renderinfo.

The more complex components place additional information in the event. These are covered in the reference section and the example applications.

Specifying The Events

The Default Event

The general syntax for a gEvent expression specifies the events to be processed in the event = (<event_list>) argument. In the examples this argument is omitted, meaning that Inq will use the default event for the component. Only those components that have an obvious default support call-back definition in this way. These are currently defined as:

Component Default Events
Component Default Event Comments
gTextField and gPasswdField gAction
Plain, toggle or radio buttons and their menu variants gAction
gComboBox gAction
gTab gChange
gDateChooser gChange
gSlider gChange
gMenu gMenuSelected
gButtonGroup gAction Events are solicited from the group, not the group's radio items

Specific Event Types

Specific events are named, for example these lines solicit the focus lost event from the text fields:

gEvent(tfCelsius, call celciusToFahrenheitCB(), event=(gFocuslost), firemodel=true);
gEvent(tfFahr, call fahrenheitToCelciusCB(), event=(gFocuslost), firemodel=true);

Binding Properties

Attaching a view to its model is a special case of Inq's ability to bind any property a component supports to a data item. The gProperty function accepts a component, a property name and a renderinfo expression to achieve this.

"gProperty" "("
             <expression>
             <identifier>
             <renderinfo>
             ")"

The expression must resolve to a GUI component. The identifier is a literal property name the component must support. In the temperature converter we have bound the foreground property of the text fields to the celsius temperature as an illustration:

gProperty(tfFahr,
          foreground,
          renderinfo({ $this.vars.celcius < 0 ? $this.vars.blue
                                              : $this.vars.red; }));
gProperty(tfCelsius,
          foreground,
          renderinfo({ $this.vars.celcius < 0 ? $this.vars.blue
                                              : $this.vars.red; }));

The text colour is blue below zero celsius and red otherwise. Property bindings can be set up to drive GUI state from the data in the node space, for example sensitising components according to data state. Further cases are shown in the example applications.