Oracle® Application Development Framework Case Manual
10g Release 2 (10.1.2) B19163-01 |
|
Previous |
Next |
This chapter contains the following sections:
The pages discussed in Lesson 4 demonstrate how to process the customer order. In this lesson, we examine how to require the customer to sign in to an existing account, and, in the case of a new customer, how to create an account. To accomplish this, we will examine the structure of these JSP pages:
registernewuser.jsp
, to take a new customer's account information
signin.jsp
, to accept an existing customer's user ID and password
accountcreated.jsp
, to confirm the new customer's account creation
In this lesson, we'll examine the layout of these new JSP pages and describe how the application returns the customer to the previous page after sig- in is verified.
The page flow proceeds with the action forward requireslogin
from the /confirmshippinginfo
data page. The forward destination is the /signin
data page. In this application, we have choosen to require sign-in before taking the customer's shipping information. Although this task could have been performed earlier, we prefer not to interfere with the customer's ability to browse the catalog until they proceed to checkout. To ensure that the customer does sign in, the initializeModelForPage()
method in the confirmshippinginfo
data action performs this test:
protected void initializeModelForPage(DataActionContext ctx) { HttpServletRequest request = ctx.getHttpServletRequest(); if (!AppUserInfo.isSignedOn(request)) { ctx.setActionForward("requireslogin"); } else { getToyStoreService(ctx).createNewOrder(AppUserInfo.signedInUser(request)); } }
If the user has signed in already, the order is created by the service method createNewOrder()
and the application displays the confirmshippinginfo.jsp
page, as described in Lesson 4. However, assuming that the customer has not signed in, the Struts method setActionForward()
is invoked with the forward requireslogin
to initiate the action mapping and display the signin.jsp
page.
After programmatically invoking the action forward and displaying the signin.jsp
page, the user must be able either to sign in or to register as a new customer for the first time. The page flow diagram represents the transition from signin.jsp
to registernewuser.jsp
as a dashed line to distinguish an ordinary page link from an action forward (represented by a solid line). The difference is that the page link does not map to an associated data action and therefore a link cannot cause the application to attach the binding container to the target page. As we will see, the register new user page is an empty form with no data displayed.
The last task of the sign-in page flow is to provide confirmation to the user once they have registered a new account. The account confirmation task is represented in the page flow diagram by the save
action forward with the target /accountcreated page forward icon. The final diagram with the sign-in page flow is shown in Figure 8-1, "Sign-In Page Flow".
The page forward has not yet been used in the Struts page flow of this application, but it is conceptually similar to a page link: in either case the target page requires no data binding. In the case of a page forward, the icon is a representation of a Struts action that always forwards to a specified destination web page.
Best Practice Tip: Although the Struts page flow diagram allows you to insert JSP page icons into the diagram, in most cases you will use the Page Forward icon instead of a JSP icon to represent the target web page. This standard practice ensures that all web pages are represented in the Struts configuration file and therefore permits the Struts controller to handle page navigation. |
We'll reserve the action mapping discussion for the confirmshippinginfo.do
action until the next lesson, when we also describe the target pages in the flow diagram.
The main design feature of the signin.jsp
page is the use of a Struts form (<html:form>
tag) to accept user input. The fields of the form are also Struts HTML elements that access the properties of the form bean:
<html:text>
, where the property
attribute is the name of the Oracle ADF attribute binding that accesses the desired business object
<html:password>
, where the property
attribute is the name of the Oracle ADF attribute binding that accesses the desired business object
When the user clicks to submit the form, with these additional attributes set, at runtime the Oracle ADF binding container populates the bindings with the values of the username and password HTML form fields. Those values are accessible by the onVerifySignin()
event-handler method of the SignInAction
class, which will handle the sign-in form's postback when the user submits the form.
The onVerifySignin()
method in the data action first verifies that neither the username
nor password
properties is blank, and then calls the validSignon()
method on the ToyStoreService
business service interface to verify whether the username/password combination represents a valid web store user. If any validation check fails, onVerifySignin()
adds a Struts ActionError
object to the ActionErrors
collection so that the view layer can present appropriate error messages to the user and so that the user is returned to the sign-in page to try again. If the sign-in validation check succeeds, then the onVerifySignin()
method calls a helper method (signIn()
method of the AppUserInfo
class) to flag the current user as signed in, and returns the appropriate page to forward the request to.
Since several different actions in the application can require the user to log in, the setForwardAction()
method in this data action uses the target
parameter to return the correct "next" page in the flow, based on which action required the user to log in.
Best Practice Tip: With the addition of a secondary resource message file for global errors identified by the keyGlobalErrors , you can ensure that error strings like those represented by the INVALIDLOGIN constant are translated into user-readable messages. You make Struts aware of the names of your message resource files in struts-config.xml , where the <message-resources> element with the key attribute defines the location of the secondary message resource:
<message-resources key="GlobalErrors" parameter="toystore.view.GlobalErrors"/> |
The registernewuser.jsp
page (used by the /register
data page) renders the data entry form that allows users to register on the site for the first time. Because the results produced in the browser of this page are nearly identical to the editexistingaccount.jsp
page, we decided to render the entire form by the single <jsp:include page="formControl.jsp">
tag. This tag works like a reusable component, including the contents of the formControl.jsp
page. The nested <jsp:param>
tags pass three parameters to the reusable component page:
dataPage
— the name of the current data page
saveButtonLabelKey
— the message bundle key to the label to be displayed on the Save button
saveButtonEvent
— the name of the event to be associated with the clicking of the Save button.
So the actual work being done lies in the formControl.jsp
component page. The page builds a data entry form with one databound control for each control value binding in the current binding container.
Note: The following discussion represents a generic, metadata-driven way of rendering the binding data in contrast to the more traditional approach of specifying each binding in the page source. In contrast to this generic technique explained below, the Toy Store application also includes the register new user page to render a data entry form in the traditional way. Both forms render the same set of controls for account data, so you can compare the two approaches and pick the one that will suit your application needs best. See Lesson 6 for a discussion of the register new user page. |
The form control page begins with some examples of using the <c:choose>
, <c:if>
, and <c:set>
tags. The following excerpt uses these tags to conditionally set up the values of local page variables named eventName
, buttonLabel
, and buttonLabelKey
, based on whether and which of the expected input parameters were provided. We'll use these variables later in the page as part of constructing the Save button at the bottom of the generated form.
<c:choose> <c:when test="${not empty param.saveButtonEvent}"> <c:set var="eventName" value="${param.saveButtonEvent}"/> </c:when> <c:otherwise> <c:set var="eventName" value="Commit"/> </c:otherwise> </c:choose> <c:if test="${not empty param.saveButtonLabel}"> <c:set var="buttonLabel" value="${param.saveButtonLabel}"/> </c:if> <c:if test="${not empty param.saveButtonLabelKey}"> <c:set var="buttonLabelKey" value="${param.saveButtonLabelKey}"/> </c:if>
The formControl.jsp
page goes on to use the <html:errors>
tag as part of a "global errors" section of the input form, where any errors that are not attribute-specific will show up:
<center> <table border="0"> <tr> <td><html:errors bundle="GlobalErrors" property="<%= ActionErrors.GLOBAL_ERROR %>"/></td> </tr> </table> </center>
Next, the form uses the value of the dataPage
parameter passed in by the <jsp:include>
tag as part of opening the <html:form>
tag. Notice that since we cannot use EL expressions directly in the <html:form>
tag's action
attribute, we first use <c:set>
to set a local page variable named name
with the EL-expression value we want, and then we use a JSP scriptlet to pass the value of this name
variable to the action
attribute:
<c:set var="name" value="${param.dataPage}.do"/> <html:form action='<%= pageContext.getAttribute("name")%>'> <!-- etc. --> </html:form>
The form includes the standard hidden field that the Oracle ADF controller layer uses to detect whether the user has tried to submit the same form multiple times in rapid succession:
<input type="hidden" name="<c:out value='${bindings.statetokenid}'/>" value="<c:out value='${bindings.statetoken}'/>"/>
Next we begin the loop that will create an HTML form field for each control value binding in the binding container. Inside the <table>
tag, we have the following <c:forEach>
iteration:
<c:forEach var="curBinding" items="${bindings.ctrlBindingList}"> <% JUControlBinding cb = (JUControlBinding)pageContext.getAttribute("curBinding"); if (cb instanceof JUCtrlValueBinding && !(cb instanceof JUCtrlRangeBinding) && !(cb instanceof JUCtrlHierNodeBinding)) { %> <!-- Build control for current control value binding in here --> <% } %> </c:forEach>
The <c:forEach>
loop iterates over the list of control value bindings from the binding container. Since this list might include control action bindings, we need to skip over these when rendering the input controls. Since we want to keep things simple, we'll also skip over RangeBindings
and TreeBindings
too. The EL expression language doesn't have a built-in instanceof
operator, so we're using a JSP scriptlet to insert a regular Java-language if
statement to perform the combination of instanceof
checks.
Note: We could have decided to generically render a set of buttons for any of the action bindings found in the binding container, which would be of typeJUCtrlActionBinding in the oracle.jbo.uicli.binding package ; instead, the application just renders a single Save button on the form.
|
Since we specified the var="curBinding"
attribute on the <c:forEach>
tag, inside the loop we can refer to this curBinding
loop variable to access the current control value binding as part of our generic form input control generation. In the following excerpt, notice how we're making use of the binding properties in our EL expressions like tooltip
, mandatory
, and label
to access this metadata from the current control binding.
<c:forEach var="curBinding" items="${bindings.ctrlBindingList}"> <% JUControlBinding cb = (JUControlBinding)pageContext.getAttribute("curBinding"); if (cb instanceof JUCtrlValueBinding && !(cb instanceof JUCtrlRangeBinding) && !(cb instanceof JUCtrlHierNodeBinding)) { %> <tr> <th align="right" title="<c:out value='${curBinding.tooltip}'/>"> <c:if test="${curBinding.mandatory}">* </c:if> <c:out value="${curBinding.label}"/> </th> <td> <c:set var="name" value="bindings.${curBinding.name}"/> <adf:inputrender model='<%= pageContext.getAttribute("name")%>'/> </td> <c:set var="name" value="${curBinding.name}"/> <td> <html:errors property='<%= pageContext.getAttribute("name") %>'/> </td> </tr> <% } %> </c:forEach>
To actually render the HTML form control, we use the <adf:inputrender>
tag, which is set up to render an appropriate tag based on the datatype of the current binding's attribute value (the use of Business Components metadata to invoke a custom renderer will be discussed in Lesson 6). We repeat our trick of using <c:set>
to set a local page variable named name
to the concatenation of the string "bindings
." with the name of the current binding, which is what the <adf:inputrender>
tag expects as the value of its model attribute. We use the <html:errors>
tag to show any attribute-level validation errors that might occur next to the control to which they are relevant. We again use the <c:set>
trick to make the value of the <html:errors>
tag's property
attribute match the name of the current binding.
Finally, as the following excerpt shows, we use a <c:choose>
tag to put the appropriately labeled Save button at the bottom of the form. Based on whether the user specified a button label or a button label key, we either use the literal label string or employ the <bean:message>
tag to look up the label key for us. We're using our local page variable eventName
that we set up at the top of the page to fill in the right name for the button to generate that event when the user clicks it.
<c:choose> <c:when test="${not empty buttonLabel}"> <input name="event_<c:out value="${eventName}"/>" type="submit" value='<c:out value="${buttonLabel}"/>'/> </c:when> <c:when test="${not empty buttonLabelKey}"> <input name="event_<c:out value="${eventName}"/>" type="submit" value='<bean:message name="buttonLabelKey"/>'> </c:when> <c:otherwise> <input name="event_<c:out value="${eventName}"/>" type="submit" value='Submit'/> </c:otherwise> </c:choose>
As previously explained, the Struts page flow uses a page forward to represent the accountcreated.jsp
page. No data action is necessary to render this page because no bindings are used to display information. The page contains a single link that allows the user to return to the home page after creating their account:
<a href="<c:url value='home.do'/>"> <bean:message key="accountcreated.gotomainpage"/></a>
In the following hands-on, you can optionally explore adding a databound text field to display the customer name in the page. This single change will necessitate changing the page forward to a data page element in the Struts diagram.
The following hands-on shows how you can easily change a Struts page forward (/accountcreated
) that displays no data bindings into an Oracle ADF data page capable of displaying the customer name in the page.
From the Struts page flow diagram, locate the /accountcreated page forward icon and double-click to open the accountcreated.jsp
page in design view.
With the accountcreated.jsp
page displayed, open the Data Control Palette and expand the ToyStoreService data control, Accounts.
Locate the Firstname attribute node under Accounts and drag the attribute node into the open page so that it appears before the message accountcreated.header
.
In the Selected Page Flow Data Binding Option dialog, select Convert the selected page to a data page to convert the selected page to a data page and click OK.
Select the Source tab and locate the new value binding. The binding should appear before the <bean:message>
tag. Type an extra space to separate the two:
<h2><c:out value="${bindings.Firstname}"/> <bean:message key="accountcreated.header"/> </h2>
Return to the Struts page flow diagram and observe the new /accountcreated data page substituted for the original page forward. Select the Source tab and observe the modified action mapping:
<action path="/accountcreated" className="oracle.adf.controller.struts.actions.DataActionMapping" type="oracle.adf.controller.struts.actions.DataForwardAction" name="DataForm" parameter="/WEB-INF/jsp/accountcreated.jsp"> <set-property property="modelReference" value="WEB_INF_jsp_accountcreatedUIModel"/> </action>
In the Application Navigator, expand toystore.view and double-click WEB_INF_jsp_accountcreatedUIModel.xml to open the newly created UI model definition file and observe the Oracle ADF data control definitions.
The UIModel.xml
definition file is created in JDeveloper the first time you drop a databound control from the Data Control Palette into your open JSP page.
In the Application Navigator, select WEB_INF_jsp_accountcreatedUIModel.xml so that it appears highlighted, and open the Structure window. Observe the AccountIterator iterator and Firstname value binding. You may double-click these items to edit the contents of the UI model definition file.
Right-click the home.do action and choose Run to launch the application. Click any category in the home page. Select any product link and add it to your cart. Proceed to checkout. When asked to sign in, click the Register as a New User link instead. Complete the form and supply a fictitious customer name and account information (be sure to observe validation errors for the entered data). Then click Submit. The customer's first name that you just created should appear in the account created page.
The data page manages the model data binding for the page. Oracle ADF provides the data page (oracle.adf.controller.struts.actions.DataForwardAction
) to prepare the binding context for databound web pages and to execute custom business service methods exposed through the model. In this case, no business methods are required, and the standard DataForwardAction
class will suffice to prepare the binding context before posting back to the page to be displayed.
Note that the binding displays the name in all lowercase. It is possible to create a service method to convert the username
attribute to initial caps and to execute that method in a custom DataForwardAction
class, similar to the ones described in previous lessons.