Guise™ Overview
Guise™ is a graphical user interface framework for Java designed from
the ground up to be both simple and elegant. If JavaServer Faces were more
like Java Swing, yet simpler and with generics, it would give you an idea
of what you will find in Guise.
Framework
Guise sports an elegant, extensible, and easy-to-use set of building
blocks. The main parts of the Guise framework are the following, from the
broadest to the most fine-grained:
Guise
- The
Guise
class represents the Guise installation. Only one instance of Guise
exists for any JVM instance. The Guise
class is useful for getting broad information about the state of the
entire Guise framework, but it otherwise provides little
functionality.
GuiseContainer
- The
GuiseContainer
is set up automatically by the particular platform of Guise (e.g. web or
Swing) you are using, and manages the installed Guise applications. Most
of the time it's sufficient to allow the GuiseContainer
to do its work on its own.
GuiseApplication
- Each
GuiseContainer
can have multiple GuiseApplications
installed. If you need added functionality, you can create your own GuiseApplication,
but it's likely the platform you use will set up a default GuiseApplication
if you take no special action. The web Guise platform, for instance,
will automatically create a default GuiseApplication
and bind your specified frames to the navigation paths you specify. For
most purposes, this default GuiseApplication
is all you need.
GuiseSession
- Depending on the platform, there may be several users accessing the
GuiseApplication—or
even a single user accessing the GuiseApplication
using various methods. Each of these access methods are allocated a GuiseSession.
Each GuiseSession
represents a user interaction with the GuiseApplication,
and has its own state such as navigation, control values, even including
perhaps a different language. The most common way to retrieve a Guise
session is using Component.getSession()
GuiseContext
- A
GuiseContext
is a short-lived object that helps a controller work with its associated
component and view. On the web platform, for instance, the
TextGuiseContext provides the controller with a writer for
constructing XHTML documents. A GuiseContext
manages the model-view-controller controller states.
Most of the time, the default implementations of the Guise pieces work
together automatically, and you can concentrate on arranging the
components of your user interface.
Component Model
A Guise user interface is made up of Components.
A Guise Component
follows the model-view-controller (MVC) paradim by containing one or more
Models,
while a Depictor
for the appropriate platform updates the view on that platform. Not all
Guise components contain models, however, although some component contain
several.
For the Guise components that contain models, the components delegate
data access to an internal, hidden reference to the model. The components
in turn implement the model interfaces. This has two implications. First,
the component model's information can be accessed directly through the
component as if it were the component's properties. Second, an existing
component can be passed to another component for data sharing. For
instance, one ValueControl,
which implements and contains an instance of ValueModel,
can be passed to another ValueControl.
These components will be linked by a single value model and be updated
accordingly.
Several models, such as LabelModel,
are found throughout the Guise framework from GroupPanel
to CheckControl,
either on their own or as base classes to other model types. A component's
model cannot be changed once the component is created, but its model's
values can be changed. If no model is specified, components will usually
create a default model delegate.
All Guise components that are CompositeComponents
can have child components, although they may not allow them to be changed.
A special composite component called a Container
allows custom adding and removal of child components, along with a Layout
that defines how the child components should be arranged in the view.
Because Guise containers implement Iterable, applications may
use the short iteration form for(Component
childComponent:compositeComponent) to iterate over container child
components.
Almost every component and model allows other objects to listen for
changes through property change listeners. In response to a property
change event, a listener may update the other components whenever it
chooses. There is no need to worry about view or context state—the Guise
framework handles all that automatically.
The following are commonly-used components in the Guise framework:
Frame
- Represents the top-most component in a hierarchy. Each application
is assigned an
ApplicationFrame,
inside which a Component
is bound to a navigation path and represents a point of navigation.
Other frames may be instantiated separately. A Frame
can contain a single child component, its content
component.
Panel
- A general container within a frame that allows custom collections of
child components with a specified layout.
ModalNavigationPanel
- A special navigation panel that represents a point of modal
navigation (i.e. the user cannot exit the panel until modality ends). A
ModalNavigationPanel
does not fully get modal status unless another component requests
modal navigation, as explained under "Navigation".
LayoutPanel
- A lightweight panel for laying out several components together.
GroupPanel
- A panel for semantically grouping together several components with
an optional label.
Label
- A string of text for labeling some object.
Heading
- A string of text representing a heading at a certain nested
level.
TextControl
- Allows users input of information in text format. Although the data
entered into a
TextControl
is in the form of text, this does not mean that the value a TextControl
represents has to be a string. A TextControl
is a generics-aware class that can represent several value types,
including String, Integer, Float,
and even Boolean. The control can have single or multiple
rows.
CheckControl
- Represents a
Boolean value and allows the user to
change the value with a check mark. A CheckControl
can be represented either as a rectangle (a traditional
checkbox) or as an ellipse (a traditional radio button).
Whether the value of a particular CheckControl
influences the values of other CheckControls
depends on whether the CheckControls
belong to the same ValuePolicyModelGroup.
Radio buttons can be created, for example, by adding several CheckControls
with ellipse check types to a MutualExclusionPolicyModelGroup.
SliderControl
- A control that contains a number-based
ValueModel,
representing the contained value as a slider. For a slider control to
function properly, its value model should be assigned a RangeValidator.
A slider control can share its model with other controls, such as a text control.
Button
- An
ActionControl
that is represented as a traditional button.
Link
- An
ActionControl
that is represented as a traditional link.
ListControl
- A control containing a
ListSelectModel
represented as a list of items for selection. The ListSelectModel
implements the List collection class and is
generics-aware.
Text
- A component that produces text to view. Although the value can be
set manually, this component is most useful when configured to
dynamically load large sections of locale-aware text from configured
resources.
Table
- A powerful yet simple-to-use component that presents optionally
editable data in a tabular format.
TreeControl
- A component that presents optionally editable data in a collapsible
tree structure.
Menu
- A group of components and sub-components that can be presented as a
vertical or horizontal pull-down menu. Menus are completely
internationalized, and automatically rearrange based upon locale and
component orientation.
Listeners
Most Guise application logic occurs by installing a listener
into a component; delegate model changes are passed to registered
component listeners. Listeners are installed using the standard Java
paradigm of addXXXListeners(). Once a component
changes, the listener may respond instantly by changing a model's values,
by requesting navigation, or by performing some
other action.
Validation
A Validator
can be installed in a ValueModel
using ValueModel.setValidator(Validator),
after which the value model will automatically validate every attempt to
update the value model's value. (The exceptions are ValueModel.clearValue(),
which sets the value to null without validation, and ValueModel.resetValue(),
which reverts the value back to the default without validation.) Because
ValueControl
is a value model, all value controls, such as TextControl,
support the installation of a validator. One convenient validator for
strings, for example, is RegularExpressionStringValidator.
Each component, including composite components, keeps a real-time
validity status which can be accessed using Component.isValid().
A composite component is valid whenever all its child components are
valid. An application can listen for a change in a component's Component.VALID_PROPERTY
to be notified in real-time when an individual component or a composite
component's validity status is changing.
To perform custom validity checks that cannot be handled by a validator
(to determine if two password fields are identical, for example), one
should override Component.determineValid()
(and not Component.isValid()),
taking care to call the parent version of the method before performing
custom validity checks.
Navigation
All navigation within a Guise application is done using relative
navigation paths. Navigation paths do not not begin with the
slash ('/') character, and are interpreted relative to the Guise
application base path. The Guise application description document is used
to map navigation paths to navigation panels. On the Guise web platform,
the web.xml file is used to indicate the Guise application
description document. For example, imagine the following
configuration:
- You have a Java servlet container hosting a web application stored
at
/server/webapp/.
- Your web application definition file is stored at
/server/webapp/WEB-INF/web.xml.
- You want your Guise application mapped to
/server/webapp/example/.
In this configuration, the Guise container base path would be
/server/webapp/, while the Guise application's base path
would be /server/webapp/example/. The following
web.xml file would reference the description document for
your Guise application, application.turf:
<servlet>
<description>Guise Example Application</description>
<servlet-name>guiseExampleApplication</servlet-name>
<servlet-class>com.guiseframework.platform.web.GuiseHTTPServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>application.turf</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
The referenced Guise application description document would be stored
at /server/webapp/WEB-INF/application.turf and would be
described using URF PLOOP. It
would map /server/webapp/example/ and
/server/webapp/example/about to the navigation panel classes
com.example.HomePanel and
com.example.AboutPanel, respectively, using navigation
paths:
`URF:
"guise"~<java:/com/guiseframework/>,
"example"~<java:/com/example/>
;
¤
*guise.DefaultGuiseApplication:
destinations=
[
*guise.ComponentDestination("", example.HomePanel),
*guise.ComponentDestination("about", example.AboutPanel)
]
;
.
A Guise session may at any time request that navigation be transferred
to another location by calling GuiseSession.navigate(String)
or GuiseSession.navigate(URI).
This navigation request can be overridden by later calls to the method, or
some later event may override navigation.
If a relative path is specified, navigation to another navigation panel
within the application will occur; if an absolute path is specified,
navigation will occur to another location on the server. In this example,
calling GuiseSession.navigate("about") will cause navigation
to occur to /server/webapp/example/about, while
GuiseSession.navigate("/some/other/application/page") will
navigate out of the Guise application entirely. Navigating to a URI, such
as with
GuiseSession.navigate(URI.create("http://www.example.com/about")),
will transfer navigation to another server.
Navigation can me nonmodal as in the previous examples, in
which subsequent navigation can occur to any other location. Guise also
allows modal navigation using GuiseSession.navigateModal(String,
ModalListener) or GuiseSession.navigateModal(URI,
ModalListener). Modal navigation will only work properly if the
navigation panel bound to the destination navigation path is an instance
of ModalNavigationPanel
Once modal navigation is initiated, subsequent navigation to other
navigation panels within the application is not permitted until the
destination navigation panel calls GuiseSession.endModalNavigation(ModalNavigationPanel).
(The sole exception is if the desination navigation panel itself requests
modal navigation to another modal navigation panel; modal navigation in
Guise can be nested.) Once modal navigation ends, the navigation panel's
modal result is available via ModalNavigationPanel.getResult(),
and any ModalListener
specified when modal navigation began will be notified.
Within Guise, most dialog-type activity should be ModalNavigationPanels.
This prevents browser forward/back functionality from creating disparities
between UI appearance and dialog state, and also causes the modal
navigation panels automatically to be released when modality ends, so that
new modal navigation will start with a newly initialized dialog.
Bookmarks
Guise can keep track of a bookmark at each navigation path,
which is equivalent to a URI's query parameters. Bookmarks allow the state
of a navigation panel to be referenced by a URI, and allow the back button
to function as expected on browsers. Whenever an application wishes to
indicate that a particular state of a navigation panel (for example, the
currently selected tab in a tabbed panel) can be referenced by a bookmark,
the application should publish the bookmark by calling GuiseSession.setBookmark(Bookmark).
This does not necessarily cause navigation to occur, but indicates that a
particular state may be referenced via bookmark information.
Whenever a user navigates to a bookmarked location (either by entering
the URI in the browser manually or by pressing the back/forward buttons,
for example), all components at the given destination that implement NavigationListener
will be notified of the navigation. Each such component should override NavigationListener.navigated(NavigationEvent)
and set the appropriate state of its child components, based upon the
given bookmark.
Resources
While it is possible to hard code strings and other resources directly
into Guise application code, Guise provide a preferred internationalized
resource system that is simple to use and extensive. This system is
divided into two levels:
- Resource Bundles
- Each Guise session uses a resource bundle with strings, integers,
booleans, and other data types. This resource bundle is created
independently for each Guise session based upon that session's locale.
Whenever a session changes locales, the resource bundle is
reloaded.
- Resource Files
- Each Guise session has available resources stored in resource files.
On the Guise web platform, these files are stored in the
WEB-INF/guise/resources/application directory.
For example, for a Guise application mapped to
/server/webapp/example/, file resources are stored in the
/server/webapp/WEB-INF/guise/resources/example/
directory.
When a resource is requested using a resource key, the Guise
session first looks to see if the resource bundle contains the requested
resource. If a string resource cannot be found in the resource bundle, and
if the resource key is a relative path, Guise searches for the resource in
the resource file area. Within the resource file area, Guise automatically
loads a resource with a filename based upon the current locale. That is,
if a resource of my/resource.html was requested from a Guise
session using the fr locale, Guise would first try to locate
the file
/server/webapp/WEB-INF/guise/resources/example/my/resource_fr.html
and, if that resource did not exist, would return the contents of the
resource
/server/webapp/WEB-INF/guise/resources/example/my/resource.html.
As an example, assume that a Guise application specifies a resource
bundle using
GuiseApplication.setResourceBundleBaseName("myresources"). If
a Guise session is created using the fr locale (or if a Guise
session later changes to that locale), the Guise session attempts to load
the resource bundle "myresources_fr.properties" using the Guise
application's class loader. If that file cannot be found, an attempt is
made to load the "myresources.properties" resource bundle. If a resource
bundle is used, it must be included in the WEB-INF/classes/
directory or otherwise bundled with the application; the same is true of
any resource files used.
The Guise session can thereafter request resources through one of the
several GuiseSession.getXXXResource(String)
methods. More commonly, a component is configured to automatically request
resources when needed by providing the component with a resource key. For
string properties a resource is referenced by surrounding the resource key
with the Start of String control character (U+0098) and the String
Terminator control character (U+009C) in the form
"\u0098resourceKey\009C"—or as a convenience by
calling Resources.createStringResourceReference(String).
For URI properties a resource is referenced using the URI
resource scheme in the form
resource:resourceKey—or as a convenience by
calling Resources.createURIResourceReference(String)
A Guise panel using a localized Label
component, for instance, might request that the label use a string from
the resource bundle stored under the key "my.label" by calling
myLabel.setLabel(Resources.createStringResourceReference("my.label"))
or myLabel.setLabel("\u0098my.label\u009C"). The code might
specify that the label's icon URI be loaded from the resource bundle
stored under the key "my.icon" by calling
myLabel.setIcon(Resources.createURIResourceReference("my.icon"))
or myLabel.setIcon(URI.create("resource:my.icon")). A Text
component might load a localized XHTML resource using the following
code:
final Text text=new Text();
text.setTextContentType(XHTML_CONTENT_TYPE);
text.setText(Resources.createStringResourceReference("my/localized/text.html"));
Internationalization
Guise is fully internationalized. It supports multiple localized
resources stored in resource bundles and/or resource files. Every
component also supports an Orientation, which specifies the
physical axes of the Flow.
Each flow (Flow.LINE
and Flow.PAGE)
has a flow direction, either increasing or decreasing from a standard
origin in the top, left-hand corner. A Guise session automatically chooses
component orientation based upon the session locale, but this can be
manually overriden by application code.
In an Arabic locale of ar, for instance, all components
would automatically be given an orientation in which line flow occured on
the X axis decreasing from right to left, and page flow occurred on the Y
axis increasing from top to bottom. Guise allows locale and/or component
orientation to be changed at any time, and on the Guise web platform the
XHTML will automatically reflect the language in the XHTML and XML, along
with the component orientation in the XHTML.
Declarative Markup
Guise supports declarative descriptions of component hierarchies
encoded in XML using URF PLOOP
stored in TURF. For specific
information on the URF PLOOP, one should consult the URF Specification, which contains a
complete PLOOP eample of a
Guise component specified by URF PLOOP in TURF.
Guise components may load their definitions from a PLOOP description
document in one of several ways:
- If the component is a
component
bound to a navigation path, when it is created the Guise session will
automatically attempt to initialize the component from a PLOOP
description document located in the classpath in the same directory as
the Java class with the same filename as the class file but with an
turf extension. For example, when creating
HomePanel from com/example/HomePanel.class,
the Guise session will check to see if there is a PLOOP description
document stored in com/example/HomePanel.turf. If so, the
component will be initialized automatically after creation.
- A component may choose to manually initialize itself by calling
GuiseSession.initializeComponent(Component),
in which case the Guise session will attempt to initialize the component
from a PLOOP description document located in the classpath same
directory as the Java class with the same filename as the class file but
with an turf extension, as just described.
- A component may alternately choose to manually initialize itself by
calling
GuiseSession.initializeComponentFromResource(Component,
String), in which case the Guise session will attempt to
initialize the component from a PLOOP description document located in
the resource area, using the given string as a resource key.
- A component may alternately choose to manually initialize itself by
calling
GuiseSession.initializeComponent(Component,
InputStream), in which case the Guise session will attempt to
initialize the component from a PLOOP description document retrieved
from the specified input stream.
In all of these methods, the Guise session will lastly call Component.initialize()
so that the component may install any event handlers or perform any other
initialization. The component may wish to retrieve references to created
comopnents using CompositeComponent.getComponentByName(String),
which will search the hierarchy under the component for the first child
with a matching name property, which would have been assigned
in the PLOOP description document.