adf

ADF Client Performance Optimization Tips

Posted on

If you are noticing client-side performance issues in your ADF Faces application and must support legacy browsers like Internet Explorer 7 and Internet Explorer 8, there are many techniques available to help optimize your application for these browsers:

  1. If you use af:region and the jsff page fragment files have more than 1 root component, optimize it by arranging these components with a single root component. For example, if you want your region contents to stretch, you might have one visible content component and a series of popup components, put the visible content component inside of a “center” facet of an af:panelStretchLayout and put all of those popups in the “bottom” facet but also make sure to assign bottomHeight=”0px”. If you don’t want the contents to stretch, simply wrap these components with an af:panelGroupLayout layout=”vertical”.
  2. Avoid using a af:panelStretchLayout where topHeight, bottomHeight, startWidth, or endWidth is set to “auto”.
  3. Minimize uses of af:panelAccordion in discloseMany=”true” mode if possible.
  4. Minimize use of components that provide overflow popups, e.g. af:panelAccordion, af:panelTabbed, af:toolbar, af:breadCrumbs, af:menuBar. If you have to use these components, make sure that your target screen size can accommodate the content without having to invoke overflow.
  5. Minimize use of components that provide custom title text truncation, e.g. af:panelHeader and af:showDetailHeader. If you have to use these components, make sure that your target screen size can accommodate the title text without having to truncate.
  6. Minimize use of af:carousel, af:table, af:tree, af:treeTable.
  7. Avoid complex user interfaces. Users will struggle to process complex application structures. Legacy browsers will struggle with processing of complex content. Keep the number of tabs simple (use less than 10). Keep the number of columns in a table small (use less than 10).
  8. Finally, don’t ignore the warnings that JDeveloper gives you.
Advertisements

BIDI via EL in ADF Faces

Posted on

At some point, you may have an inlineStyle whose value is BIDI sensitive; in LTR (left-to-right) mode, you may want a “left” style but in RTL (right-to-left) mode, you may want a “right” style.  You don’t have to write a managed bean to give you the corresponding style, you can use EL (expression language).

Here’s an example assigning a padding for the “start” side of 10px:

inlineStyle="padding-#{adfFacesContext.rightToLeft?'right':'left'}:10px;"
See oracle.adf.view.rich.context.AdfFacesContext for other properties.

Defining an attribute-class for an array of objects

Posted on Updated on

When defining the attributes for an ADF Faces af:pageTemplate or af:declarativeComponent inside of an af:xmlContent, you need to provide an attribute-class definition. This looks something like:

<attribute-class>java.lang.String</attribute-class>

but what if you needed an array of Strings? That has a bit stranger syntax:

<attribute-class>[[Ljava.lang.String;</attribute-class>

Why is this not just “java.lang.String[]”? These attribute-class entries are the result of calling Class.forName.  The Class.forName on an array of objects looks like “[[Lsome.package.SomeClass;”.

One-off ADF component skin appearance

Posted on Updated on

Have you ever wanted to make an ADF Faces Rich Client component have a unique appearance but not change all other instances of that component? You can as long as the things you want to change are not skin properties or skin icons (server-side aspects like properties and icons are not part of the client-side generated style sheet so this client-side solution will not allow you to change them).

Let’s start with an interactive example; the skinning demo pages for the ADF Faces Rich Client:
http://jdevadf.oracle.com/adf-richclient-demo/components/skinningKeys/inputText.jspx

On this page, there is a right-hand side where you can see some example inputText components.  When you click on the checkboxes on the left-hand side of this page, the appearance of those start to change.  However, the inputText for the find field at the top of the page does not change.  This is because of some containment selector definitions in the skin used on this page that confines the custom styling to instances of the inputText that are contained in another component that has a particular style class.  Since the find field is not inside of that container, these custom styles do not apply to that field.

Let’s say your page has a structure like this:

<af:panelGroupLayout id="pgl1" layout="scroll" styleClass="SpecialMarker">
  <af:inputText id="it1" label="Name"/>
</af:panelGroupLayout>

To style that inputText specially in this container, you would create skinning definitions like this:

.SpecialMarker af|inputText {background-color:pink}
.SpecialMarker af|inputText::access-key {color: aqua;}
.SpecialMarker af|inputText::content {background-color:red}
.SpecialMarker af|inputText::label {font-weight: bold}

Alternatively, you could put your marker directly on the inputText like this:

<af:inputText id="it1" label="Name" styleClass="SpecialMarker"/>

and have skinning definitions like this:

af|inputText.SpecialMarker {background-color:pink}
af|inputText.SpecialMarker af|inputText::access-key {color: aqua;}
af|inputText.SpecialMarker af|inputText::content {background-color:red}
af|inputText.SpecialMarker af|inputText::label {font-weight: bold}

Custom Java Bind Variable in a Where Clause of an ADF View Object

Posted on Updated on

You can make a JDeveloper model View Object (VO) use a data query that has a custom variable accessor in Java.  This is useful for cases where you want your where clause to select based on the current user name from the ADF Security context.

Go to the “Query” tab of your view object:


Add a new Bind Variable and make sure to specify “Expression” and give the value the Groovy expression “adf.object.viewObject.yourPropertyName”.  The “adf.object.viewObject” portion is essentially like saying “this object” and then it will call getYourPropertyName() to fetch the value:


Back in your query expression, refer to this new bind variable by its name but prepend “:” to indicate that it is a bind variable as shown above.

Select the “Java” tab of your view object and then click the pencil icon to edit the settings:


Select the “Generate View Object Class” and “Include bind variable accessors” options:


This will generate a new “.java” entry under your view object in the JDeveloper application navigator:


Finally, open that Java file and change the implementation of your get accessor as you wish.  In my case, I am simply returning the user name property from the ADF security context.  I also changed my set accessor to do nothing:

Decrypting Margin, Border, and Padding Styles

Posted on Updated on

When working with ADF Faces skins, JSF inlineStyles, or just CSS in general, you may have come across some explicit style definitions like this which is very easy to understand:

padding-top: 15px;

However, you may have also come across more cryptic style definitions like these but wonder which measurement applies to which side of the element:

padding: 1px 2px 3px 4px;
padding: 5px 6px 7px;
padding: 8px 9px;
padding: 10px;

Here’s a key for decoding these definitions:

padding:   top   right   bottom   left;
padding:   top   right/left   bottom;
padding:   top/bottom   right/left;
padding:   top/bottom/right/left;

This same pattern applies to margins and borders.

Aside from the cryptic definitions, see my other post displaying how the CSS Box Model treats margins, borders, and padding with respect to width and height dimensions.  If you are an ADF Faces application developer be sure to also read the Layout Basics page.

Adding Skin and Settings menus to your JDeveloper ADF application

Posted on Updated on

Ever want to display menus in your Oracle JDeveloper 11g ADF Faces rich client application so users can easily change skins or other settings on the fly?  Perhaps you don’t want to expose these menus to your end users but at least want some easy way for you to test these different options while you develop or debug your application.  This post will show you how to get it done similar to what you see on the hosted ADF Faces Rich Client demos.

Screen shot of skin and settings menus
Example skin and settings menus

Before we begin with the changes, let’s start with some background:

Skins

A skin is an Apache MyFaces Trinidad construct that is also used in ADF Faces.  A skin is very much like a standard CSS style sheet that you use to define styles used by an HTML page.  Skins abstract implementation details in the same manner that components in JavaServer Faces (JSF) abstract you from underlying HTML implementation details.  The syntax of a skin follows that of the CSS 3 spec.  Skins can extend other skins, have blocks for user agent/browser-specific definitions, accessibility-specific definitions, and other properties that would normally not be available in a CSS file.

If you are interested in creating your own skin then I would recommend reviewing these pages:

Settings

The ADF Faces framework uses settings defined in the WEB-INF/trinidad-config.xml file of your application.  This file allows you to control settings such as:

  • Which skin used by your application
  • Whether a screen reader is used
  • Whether the page should be optimized for high contrast colors
  • Whether the page should be optimized for large fonts
  • Whether animation should be enabled in component interactions

See the configuration documentation for more details on this file.

Now here are the details for how to create these menus:

The Configuration File

If your application does not already have one, create a new WEB-INF/trinidad-config.xml file.  We want to make these settings dynamic so let’s use EL expressions for the values like this:

<?xml version="1.0" encoding="UTF-8"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
 <skin-family>#{skins.skinFamily}</skin-family>
 <accessibility-mode>#{accessibility.accessibilityMode}</accessibility-mode>
 <accessibility-profile>#{accessibility.accessibilityProfile}</accessibility-profile>
 <animation-enabled>#{animation.animationEnabled}</animation-enabled>
</trinidad-config>

I am using separate managed beans here primarily for organization but you could just use a single bean.  The skin-family specifies what the active skin is.  The accessibility-mode specifies whether the page should be targeted for a screen reader.  The accessibility-profile specifies whether high contrast colors and whether large fonts should be targeted for the page.  The animation-enabled specifies whether the user will see animations in components that provide them.

The Menu Components

Some af:menuBar (most likely one that exists in your application’s page template) will need some af:menu components.  In this example, we have one menu that lists all of the skins that we want to expose and another menu that lists the settings.

ADF Faces comes with some built-in skins that have an inheritance structure like this:

  • simple — plain, nearly void of color, basic appearance
    • blafplus-medium — rectangular, solid blue colors
      • blafplus-rich — rounded, solid blue colors
    • fusion — blue colors with a rich three-dimensional appearance
      • fusion-projector — similar to the fusion skin but with colors suited for poor quality screen projectors

We will make our skin menu use radio menu items to display which skin is active:

 <af:menu text="#{bundle.MENU_SKIN}" id="skins">
   <af:commandMenuItem
     id="skin1"
     text="blafplus-rich"
     type="radio"
     actionListener="#{skins.skinMenuAction}"
     selected="#{skins.skinFamily=='blafplus-rich'}"/>
   <af:commandMenuItem
     id="skin2"
     text="blafplus-medium"
     type="radio"
     actionListener="#{skins.skinMenuAction}"
     selected="#{skins.skinFamily=='blafplus-medium'}"/>
   <af:commandMenuItem
     id="skin3"
     text="fusion"
     type="radio"
     actionListener="#{skins.skinMenuAction}"
     selected="#{skins.skinFamily=='fusion'}"/>
   <af:commandMenuItem
     id="skin4"
     text="fusion-projector"
     type="radio"
     actionListener="#{skins.skinMenuAction}"
     selected="#{skins.skinFamily=='fusion-projector'}"/>
   <af:commandMenuItem
     id="skin5"
     text="simple"
     type="radio"
     actionListener="#{skins.skinMenuAction}"
     selected="#{skins.skinFamily=='simple'}"/>
 </af:menu>

The settings menu items will be presented as individual check mark options.  I am using a group to separate the animation setting from the others that are more accessibility-related:

 <af:menu text="#{bundle.MENU_SETTINGS}" id="config">
   <af:group id="acc">
     <af:commandMenuItem
       id="accMode"
       text="#{bundle.ACCESSIBILITY_LABEL_SCREEN_READER}"
       shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_SCREEN_READER}"
       selected="#{accessibility.screenReader}"
       type="check"
       actionListener="#{accessibility.modeMenuAction}"/>
     <af:commandMenuItem
       id="contrast"
       text="#{bundle.ACCESSIBILITY_LABEL_HIGH_CONTRAST}"
       shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_HIGH_CONTRAST}"
       selected="#{accessibility.highContrast}"
       type="check"
       actionListener="#{accessibility.colorContrastMenuAction}"/>
     <af:commandMenuItem
       id="fonts"
       text="#{bundle.ACCESSIBILITY_LABEL_LARGE_FONTS}"
       shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_LARGE_FONTS}"
       selected="#{accessibility.largeFonts}"
       type="check"
       actionListener="#{accessibility.fontSizeMenuAction}"/>
   </af:group>
   <af:commandMenuItem
     id="animate"
     text="#{bundle.MENU_ITEM_ENABLE_ANIMATIONS}"
     type="check"
     selected="#{animation.animationEnabled=='true'}"
     actionListener="#{animation.animationMenuAction}"/>
 </af:menu>

Managed Beans

We could have hard-coded the values we were after in the configuration file but then you’d have to restart your application when you make changes.  Since we want these settings to be dynamic, we use EL to point these values to managed beans.  These same beans are used to handle the menu item actions as well as convey their selected states.

The managed beans can be defined in the faces-config.xml like this (using session scope to preserve the settings for the span of the user’s session):

<!-- A managed bean that we use to track the user's skin preference. -->
<managed-bean>
  <managed-bean-name>skins</managed-bean-name>
  <managed-bean-class>foo.SkinPreferences</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<!-- A managed bean that we use to track the user's accessibility preference. -->
<managed-bean>
  <managed-bean-name>accessibility</managed-bean-name>
  <managed-bean-class>foo.AccessibilityPreferences</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<!-- A managed bean that we use to track the user's animation preference. -->
<managed-bean>
  <managed-bean-name>animation</managed-bean-name>
  <managed-bean-class>foo.AnimationPreferences</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

The session-scoped SkinPreferences managed bean defines your default skin and looks like this:

public class SkinPreferences implements Serializable
{
 public String getSkinFamily()
 {
   return _skinFamily;
 }

 public void setSkinFamily(String family)
 {
   _skinFamily = family;
 }

 public void skinMenuAction(ActionEvent ae)
 {
   RichCommandMenuItem menuItem = (RichCommandMenuItem)ae.getComponent();
   setSkinFamily(menuItem.getText());
   reloadThePage();
 }

 public static void reloadThePage()
 {
   // Since the page might be using PPR navigation,
   // we need to issue a redirect to ensure the entire page redraws:
   FacesContext fContext =
     FacesContext.getCurrentInstance();
   String viewId =
     fContext.getViewRoot().getViewId();
   String actionUrl =
     fContext.getApplication().getViewHandler().getActionURL(fContext, viewId);
   try
   {
     ExternalContext eContext = fContext.getExternalContext();
     String resourceUrl = actionUrl;
     eContext.redirect(resourceUrl);
   }
   catch (IOException ioe)
   {
     System.err.println("Problem trying to reload the page:");
     ioe.printStackTrace();
   }
 }

 private String _cachedTabBarHeight = null;
 private String _skinFamily = _DEFAULT_SKIN;
 private static final String _DEFAULT_SKIN = "fusion";
 private static final long serialVersionUID = 1L;
}

The session-scoped AccessibilityPreferences managed bean looks like this:

public class AccessibilityPreferences implements Serializable
{
  public String getAccessibilityMode()
  {
    return _screenReader ? "screenReader" : "default";
  }

  public AccessibilityProfile getAccessibilityProfile()
  {
    // Use safe double-check locking just in case we have multiple threads coming
    // through.
    if (_profile == null)
    {
      synchronized (this)
      {
        if (_profile == null)
          _profile = _createAccessibilityProfile();
      }
    }
    return _profile;
  }

  public boolean isScreenReader()
  {
    return _screenReader;
  }

  public boolean isHighContrast()
  {
    return _highContrast;
  }

  public boolean isLargeFonts()
  {
    return _largeFonts;
  }

  public void setScreenReader(boolean screenReader)
  {
    _screenReader = screenReader;
  }

  public synchronized void setHighContrast(boolean highContrast)
  {
    _highContrast = highContrast;

    // We need to re-create the AccessibilityProfile instance if any of the profile
    // properties change.
    // Null out our old cached copy.
    // It will be re-created the next time that getAccessibilityProfile() is called.
    _profile = null;
  }

  public synchronized void setLargeFonts(boolean largeFonts)
  {
    _largeFonts = largeFonts;

    // We need to re-create the AccessibilityProfile instance if any of the profile
    // properties change.
    // Null out our old cached copy.
    // It will be re-created the next time that getAccessibilityProfile() is called.
    _profile = null;
  }

  public void modeMenuAction(ActionEvent ae)
  {
    setScreenReader(!isScreenReader());
    SkinPreferences.reloadThePage();
  }

  public void colorContrastMenuAction(ActionEvent ae)
  {
    setHighContrast(!isHighContrast());
    SkinPreferences.reloadThePage();
  }

  public void fontSizeMenuAction(ActionEvent ae)
  {
    setLargeFonts(!isLargeFonts());
    SkinPreferences.reloadThePage();
  }

  // Creates the AccessibilityProfile instance based on the user's current
  // accessibility preferences.
  private AccessibilityProfile _createAccessibilityProfile()
  {
    AccessibilityProfile.ColorContrast colorContrast = isHighContrast() ?
      AccessibilityProfile.ColorContrast.HIGH :
      AccessibilityProfile.ColorContrast.STANDARD;

    AccessibilityProfile.FontSize fontSize = isLargeFonts() ?
      AccessibilityProfile.FontSize.LARGE :
      AccessibilityProfile.FontSize.MEDIUM;

    return AccessibilityProfile.getInstance(colorContrast, fontSize);
  }

  // We hold a reference to a cached copy of the AccessibilityProfile so that we do
  // not need to re-create this object each time getAccessibilityProfile() is
  // called.
  // Note that we mark this as volatile so that we can safely perform double-checked
  // locking on this variable.
  private volatile AccessibilityProfile _profile;

  private boolean _screenReader;
  private boolean _highContrast;
  private boolean _largeFonts;

  private static final long serialVersionUID = 1L;
}

The session-scoped AnimationPreferences managed bean looks like this:

public class AnimationPreferences implements Serializable
{
  public boolean isAnimationEnabled()
  {
    return _animationEnabled;
  }

  public void setAnimationEnabled(boolean enabled)
  {
    _animationEnabled = enabled;
  }

  public void animationMenuAction(ActionEvent ae)
  {
    setAnimationEnabled(!isAnimationEnabled());
    SkinPreferences.reloadThePage();
  }

  private boolean _animationEnabled = true;
  private static final long serialVersionUID = 1L;
}

Adding an Accessibility Page

If you wanted to take this a step further (and do not have built-in, predefined user-specific settings after a user sign in screen), you should provide an “Accessibility” link.  This link should be exposed as early as possible in your page so that screen reader users can easily identify that you are providing them options.  This page should then provide a reduced user interface for simple settings configuration.

Screen shot of the accessibility preferences page
The accessibility preferences page

Extra faces-config.xml changes:

<!--
  A managed bean that holds onto the user's accessibility preferences while
  editing these value on the Accessibility Preferences page.  Note that unlike
  the session-scoped AccessibilityPreferences bean, this bean only requires
  request-scope, since it is only holding the values for a short time (for
  the life of one request).
  -->
<managed-bean>
  <managed-bean-name>accessibilityHolder</managed-bean-name>
  <managed-bean-class>foo.AccessibilityPreferencesHolder</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <!--
    We use a managed-property to inject the AccessibilityPreferences
    instance into our holder.  This allows the holder both to determine
    its initial values as well as apply back any changes.
    -->
  <managed-property>
    <property-name>accessibilityPreferences</property-name>
    <value>#{accessibility}</value>
  </managed-property>
</managed-bean>

The accessibility page should contain content like this:

<af:panelHeader id="ph1" text="#{bundle.ACCESSIBILITY_TITLE}">
  <af:panelFormLayout id="pfl1">
    <f:facet name="footer">
      <af:panelGroupLayout id="pgl2" layout="horizontal">
        <f:facet name="separator">
          <af:spacer id="s1" width="4px"/>
        </f:facet>
        <af:commandButton
          text="#{bundle.OK}"
          id="ok"
          action="#{accessibilityHolder.ok}"/>
        <af:commandButton
          text="#{bundle.CANCEL}"
          id="cancel"
          action="#{accessibilityHolder.cancel}"/>
      </af:panelGroupLayout>
    </f:facet>
    <af:selectBooleanCheckbox
      value="#{accessibilityHolder.screenReader}"
      id="sbc1"
      text="#{bundle.ACCESSIBILITY_LABEL_SCREEN_READER}"
      shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_SCREEN_READER}"/>
    <af:selectBooleanCheckbox
      value="#{accessibilityHolder.highContrast}"
      id="sbc2"
      text="#{bundle.ACCESSIBILITY_LABEL_HIGH_CONTRAST}"
      shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_HIGH_CONTRAST}"/>
    <af:selectBooleanCheckbox
      value="#{accessibilityHolder.largeFonts}"
      id="sbc3"
      text="#{bundle.ACCESSIBILITY_LABEL_LARGE_FONTS}"
      shortDesc="#{bundle.ACCESSIBILITY_SHORT_DESC_LARGE_FONTS}"/>
  </af:panelFormLayout>
</af:panelHeader>

The request-scoped AccessibilityPreferencesHolder managed bean looks like this:

/**
 * AccessibilityPreferencesHolder is a request-scope managed bean that temporarily
 * holds on to accessibility preferences while the end user is editing them in the
 * accessibility preferences page.  While interacting with the accessibility page,
 * we avoid pushing values directly into the AccessibilityPreferences instance,
 * since it is possible that the end user may leave the page without accepting these
 * changes.
 *
 * The values stored in this temporary object are only pushed into the
 * AccessibilityPreferences instance when the user commits the changes by clicking
 * the "OK" button
 * on the accessibility preferences page.  When leaving the page via any other means
 * (Cancel button or other command components), any changes stored in the
 * AccessibilityPreferencesHolder are discarded.
 *
 * This object is exposed as a rqeuest-scope managed bean with the name
 * "accessibilityHolder" (see faces-config.xml).  It can be accessed via the EL
 * expression "#{accessibilityHolder}".
 */
public class AccessibilityPreferencesHolder
{
  /**
   * Tests whether the user prefers screen reader-optimized content.
   */
  public boolean isScreenReader()
  {
    return _screenReader;
  }

  /**
   * Tests whether the user prefers high contrast-optimized content.
   */
  public boolean isHighContrast()
  {
    return _highContrast;
  }

  /**
   * Tests whether the user prefers content optimized for large fonts.
   */
  public boolean isLargeFonts()
  {
    return _largeFonts;
  }

  /**
   * Stores the screen reader preference.
   */
  public void setScreenReader(boolean screenReader)
  {
    _screenReader = screenReader;
  }

  /**
   * Stores the high contrast preference.
   */
  public void setHighContrast(boolean highContrast)
  {
    _highContrast = highContrast;
  }

  /**
   * Stores the large fonts preference.
   */
  public void setLargeFonts(boolean largeFonts)
  {
    _largeFonts = largeFonts;
  }

  /**
   * Setter for injecting the AccessibilityPreferences managed-property.
   * The managed-bean definition specifies that the AccessibilityPreferences
   * instance should be injected into this object via the
   * setAccessibilityPreferences() method. (See faces-config.xml.)
   */
  public void setAccessibilityPreferences(AccessibilityPreferences preferences)
  {
    // Use the session-scoped AccessibilityPreferences to initialize our
    // values to the user's current preferences.
    _largeFonts = preferences.isLargeFonts();
    _highContrast = preferences.isHighContrast();
    _screenReader = preferences.isScreenReader();

    // Keep a reference to the AccessibilityPreferences instance.  We'll need
    // access to this object in order to commit the user's new preferences.
    _preferences = preferences;
  }

  /**
   * Handles OK button clicks.
   */
  public String ok()
  {
    // Store the user's new preferences, making them active.
    _preferences.setScreenReader(isScreenReader());
    _preferences.setHighContrast(isHighContrast());
    _preferences.setLargeFonts(isLargeFonts());

    // Specify where to navigate to next.
    return "home"; // change this for your app's needs
  }

  /**
   * Handles cancel button clicks.
   */
  public String cancel()
  {
    // Don't save any new values - just return.
    return "home"; // change this for your app's needs
  }

  // User's current preferences.
  private boolean _largeFonts;
  private boolean _highContrast;
  private boolean _screenReader;

  // Injected AccessbilityPreferences instance.
  private AccessibilityPreferences _preferences;
}