4 Using ADF Faces Client-Side Architecture

This chapter outlines the ADF Faces client-side architecture.

This chapter includes the following sections:

4.1 About Using ADF Faces Architecture

ADF Faces extends the JavaServer Faces architecture, adding a client-side framework on top of the standard server-centric model. The majority of ADF Faces components are rendered in HTML that is generated on the server-side for a request. In addition, ADF Faces allows component implementations to extend their reach to the client using a client-side component and event model.

The ADF Faces framework already contains much of the functionality for which you would ordinarily need to use JavaScript. In many cases, you can achieve rich component functionality declaratively, without the use of JavaScript. However, there may be times when you do need to add your own JavaScript, for example custom processing in response to a client-side event. In these cases, you can use the client-side framework.

The JavaScript class that you will interact with most is AdfUIComponent and its subclasses. An instance of this class is the client-side representation of a server-side component. You can think of a client-side component as a simple property container with support for event handling. Client-side components primarily exist to add behavior to the page by exposing an API contract for both application developers as well as for the framework itself. It is this contract that allows, among other things, toggling the enabled state of a button on the client.

Each client component has a set of properties (key/value pairs) and a list of listeners for each supported event type. All ADF Faces JavaScript classes are prefixed with Adf to avoid naming conflicts with other JavaScript libraries. For example, RichCommandButton has AdfRichCommandButton, RichDocument has AdfRichDocument, and so on.

In the client-side JavaScript layer, client components exist mostly to provide an API contract for the framework and for developers. Because client components exist only to store state and provide an API, they have no direct interaction with the document object model (DOM) whatsoever. All DOM interaction goes through an intermediary called the peer. Peers interact with the DOM generated by the Java renderer and handle updating that state and responding to user interactions.

Peers have a number of other responsibilities, including:

  • DOM initialization and cleanup

  • DOM event handling

  • Geometry management

  • Partial page response handling

  • Child visibility change handling

This separation isolates the component and application developer from changes in the DOM implementation of the component and also isolates the need for the application to know whether a component is implemented in HTML DOM at all (for example the Flash components).

In JSF, as in most component-based frameworks, an intrinsic property of the component model is that components can be nested to form a hierarchy, typically known as the component tree. This simply means that parent components keep track of their children, making it possible to walk over the component tree to find all descendents of any given component. While the full component tree exists on the server, the ADF Faces client-side component tree is sparsely populated.

For performance optimization, client components exist only when they are required, either due to having a clientListener handler registered on them, or because the page developer needs to interact with a component on the client side and has specifically configured the client component to be available. You don't need to understand the client framework as except for exceptional cases, you use most of the architectural features declaratively, without having to create any code.

For example, because the framework does not create client components for every server-side component, there may be cases where you need a client version of a component instance. Section 4.4, "Instantiating Client-Side Components," explains how to do this declaratively. You use the Property Inspector in JDeveloper to set properties that determine whether a component should be rendered at all, or simply be made not visible, as described in Section 4.8, "Understanding Rendering and Visibility."

Note:

It is also possible for JavaScript components to be present that do not correspond to any existing server-side component. For example, some ADF Faces components have client-side behavior that requires popup content. These components may create AdfRichPopup JavaScript components, even though no server-side Java RichPopup component may exist.

Other functionality may require you to use the ADF Faces JavaScript API. For example, Section 4.5, "Locating a Client Component on a Page," explains how to use the API to locate a specific client-side component, and Section 4.6, "Accessing Component Properties on the Client," documents how to access specific properties.

A common issue with JavaScript-heavy frameworks is determining how best to deliver a large JavaScript code base to the client. If all the code is in a single JavaScript library, there will be a long download time, while splitting the JavaScript into too many libraries will result in a large number of roundtrips. To help mitigate this issue, ADF Faces aggregates its JavaScript code into partitions. A JavaScript library partition contains code for components and/or features that are commonly used together. For more information, see Section 4.9, "JavaScript Library Partitioning."

4.2 Listening for Client Events

In a traditional JSF application, if you want to process events on the client, you must listen to DOM-level events. However, these events are not delivered in a portable manner. The ADF Faces client-side event model is similar to the JSF events model, but implemented on the client. The client-side event model abstracts from the DOM, providing a component-level event model and lifecycle, which executes independently of the server. Consequently, you do not need to listen for click events on buttons. You can instead listen for AdfActionEvent events, which can be caused by key or mouse events.

Events sent by clients are all subclasses of the AdfBaseEvent class. Each client event has a source, which is the component that triggered the event. Events also have a type (for example, action or dialog), used to determine which listeners are interested in the event. You register a client listener on the component declaratively using the af:clientListener tag.

4.2.1 How to Listen for Client Events

You use the af:clientListener tag to call corresponding Javascript in response to a client event. For example, suppose you have a button that, in response to a click, should display a "Hello World" alert. You need to first create the JavaScript function that will respond to the event by displaying the alert. You then add the client listener to the component that will invoke that function.

Before you begin

It may be helpful to have an understanding of client event processing. For more information, see Section 4.2, "Listening for Client Events."

To listen for a client event:

  1. Implement the JavaScript function. For example, to display the alert, you might create the JavaScript function shown in Example 4-1.

    Example 4-1 JavaScript Event Handler

    function sayHello(event)
      {
        alert("Hello, world!")
      }
    
  2. From the Operations panel of the Component Palette, drag and drop a Client Listener as a child to the component that will raise the event.

    Enter the function created in Step 1, as well as the type of action that the listener should respond to. Example 4-2 shows the code that would be created for the listener for the sayHello function.

    Example 4-2 Registering a Client Listener

    <af:commandButton text="Say Hello">
      <af:clientListener method="sayHello" type="action"/>
    </af:commandButton>
    

Tip:

Because the button has a registered client listener, the framework will automatically create a client version of the component.

When the button is clicked, because there is a client version of the component, the AdfAction client event is invoked. Because a clientListener tag is configured to listen for the AdfAction event, it causes the sayHello function to execute. For more information about client-side events, see Section 6.3, "Using JavaScript for ADF Faces Client Events."

4.3 Adding JavaScript to a Page

You can either add inline JavaScript directly to a page or you can import JavaScript libraries into a page. When you import libraries, you reduce the page content size, the libraries can be shared across pages, and they can be cached by the browser. You should import JavaScript libraries whenever possible. Use inline JavaScript only for cases where a small, page-specific script is needed.

Performance Tip:

Including JavaScript only in the pages that need it will result in better performance because those pages that do not need it will not have to load it, as they would if the JavaScript were included in a template. However, if you find that most of your pages use the same JavaScript code, you may want to consider including the script or the tag to import the library in a template.

Note, however, that if a JavaScript code library becomes too big, you should consider splitting it into meaningful pieces and include only the pieces needed by the page (and not in a template). This approach will provide improved performance, because the browser cache will be used and the HTML content of the page will be smaller.

4.3.1 How to Use Inline JavaScript

Create the JavaScript on the page and then use a clientListener tag to invoke it.

Before you begin

It may be helpful to have an understanding of adding JavaScript to a page. For more information, see Section 4.3, "Adding JavaScript to a Page."

To use inline JavaScript:

  1. Add the MyFaces Trinidad tag library to the root element of the page by adding the code shown in bold in Example 4-3.

    Example 4-3 MyFaces Trinidad Tag Library on a Page

    <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
              xmlns:f="http://java.sun.com/jsf/core"
              xmlns:h="http://java.sun.com/jsf/html"
              xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
              xmlns:trh="http://myfaces.apache.org/trinidad/html">
    
  2. In the Component Palette, from the Layout panel, in the Core Structure group, drag and drop a Resource onto the page.

    Note:

    Do not use the f:verbatim tag in a page or template to specify the JavaScript.

  3. In the Insert Resource dialog, select javascript from the dropdown menu and click OK.

  4. Create the JavaScript on the page within the <af:resource> tag.

    For example, the sayHello function shown in Example 4-1 might be included in a JSF page as shown in Example 4-4.

    Example 4-4 Inline JavaScript

    <af:resource>
      function sayHello()
      {
        alert("Hello, world!")
      }
    </af:resource>
    
  5. In the Structure window, right-click the component that will invoke the JavaScript, and choose Insert inside component > ADF Faces > Client Listener.

  6. In the Insert Client Listener dialog, in the Method field, enter the JavaScript function name. In the Type field, select the event type that should invoke the function.

4.3.2 How to Import JavaScript Libraries

Use the af:resource tag to access a JavaScript library from a page. This tag should appear inside the document tag's metaContainer facet.

Before you begin

It may be helpful to have an understanding of adding JavaScript to a page. For more information, see Section 4.3, "Adding JavaScript to a Page."

To access a JavaScript library from a page:

  1. Below the document tag, add the code shown in bold in Example 4-5 and replace /mySourceDirectory with the relative path to the directory that holds the JavaScript library.

    Example 4-5 Accessing a JavaScript Library

    <af:document>
      <f:facet name="metaContainer">
        <af:resource source="/mySourceDirectory"/>
      </facet>
      <af:form></af:form>
    </af:document>
    
  2. In the Structure window, right-click the component that will invoke the JavaScript, and choose Insert inside component > ADF Faces > Client Listener.

  3. In the Insert Client Listener dialog, in the Method field, enter the fully qualified name of the function. For example, if the sayHello function was in the MyScripts library, you would enter MyScripts.sayHello. In the Type field, select the event type that should invoke the function.

4.3.3 What You May Need to Know About Accessing Client Event Sources

Often when your JavaScript needs to access a client component, it is within the context of a listener and must access the event's source component. Use the getSource() method to get the client component. Example 4-6 shows the sayHello function accessing the source client component in order to display its name.

Example 4-6 Accessing a Client Event Source

function sayHello(actionEvent)
{
  var component=actionEvent.getSource();
  
  //Get the ID for the component
  var id=component.getId();

  alert("Hello from "+id);
}

For more information about accessing client event sources, see Section 6.3, "Using JavaScript for ADF Faces Client Events." For more information about accessing client-side properties, see Section 4.6, "Accessing Component Properties on the Client." For a complete description of how client events are handled at runtime, see Section 6.3.7, "What Happens at Runtime: How Client-Side Events Work."

4.4 Instantiating Client-Side Components

By default, the framework does not make any guarantees about which components will have corresponding client-side component instances. To interact with a component on the client, you will usually register a clientListener handler. When a component has a registered clientListener handler, it will automatically have client-side representation. You can also explicitly configure a component to be available on the client by setting the clientComponent attribute to true.

4.4.1 How to Configure a Component to for a Client-Side Instance

You can manually configure a component to have a client side instance using the clientComponent attribute.

Performance Tip:

Only set clientComponent to true if you plan on interacting with the component programmatically on the client.

Note:

When the framework creates a client component for its own uses, that client component may only contain information the framework needs at that time. For example, not all of the attributes may be available.

Before you begin

It may be helpful to have an understanding of client-side instances. For more information, see Section 4.4, "Instantiating Client-Side Components."

To configure a component for a client-side instance:

  1. In the Structure window, select the component that needs a client-side instance.

  2. In the Property Inspector, set ClientSide to true.

4.4.2 What Happens When You Set clientComponent to true

When you set the clientComponent attribute to true, the framework creates an instance of an AdfUIComponent class for the component. This class provides the API that you can work with on the client side and also provides basic property accessor methods (for example, getProperty() and setProperty()), event listener registration, and event delivery-related APIs. The framework also provides renderer-specific subclasses (for example, AdfRichOutputText) which expose property-specific accessor methods (for example, getText() and setText()). These accessor methods are simply wrappers around the AdfUIComponent class's getProperty() and setProperty() methods and are provided for coding convenience.

For example, suppose you have an outputText component on the page that will get its value (and therefore the text to display) from the sayHello function. That function must be able to access the outputText component in order to set its value. For this to work, there must be a client-side version of the outputText component. Example 4-7 shows the JSF page code. Note that the outputText component has an id value and the clientComponent attribute is set to true. Also, note there is no value in the example, because that value will be set by the JavaScript.

Example 4-7 Adding a Component

<af:commandButton text="Say Hello">
  <af:clientListener method="sayHello" type="action"/>
</af:commandButton>

<af:outputText id="greeting" value="" clientComponent="true">

Because the outputText component will now have client-side representation, the JavaScript will be able to locate and work with it.

4.5 Locating a Client Component on a Page

When you need to find a client component that is not the source of an event, you can use the AdfUIComponent.findComponent(expr) method. This method is similar to the JSF UIComponent.findComponent() method, which searches for and returns the UIComponent object with an ID that matches the specified search expression. The AdfUIComponent.findComponent(expr) method simply works on the client instead of the server.

Example 4-8 shows the sayHello function finding the outputText component using the component's ID.

Example 4-8 Finding a Client Component Using findComponent()

function sayHello(actionEvent)
{
  var buttonComponent=actionEvent.getSource();
  
  //Find the client component for the "greeting" af:outputText
  var greetingComponent=buttonComponent.findComponent("greeting");

  //Set the value for the outputText component
  greetingComponent.setValue("Hello World")
}

ADF Faces also has the AdfPage.PAGE.findComponentByAbsoluteId(absolute expr) method. Use this method when you want to hard-code the String for the ID. Use AdfUIComponent.findComponent(expr) when the client ID is being retrieved from the component.

Note:

There is also a confusingly named AdfPage.PAGE.findComponent(clientId) method, however this function uses implementation-specific identifiers that can change between releases and should not be used by page authors.

4.5.1 What You May Need to Know About Finding Components in Naming Containers

If the component you need to find is within a component that is a naming container (such as pageTemplate, subform, table, and tree), then instead of using the AdfPage.PAGE.findComponentByAbsoluteId(absolute expr) method, use the AdfUIComponent.findComponent(expr) method. The expression can be either absolute or relative.

Tip:

You can determine whether or not a component is a naming container by reviewing the component tag documentation. The tag documentation states whether a component is a naming container.

Absolute expressions use the fully qualified JSF client ID (meaning, prefixed with the IDs of all NamingContainer components that contain the component) with a leading NamingContainer.SEPARATOR_CHAR character, for example:

":" + (namingContainersToJumpUp * ":") + some ending portion of the clientIdOfComponentToFind

For example, to find a table whose ID is t1 that is within a panel collection component whose ID is pc1 contained in a region whose ID is r1 on page that uses the myTemplate template, you might use the following:

:myTemplate:r1:pc1:t1

Alternatively, if both the components (the one doing the search and the one being searched for) share the same NamingContainer component somewhere in the hierarchy, you can use a relative path to perform a search relative to the component doing the search. A relative path has multiple leading NamingContainer.SEPARATOR_CHAR characters, for example:

":" + clientIdOfComponentToFind

In the preceding example, if the component doing the searching is also in the same region as the table, you might use the following:

::somePanelCollection:someTable

Tip:

Think of a naming container as a folder and the clientId as a file path. In terms of folders and files, you use two sequential periods and a slash (../) to move up in the hierarchy to another folder. This is the same thing that the multiple colon (:) characters do in the findComponent() expression. A single leading colon (:) means that the file path is absolute from the root of the file structure. If there are multiple leading colon (:) characters at the beginning of the expression, then the first one is ignored and the others are counted, one set of periods and a slash (../) per colon (:) character.

Note that if you were to use the AdfPage.findComponentByAbsoluteId() method, no leading colon is needed as, the path always absolute.

When deciding whether to use an absolute or relative path, keep the following in mind:

  • If you know that the component you are trying to find will always be in the same naming container, then use an absolute path.

  • If you know that the component performing the search and the component you are trying to find will always be in the same relative location, then use a relative path.

There are no getChildren() or getFacet() functions on the client. Instead, the AdfUIComponent.visitChildren() function is provided to visit all children components or facets (that is all descendents). Because ADF Faces uses a sparse component tree (that is, client components are created on an as-needed basis, the component that the getParent() method might return on the client may not be the actual parent on the server (it could be any ancestor). Likewise, the components that appear to be immediate children on the client could be any descendants. For more information, see the ADF Faces JavaScript documentation.

4.6 Accessing Component Properties on the Client

For each built-in property on a component, convenience accessor methods are available on the component class. For example, you can call the getValue() method on a client component and receive the same value that was used on the server.

Note:

All client properties in ADF Faces use the getXyz function naming convention including boolean properties. The isXyz naming convention for boolean properties is not used.

Constants are also available for the property names on the class object. For instance, you can use AdfRichDialog.STYLE_CLASS constant instead of using "styleClass".

Note:

In JavaScript, it is more efficient to refer to a constant than to code the string, as in some JavaScript execution environments, the latter requires an object allocation on each invocation.

When a component's property changes, the end result should be that the component's DOM is updated to reflect its new state, in some cases without a roundtrip to the server. The component's role in this process is fairly limited: it simply stores away the new property value and then notifies the peer of the change. The peer contains the logic for updating the DOM to reflect the new component state.

Note:

Not all property changes are handled through the peer on the client side. Some property changes are propagated back to the server and the component is rerendered using PPR.

Most property values that are set on the client result in automatic synchronization with the server (although some complex Java objects are not sent to the client at all). There are however, two types of properties that act differently: secured properties and disconnected properties.

Secured properties are those that cannot be set on the client at all. For example, say a malicious client used JavaScript to set the immediate flag on a commandLink component to true. That change would then be propagated to the server, resulting in server-side validation being skipped, causing a possible security hole (for more information about using the immediate property, see Section 5.2, "Using the Immediate Attribute"). Consequently, the immediate property is a secured property.

Attempts to set secured property from JavaScript will fail. For more information, see Section 4.6.3, "How to Unsecure the disabled Property." Table 4-1 shows the secure properties on the client components.

Table 4-1 Secure Client Properties

Component Secure Property

AdfRichChooseColor

colorData

AdfRichComboboxListOfValue

disabled

readOnly

AdfRichCommandButton

disabled

readOnly

blocking

AdfRichCommandImageLink

blocking

disabled

partialSubmit

AdfRichCommandLink

readOnly

AdfRichDialog

dialogListener

AdfRichDocument

failedConnectionText

AdfRichInputColor

disabled

readOnly

colorData

AdfRichInputDate

disabled

readOnly

valuePassThru

AdfRichInputFile

disabled

readOnly

AdfRichInputListOfValues

disabled

readOnly

AdfRichInputNumberSlider

disabled

readOnly

AdfRichInputNumberSplinBox

disabled

readOnly

maximum

minimum

stepSize

AdfRichInputRangeSlider

disabled

readOnly

AdfRichInputText

disabled

readOnly

secret

AdfRichPopUp

launchPopupListener

model

returnPopupListener

returnPopupDataListener

createPopupId

AdfRichUIQuery

conjunctionReadOnly

model

queryListener

queryOperationListener

AdfRichSelectBooleanCheckbox

disabled

readOnly

AdfRichSelectBooleanRadio

disabled

readOnly

AdfRichSelectManyCheckbox

disabled

readOnly

valuePassThru

AdfRichSelectManyChoice

disabled

readOnly

valuePassThru

AdfRichSelectManyListBox

disabled

readOnly

valuePassThru

AdfRichSelectManyShuttle

disabled

readOnly

valuePassThru

AdfRichSelectOneChoice

disabled

readOnly

valuePassThru

AdfRichSelectOneListBox

disabled

readOnly

valuePassThru

AdfRichSelectOneRadio

disabled

readOnly

valuePassThru

AdfRichSelectOrderShuttle

disabled

readOnly

valuePassThru

AdfRichUITable

filterModel

AdfRichTextEditor

disabled

readOnly

AdfUIChart

chartDrillDownListener

AdfUIColumn

sortProperty

AdfUICommand

actionExpression

returnListener

launchListener

immediate

AdfUIComponentRef

componentType

AdfUIEditableValueBase

immediate

valid

required

localValueSet

submittedValue

requiredMessageDetail

AdfUIMessage.js

for

AdfUINavigationLevel

level

AdfUINavigationTree

rowDisclosureListener

startLevel

immediate

AdfUIPage

rowDisclosureListener

immediate

AdfUIPoll

immediate

pollListener

AdfUIProgress

immediate

AdfUISelectBoolean

selected

AdfUISelectInput

actionExpression

returnListener

AdfUISelectRange

immediate

rangeChangeListener

AdfUIShowDetailBase

immediate

disclosureListener

AdfUISingleStep

selectedStep

maxStep

AdfUISubform

default

AdfUITableBase

rowDisclosureListener

selectionListener

immediate

sortListener

rangeChangeListener

showAll

AdfUITreeBase

immediate

rowDisclosureListener

selectionListener

focusRowKey

focusListener

AdfUITreeTable

rowsByDepth

rangeChangeListener

AdfUIValueBase

converter


ADF Faces does allow you to configure the disabled property so that it can be made unsecure. This can be useful when you need to use JavaScript to enable and disable buttons.

Disconnected properties are those that can be set on the client, but that do not propagate back to the server. These properties have a lifecycle on the client that is independent of the lifecycle on the server. For example, client form input components (like AdfRichInputText) have a submittedValue property, just as the Java EditableValueHolder components do. However, setting this property does not directly affect the server. In this case, standard form submission techniques handle updating the submitted value on the server.

A property can be both disconnected and secured. In practice, such properties act like disconnected properties on the client: they can be set on the client, but will not be sent to the server. But they act like secured properties on the server, in that they will refuse any client attempts to set them.

4.6.1 How to Set Property Values on the Client

The ADF Faces framework provides setXYZ convenience functions that call through to the underlying ADFUIComponent.setProperty function, passing the appropriate property name (for more information, see the ADF Faces JavaScript JavaDoc). Example 4-9 shows how you might use the setProperty function to set the backgroundcolor property on an inputText component to red when the value changes.

Example 4-9

<af:form>
  <af:resource type="javascript">
    function color(event) {
        var inputComponent = event.getSource();
        inputComponent.setproperty("inlineStyle", "background-color:Red");
    }
  </af:resource>
  <af:outputText id="it" label="label">
    <af:clientListener method="color" type="valueChange"/>
  </af:inputText>
</af:form>

By using these functions, you can change the value of a property, and as long as it is not a disconnected property or a secure property, the value will also be changed on the server.

4.6.2 What You May Need to Know About Setting Properties on the Client

There may be cases when you do not want the value of the property to always be delivered and synchronized to the server. For example, say you have inputText components in a form, and as soon as a user changes a value in one of the components, you want the changed indicator to display. To do this, you might use JavaScript to set the changed attribute to true on the client component when the valueChangeEvent event is delivered. Say also, you do not want the changed indicator to display once the user submits the page, because at that time, the values are saved.

Say you use JavaScript to set the changed attribute to true when the valueChangeEvent is delivered, as shown in

Example 4-10 Using JavaScript to Set the changed Property

<af:form>
  <af:resource type="javascript">
    function changed(event) {
        var inputComponent = event.getSource();
        inputComponent.setChanged(true);
    }
  </af:resource>
  <af:inputText id="it" label="label">
    <af:clientListener method="changed" type="valueChange"/>
  </af:inputText>
  <af:commandButton text="Submit"/>
</af:form>

Using this example, the value of the changed attribute, which is true, will also be sent to the server, because all the properties on the component are normally synchronized to the server. So the changed indicator will continue to display.

To make it so the indicator does not display when the values are saved to the server, you might use one of the following alternatives:

  • Move the logic from the client to the server, using an event listener. Use this alternative when there is an event being delivered to the server, such as the valueChangeEvent event. Example 4-11 shows example JSP code.

    Example 4-11 JSP Code for Setting Property Values on the Server

    <af:form>
      <af:inputText label="label"
                    autoSubmit="true"
                    changed="#{test.changed}"
                    valueChangeListener="#{test.valueChange}"/>
      <af:commandButton text="Submit" />
    </af:form>
    

    Example 4-12 shows the corresponding managed bean code.

    Example 4-12 Using a Managed Bean to Set a Property Value

    import javax.faces.event.ValueChangeEvent;
    import oracle.adf.view.rich.context.AdfFacesContext;
     
    public class TestBean {
        public TestBean() {}
        
        public void valueChange(ValueChangeEvent valueChangeEvent) 
        {
          setChanged(true);
          AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
          adfFacesContext.addPartialTarget(valueChangeEvent.getComponent());   
          FacesContext.getCurrentInstance().renderResponse();
        }
        
        public void setChanged(boolean changed) 
        {
            _changed = changed;
        }
     
        public boolean isChanged() 
        {
            return _changed;
        }    
        private boolean _changed;
    }
    
  • Move the logic to the server, using JavaScript that invokes a custom server event and a serverListener tag. Use this when there is no event being delivered. Example 4-13 shows the JSP code.

    Example 4-13 JSP Code for Setting Property Values Using JavaScript and a Server Listener

    <af:form>
      <af:resource type="javascript">
        function changed(event) 
        {
          var inputComponent = event.getSource();
          AdfCustomEvent.queue(inputComponent, "myCustomEvent", null, true);
        }
      </af:resource>
      <af:inputText label="label" changed="#{test2.changed}">
        <af:serverListener type="myCustomEvent"
                           method="#{test2.doCustomEvent}"/>
        <af:clientListener method="changed" type="valueChange"/>
      </af:inputText>
      <af:commandButton text="Submit"/>
    </af:form>
    

    Example 4-14 shows the managed bean code.

    Example 4-14 Using a Custom Event to Set a Property Value

    package test;
     
    import javax.faces.context.FacesContext;
     
    import oracle.adf.view.rich.context.AdfFacesContext;
    import oracle.adf.view.rich.render.ClientEvent;
     
    public class Test2Bean
    {
      public Test2Bean()
      {
      }
     
      public void doCustomEvent(ClientEvent event)
      {
        setChanged(true);
        AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
        adfFacesContext.addPartialTarget(event.getComponent()); 
        FacesContext.getCurrentInstance().renderResponse();
      }
     
      public void setChanged(boolean changed)
      {
        _changed = changed;
      }
     
      public boolean isChanged()
      {
        return _changed;
      }
      private boolean _changed;
     
    }
    
  • On the client component, set the changed attribute to true, which will propagate to the server, but then use an actionListener on the command component to set the changed attribute back to false. Example 4-15 shows the JSP code.

    Example 4-15 JSP Code for Using a Listener on a Command Component to Set a Property Value

    <af:form>
      <af:resource type="javascript">
        function changed(event) {
          var inputComponent = event.getSource();
          inputComponent.setChanged(true);
        }
      </af:resource>
      <af:inputText binding="#{test3.input}" label="label">
        <af:clientListener method="changed" type="valueChange"/>
      </af:inputText>
      <af:commandButton text="Submit" actionListener="#{test3.clear}"/>
    </af:form>
    

    Example 4-16 shows the corresponding managed bean code.

    Example 4-16 Using an ActionLIstener to Set a Property Value

    package test;
     
    import javax.faces.event.ActionEvent;
     
    import oracle.adf.view.rich.component.rich.input.RichInputText;
     
    public class Test3Bean
    {
      public Test3Bean()
      {
      }
      
      public void clear(ActionEvent actionEvent)
      {
        _input.setChanged(false);
      }
     
      public void setInput(RichInputText input)
      {
        _input = input;
      }
     
      public RichInputText getInput()
      {
        return _input;
      }  
      
      private RichInputText _input;
    }
    

4.6.3 How to Unsecure the disabled Property

You use the unsecured property to set the disabled property to be unsecure. You need to manually add this property and the value of disabled to the code for the component whose disabled property should be unsecure. For example, the code for a button whose disabled property should be unsecured would be:

<af:commandButton text="commandButton 1" id="cb1" unsecure="disabled"/>

Once you set the unsecure attribute to disabled, a malicious JavaScript could change the disabled attribute unwittingly. For example, say you have an expense approval page, and on that page, you want certain managers to be able to only approve invoices that are under $200. For this reason, you want the approval button to be disabled unless the current user is allowed to approve the invoice.

If you did not set the unsecured attribute to disabled, the approval button would remain disabled until a round-trip to the server occurs, where logic determines if the current user can approve the expense. But because you want the button to display correctly as the page loads the expense, say you set the unsecure attribute to disabled. Now you can use JavaScript on the client to determine if the button should be disabled. But now, any JavaScript (including malicious JavaScript that you have no control over) can do the same thing.

To avoid the malicious JavaScript, the application has to always assume that the button may have been enabled by malicious client side Javascript and therefore needs to always recheck that the current manager has the appropriate spending authority before performing the approval. In the expense report approval screen, you might have JavaScript that checks that the amount is under $200, but you still need to have the action for the approval button perform the logic on the server. Adding the logic to the server ensures that the disabled attribute does not get changed when it should not.

Similarly, if you allow your application to be modified at runtime, and you allow users to potentially edit the unsecure and/or the disabled attributes, you must ensure that your application still performs the same logic as if the round-trip to the server had occurred.

4.6.4 What Happens at Runtime: How Client Properties Are Set on the Client

Calling the setProperty() function on the client sets the property to the new value, and synchronously fires a PropertyChangeEvent event with the old and new values (as long as the value is different). Also, setting a property may cause the component to rerender itself.

4.7 Using Bonus Attributes for Client-Side Components

In some cases you may want to send additional information to the client beyond the built-in properties. This can be accomplished using bonus attributes. Bonus attributes are extra attributes that you can add to a component using the clientAttribute tag. For performance reasons, the only bonus attributes sent to the client are those specified by clientAttribute.

The clientAttribute tag specifies a name/value pair that is added to the server-side component's attribute map. In addition to populating the server-side attribute map, using the clientAttribute tag results in the bonus attribute being sent to the client, where it can be accessed through the AdfUIComponent.getProperty("bonusAttributeName") method.

The framework takes care of marshalling the attribute value to the client. The marshalling layer supports marshalling of a range of object types, including strings, booleans, numbers, dates, arrays, maps, and so on. For more information on marshalling, see Section 6.4.3, "What You May Need to Know About Marshalling and Unmarshalling Data."

Performance Tip:

In order to avoid excessive marshalling overhead, use client-side bonus attributes sparingly.

Note:

The clientAttribute tag should be used only for bonus (application-defined) attributes. If you need access to standard component attributes on the client, instead of using the clientAttribute tag, simply set the clientComponent attribute to true. For more information, see Section 4.4, "Instantiating Client-Side Components."

4.7.1 How to Create Bonus Attributes

You can use the Component Palette to add a bonus attribute to a component.

Before you begin

It may be helpful to have an understanding of bonus attributes. For more information, see Section 4.7, "Using Bonus Attributes for Client-Side Components."

To create bonus attributes:

  1. In the Structure window, select the component to which you would like to add a bonus attribute.

  2. In the Component Palette, from the Operations panel, drag and drop a Client Attribute as a child to the component.

  3. In the Property Inspector, set the Name and Value attributes.

4.7.2 What You May Need to Know About Marshalling Bonus Attributes

Although client-side bonus attributes are automatically delivered from the server to the client, the reverse is not true. That is, changing or setting a bonus attribute on the client will have no effect on the server. Only known (nonbonus) attributes are synchronized from the client to the server. If you want to send application-defined data back to the server, you should create a custom event. For more information, see Section 6.4, "Sending Custom Events from the Client to the Server."

4.8 Understanding Rendering and Visibility

All ADF Faces display components have two attributes that relate to whether or not the component is displayed on the page for the user to see: rendered and visible.

The rendered attribute has very strict semantics. When rendered is set to false, there is no way to show a component on the client without a roundtrip to the server. To support dynamically hiding and showing page contents, the framework adds the visible attribute. When set to false, the component's markup is available on the client but the component is not displayed. Therefore calls to the setVisible(true) or setVisible(false) method will, respectively, show and hide the component within the browser (as long as rendered is set to true), whether those calls happen from Java or from JavaScript. However, because visible simply shows and hides the content in the DOM, it doesn't always provide the same visual changes as using the rendered would.

Performance Tip:

You should set the visible attribute to false only when you absolutely need to be able to toggle visibility without a roundtrip to the server, for example in JavaScript. Nonvisible components still go through the component lifecycle, including validation.

If you do not need to toggle visibility only on the client, then you should instead set the rendered attribute to false. Making a component not rendered (instead of not visible) will improve server performance and client response time because the component will not have client-side representation, and will not go through the component lifecycle.

Example 4-17 shows two outputText components, only one of which is rendered at a time. The first outputText component is rendered when no value has been entered into the inputText component. The second outputText component is rendered when a value is entered.

Example 4-17 Rendered and Not Rendered Components

<af:panelGroupLayout layout="horizontal">
  <af:inputText label="Input some text" id="input"
                value="#{myBean.inputValue}"/>
  <af:commandButton text="Enter"/>
</af:panelGroupLayout>
<af:panelGroupLayout layout="horizontal">
  <af:outputLabel value="You entered:"/>
  <af:outputText value="No text entered" id="output1"
                 rendered="#{myBean.inputValue==null}"/>
  <af:outputText value="#{myBean.inputValue}"
                 rendered="#{myBean.inputValue !=null}"/>
</af:panelGroupLayout>

Provided a component is rendered in the client, you can either display or hide the component on the page using the visible property.

Example 4-18 shows how you might achieve the same functionality as shown in Example 4-17, but in this example, the visible attribute is used to determine which component is displayed (the rendered attribute is true by default, it does not need to be explicitly set).

Example 4-18 Visible and Not Visible Components

<af:panelGroupLayout layout="horizontal">
  <af:inputText label="Input some text" id="input"
                value="#{myBean.inputValue}"/>
  <af:commandButton text="Enter"/>
</af:panelGroupLayout>
<af:panelGroupLayout layout="horizontal">
  <af:outputLabel value="You entered:"/>
  <af:outputText value="No text entered" id="output1"
                 visible="#{myBean.inputValue==null}"/>
  <af:outputText value="#{myBean.inputValue}"
                 visible="#{myBean.inputValue !=null}"/>
</af:panelGroupLayout>

However, because using the rendered attribute instead of the visible attribute improves performance on the server side, you may instead decide to have JavaScript handle the visibility.

Example 4-19 shows the page code for JavaScript that handles the visiblity of the components.

Example 4-19 Using JavaScript to Turn On Visibility

function showText() 
{
    var output1 = AdfUIComponent.findComponent("output1")
    var output2 = AdfUIComponent.findComponent("output2")
    var input = AdfUIComponent.findComponent("input")
    
    if (input.getValue() == "")
    {
      output1.setVisible(true);
    }
    else 
    {
      output2.setVisible(true)
    }
    
 }
   

4.8.1 How to Set Visibility Using JavaScript

You can create a conditional JavaScript function that can toggle the visible attribute of components.

Before you begin

It may be helpful to have an understanding of how components are displayed. For more information, see Section 4.8, "Understanding Rendering and Visibility."

To set visibility:

  1. Create the JavaScript that can toggle the visibility. Example 4-19 shows a script that turns visibility on for one outputText component if there is no value; otherwise, the script turns visibility on for the other outputText component.

  2. For each component that will be needed in the JavaScript function, expand the Advanced section of the Property Inspector and set the ClientComponent attribute to true. This creates a client component that will be used by the JavaScript.

  3. For the components whose visibility will be toggled, set the visible attribute to false.

Example 4-20 shows the full page code used to toggle visibility with JavaScript.

Example 4-20 JavaScript Toggles Visibility

<f:view>
<af:resource>
  function showText() 
  {
      var output1 = AdfUIComponent.findComponent("output1")
      var output2 = AdfUIComponent.findComponent("output2")
      var input = AdfUIComponent.findComponent("input")
      
      if (input.value == "")
      {
      output1.setVisible(true);
      }
      else 
      {
      output2.setVisible(true)
      }
      
  }
</af:resource>
<af:document>
  <af:form>
    <af:panelGroupLayout layout="horizontal"> 
      <af:inputText label="Input some text" id="input"
                    value="#{myBean.inputValue}" clientComponent="true"
                    immediate="true"/>
      <af:commandButton text="Enter" clientComponent="true">
        <af:clientListener method="showText" type="action"/>
      </af:commandButton>
    </af:panelGroupLayout>
    <af:panelGroupLayout layout="horizontal">
       <af:outputLabel value="You entered:" clientComponent="false"/>
       <af:outputText value="No text entered" id="output1"
                      visible="false" clientComponent="true"/>
       <af:outputText value="#{myBean.inputValue}" id="output2"
                      visible="false" clientComponent="true"/>
    </af:panelGroupLayout>
  </af:form>
 </af:document>
</f:view>

4.8.2 What You May Need to Know About Visible and the isShowing Function

If the parent of a component has its visible attribute set to false, when the isVisible function is run against a child component whose visible attribute is set to true, it will return true, even though that child is not displayed. For example, say you have a panelGroupLayout component that contains an outputText component as a child, and the panelGroupLayout component's visible attribute is set to false, while the outputText component's visible attribute is left as the default (true). On the client, neither the panelGroupLayout nor the outputText component will be displayed, but if the isVisible function is run against the outputText component, it will return true.

For this reason, the framework provides the isShowing() function. This function will return false if the component's visible attribute is set to false, or if any parent of that component has visible set to false.

4.9 JavaScript Library Partitioning

A common issue with JavaScript-heavy frameworks is determining how best to deliver a large JavaScript code base to the client. On one extreme, bundling all code into a single JavaScript library can result in a long download time. On the other extreme, breaking up JavaScript code into many small JavaScript libraries can result in a large number of roundtrips. Both approaches can result in the end user waiting unnecessarily long for the initial page to load.

To help mitigate this issue, ADF Faces aggregates its JavaScript code into partitions. A JavaScript library partition contains code for components and/or features that are commonly used together. By default, ADF Faces provides a partitioning that is intended to provide a balance between total download size and total number of roundtrips.

One benefit of ADF Faces's library partitioning strategy is that it is configurable. Because different applications make use of different components and features, the default partitioning provided by ADF Faces may not be ideal for all applications. As such, ADF Faces allows the JavaScript library partitioning to be customized on a per-application basis. This partitioning allows application developers to tune the JavaScript library footprint to meet the needs of their application.

ADF Faces groups its components' JavaScript files into JavaScript features. A JavaScript feature is a collection of JavaScript files associated with a logical identifier that describes the feature. For example, the panelStretchLayout client component is comprised of the following two JavaScript files

  • oracle/adf/view/js/component/rich/layout/
    AdfRichPanelStretchLayout.js

  • oracle/adfinternal/view/js/laf/dhtml/rich/
    AdfDhtmlPanelStretchLayoutPeer.js

These two files are grouped into the AdfRichPanelStretchLayout feature.

JavaScript features are further grouped into JavaScript partitions. JavaScript partitions allow you to group JavaScript features into larger collections with the goal of influencing the download size and number of round trips. For example, since the panelStretchLayout component is often used with the panelSplitter component, the features for these two components are grouped together in the stretch partition, along with the other ADF Faces layout components that can stretch their children. At runtime, when a page is loaded, the framework determines the components used on the page, and then from that, determines which features are needed (feature names are the same as the components' constructor name). Only the partitions that contain those features are downloaded.

Features and partitions are defined using configuration files. ADF Faces ships with a default features and partitions configuration file. You can overwrite the default partitions file by creating your own implementation. When you create custom ADF Faces components, you can create your own features and partition configuration files for those components.

By default, JavaScript partitioning is turned on. Whether or not your application uses JavaScript partitioning is determined by a context parameter in the web.xml file. For more information, see Section A.2.3.17, "JavaScript Partitioning."

4.9.1 How to Create a JavaScript Feature

You create a JavaScript feature by creating an adf-js-features.xml file, and then adding entries for the features.

Note:

You create JavaScript features when you create custom ADF Faces components. All existing ADF Faces components already have features created for them, and these cannot be changed.

Before you begin

It may be helpful to have an understanding of JavaScript partitioning works. For more information, see Section 4.9, "JavaScript Library Partitioning."

To create a JavaScript feature:

  1. If not already created, create a META-INF directory for your component.

  2. Right-click the META-INF directory, and choose New from the context menu.

  3. In the New Gallery, expand General, select XML and then XML Document, and click OK.

    Tip:

    If you don't see the General node, click the All Technologies tab at the top of the Gallery.

  4. Enter adf-js-features.xml as the file name and save it in the META-INF directory.

  5. In the source editor, replace the generated code with the code shown in Example 4-21.

    Example 4-21 XML for adf-js-features.xml File

    <?xml version="1.0" encoding="utf-8" ?>
    <adf-js-features xmlns="http://xmlns.oracle.com/adf/faces/feature">
     
    </adf-js-features>
    
  6. Add the following elements to populate a feature with the relevant component files and dependencies.

    • features: The root element of the configuration file.

    • feature: Create as a child to the features element. This element must contain one feature-name child element and can also contain any number of feature-class, as well as any number of feature-dependency elements.

    • feature-name: Create as a child to the feature element. Specifies the name of the feature. You must use the client component's constructor name for this value.

    • feature-class: Create as a child to the feature element. Specifies the location of the single JavaScript file or class to be included in this feature. There can be multiple feature-class elements.

    • feature-dependency: Create as a child to the feature element. Specifies the name of another feature that this feature depends on. For example, if one component B extends component A, then the feature that represents component A must be listed as a dependency for component B. By noting dependencies, the framework can ensure that any dependent classes are available, even if the two features are not in the same partition.

    Example 4-22 shows the feature element for a fictitious custom component that uses popup components (and therefore has a dependency to the popup feature).

    Example 4-22 JavaScript Features Configuration

    <features xmlns="http://xmlns.oracle.com/adf/faces/feature">
      <feature>
        <feature-name>AcmeMyPane</feature-name>
        <feature-class>
          oracle/adfdemo/acme/js/component/AcmeMyPane.js
        </feature-class>
        <feature-class>
          oracle/adfdemo/acme/js/event/AcmePaneSelectEvent.js
        </feature-class>
        <feature-class>
          oracle/adfdemo/acme/js/component/AcmeMyPanePeer.js
        </feature-class>
    
      <!-- Dependencies -->
     
      <!-- Popup hints -->
      <feature-dependency>AdfRichPopup</feature-dependency>
     
    </feature>
    

4.9.2 How to Create JavaScript Partitions

You create a JavaScript partition by creating an adf-js-partitions.xml file, and then adding entries for the features.

Note:

ADF Faces provides a default adf-js-partitions.xml file (see Section F.1.1, "The adf-js-partitions.xml File"). If you want to change the partition configuration, you need to create your own complete adf-js-partitions.xml file. At runtime, the framework will search the WEB-INF directory for that file. If one is not found, it will load the default partition file.

Before you begin

It may be helpful to have an understanding of JavaScript partitioning works. For more information, see Section 4.9, "JavaScript Library Partitioning."

To create JavaScript partitions:

  1. Right-click the WEB-INF directory, and choose New from the context menu.

  2. In the New Gallery, expand General, select XML and then XML Document, and click OK.

    Tip:

    If you don't see the General node, click the All Technologies tab at the top of the Gallery.

  3. Enter adf-js-partitions.xml as the file name and save it in the WEB-INF directory.

  4. In the source editor, replace the generated code with the code shown in Example 4-23.

    Example 4-23 XML for adf-js-partitions.xml File

    <?xml version="1.0" encoding="utf-8" ?>
    <partitions xmlns="http://xmlns.oracle.com/adf/faces/partition">
     
    </partitions>
    
  5. Add the following elements to populate a partition with the relevant features.

    • partitions: The root element of the configuration file.

    • partition: Create as a child to the partitions element. This element must contain one partition-name child element and one or more feature elements.

    • partition-name: Create as a child to the partition element. Specifies the name of the partition. This value will be used to produce a unique URL for this partition's JavaScript library.

    • feature: Create as a child to the partition element. Specifies the feature to be included in this partition. There can be multiple feature elements.

    Tip:

    Any feature configured in the adf-js-features.xml file that does not appear in a partition is treated as if it were in its own partition.

    Example 4-24 shows the partition element for the tree partition that contains the AdfRichTree and AdfRichTreeTable features.

    Example 4-24 JavaScript Partition Configuration

    <partition>
      <partition-name>tree</partition-name>
      <feature>AdfUITree</feature>
      <feature>AdfUITreeTable</feature>
      <feature>AdfRichTree</feature>
      <feature>AdfRichTreeTable</feature>
    </partition>
    

4.9.3 What Happens at Runtime: JavaScript Partitioning

ADF Faces loads the library partitioning configuration files at application initialization time. First, ADF Faces searches for all adf-js-features.xml files in the META-INF directory and loads all that are found (including the ADF Faces default feature configuration file).

For the partition configuration file, ADF Faces looks for a single file named adf-js-partitions.xml in the WEB-INF directory. If no such file is found, the ADF Faces default partition configuration is used.

During the render traversal, ADF Faces collects information about which JavaScript features are required by the page. At the end of the traversal, the complete set of JavaScript features required by the (rendered) page contents is known. Once the set of required JavaScript features is known, ADF Faces uses the partition configuration file to map this set of features to the set of required partitions. Given the set of required partitions, the HTML <script> references to these partitions are rendered just before the end of the HTML document.