Monet

by David Huynh, July 21, 2003

Mo-net: (www.m-w.com)

Pronunciation: mO-'nA
Function: biographical name
Claude 1840-1926 French painter; perfected style of Impressionism; notable for producing series of the same subjects in various lighting conditions, as "Haystacks" and "Rouen Cathedral"

Monet is the new user interface framework of Haystack, still to be written. It makes extensive use of Mystique's dynamism to keep the UI up-to-date with respect to the underlying data model. Its core features include:

  1. Unified paradigm for presentation, including screen display and printing
  2. XHTML (subset) and SVG UI element library and extension
  3. Support for internationalization
  4. Uniformity in direct manipulation and editing (hopefully including uniform zooming and scrolling)
  5. Multiple window interface

Why internationalization (I18N)? While I18N itself is not a research issue, forcing ourselves to think about I18N while building Haystack makes us reflect on various issues that arise because of I18N. For example, a resource may have different titles in different languages (e.g. Friday in English and vendredi in French): how do we model titles that are language sensitive?

Basics

When a programmer wants to display some information to the user, the programmer needs to specify:

  1. What to present (e.g. one or more items, zero or more relationships)
  2. How to present (e.g. include an icon, emphasize the text, use a particular layout, use a particular UI element)
  3. Optional contextual information (e.g. UI behaviors, font settings)
  4. The presentation environment, like a graphics context (encapsulating details about printer or screen display, etc.)
Given these specifications, the UI framework will attempt to use the current context and various information in the RDF store to locate the code that knows how to satisfy the specifications. While the information is being presented, the user may make change to it. Such changes are propagated backward up the callstack and get persisted for later use.

To make a presentation of a single item, the programmer calls:

InfoClosure settings = monet:present(
...item to present...,
statements:{ ...specifications of how to present... },
closure:{ ...optional contextual information... },
pe as monet:PresentationEnvironment
)

Note that there can be more than one thing to present, and things to present can be resources as well as literals.

The implementation of the function monet:present is as follows:

function monet:present(
RDFNode item,
IRDFContainer specs, // specifications of what and how to present
InfoClosure contextSpecs optional,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure propagatedSettings // settings that must be propagated upward for persistence
) accesses(
IRDFDataAccessPoint __rdfDAP__,
Context __context__
)
Function viewProducer
InfoClosure instanceSettings
InfoClosure classSettings

...code to retrieve the requested view producing function, if any, given in specs...
if viewProducer == null
...code to locate the appropriate view producer given item and specs...

...code to locate previous class settings, if any, given the viewProducer...
if classSettings == null
classSettings = closure:{}
...code to register class settings...

...code to locate previous instance settings, if any, given item, specs, and viewProducer...
if instanceSettings == null
instanceSettings = closure:{}
...code to register instance settings...

Context context = new Context(__context__, [ contextSpecs, classSettings, instanceSettings ])
let __context__ = context
IRDFDataAccessPoint rdfDAP = new LayeredRDFDataAccessPoint([
__rdfDAP__, // base RDF information
specs, // what and how to present
__context__ // current context
])

instanceSettings2, classSettings2, propagatedSettings = viewProducer(item, rdfDAP, pe)

instanceSettings.incorporate(instanceSettings2)
classSettings.incorporate(classSettings2)

The function takes an item and a description of how to present in the form of an RDF container (i.e. a set of RDF statements), in the parameter specs. If the specifications do not include an explicit selection of the view producing function to use, monet:present attempts to locate one.

With the view producing function known, monet:present attempts to retrieve past settings that apply to all invocations of that view producing function. These settings are stored in an information closure bound to the variable classSettings. If no past settings are found, a new closure is created.

monet:present also attempts to retrieve past settings that apply only to the invocations of the view producing function on the given specifications. These settings are also stored in an information closure, bound to the variable instanceSettings. A new closure is created if no past instance settings are found.

A new context is set that encapsulates all past settings as well as the optional contextual information and the current context. The view producing function is then called with a layered RDF data access point wrapping the base RDF information source, the view request specifications, and the new context.

The view producing function is supposed to return 3 results: new instance settings, new class settings, and settings that it doesn't understand and that should be propagated upward for persistence.

View Producers

A sample view producing function looks something like this:

function :presentEmailMessageAsSummary @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
RDFNode email,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
Resource author = rdfDAP.extract(email, dc:author, ?x)

Function p0 = monetUtil:presentIcon(email)
Function p1 = monet:present(rdfDAP.extract(email, mail:subject, ?x), {})
Function p2 = monet:present(
author,
statements:{ ...specifications requesting display of name only... },
closure:{
asserts { monet:OnClickEvent
monet:handler :showOtherDocumentsByAuthor(author)
monet:log "true"
}
}
)

classSettings = monet:present(
monet:subst(
xhtml:{ <span>{%0} <b>{%1}</b> from {%2}</span> },
[ p0, p1, p2 ]
),
statements:{},
pe as monet:PresentationEnvironment
)

First, the function retrieves the author of the email. Next, the function creates 3 curried functions whose jobs are to present the icon, subject, and author of the email, respectively. Note that the 2 calls to monet:present return curried functions rather than results because they are not provided the parameter named monet:PresentationEnvironment. Likewise, the call to the function monetUtil:presentIcon also returns a curried function.

monet:present is called once again, this time to present an XHTML fragment. But first, the XHTML fragment is substituted with the 3 curried functions so that the presentations (i.e. views) ultimately produced by these 3 functions fit into the XHTML fragment template. This final call to monet:present is given a presentation environment so that the presentation can actually be rendered. The propagated settings returned from monet:present are assumed to be class settings.

The view producing function responsible for presenting XHTML fragments calls the substituted curried functions with a presentation environment so that the curried functions can render their stuff.

There are several improvements with this new paradigm for writing view producing functions:

  1. View producing functions are written in a very imperative fashion. This paradigm is, in my opinion, more familiar to naive programmers than the declarative fashion in which slides et. al. are written.
  2. Monet does make explicit the distinction between instance settings and class settings, and it also handles the persistence of these settings on behalf of UI programmers.
  3. There is no longer a need to use data sources explicitly--dynamism is already a core feature of Mystique.
  4. View producing functions and monet:present do not make assumptions about the presentation environment: that means we can substitute a different presentation environment (e.g. a printing presentation environment, an HTML output p.e.) and benefit from this whole presentation architecture.

In case you think that the sample view producing function above is long, we can always shorten it as follows:

function :presentEmailMessageAsSummary @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
RDFNode email,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
classSettings = monet:presentXHTML(
xhtml:{ <span>{%0} <b>{%1}</b> from {%2}</span> },
[
monetUtil:presentIcon(email),
monet:present(rdfDAP.extract(email, mail:subject, ?x), {}),
monet:present(
rdfDAP.extract(email, dc:author, ?x),
{ ...specifications requesting display of name only... },
monetUtil:setOnClickHandler(:showOtherDocumentsByAuthor(rdfDAP.extract(email, dc:author, ?x)), true)
)
],
pe as monet:PresentationEnvironment
)

Here, the utility function monetUtil:setOnClickHandler creates a closure with the on-click handler and logging flag set. The function monet:presentXHTML is specialized to present XHTML fragments: it calls monet:subst and monet:present to do the job:

function monet:presentXHTML(
XHTML xhtml,
List substitutions,
InfoClosure contextSpecs optional,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns (
InfoClosure propagatedSettings
)
propagatedSettings = monet:present(
monet:subst(xhtml, substitutions),
{},
contextSpecs,
pe as monet:PresentationEnvironment
)

Note that two different calls are made to retrieve the author of the email: this can be optimized away into just one call by the Mystique compiler.

Multi-View Producers

Often we want to present several items together, arranged in some desired fashion. This need has been satisfied by layout managers in Ozone. Monet takes an approach not too different from that used for presenting just one item. To present several items, encapsulate them in a Collection object and call monet:presentMany:

InfoClosure settings = monet:presentMany(
...a collection...,
statements:{ ...specifications of how to present... },
closure:{ ...optional contextual information... },
pe as monet:PresentationEnvironment
)

The implementation of monet:presentMany resembles that of monet:present. The given Collection object is reifiable to an RDF resource, from which persisted settings are hung. To create such a Collection object, use the (to be written) CollectionUtilities Java class. Example of presenting participants of a meeting:

function :presentMeeting @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
RDFNode meeting,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
Collection participants = CollectionUtilities.findCollection(
statements:{ meeting meeting:participant ?x },
conditions:{}
)

classSettings = monet:presentXHTML(
xhtml:{ <span>{%0} <b>{%1}</b> with {%2}</span> },
[
monetUtil:presentIcon(meeting),
monetUtil:presentTitle(meeting),
monet:presentMany(
participants,
statements:{ ...view request specifications... },
closure:{ ...contextual information... }
)
],
pe as monet:PresentationEnvironment
)

Writing a view producing function for presenting several items is almost like writing a function for presenting one item:

function :presentItemsInRows @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
Collection items,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
Statements itemSpecs = ...code to find out item specs...
InfoClosure contextSpecs = ...code to find out contextual information...
Collection propagatedSettingsCollection = map(
monet:presentARow(
itemSpecs,
contextSpecs,
pe as monet:PresentationEnvironment
),
items,
monet:Item
)

instanceSettings = InfoClosureUtilities.combine(propagatedSettingsCollection)

function :presentARow(
Statements itemSpecs,
InfoClosure contextSpecs,
RDFNode item as monet:Item,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure propagatedSettings
)
propagatedSettings = monet:presentXHTML(
xhtml:{ <p>{%0}</p> },
[ monet:present(item, itemsSpecs, contextSpecs) ],
pe as monet:PresentationEnvironment
)

The highlighted code shows exactly how each item's presentation is wrapped up.

Empty Collections

In the case that the collection in the previous example is empty, we might decide to show a special message saying "No items in this collection". This can be done in two ways. First, the multi-view producing function can be made smarter with a test:

function :presentItemsInRows @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
Collection items,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
Statements itemSpecs = ...code to find out item specs...
InfoClosure contextSpecs = ...code to find out contextual information...

if items.size() == 0
monet:presentXHTML(
xhtml:{ <p>No items in this collection</p> },
[],
pe as monet:PresentationEnvironment
)
else
Collection propagatedSettingsCollection = map(
monet:presentARow(
itemSpecs,
contextSpecs,
pe as monet:PresentationEnvironment
),
items,
monet:Item
)

instanceSettings = InfoClosureUtilities.combine(propagatedSettingsCollection)

This might not have the desirable behavior depending on how the multi-view is used. For example, if it were used to show the participants of a meeting in a view that reads "Weekly meeting with John and Jane", then if the two participants were removed from the meeting, then the view would read "Weekly meeting with No items in list". This is arguably not the right behavior. To remedy this problem, we need to modify the caller of the multi-view function. That is, we need to modify the view producing function of meetings:

function :presentMeeting @{
rdf:type monet:ViewProducer
...other metadata to specify the applicability of this view producer...
} (
RDFNode meeting,
IRDFDataAccessPoint rdfDAP,
PresentationEnvironment pe as monet:PresentationEnvironment
) returns(
InfoClosure instanceSettings,
InfoClosure classSettings,
InfoClosure settingsToPropagate
)
Collection participants = CollectionUtilities.findCollection(
statements:{ meeting meeting:participant ?x },
conditions:{}
)
Function pIcon = monetUtil:presentIcon(meeting)
Function pTitle = monetUtil:presentTitle(meeting)

if participants.size() == 0
classSettings = monet:presentXHTML(
xhtml:{ <span>{%0} <b>{%1}</b> (no participants)</span> },
[
pIcon,
pTitle
],
pe as monet:PresentationEnvironment
)
else
classSettings = monet:presentXHTML(
xhtml:{ <span>{%0} <b>{%1}</b> with {%2}</span> },
[
pIcon,
pTitle,
monet:presentMany(
participants,
statements:{ ...view request specifications... },
closure:{ ...contextual information... }
)
],
pe as monet:PresentationEnvironment
)

Sorting

We can set the sorting order in :presentMeeting as follows:

...
Collection unsortedParticipants = CollectionUtilities.findCollection(
statements:{ meeting meeting:participant ?x },
conditions:{}
)
Collection valuesToSortBy = map(monet:getTitle, participants)
List participants = ListUtilities.sort(unsortedParticipants, valuesToSortBy)
...

Internationalization

So far we have embedded XHTML fragments inside the code of view producing functions. However, because these XHTML fragments have been encoded as templates with substitutions in the form {%number}, we can extract these XHTML fragments out for internationalization. So, instead of calling:

monet:presentXHTML(
xhtml:{ <span>{%0} <b>{%1}</b> from {%2}</span> },
...

we call:

monet:presentXHTML(
monet:findResource(rdfDAP, mail:summaryViewTemplate),
...

The function monet:findResource is as follows:

function monet:findResource(
IRDFDataAccessPoint rdfDAP,
Resource uri,
String locale optional
) returns(
RDFNode resource
) accesses(
Context __context__
)
if locale == null
locale = __context__.extract(locale:Locale, locale:code, ?x)

edu.mit.lcs.haystack.locale.LocaleUtilities.getResource(rdfDAP, locale, uri)
resource = rdfDAP.queryExtract({ uri content:content ?x ; locale:inLocale locale })[0]

Of course, mail:summaryViewTemplate has to have some metadata somewhere, most probably in the same file that uses it:

function :install @{
rdf:type hs:InstallationFunction
}() accesses(IRDFDataAccessPoint __rdfDAP__)
__rdfDAP__.add({
mail:summaryViewTemplate
rdf:type locale:Resource
locale:baseMystiqueFileName "/mystique/ui/mailResource" .
})

That function adds metadata to describe where the localization resource named mail:summaryViewTemplate is located--in this case, it is located in some mystique file whose path starts with "/mystique/ui/mailResource". The locale dictates the remaining of the file path. This works just like properties resource bundles in Java. For example, we'll find that "/mystique/ui/mailResource_fr_CA.myst" contains:

function :install @{
rdf:type hs:InstallationFunction
}() accesses(IRDFDataAccessPoint __rdfDAP__)
Attributes attributes = attributes:{ locale:inLocale "fr_CA" }
__rdfDAP__.add({
mail:summaryViewTemplate
content:content xhtml:{ <span>{%0} <b>{%1}</b> de {%2}</span> } ; @ attributes .

:helloWorldTemplate
content:content "Salut, tout le monde" ; @ attributes .

:welcomeTemplate
content:content xhtml:{
<p>Bonjour, <b>%1</b></p>
} ; @ attributes .
})

These kinds of localization resource files should contain only localization resources, so that they can be sent to human translators who don't know much about programming. We can also put a good UI on top of these files so that the translators don't see the Mystique code directly. We can also extend the substitution format {%number} to allow comments:

xhtml:{ <span>{%0, icon} <b>{%1, email subject}</b> de {%2, author}</span> }

These comments help translators determine the appropriate translations.

Input Methods

One of the major issues of internationalization involves input methods--mechanisms for inputting characters not available immediately on the keyboard. Java does have a framework for implementing and using input methods. Unfortunately, this framework is tied to AWT. We will have to create our own input method framework.

Help

Context Menus and Drag and Drop

Mixing XHTML and SVG

Miscellaneous

enumeration for parameters decorators

Migration Plan