UIX Developer's Guide |
Contents |
Previous |
Next |
A robust application always handles error conditions. Web applications present some interesting twists on error handling that you may not have encountered if you've only written more traditional client-server applications. This chapter tells how to handle errors during data binding and event handling, how to display those errors to the client, and how to use client-side validation to improve the user's experience.
This chapter contains the following sections:
UIX data binding presents an interesting problem for error
handling. Data binding happens as the page renders. Values are
retrieved only at the moment they are needed, and DataProviders
are only asked for their DataObjects
the first moment those DataObjects
are needed. But once a line of HTML
has been sent on its way across the internet, it's too late to get it
back. You can no longer cleanly redirect to an error page, or render
an elegant error section in place of the failed portion. Instead,
you're forced to soldier on and finish the page as best you can. And
it looks extremely unprofessional to dump a stack trace in the middle
of a page, not to mention how much this may scare users!
This seems to imply a harsh fact about DataProviders
, DataObjects
, BoundValues
, and all the other pieces of data
binding code in your application. It would seem that none of these
can ever fail - they've always got to return a valid result.
Thankfully, this isn't exactly the case. There are plenty
of alternatives.
The simplest option is always to do nothing. Catch
the exception, log it to our ErrorLog
API, and then drop it! For example:
public class SimpleDataObject implements DataObject
{
public Object selectValue(RenderingContext context,
Object key)
{
try
{
// Try to get the value
return _aMethodThatMayFail(context, key);
}
catch (SQLException sqle)
{
// Oh no - an error! But just log it...
context.getErrorLog().logError(sqle);
// and return null.
return null;
}
}
}
If you're horrified by the thought of code like this, that's partly a good thing. It's always better to display an elegant error message to a user than to fail silently. Displaying blank cells or fields will confuse users or, even worse, lead them to submit the wrong data!
However, keeping all code up to these high standards is sometimes counterproductive. The more error cases you try to handle elegantly, the more error handling code you'll write. And error handling code is usually the least tested code in an application - and therefore it can be the buggiest code in an application!
There are already exceptions you're probably not handling. For
example, do you always catch NullPointerExceptions
? Such an exception means
an unexpected event has happened. It'd be going overboard to design
into your application how to present to the user not only expected
errors - like a database connection that fails - but also the
unexpected errors - like NullPointerExceptions
or MissingResourceExceptions
. It's difficult to
decide where to draw that line, but choosing not to handle RuntimeExceptions
is a reasonable choice.
NullPointerExceptions
and other RuntimeExceptions
thrown by databinding code.
We automatically catch all exceptions thrown by DataProviders
and DataObjects
, log the errors, and return null,
just like the example above. Once the exception has reached our
layer, this is the best and only alternative left.
Finally, remember that this error isn't altogether silently dropped - it is being logged on the servlet engine's error log. (The UIX error log will by default send messages and errors to the servlet engine's error log, but it can be replaced to use a different strategy). Even in a production application, you should be monitoring the log for errors. However, the errors are invisible to the user, which can mean they have no visible indication that anything's gone wrong.
The major problem with the first technique is that the user gets no feedback when an error happens. They may see curiously empty tables or fields without any hints that something went wrong. A simple modification of the first technique solves this problem. As soon as an error happens, instead of just logging it, store the error and support returning the error from the data. For example, take the following UIX:
<dataScope>
<!-- Get some data -->
<provider>
<data name="dataForTable">...</data>
</provider>
<contents>
<!-- Put the data in a table -->
<table>...</table>
</contents>
</dataScope>
Now, let's add an extra piece after the table:
...
<contents>
<!-- Put the data in a table -->
<table>...</table>
<!-- And warn the user of an error if one happened, -->
<header messageType="error" data:rendered="error@dataForTable">
<contents>
An error occurred:
<styledText data:text="exception@dataForTable"/>
</header>
</contents>
...
This bit of UIX adds an error message. It's rendered only if the magic "error" flag is somehow set. (See Dynamic Structure for UIX Pages for more information on the "rendered" attribute.) Then it shows the exception - you could present friendlier output if you wished. Now, how to update the data source to support this?
public class SimpleDataObject2 implements DataObject
{
public Object selectValue(RenderingContext context,
Object key)
{
// Have we encountered an error yet?
if ("error".equals(key))
{
return (_exception == null) ? Boolean.FALSE : Boolean.TRUE;
}
// What exception have we seen?
else if ("exception".equals(key))
{
if (_exception == null) return null;
return _exception.toString();
}
try
{
// Try to get the value
return _aMethodThatMayFail(context, key);
}
catch (SQLException sqle)
{
// Oh no - an error! But log it...
context.getErrorLog().logError(sqle);
// ...store it...
_exception = sqle;
// and return null.
return null;
}
}
// Cached exception
private Exception _exception;
}
This is a lot like the previous example, but we don't just throw
away exceptions after logging them - they're also stored away for
later use. We also add two new keys to our DataObject
that expose this error state.
This strategy works fairly well. It has extremely low overhead
when there aren't errors and lets users know when errors have
occurred. On the downside, the user still sees potentially incorrect
data and may have to scroll far down the page to notice the error, and
you've put error handling code into each DataObject
. You can solve this last issue and
make the error storing code generic, but the user's problems are
inherent to this technique.
If you want to show a databinding error at the top of a page or the top of a section - that is, before the data is actually used - you've got two choices. First, you can get all the pieces of data in one pass, so that there's no chances of failure in the middle of rendering. We'll consider this first option here, and leave the second option for the next section.
Let's show how this works with a UIX <method>
element (or the Java MethodDataProvider
class). We'll define
a method that tries to get the data, but if it fails, it instead
returns a simple DataObject
that just
contains error codes:
import oracle.cabo.ui.data.DictionaryData;
...
static public DataObject getResultSet(
RenderingContext context, String namespace, String name)
{
DictionaryData data = new DictionaryData();
try
{
ResultSet result = _getJDBCResultSet(...);
data.put("ename", result.getObject("ENAME"));
data.put("id", result.getObject("ID");
data.put("manager", result.getObject("MANAGER");
// .. etc...
// And mark that we didn't have any errors
data.put("errors", "no");
}
catch (SQLException sqle)
{
// No - an error. Mark the error case, store
// the exception, and return.
data.put("errors", "yes");
data.put("exception", sqle.toString());
}
return data;
}
Stepping through this code, we can see that it:
DictionaryData
. This
class is a crude but simple way to store a small, fixed set of data -
and it never throws an exception.
Now, here's the UI that uses this data:
<dataScope>
<provider>
<!-- Get the result set. Note that this _is not_ enough
to trigger actually calling getResultSet(); that won't
happen until we try to access a piece of the result -->
<data name="resultSet">
<method class="...." method="getResultSet"/>
</data>
</provider>
<contents>
<!-- Now: did we have an error? Ask for the "errors" key
first - this will at last trigger the call to
getResultSet(), which will get all of the data and
set the "errors" key. -->
<switcher data:childName="errors@resultSet">
<case name="no">
<!-- No errors: show the result set -->
<labeledFieldLayout>
<contents>
<messageStyledText name="id" prompt="Employee ID:"
data:text="id@resultSet"/>
<messageTextInput name="ename" prompt="Employee name:"
data:text="ename@resultSet"/>
</contents>
</labeledFieldLayout>
</case>
<case name="yes">
<!-- An error. Show the exception -->
<header messageType="error">
<contents>
<styledText data:text="exception@resultSet"/>
</contents>
</header>
</case>
</switcher>
</contents>
</dataScope>
There's a good bit of UIX here. Let's walk through it.
<data name="resultSet">
<method class="...." method="getResultSet"/>
</data>
The key point to remember here is that merely referencing
a data provider does not load the data. That won't
happen until a piece of data is requested from that provider.
<switcher>
. (The Java equivalent is the SwitcherBean
). Dynamic Structure for UIX Pages describes this in more detail; but for now, just note that it switches between any number of cases based on a childName
string attribute:
<switcher data:childName="errors@resultSet">
<case name="no">
...
</case>
<case name="yes">
...
</case>
</switcher>
Here, we've used the "errors" key to drive the "childName"
attribute. As you may remember, this key
is set to "no" when we get all the data successfully, and
set to "yes" when there's an error.
<case>
, is nothing you haven't
seen before. When there's an error, we show an error message.
Otherwise, we show the results as expected.
(Though we used a DataProvider
to show
off this strategy, you can still use it even if you're just writing a
DataObject
. It's just easier with DataProviders
. Also, you don't have to copy
each bit of data into a DictionaryData
- the
only important rule of this strategy is that all requests after the
first must succeed completely.)
This choice does give you the best of all worlds for output. When there's an error, the output is cut off immediately, and the user immediately sees an error message. But there's a cost to your development time and to your application performance. Your Java code is forced to get every bit of data up front, even if it only needs one or two small pieces. Even worse, you've forced yourself to write a list of all the pieces of information this code can ever create and to keep that list up to date. That's a maintenance headache, and writing this kind of code is tedious.
All of these choices so far require adding extra intelligence to
all of your DataProviders
and DataObjects
. It'd be much easier if those could
be left alone, and instead we could put the responsibility for error
handling into the UIX framework itself. We have a bean that makes
this much easier: the <try>
element,
and its Java counterpart, the TryBean
.
Instead of forcing your data provider to handle error conditions,
you simply add a <try>
element around the section. Let's
write a DataProvider
that always
fails:
static public DataObject throwException(
RenderingContext context,
String namespaceURI,
String name) throws Exception
{
throw new java.sql.SQLException("Big problem");
}
And now, some UIX that uses this.
<try>
<contents>
<dataScope>
<provider>
<data name="noGood">
<method class="..." method="throwException"/>
</data>
</provider>
<contents>
<text text="This won't work: "/>
<text data:text="notAChance@noGood"/>
</contents>
</dataScope>
</contents>
</try>
Run this example, and you'll see... nothing. When we enter a <try>
, we immediately start buffering the
output. Instead of sending bytes directly off to the network, they're
stored in a buffer. If the section finishes without errors, the
buffer is immediately flushed and sent to the network. (Unless, that
is, the<try>
is itself inside of another <try>
- we
support nesting.) But if an error occurs, the entire section is
dropped.
If you do want to let the user in on the error (usually a good
idea), <try>
supports a <catch> section.
It also stores the error
object as a property on the RenderingContext
, so you can use it in your
error message. Here's a UIX example complete with the <catch>
section:
<try>
<contents>
<dataScope>
<provider>
<data name="noGood">
<method class="..." method="throwException"/>
</data>
</provider>
<contents>
<text text="This won't work: "/>
<text data:text="notAChance@noGood"/>
</contents>
</dataScope>
</contents>
<!-- Catch the error if it happens -->
<catch>
<header messageType="error">
<!-- And for the text of the error message, just show
the exception that caused it -->
<boundAttribute name="text">
<contextProperty select="ui:currentThrowable"/>
</boundAttribute>
</header>
</catch>
</try>
Note how we got the Throwable
at the
"ui:currentThrowable
" key on the rendering
context. This is where the <try>
bean stores the error. From Java, you'll be able to access the error
with the following code:
import oracle.cabo.ui.UIConstants;
...
RenderingContext ctxt = ...;
Throwable error = (Throwable)
ctxt.getProperty(UIConstants.MARLIN_NAMESPACE,
UIConstants.CURRENT_THROWABLE_PROPERTY);
To make it easier to show errors, UIX provides a
<displayException>
element that can be
used inside of <catch>
. In addition
to showing the type of exception and the exception's message
(which is all you get with the previous example), you'll also
get a stack trace:
<try>
<contents>
<!-- etc. -->
</contents>
<!-- Catch the error if it happens -->
<catch>
<displayException/>
</catch>
</try>
This buffering is closely akin to what JSPs are already doing
today, and why developers used to JSPs may not have been exposed to
this problem in web applications. The problem with the built-in JSP
solution is that it's all or nothing: either you buffer the entire
page, or you buffer none of it. There's no simple way to selectively
buffer portions of a page. (The JSP 1.2 specification includes a
TryCatchFinally
tag interface that would let you address
this problem with a custom tag; the UIX solution works with all
versions.) And if you're buffering the entire page, the user has to
wait until the entire page is generated before a single byte is even
sent across the web, much less displayed on their browser. Selective
buffering lets you send results as soon as they're completed.
So, why not always use <try>
? It's certainly
much simpler to add this to a pre-existing page than any of the other
approaches - except for, of course, ignoring the errors altogether.
The most significant downside of <try>
is
efficiency. It's faster not to buffer output unnecessarily. So,
always buffering the entire contents of all pages is a waste of time.
Be selective with <try>
- wrap it around a
<table>
or a group of form elements. But not too
selective! Don't use it around each individual form element - there's
a cost to turning on and off buffering.
A second disadvantage is that a single error will discard
all of the output inside the <try>
.
Usually, that's a good thing - no output is better than wrong output!
But this missing output can make it difficult to identify problems,
especially if you don't include a <catch>
section
or don't have access to the error log. This is more of an issue at
development time than for users.
If you start writing BoundValues
,
DataObjects
, and DataProviders
to take
advantage of TryBean
, you'll likely run right into a
roadblock. None of these interfaces actually let you throw any
exceptions! Java methods always let you throw subclasses of
RuntimeException
, but you can't throw
IOException
, SQLException
,
SAXException
, or any other real exception you're likely
to encounter! In retrospect, our design of these APIs may have been a
mistake; it would have been much simpler to allow these methods to
throw any exception. But, in the meantime, you'll need a way out.
The solution is the UnhandledException
class, in the
oracle.bali.share.util
package. It's a subclass of
RuntimeException
, so it can be thrown from any piece of
code. And it can wrap any Throwable
or
Exception
passed to its constructor. A quick example of
a DataObject
may help explain:
public class SomeDataObject implements DataObject
{
public Object selectValue(RenderingContext context, Object key)
{
try
{
...
Try to get the data...
...
}
catch (SQLException sqle)
{
// We can't throw a SQLException from a DataObject. But
// we can throw an UnhandledException!
throw new UnhandledException(sqle);
}
}
}
You may be curious how a DataProvider
example we gave
above was able to throw a
SQLException
. We bend the rules xfor
DataProviders
accessed via the
<method>
element. Because such
DataProviders
don't actually implement the interface,
they're free to throw any exception as long as it's in their method's
throws
declaration.
You've seen a variety of options for handling databinding problems.
Because there's so many options, it's difficult to describe a
one-size-fits-all strategy. Some of the decisions may depend on your
personal preferences as a developer. But, a sensible and simple
strategy might be to use a combination of <try>
and
"doing nothing":
<try>
sparingly
around sections that are subject to databinding problems.
Of course, relying on <try>
is no
substitute for writing robust code! No matter what error handling
code you have in place, your first priority should always be producing
fewer errors to begin with.
Handling errors in event handling is thankfully a much simpler task. Since you haven't produced any output yet, you're free to respond in any way you see fit. In general, there's two options: go to a generic error page, or display "inline errors". "Inline errors" are used to show user errors in the same page as the content, and we'll talk about how to handle those in the next section.
Many errors in event handling are unpredictable: a database refuses a connection, a query fails, or the server is out of memory. None of these are expected errors, and there's no way a user can fix the problem. It's best to simply show the user a simple error page letting them know what happened, and this is simple with the UIX Controller.
First, remember that your event handlers are free to throw any
exception at all. This shouldn't be an excuse to not handle any
errors. Some errors should always be caught and handled elegantly,
like a NumberFormatException
thrown when your users don't
enter numbers correctly. But when you do encounter an exception that
can't be handled well, simply let it escape out of your
EventHandler
.
When an exception is thrown, the UIX Controller will call the
PageBroker.renderError(BajaContext, Throwable)
method.
That method will, by default, take the following steps:
Throwable
as a ServletRequest
attribute at the "oracle.cabo.servlet.eventError"
key.
The error page can be set either by calling
setErrorPage()
or with the
oracle.cabo.servlet.errorPage
servlet configuration
parameter, so they can be retrieved at any time.
When you want to display errors, you have to describe them. UIX
has a regular format for these descriptions: the MessageData
class. The MessageData
API allows your event handlers to
describe multiple errors for a single page, and specify how those
errors match up to components in a page. Within your user interface,
the <messageBox>
summarizes the
errors, and UIX's inline messaging components let you display those
errors inline with your components.
The MessageData
class is a special
version of DataObject
that makes it much
easier to put server-side error messages into your pages.
A single MessageData
object gathers up
a list of any number of messages. You'll create a MessageData
object and add each message
in succession, then use that MessageData
for rendering.
Each individual message can either be page-level, if it applies to the total contents of the page ("The username and password combination you entered is incorrect") or component-level, if it applies only to a single component message ("The date must be after today."). Each message contains the following pieces on information:
<boundMessage>
(which we'll learn
soon). For page-level messages, this can be null
.
UIConstants
interface, usually one of:
UIConstants.MESSAGE_TYPE_INFO
: for
informational messages.
UIConstants.MESSAGE_TYPE_WARNING
: for
warnings.
UIConstants.MESSAGE_TYPE_ERROR
: for
errors.
<messageBox>
, and displaying a
component-level message, we add a link in the summary that will take
users to the component. This lets you control that link text.
For page-level messages, this lets you provide an abbreviated
title for the message.
The following example builds up a series of page-level error
messages in response to a SQLException
:
static public MessageData getMessages(SQLException exception)
{
MessageData msgs = new MessageData();
while (exception != null)
{
// Create a title and get the message
String title = "ORA-" + exception.getErrorCode();
String message = exception.getLocalizedMessage();
// Add the message
msgs.addError(null, // null select - a page-level error
message, // the error message
null, // description URL - only for component-level
title, // the error title
null); // message description - let it default to the
// message itself
// And now go to the next exception in the chain.
exception = exception.getNextException();
}
return msgs;
}
The MessageData
class has many more
convenience methods that let you add errors, warnings, and
informational messages; this example used only one. See its API
documentation for more information.
It's an important point to make here that this class and all the
other related classes and elements are just conveniences. This is all
standard databinding, and not especially complicated databinding at
that! Still, we recommend that you use MessageData
and the UIX elements you'll see
shortly, since they'll keep you from making accidental errors.
Once you've created the MessageData
object, you have to get it into the page. You can do this with
ordinary databinding techniques, like the UIX <method>
element. But if you are in a UIX
Controller event handler, there's a much easier way. Event handlers
have a shortcut for passing DataProviders
from the event handler into your page, and MessageDatas
have a convenience method for
turning themselves into DataProviders
.
Let's see this in action:
// An EventHandler that may produce an error
static public EventResult doSomething(
BajaContext context,
Page page,
PageEvent event)
{
try
{
// .... Do the usual stuff ....
return ...;
}
// An error!
catch (SQLException sqle)
{
// Turn the exception into a MessageData object
MessageData msgs = getMessages(sqle);
DataProvider provider = msgs.toDataProvider();
UIEventResult result = new UIEventResult();
result.setDataProvider(provider);
return result;
}
}
Let's quickly walk through this example. We'll skip over the
standard code for all event handlers, and focus on the catch
section.
SQLException
into
a MessageData
, using the function we wrote
above.
MessageData
into
a DataProvider
using its toDataProvider()
convenience method. Recall
that a DataProvider
is an API for
dynamically retrieving any number of DataObjects
. This is a simple one - it returns
the MessageData
at a special, default
namespace and name we've reserved for messaging. (You could use any
namespace or name - but you'll write less code if you stick to the
default, and there's no downside.)
UIEventResult
. This
class is a subclass of the standard EventResult
, with one extra ability. It can
pass a DataProvider
from the event handling
code into the rendering code.
setDataProvider()
to
push the error messages across to our rendering code. The DataProvider
in the UIEventResult
is guaranteed to be made available
when you render.
UIEventResult
.
If you're looking closely, you may notice something interesting.
We never actually set anything on the result (other than the DataProvider
). We used the default constructor,
and we never called EventResult.setResult()
. EventResults
created like this have a special
meaning to the UIX Controller. They mean "do not navigate" - return
to the same page - no matter what the page flow engine is. This is
crucial for showing errors, and lets you write this sort of an event
handler without knowing about the page flow engine.
The <messageBox>
XML element and
the MessageBoxBean
Java class are used to
show a summary of all the messages for a page.
In most cases, you'll only need to set a single attribute on this
bean: "automatic"
. When "automatic"
is set to "true" (in Java, MessageBoxBean.setAutomatic(true)
), the <messageBox>
enters automatic mode. It
will automatically look for a MessageData
instance at the default location. If it doesn't find one, it renders
nothing at all (as per our current UI specification). When one is
available, it writes out each message inside the correct bit of
content.
The only other feature that will be used commonly is the "message"
attribute (setMessage()
method). This method lets you set
the main message for the entire box. MessageData
also supports a setMessage()
method, and in "automatic" mode,
the message box will pull its message from the MessageData
.
<messageBox>
is trivial to use
from UIX:
<pageLayout>
<messages>
<!-- A message box belongs at the top of the page's content -->
<messageBox automatic="true"/>
</messages>
<contents>
<!-- and then follows the rest of the page -->
</contents>
</pageLayout>
Note that the <messageBox>
was added inside the special <messages>
element: this will place the messages in the correct location
for your look-and-feel. It's entirely legal to place
<messageBox>
anywhere on your
page, but if you're using <pageLayout>
,
this is the correct location.
<messageBox>
has several
other attributes that are less frequently used. For example,
you can override the namespace and name where it will search
for the MessageData
, but as we said
above, it's much easier to just use the default.
It's possible to use the <messageBox>
out of automatic mode for
purposes other than showing error or warning messages. You'll need to
add messages explicitly, and set the message type, and title. For
example, <messageBox>
is used on
confirmation pages ("Are you sure you want to delete this file?").
The special messageType
value of
"confirmation" (UIConstants.MESSAGE_TYPE_CONFIRMATION
from Java)
gives you the correct look for this use.
A major feature of the UIX renderers and the user interface specification they implement is inline messaging. Some web pages display all the errors in a big clump at the top and force the user to figure out which field they need to change to fix each error. UIX solves this usability problem by letting you display errors inline with the input controls that caused the problems.
The component central to this interface is the <inlineMessage>
XML element (the InlineMessageBean)
in Java). Usually, it
just displays a prompt in front of some other control:
But when an error is attached, the component automatically expands to show an error message and icons:
Here's the UIX for the <inlineMessage>
shown in both of these two pictures:
<inlineMessage prompt="Old password:">
<contents>
<textInput name="pwd" secret="true"/>
</contents>
<boundMessage select="pwdError"/>
</inlineMessage>
There's nothing very tricky here. The "prompt"
attribute sets up the "Password:" on
the left side, then the <textInput>
inside is responsible for that input field. These may be new
elements, but there's nothing conceptually new about them. The only
element that's really new is the <boundMessage>
element.
The <boundMessage>
element is
what sets up all the databinding magic that supports the user
interface side of MessageData
. It
databinds five of the attributes of an InlineMessageBean
to get their values from a
single MessageData
. If the "select"
attribute of <boundMessage>
matches the select value on
one of the messages added to the MessageData
object, the message appears next to
the component, an icon appears, and more. If it doesn't match
anything in MessageData
, the inlineMessage
renders directly.
From Java, this functionality is achieved with the MessageData.bindNode()
method:
InlineMessageBean imb = ....;
// Attach this node to the "pwdError" message
MessageData.bindNode(imb, "pwdError");
The moment you try putting two <inlineMessage>
elements one after the
other, you'll notice something's not right:
<inlineMessage prompt="Your username:">
<contents>
<textInput name="user"/>
</contents>
</inlineMessage>
<inlineMessage prompt="Password:">
<contents>
<textInput name="pwd" secret="true"/>
</contents>
</inlineMessage>
The two <textInput>
elements
didn't line up at all. To make them line up, you have to put the two
inside one of a couple layout components, either <tableLayout>
or <labeledFieldLayout>
. Check out the
documentation of these components for information on their usage.
It's extremely common to use inline messaging around form elements.
Because of this, we've added a number of convenience elements that
join the features of <inlineMessage>
with the features of another element. For example, the <messageTextInput>
element acts as
a combination of an <inlineMessage>
with a <textInput>
. The following
two pieces of UIX are functionally identical:
<inlineMessage prompt="Username:">
<contents>
<textInput name="user"/>
</contents>
<boundMessage select="userMsg"/>
</inlineMessage>
...and:
<messageTextInput name="user" prompt="Username:"> <boundMessage select="userMsg"/> </messageTextInput>
Note in particular that both versions support the <boundMessage>
syntax. Other convenience
elements include:
<messageCheckBox>
<messageChoice>
<messageDateField>
<messageFileUpload>
<messageList>
<messageLovField>
<messageRadioButton>
<messageRadioGroup>
<messageStyledText>
These are all handy, but keep in mind that they're simply
conveniences. If you need inline messaging around a component not
listed here, just wrap it in an <inlineMessage>
. These conveniences are
also available with our Java API - MessageTextInputBean
, MessageCheckBox
, etc.
With these pieces explained, let's walk through a full example. We'll define a page for changing a password, using three fields:
We'll check for several error conditions in an event handler, and redisplay the page with inline messages if any fail.
First, the UIX:
<page xmlns="http://xmlns.oracle.com/uix/controller"
xmlns:ctrl="http://xmlns.oracle.com/uix/controller">
<content>
<form xmlns="http://xmlns.oracle.com/uix/ui"
name="default" method="post">
<contents>
<pageLayout>
<contents>
<messageBox automatic="true"/>
<labeledFieldLayout>
<contents>
<messageTextInput prompt="Old password:"
name="old" secret="true">
<boundMessage select="oldError"/>
</messageTextInput>
<messageTextInput prompt="New password:"
name="new" secret="true"
tip="Passwords must be between 4 and 12 characters">
<boundMessage select="newError"/>
</messageTextInput>
<messageTextInput prompt="Confirm:"
name="confirm" secret="true"/>
</contents>
</labeledFieldLayout>
</contents>
<contentFooter>
<submitButton text="Submit" ctrl:event="newPassword"/>
</contentFooter>
</pageLayout>
</contents>
</form>
</content>
<handlers>
<event name="newPassword">
<method class="oracle.cabo.doc.demo.PasswordDemo"
method="handleNewPassword"/>
</event>
</handlers>
</page>
The contents are largely self-explanatory. We've added a <form>
and <submitButton>
to send the entered
results, and two of the three <messageTextInput>
elements are bound to
messaging. We've also put a <messageBox>
at the top of the content.
Note that nothing in this page needs to describe the types of errors
that may occur, or have any reference to the business logic of the
page. This is as it should be!
Now, the source code for handling the event. We've stripped out any real password checking code; the old password is hardcoded, and the new password isn't saved anywhere. But the messaging code is all there:
static public EventResult handleNewPassword(
BajaContext context,
Page page,
PageEvent event) throws Throwable
{
MessageData messages = new MessageData();
// Get all three parameters
String oldPassword = event.getParameter("old");
String newPassword = event.getParameter("new");
// Safeguard against null values. Always check parameters
if (newPassword == null)
newPassword = "";
String confirm = event.getParameter("confirm");
if (!"abcd".equals(oldPassword))
messages.addError("oldError",
"The old password was not correct (try abcd)",
null,
"Old password",
null
);
if (!newPassword.equals(confirm))
messages.addError(null,
"The two values for the new password didn't match.",
null,
"Error",
null);
if ((newPassword.length() < 4) ||
(newPassword.length() > 12))
messages.addError("newError",
"Passwords must be between 4 and 12 characters long",
null,
"Password length",
null);
// Are there any error messages yet? If not, then all is good,
// and move on to the success page.
if (messages.getLength() == 0)
{
// A real application would store the new password here!
return new EventResult(new Page(page, "success"));
}
else
{
UIEventResult result = new UIEventResult();
result.setDataProvider(messages.toDataProvider());
return result;
}
}
This code is also pretty self-explanatory or just a repetition of pieces you've seen before. We check that the old password is correct, that the new password is between 4 and 12 characters, and that the "new" and "confirm" fields match, to be sure that the user didn't mistype the password. Any problems add a message to the list; if there's any messages, we return to the current page and display some errors. Otherwise, we move on. Pretty simple stuff!
Before moving on, let's learn about a few of the extra features of message beans.
All message beans support a "tip" attribute. This adds a piece of text that guides users in how to use this field. It'll still be visible when an error is displayed, so use it for helpful information that's always useful. The most common example is text that explains the correct format or values:
<messageDateField prompt="Date:" name="date"
tip="DD/MM/YYYY"/>
The message beans also all support a required
attribute.
We'll go into more detail about the allowed values of required
later in this chapter and what those values mean. From
the perspective of the message beans, this simply indicates
whether we should show an icon to tell the user he must enter
a value:
<inlineMessage prompt="Username:" required="yes">
<contents>
<textInput name="user"/>
</contents>
</inlineMessage>
The message beans also all support an <end>
child element (and a setEnd()
method in Java). This
element will be added at the end (generally on the right) of
the contents:
<messageTextInput prompt="Username:" name="user">
<end>
<button text="Press Me" onClick="alert('Boo!')"/>
</end>
</messageTextInput>
One last item before moving on! In some circumstances, the layout
of the message beans may be too confining, and may prevent you from
lining up items as you would want. For these cases, you can arrange
just the slices of the message beans you need with two special
components: <messagePrompt>
and
<messageText>
, or in Java the MessagePromptBean
and MessageTextBean
.
The <messagePrompt>
displays just
the prompt, the required icon, and any error icons. The <messageText>
displays the error message
and any tip. Both support the <boundMessage>
element (and MessageData.bindNode()
), and you'll generally
bind the two to the same error message. In the following example,
we've broken apart the normal inline message bean to lay it out
vertically. Note especially the <boundMessage>
elements.
<stackLayout> <contents> <messagePrompt prompt="Username:" required="yes"> <boundMessage select="userError"/> </messagePrompt> <textInput name="user"/> <messageText tip="Your username"> <boundMessage select="userError"/> </messageText> </contents> </stackLayout>
Ideally, user errors are caught before the results are sent to the server. This is faster for the user; they don't have to wait for the error page to get loaded. It also saves load on the server, which doesn't have to parse these incorrect results. UIX offers support for this feature, called "client-side validation".
As you start using client-side validation, it's critical that you not come to rely on it. Here's a golden rule of multiple tier applications: "Validate on all tiers". Your middle-tier code - the code running in the servlet engine, for instance - must always check all values, even those that should have been verified by the client. And your database should verify values sent to it to by the middle-tier. This may seem like unnecessary work that'll just slow down your application, but it's essential that you follow this rule. Malicious users can change URLs or dummy up HTTP posts; databases used today with JavaScript-enabled browsers may be used tomorrow with simple handheld browsers. Failure to follow this rule will, at best, leave you with a flaky application. At worst, you'll open up security holes!
Our first type of client-side validation is the "required"
attribute. This attribute defines
whether the user must enter some data, or whether they can leave a
value blank. Try hitting "Submit" on the following page, first
without entering any text; you'll see an error alert that stops you
from submitting the page:
<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<messageTextInput prompt="Username:" name="user" required="yes"/>
<submitButton text="Submit"/>
</contents>
</form>
The "required" attribute is also supported on <choice>
and <messageChoice>
elements; if used here,
you'll want to add an empty option, and usually mark it as selected:
<form name="defaultForm" xmlns="http://xmlns.oracle.com/uix/ui">
<contents>
<messageChoice prompt="Withholding:" name="withhold" required="yes">
<contents>
<option selected="true"/>
<option text="10%" value="10"/>
<option text="20%" value="20"/>
<option text="30%" value="30"/>
</contents>
</messageChoice>
<submitButton text="Submit"/>
</contents>
</form>
There are four legal values for the "required"
attribute:
"yes"
: in Java, UIConstants.REQUIRED_YES
: only non-blank values are allowed.
"no"
: in Java, UIConstants.REQUIRED_NO
: blank values are always allowed.
"validaterOnly"
: in Java, UIConstants.REQUIRED_VALIDATER_ONLY
: blank
values are legal if and only if the attached client-side validaters
allow blank values. (See below for information on attaching
validaters.)
"uiOnly"
: in Java, UIConstants.REQUIRED_UI_ONLY
: it appears to the
user as if the value is required, but others it behaves as
REQUIRED_VALIDATER_ONLY. The case where this behavior is useful is
fairly obscure, but it occasionally is useful.
A very common type of input data is a numeric field. The UIX
DecimalValidater
class and <decimal>
element support this behavior.
It's a simple element; try entering random text in the following
example, then hitting "Submit".
<messageTextInput prompt="Any number:" name="number">
<onSubmitValidater>
<decimal/>
</onSubmitValidater>
</messageTextInput>
The <decimal>
element is simple
enough - it doesn't support any attributes. What's of much more
interest here is the <onSubmitValidater>
element that wraps it.
This element defines what type of validater is used. There's only two
types: <onSubmitValidate>
and <onBlurValidater>
. From Java, these are
set with the setOnSubmitValidater()
and
setOnBlurValidater()
methods. An
"on-submit" validater executes when the user has completed the form
and hits a "submit" button, while an "on-blur" validater executes as
soon as the user leaves the field. To see the difference, try the
following example. Type random text in the field, then hit the "tab"
button:
<messageTextInput prompt="Any number:" name="number">
<onBlurValidater>
<decimal/>
</onBlurValidater>
</messageTextInput>
In general, stick to on-submit validation unless you have a very specific reason. Forcing users to enter a correct value before they can enter any other can be extremely irritating.
A second common type of input data is a date field. UIX provides
this functionality with its <dateField>
element and DateFieldBean
, as well as their messaging
counterparts, <messageDateField>
element and MessageDateFieldBean
. (Again,
a reminder - a <messageDateField>
is
equivalent to a <dateField>
inside
an <inlineMessage>
.) Here's an
example of <dateField>
in action;
click the calendar icon on the right side of the field:
<messageDateField prompt="Date:" name="date"/>
Pretty simple. But you might want to change the format of the
result if the default isn't what you want, or if you also want to edit
the time as well as the date. It's a bit tricky how this is achieved
- instead of directly setting an attribute on the <dateField>
, you add an <onSubmitValidater>
with the special <date>
element (or DateValidater
object). For all the details,
check out the documentation of the <date>
element, but trying out this
example should give you a feel for what you can accomplish:
<tableLayout>
<contents>
<messageDateField prompt="Date:" name="date" columns="20">
<onSubmitValidater>
<date dateStyle="long"/>
</onSubmitValidater>
</messageDateField>
<messageDateField prompt="Date and Time:" name="dateandtime"
columns="20">
<onSubmitValidater>
<date dateStyle="short" timeStyle="short"/>
</onSubmitValidater>
</messageDateField>
<messageDateField prompt="Patterned:" name="pattern">
<onSubmitValidater>
<date pattern="yyyy/MM/dd"/>
</onSubmitValidater>
</messageDateField>
</contents>
</tableLayout>
Finally, UIX also supports use of regular expressions for
validation with the <regExp>
element
or RegExpValidater
class. We support the
syntax used by Javascript. Describing this full syntax is outside of
the scope of this document - pick up a Javascript reference - but
here's an example of entering a social security number (please, don't
enter your real number!):
<messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
<onSubmitValidater>
<regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
</onSubmitValidater>
</messageTextInput>
In many cases, you may want only some buttons on your page to
validate the user's data but want to skip validation on other buttons.
For example, a "Back" button should never force the user to enter
correct results, since those results may never get used. To turn off
validation on a component-by-component basis, use the "unvalidated"
attribute (setUnvalidated()
method).
Here's the regular expression example again, but now with two buttons,
one of which does not perform validation. Try entering an invalid
social security number; the "Submit" button will validate, but the
"Back" button will not.
<messageTextInput prompt="SSN:" name="ssn" text="123-45-6789">
<onSubmitValidater>
<regExp pattern="[0-9]{3}-[0-9]{2}-[0-9]{4}"/>
</onSubmitValidater>
</messageTextInput>
<submitButton text="Submit"/>
<submitButton text="Back" unvalidated="true"/>
Even though the UIX framework provides a powerful system for handling errors, there may be times when developers need to override that system to perform additional actions. For instance, a developer might want to take special Javascript actions when links are clicked, in addition to or instead of the work that the UIX validation system would normally perform.
For this reason, UIX makes available a few client-side Javascript functions which can be called by developers. The functions are implemented in libraries supplied with UIX. Many functions in these files are used internally by UIX and should not be called directly by developers; these function names are prefixed by an underscore "_". Behavior and arguments of these functions are subject to change between releases and should be considered off limits.
However, on function in particular could be used for developers who
need to implement special form submission behaviors: the
submitForm()
function. This function is the same one used
by the UIX Components renderers, and it allows developers to hook into
the client-side validation framework that UIX has implemented.
Developers who need to submit forms manually in UIX pages must call this method rather than submit the form DOM element directly. The following parameters should be provided:
The function will return "true" if the form was actually submitted, or "false" if the form was not submitted for any reason, such as an error or a failed validation.
In deployment versions of UIX, the Javascript file containing these
functions will be reduced for higher performance. Therefore, to see a
copy containing comments and documentation, consult the source release
for your UIX version and look in the
oracle.cabo.ui.jsLibs
directory.