UIX Developer's Guide |
![]() Contents |
![]() Previous |
![]() Next |
By now, you should be getting used to creating UIX pages. But you may be pretty tired of writing the same bits of UIX over and over again in all of your pages. This is not only tedious, but also a maintenance nightmare - to add another global button or change your branding images, you might need to update every last UIX file in your application.
There's got to be a better way - and there is. UIX offers two features that help you divide your pages into reusable pieces - "includes" and templating. Your user interfaces will be more maintainable, more customizable, and will even use less memory.
This chapter contains the following sections:
You've probably written a few pages that look like this:
<pageLayout>
<globalButtons>
<globalButtonBar>
...
</globalButtonBar>
</globalButtons>
<tabs>
<tabBar>
...
</tabBar>
</tabs>
<productBranding>
<image .../>
</productBranding>
<!-- etc... -->
</pageLayout>
There's a good chance that all of your pages look like
this. Each has a <pageLayout>
, with the same
<globalButtons>
, the same <tabs>
, the same branding. It's
easy enough to cut-and-paste your way through this, but what about
when you need to change content across all of your pages - like adding
another global button? You'll have to modify every UIX file. That's
tedious and error prone. It also wastes memory and time - each page
has to load and reparse the same set of content, and the cached
result trees aren't shared at all. Wouldn't it be better to define
these pieces of UIX once, then reuse them?
UIX's <include>
element offers you a way out of this problem.
Instead of repeating the same content over and over again, you can
write the content once, then include it from each page that needs it.
For example, here's what your page might look like:
<pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<globalButtons>
<include ctrl:node="gbInclude"/>
</globalButtons>
<tabs>
<include ctrl:node="tabsInclude"/>
</tabs>
<productBranding>
<include ctrl:node="pbInclude"/>
</productBranding>
<!-- etc... -->
</pageLayout>
And here's one of the included pieces:
<globalButtonBar xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<globalButton .../>
<globalButton .../>
</contents>
</globalButtonBar>
Let's look at one of those<include>
elements:
<include ctrl:node="gbInclude"/>
UIX <include>
elements only have one attribute: node
.
Here, we've put that attribute in the Controller namespace. This tells UIX
to interpret the target not as a file name, but as a UIX Controller page name.
That's why there's no .uix
after gbInclude
. We'll see other
syntaxes for using the node
attribute a little later, but if you're
using uiXML, this is the preferred form.
The target UIX files can look just like any other Controller-based uiXML file. They start with:
<?xml version="1.0" encoding="UTF-8"?>
<!-- an included file -->
<page xmlns="http://xmlns.oracle.com/uix/controller">
... etc.
...and can include any data binding or content. (As of UIX 2.0.5,
included files can include event handling, but these handlers are
ignored. We hope to address this limitation in the near future.) However,
you're also allowed to omit the <page>
and <content>
elements and go straight to the content:
<?xml version="1.0" encoding="UTF-8"?>
<!-- an included file, but just content -->
<pageLayout xmlns="http://xmlns.oracle.com/uix/ui">
... etc.
UIX Controller page names can be specified in UIX both as relative names or full page names. If the name starts with a forward slash ("/"), it's treated as a full name (after removing the slash). So, for example, if you placed all of your include files inside a "/common" subdirectory off of the root of your UIX directory, you could include them with:
<include ctrl:node="/common/includedName"/>
If there isn't a slash, UIX interprets the path as relative to the
including file. For example, if you're in page "foo/somePage",
then these two includes:
<include ctrl:node="anotherPage"/>
<include ctrl:node="bar/thirdPage"/>
are treated as includes of "foo/anotherPage" and "foo/bar/thirdPage".
It's worth remembering that including with "ctrl:node" is including a UIX Controller page, not a uiXML file. In particular, this means that uiXML-based or UIX Components-based pages explicitly registered with the UIX Controller can be included even if no UIX file is accessible.
If you're not using the UIX Controller, you can still use <include>
. If
you omit the Controller namespace from the "node" attribute, UIX interprets
the value as a file name:
<pageLayout>
<globalButtons>
<include node="gbInclude.uix"/>
</globalButtons>
...etc...
</pageLayout>
If you write your includes this way, the target included file
must not start with a <page>
element; it must
begin directly with the UI content. Because of this, this type of
include cannot include any event handlers (though, as noted above,
The UIX Controller doesn't yet support event handlers in included files anyway).
In addition, you can also databind the node
attribute:
<pageLayout xmlns:data="http://xmlns.oracle.com/uix/ui">
<globalButtons>
<include data:node="gbInclude@demo:someSource"/>
</globalButtons>
...etc...
</pageLayout>
In this example, the node is retrieved at render-time from the
someSource DataObject
. For more information on this
technique, see Dynamic Structure for
UIX Pages.
The most fundamental problem with <include>
is that you're
pretty much committed to taking the include file "as is". From the
parent page, you can't set attributes on elements inside the included
file, or add any elements. This is pretty limiting. There is a
way around this: DataObjects
seen
by the parent page are still visible inside the included page. So,
you can code:
<dataScope>
<provider>
<data name="demo:someData"> ...</data>
</provider>
<contents>
<include .../>
</contents>
</dataScope>
... and then reference "demo:someData"
inside the included file. And if you put UINodes
inside the DataObject
, you could even use <include data:node="..."/>
to get child
elements inside. But this is a bit messy, and very
inelegant.
You might have tried writing the includes this way:
<pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<include ctrl:node="pageIncludes"/>
...
</pageLayout>
and then change the included file to look like:
<globalButtons>
<globalButtonBar>
...
</globalButtonBar>
</globalButtons>
<tabs>
<tabBar>
...
</tabBar>
</tabs>
...etc...
That'd be convenient, but it doesn't work. The included file has to be a valid XML file, and that means that it has to have only one root element! Or, perhaps you might have tried writing
<pageLayout xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<include ctrl:node="gbInclude"/>
...
</pageLayout>
with an included file that looked like:
<globalButtons>
<globalButtonBar>
...
</globalButtonBar>
</globalButtons>
This way, you wouldn't have to type <globalButtons>
in every
including file. But, unfortunately, this also doesn't work. The
<include>
element can only include UINode
UIX elements - elements that map directly
to a UIX Components UINode
. <globalButtons>
isn't such an element - it's a wrapper element used by
<pageLayout>
, and it can't stand on its own.
These limitations are especially grating for <pageLayout>
.
UIX templates solve all of these problems!
Templating takes an entirely different tack to solving the problem of building modular UIX files. Templating lets you define new UINode elements in terms of pre-existing elements. These new elements can have all the attributes and child elements of any built-in node. Instead of forcing you to extract many separate bits of content from the bottom of the node tree, you can start from the top of the tree:
While using templates is easy - it's just like using any other UIX
element - building template definitions is a bit harder. Template
definitions rely on data binding, and it helps to have a basic
knowledge of the UIX Components APIs - if you don't know the difference
between "named children" and "indexed children", this might be a bit
difficult. But templating is much easier than writing the Java Renderers
and NodeParser
classes that you originally needed to
write!
In the subsequent examples, we'll build a simple template which we will define in a file called demoPageLayout.uit, which will:
<pageLayout>
First, we'll use this template, then show how to write the template definition.
Using a template in your UIX file is straightforward. First, you must add tags to your UIX file to load the template. Second, you refer to the template in your content.
Templates are loaded by a new section of the UIX Controller <page>
element: <templates>
. Unlike most sub-elements of <page>
,
this element must be placed in the UIX Components namespace:
<page xmlns="http://xmlns.oracle.com/uix/controller">
<templates xmlns="http://xmlns.oracle.com/uix/ui">
...
</templates>
</page>
This element must appear before <head>
or <content>
-
that is, you have to define the templates before you use them.
A <templates>
element contains a series of <templateImport>
elements, each of which locates a template definition file:
<page xmlns="http://xmlns.oracle.com/uix/controller">
<templates xmlns="http://xmlns.oracle.com/uix/ui">
<templateImport source="demoPageLayout.uit"/>
<templateImport source="demoSomethingElse.uit"/>
</templates>
</page>
We've used the ".uit" suffix as a convention to distinguish these
files from normal .uix files - template definition files cannot be
displayed directly. You're also allowed to use
<templateDefinition>
elements here to define a template inline -
but we'll leave that element for later. (There is not yet a
UIX Components-only syntax for loading templates. It is not clear yet if one
will be needed. We will, however, be providing a tool that builds a
Java bean class based on a template file.)
Each template definition file defines one element; like other UIX elements, they must belong to a specific namespace. The elements defined by templates cannot share namespaces with other UIX elements; for this example, let's use a demo namespace:
xmlns:demoTmps="http://www.example.org/demo/templates"
We'll now use our new "demoPageLayout" element in this namespace (assuming, of course, that it was properly defined in "demoPageLayout.uit"):
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:demoTmps="http://www.example.org/demo/templates">
<templates xmlns="http://xmlns.oracle.com/uix/ui">
<templateImport source="demoPageLayout.uit"/>
</templates>
<content>
<demoTmps:demoPageLayout xmlns="http://xmlns.oracle.com/uix/ui"
selectedTab="3">
<start>
<sideNav></sideNav>
</start>
<demoTmps:topHead>
<header text="A first header">
</header>
</demoTmps:topHead>
<contents>
<header text="A second header">
</header>
</contents>
</demoTmps:demoPageLayout>
</content>
</page>
Let's walk through this example; there's some trickiness here with namespaces:
<templateImport>
element. Note that it's inside of the new <templates>
element.
<demoPageLayout>
element.
Note that this element is in the demoTmps
namespace, but we've
switched the default namespace to UIX Components.
selectedTab
attribute, which will
designate which tab (in the template) is selected.
<start>
element. When we write the definition
of <demoPageLayout>
, we'll describe it as a
<pageLayout>
element plus extra features. So it
supports all the attributes and elements of <pageLayout>
.
Since this element comes from <pageLayout>
, which is in
the UIX Components namespace, <start>
must also be in the UIX Components
namespace.
<demoTmps:topHead>
element. This is a new element
we're defining to be the first element of <pageLayout>
's contents. Since it's an
addition to <pageLayout>
, it goes in the demoTmps
namespace.
<contents>
element - no change here over
how this element is used.
That's it - other than being in a new namespace, this element looks a lot like any other UIX element. Next, we'll discuss how you'd define that template.
First, let's write out the full template definition file (demoPageLayout.uit). Then, we'll walk through it, section by section.
<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="demoPageLayout">
<!-- define the template's type information -->
<type base="ui:pageLayout">
<namedChild name="topHead"/>
<attribute name="selectedTab" javaType="int"/>
</type>
<!-- define the content of the page -->
<content>
<pageLayout>
<!-- magic incantation to be explained below -->
<attributeMap><rootAttributeMap/></attributeMap>
<!-- another magic incantation -->
<childMap><rootChildMap/></childMap>
<!-- set up a default set of tabs -->
<tabs>
<tabBar data:selectedIndex="selectedTab@ui:rootAttr">
<contents>
<link text="Tab 1"/>
<link text="Tab 2"/>
<link text="Tab 3"/>
<link text="Tab 4"/>
<link text="Tab 5"/>
</contents>
</tabBar>
</tabs>
<!-- set up a default set of global buttons -->
<globalButtons>
<globalButtonBar><!-- ... --></globalButtonBar>
</globalButtons>
<!-- now, the content of the pageLayout -->
<contents>
<!-- Start with the "topHead" child at the top -->
<rootChild name="topHead"/>
<!-- Then add the indexed children -->
<rootChild name="contents"/>
</contents>
</pageLayout>
</content>
</templateDefinition>
Here's the <templateDefinition>
element we just defined:
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="demoPageLayout">
Like everything else in templating, the <templateDefinition>
element belongs in the UIX Components namespace. We've also set up a couple
of other namespaces - one for explicitly referring to UIX Components, and a
second for data binding.
There's also two new attributes, used to define the name of the
element we're defining: targetNamespace
and localName
. Together,
these attributes in our example say that we're defining a
<demoPageLayout>
element in the
"http://www.example.org/demo/templates" namespace.
The <type>
section is used to define the syntax
of the template element; that is, how developers can pass information into your
template. Here's the above example:
<!-- define the template's type information -->
<type base="ui:pageLayout">
<namedChild name="topHead"/>
<attribute name="selectedTab" javaType="int"/>
</type>
Again, let's walk through each line of this example:
<type base="ui:pageLayout">
<type>
elements can have a base
attribute. This
attribute specifies that type of element you're extending.
By specifying ui:pageLayout
, we've stated that
the <demoPageLayout>
element will support all of
the features that a normal <pageLayout>
element would support - plus whatever we add, but minus
what we override.
If the base
attribute is omitted, the template element will
support all of the default UIX Components attributes.
<namedChild name="topHead"/>
topHead
. This
means that <demoPageLayout>
supports a child element named
<topHead>
, which will in turn contain a piece of UI content.
You'll see below how to plug that bit of content in. A template
can use any number of <namedChild>
elements, or use none at
all.
<attribute name="selectedTab" javaType="int"/>
selectedTab
.
Every attribute that's supported above and beyond those
from the "base" element should be listed here.
The javaType
attribute is required - it identifies
the type of the attribute. You can use a full Java class
name, but we support the following abbreviations:
int
: for java.lang.Integer
boolean
: for java.lang.Boolean
(true, false)
string
: for java.lang.String
char
: for java.lang.Character
(any one letter, number, or symbol)
UIX includes built-in parsing support for these types, as well as support for the following "complex types" (see below for the meaning of this term):
Other Java types are allowed, but can only be used in conjunction with data binding.
You might wonder why we've used this custom format for type
information instead of one of the standards - in particular, why
haven't we used XML Schema? We considered using these standards, but
they suffered from three problems. First, they are comparatively very
expensive to parse - we needed to load template files quickly.
Second, XML Schema is actually too expressive; it can define nearly unlimited
number of types of element syntax, but templating only allows a much
smaller set of syntax. Third, XML Schema has no built-in support for
mapping to Java types. However, none of this prevents us from
generating an XML Schema document off of the <type>
element in a
template definition - and we will be adding exactly this functionality.
The content of a <template>
page is defined
inside a <content>
element:
<!-- define the content of the page -->
<content>
<pageLayout>
...
</pageLayout>
</content>
The content in here defines what this template will look like.
Here we've chosen to start with a <pageLayout>
element to render
our page layout. That makes sense, since we're claiming to be an extension
of a <pageLayout>
, but there's absolutely no requirement that
the element you extend actually appear in your content at all! The
<type>
information only talks about how developers can pass
information into your template - but your template has complete
control over how to use that information.
Now is a good time to bring up a subtle point to help Java coders
understand templates. When this template is used in pages, you might
think that each of those pages now contains <pageLayout>
element - and if you walked through that page using UIX Components, you'd
eventually find the page layout UINode
.
But this isn't at all how templates work. The UINode
that gets added to your page is just a
<demoPageLayout> - and only that. It doesn't matter how
complicated the content of the template is - to the page that uses it,
it seems as though only a single UINode
has been created. When that node is asked to output itself, it
privately uses a complex - but shared - UINode
tree. The exact details of how this
happens are a little too complex to describe here - and you don't need
to understand this code to use templates - but the curious can look at
the oracle.cabo.ui.composite
package.
One effect of this approach may affect you. You can freely use
<dataScope>
elements inside your template to define data
providers - they will have no side effects on the file that
uses the template. This is a feature - there's no need to worry about
strange side effects and name conflicts.
Now, let's start to take advantage of some of the content set on
(and added inside of) the <demoPageLayout>
element. First,
let's consider the attributes. Attributes can be retrieved either
individually or all together as a group.
First, let's see how to grab all of the attributes:
<pageLayout>
<attributeMap><rootAttributeMap/></attributeMap>
...
This line, <attributeMap>
, etc., says "take all of the attributes defined on <demoPagePageLayout>
, and add them to
<pageLayout>
." So, for example, if a developer writes:
<demoTmps:demoPageLayout rendered="true"
title="Some title"> ...
...then both of those attributes will be implicitly set on the <pageLayout> as well. Anything specified directly on the <pageLayout> inside the template will override the parent attributes. For example, if you always wanted quick links off, you could write:
<pageLayout quickLinksShown="false">
<attributeMap><rootAttributeMap/></attributeMap>
...
... and quick links will be off, no matter what's set on
<demoPageLayout>
.
Attributes can also be accessed one at a time. For this feature,
templates automatically have a special DataObject
available for databinding. This
DataObject
is named "rootAttr", and is in
the UIX Components namespace:
<tabs>
<tabBar data:selectedIndex="selectedTab@ui:rootAttr">
<contents>
...
</contents>
</tabBar>
</tabs>
Here, we've declared that the "selectedIndex" of the <tabBar>
should follow the "selectedTab" attribute of <demoPageLayout>
.
One more thing: you might be concerned about using <rootAttributeMap>
here - after all, that does put
the selectedTab
attribute onto <pageLayout>
along with all the attributes it expects. However, uiXML elements ignore attributes they don't know anything about. So, while you can run into problems when you reuse attribute names, this instance is safe.
Just like attributes, named children can be used all together or one at a time. First, the code for grabbing all the named children:
<pageLayout>
...
<childMap><rootChildMap/></childMap>
...
This line, <childMap>
, etc., says "take all of the named children defined on <demoPagePageLayout>
, and add them to <pageLayout>
." So, for example, if a developer writes:
<demoTmps:demoPageLayout>
<pageHeader>
<globalHeader>...</globalHeader>
</pageHeader>
</demoTmps:demoPageLayout>
... then that pageHeader
named child will be implicitly copied to
the <pageLayout>
- along with start
, end
, contentFooter
,
and all the other named children supported by <pageLayout>
.
Again, any children defined explicitly inside the template will act as
overrides.
The <demoTmps:topHead>
named child isn't supported by page
layout - so <rootChildMap>
isn't enough to get that child to be
rendered. For that, you have to explicitly include it, as we did in
our example, by using the <rootChild>
element:
<pageLayout>
...
<contents>
<rootChild name="topHead"/>
...
</contents>
This <rootChild>
element will insert the topHead
named child as the first (consequently, top) element inside the <pageLayout>
. You can use this element in ways you might not have guessed - for example, it's perfectly fine to reuse the same named child multiple times.
Unlike named children and attributes, indexed children (that is,
the children inside of <contents>
) can only be accessed as a
group. You'll still use the <rootChild>
element, but
with a value for its name
attribute: contents
:
<pageLayout>
<contents>
<rootChild name="topHead"/>
<rootChild name="contents"/>
</contents>
</pageLayout>
This example puts the indexed children immediately after (below)
the topHead
named child.
Two important caveats:
<rootChild name="contents"/>
can only be used inside
<contents>
. It may never be placed in any other element.
<rootChild name="contents"/>
can only be used once per
<contents>
. It's perfectly legal to add it more than once in
your template, but only one instance can be inside each
<contents>
.
Template definitions can refer to and use other templates. In fact, templates can be refinements of other templates. This is an extremely powerful tool of UIX. For instance, you might set up a whole series of templates:
Now, a single change to the common page layout propagates to every single page in your application! This is a dramatic improvement in both maintainability and customizability - customers can replace pieces of content for an entire application by touching only a single file. And all at very, very little cost to the page developers. Once a template is written, all the developers see is ordinary UIX. Contrast this with JSP templating implementations, where using templates requires radically changing the appearance and design of your JSP code.
How do you refer to other templates? Add a <templates>
element to the
<templateDefinition>
element (this is similar to adding a template to a UIX file):
<templateDefinition ...>
<templates>
<templateImport source="subtemplate.uit"/>
</templates>
<type> ...</type>
<content> ...</content>
</templateDefinition>
This <templates>
elements must appear before both
<type>
and <content>
, so that these elements can use the
included templates. The included templates can be used inside of the
<content>
element, or used for the base
attribute on the
<type>
element.
The whole idea of writing a template is to provide a reusable
component for many other developers. But reusable components aren't
very reusable if they aren't documented. You might use simple XML
comments for documentation, but comments are difficult to parse, are
easily lost in transformations, and can't contain their own child
elements. Towards this end, <templateDefinition>
supports
several additional elements only for documentation purposes.
First, each <templateDefinition>
file can contain
<version>
, <author>
, and <documentation>
elements.
Each of these elements can contain any attributes, text or child
elements - the content is completely left alone by our runtime (though
it must be well-formed XML).
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="demoPageLayout">
<author>J. Developer</author>
<version>2.0</version>
<documentation>
This element defines a subtype of <pageLayout>.
It adds a prebuilt set of tabs and global buttons, and
provides a new <topHead> child element.
</documentation>
...
</templateDefinition>
In addition, <documentation>
elements can be added
as children of <attribute>
and <namedChild>
elements
to document these entries directly:
<type base="ui:pageLayout">
<namedChild name="topHead">
<documentation>
will be added above the page's contents
</documentation>
</namedChild>
<attribute name="selectedTab" javaType="int">
<documentation>
the index of the selected tab
</documentation>
</attribute>
</type>
The terminology of attributes in UIX can cause a great deal of
confusion. When we discuss attributes for templates, we technically
mean UIX Components UINode attributes, not XML attributes. For
simple types - like strings, integers, characters, and Booleans - the
two terms are one and the same. But for more complex Java types -
DataObjects
and DataObjectLists
, for instance - a single XML
attribute isn't sufficient to represent the full type. Consequently,
for these types, we use a child element instead of an attribute. For
example, take the following template definition:
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="someWidget">
<type>
<attribute name="someData"
javaType="oracle.cabo.ui.data.DataObject"/>
</type>
<content>
...
</content>
</templateDefinition>
The "someData" attribute would actually be written to in UIX as:
<demoTmps:someWidget>
<demoTmps:someData key1="value1" key2="value2"/>
</demoTmps:someWidget>
(For information on the syntax for defining DataObjects and DataObjectLists in UIX, see the Data Binding chapter).
As if this all wasn't confusing enough, "complex attributes" can still be databound. Even though they have to be specified with elements when they're set to fixed values, they're data bound as if they were normal attributes:
<demoTmps:someWidget data:someData="key@aDataObject"/>
At some point, you may very well find that some parts of your
template UI can't be expressed in UIX. If you do, let us know - there
may be some missing, generic functionality that'll get you that last
mile. But you can integrate Java code inside your templates using
UIX Components DataProviders
and the UIX
<dataScope> element.
<dataScope> elements inside of templates are automatically
scoped. You can add all the <dataScope>
elements you want and
be sure that they will not affect the UIX of any developer using that
template. So, for example, you don't need to make the name
attribute of a <data>
element unique.
The Java code backing these DataProviders
is just like any other DataProvider
, so you'll use all the usual techniques for creating DataObjects
.
There is one trick you'll need to learn, though, if you want to get
attributes off of the template element. For example, say
you've defined a template element "foo" with a "disabled" attribute:
<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="foo">
<type>
<attribute name="disabled" javaType="boolean"/>
</type>
<content>
<dataScope>
<provider>
<data name="someData">
<method class="..." method="getTemplateData"/>
</data>
</provider>
<contents>
...
</contents>
</dataScope>
</content>
Now, inside your Java code, you want access to that "disabled"
attribute. To get it, you'll need to use two RenderingContext
methods you've probably not
used before: getParentContext()
and getAncestorNode()
:
static public DataObject getTemplateData(
RenderingContext context,
String namespace,
String localName)
{
// Get the parent rendering context
RenderingContext parentContext =
context.getParentContext();
// Get the template UINode
UINode templateNode =
parentContext.getAncestorNode(0);
// Get the value of DISABLED - note that we use
// the parent context
Object disabledObj =
templateNode.getAttributeValue(parentContext,
DISABLED_ATTR);
boolean disabled = Boolean.TRUE.equals(disabledObj);
DataObject someDataObject = ...;
return someDataObject;
}
The reasons for this are a bit too involved to get into here.
Succinctly, the template renders using its own RenderingContext
to avoid interfering with the
outer components. But you don't have to understand the full
details to follow this recipe.
rendered
Attribute - a Templating CaveatThe rendered
attribute is a rather special UIX attribute. Unlike
all others, it's used not by the component itself, but instead by the
parent. This is because the parent has to know whether or not to
reserve layout space for that child. For example, in a <pageLayout>
, the advertising images get
arranged very differently depending on whether they are all present or
only one is present. It's the <pageLayout>
itself that's responsible for
arranging the advertisements, so it has to know if they'll be rendered
at all.
This means that "rendered" must be set on the instantiation of the template, not internally to the template definition itself.
For example, say you wanted to have a page layout template with a train that is sometimes disabled and sometimes not. You might try to write this sort of a template:
<templateDefinition ...>
...
<content>
<train data:rendered="doWeNeedATrain@aPrivateDataSource">
<contents>
...
</contents>
</train>
</content>
...
</templateDefinition>
...and then use this template inside a <pageLayout>:
<pageLayout>
<location>
<myNS:myTrain/>
</location>
...
</pageLayout>
It seems like this should work - but it won't. <pageLayout>
will ask if the <myNS:myTrain>
is rendered - not if its
content is rendered. So you'd be forced to write instead:
<pageLayout>
<location>
<myNS:myTrain data:rendered="doWeNeedATrain@aNotSoPrivateDataSource"/>
</location>
...
</pageLayout>
In this example, you could preserve your abstraction by building
this logic into a <pageLayout>
template:
<templateDefinition ...>
...
<content>
<pageLayout>
<location>
<myNS:myTrain data:rendered="doWeNeedATrain@aPrivateDataSource"/>
</location>>
...
</pageLayout>
</content>
...
</templateDefinition>
Templates interact subtly with databinding, and you should be aware of the rules as you write templates.
A major goal of the template feature is that developers and
designers using a template-based element should not have to know the
element is implemented using templates. This means that the template
should not have any side effects. So, a subtlety: <dataScope>
s used inside a template are
not visible outside the template! An example may help here. First
the UIX file, then the template it uses:
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:data="http://xmlns.oracle.com/uix/ui"
xmlns:demoTmps="http://www.example.org/demo/templates">
<templates xmlns="http://xmlns.oracle.com/uix/ui">
<templateImport source="templateData.uit"/>
</templates>
<content>
<dataScope xmlns="http://xmlns.oracle.com/uix/ui">
<provider>
<data name="commonName">
<inline value="Outer text"/>
</data>
<data name="rareName">
<inline value="Rare text"/>
</data>
</provider>
<contents>
<demoTmps:styledText data:text="value@commonName"/>
</contents>
</dataScope>
</content>
</page>
<?xml version="1.0" encoding="UTF-8"?>
<templateDefinition xmlns="http://xmlns.oracle.com/uix/ui"
xmlns:ui="http://xmlns.oracle.com/uix/ui"
xmlns:data="http://xmlns.oracle.com/uix/ui"
targetNamespace="http://www.example.org/demo/templates"
localName="styledText">
<!-- define the template's type information -->
<type base="ui:styledText">
</type>
<!-- define the content of the page -->
<content>
<dataScope>
<provider>
<data name="commonName">
<inline value="Inner text"/>
</data>
</provider>
<contents>
<styledText data:text="value@commonName"/>
<styledText>
<attributeMap><rootAttributeMap/></attributeMap>
</styledText>
<styledText data:text="value@rareName"/>
</contents>
</dataScope>
</content>
</templateDefinition>
If you run that example, you'll see the following output:
Inner textOuter textRare text
All of the actual content gets output inside the inner template, so
let's walk through the template contents. Consider the first <styledText>
in the template:
<styledText data:text="value@commonName"/>
This resolves to "Inner text," which makes sense looking at this
.uit
file - the one <dataScope>
defines the commonName
data. But the UIX file that uses this
template also defines "commonName". So, the first rule: databinding
inside a template gives priority to the data defined in the template.
Now, the second <styledText>
:
<styledText>
<attributeMap><rootAttributeMap/></attributeMap>
</styledText>
This is a more typical template line. Recall that the <attributeMap>
, etc.,
line says, "take all of the attributes defined on the parent element in the UIX file
and add them to this <styledText>
. Therefore, this <styledText>
should write out the "text" attribute of the parent element that is defined in the UIX file as:
<demoTmps:styledText data:text="value@commonName"/>
But how on earth does this result in "Outer text"? The template
defines "value@commonName" to be "innerText"! Well, this is the
really key point - the "data:text" defined on <demoTmps:styledText>
has to evaluate
independently of how the template is implemented. So the databinding
in the UIX file can't even see the <dataScope>
defined in the template, but
instead sees only its own <dataScope>
. Rule number two:
Databinding outside of the template always ignores
databinding defined inside of the template.
For completeness sake, the last <styledText>
:
<styledText data:text="value@rareName"/>
This results in "Rare text." The only databinding for the
rareName
data is in the outer UIX. So, rule number three:
databinding inside a template can see data defined outside
of the template.
Here are those three rules again:
Once you've developed a whole set of templates, it'll begin to be
tedious including all of them in each file and maintaining a long list
of <templateImport>
elements. UIX supports a
<templateLibrary>
element that lets you group templates together
and include them with a single <templateImport>
.
The syntax is simple. In one more .uit
file, you'll define
a single <templateLibrary>
element and explicitly import all
of the templates you want to group together:
<templateLibrary xmlns="http://xmlns.oracle.com/uix/ui">
<templateImport source="firstTemplate.uit"/>
<templateImport source="secondTemplate.uit"/>
<templateImport source="thirdTemplate.uit"/>
</templateLibrary>
Then, just import the library file anywhere it is needed.
<templates xmlns="http://xmlns.oracle.com/uix/ui">
<templateImport source="library.uit"/>
</templates>
Template libraries are a bit like using "wide imports" in Java:
import oracle.cabo.ui.*;
But, unlike wide Java imports, you don't run into ambiguity problems, because you'll still explicitly specify the namespace of each UIX element when they're used.
All this is great - if you're using XML to build your user
interfaces. But it doesn't seem to help you if you're using our Java
API. What's more, templates look like second class citizens even if
you're using XML - you need to import the template files in every
file. Thankfully, there's a solution at hand - the UIExtension
interface.
The UIExtension
interface is a very
simple interface that lets you bundle up rendering and parsing
behavior for an extension in a single object. And the oracle.cabo.ui.composite.TemplateUIExtension
and
oracle.cabo.ui.composite.TemplateLibrary
classes provide a simple way to use your .uit
files as extensions - at
which point they're first class parts of UIX that can be used in your
XML without imports, even from Java.
Turning templates into a UIExtension
is
a three step process:
<templateLibrary>
files.
TemplateLibrary
object. Say that your templates
are in a "library.uit" file in the "c:\foo\" directory:
// Create a "NameResolver", which is used
// to locate the library and any files it includes
NameResolver resolver = new DefaultNameResolver(new File("c:\\foo\\"), null);
// Create the library - see the Javadoc for information on this function
TemplateLibrary library = new TemplateLibrary(resolver,
"library.uit",
null, null, null);
TemplateUIExtension
:
UIExtension extension = new TemplateUIExtension(library);
A UIExtension
can be registered on a
LookAndFeelManager
or a ParserManager
:
// Register the rendering half of the extension
LookAndFeelManager.getDefaultLookAndFeelManager().
registerUIExtension(extension);
// Register the XML parsing half of the extension
ParserManager manager = ...;
extension.registerSelf(manager);
But there is an easier way. Just use the
<template-library>
element in uix-config.xml
.
<?xml version="1.0" encoding="ISO-8859-1"?>
<configurations xmlns="http://xmlns.oracle.com/uix/config">
<application-configuration>
<ui-extensions>
<template-library>c:\foo\library.uit</template-library>
<template-library>aRelativePath/library/library.uit</template-library>
</ui-extensions>
</application-configuration>
</configurations>
For more information on uix-config.xml
, see the Configuration chapter.
If you're a Java-only programmer using our beans, all that really
matters is registering the rendering half of the
UIExtension
, which is handled by the call to
LookAndFeelManager.registerUIExtension()
. (In other
words, you don't need to bother with registering the templates on the
ParserManager
.) Once you've registered the
TemplateUIExtension
, you can use those components
directly from Java! You just need to create a bean with the right
namespace and name. For example, if we'd registered the
demoPageLayout.uit
file, the following Java code would
gain access to that component:
// Create a DemoPageLayout bean
BaseWebBean bean = new BaseWebBean("http://www.example.org/demo/templates",
"demoPageLayout", null);
// Set "selectedTab"
bean.setAttributeValue(AttributeKey.getAttributeKey("selectedTab"),
new Integer(1));
Now, this is nice, but what you really wanted to write is:
// Wouldn't this be better?
DemoPageLayoutBean bean = new DemoPageLayoutBean();
bean.setSelectedTab(1);
You can write the DemoPageLayoutBean
class yourself,
but that's a pain. In the future, UIX will provide tools to
automatically convert a .uit
file into the corresponding
Java source code for a bean class. We'll also be providing tools to
convert from .uit
files into XML Schema .xsd
files, so you'll be able to verify your UIX and use the templates in
the schema-driven XML editor in JDeveloper9i.
Templates are first class citizens in UIX Components: if you want, you can think of them as an easy way to build UIX Components components without writing code.
Since templates provide so much functionality above and beyond what
<include>
can do, why use <include>
at all? There are a
few reasons.
First, <include>
is really simple. Not that it's so hard to
use a template - but writing an included UIX file is much easier than
writing a template file.
Second, <template>
is UIX Components-only. Now, it can use ctrl:
attributes - like ctrl:event
, etc., but it can't specify event
handlers. (As we noted above, UIX 2.0.5 ignores event handlers
specified on included pages - but this restriction will be lifted,
while templates can never support event handlers).
Third, templates can sometimes be too opaque. Remember that a
template element looks like just a single UINode
to the including file. But an
<include> looks like its whole tree of UINodes
. This has rendering implications - for
example, at this time, <header>
elements defined inside of a
<templateDefinition>
are ignored by quicklinks.
UIX offers an additional form of includes to merge in non-UIX content. UIX can include Servlets and JSPs, as well as including HTML directly.
To include Servlets and JSPs, use the <servletInclude>
element (accessible in Java via the ServletIncludeBean)
. It supports two
attributes, but most developers will just use "source"
; this is the value you'd use for the
"page" attribute for the JSP <jsp:include>
tag. For example, if you
wrote in a JSP:
<jsp:include page="foo.jsp"/>
Then you'd write in UIX:
<servletInclude source="foo.jsp"/>
The <servletInclude>
element can include both servlets and
JSPs. (Exception: when used with the combination of JSP and Apache
JServ, it can only include JSPs.) However, it can't include an
arbitrary URL - for example, you can't grab the contents of some HTML
stored on a different server. The <urlInclude>
element lets you
do just that:
<urlInclude source="http://www.example.org/somefile.html"/>
UIX will automatically copy some of the required HTTP request
headers - like User-Agent
- and explicitly set others, like
Accept-Language
. Because the HTML will be inserted as is, you'll
generally not want to insert an entire HTML file. Usually, the target
should only be a snippet of HTML. (We may in the future add filtering
to automatically strip out <HTML>
, <HEAD>
, and
<BODY>
tags, convert relative HREF attributes to absolute, etc.)
Once you've mastered includes and templating, you should be able to build much more modular, reusable UIX, improving developer productivity and application customizability.