skin

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}

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;
}