Thursday, October 25, 2012

Creating an adaptive layout in ADF for desktop vs. tablet

It is often ideal to create pages tailored to be accessed on a mobile device or on the desktop, but not every company has the resources to author and maintain two web sites. What if you could define most of your pages to be the same for both devices but simply adapt the layout based on the device?

The Oracle ADF framework has the necessary tools to make this easy. For example, take an application where you desire to show a list of navigation links that is always accessible and a main content area. On the desktop, you usually have a larger screen that you can work with. There you may choose to use a splitter pane that the user can collapse that is shown on the left side of the browser. On the right would be the details of the current page. On a mobile device, you may not be able to spare the width required to show the splitter and would instead rather show the navigation links in a popup.

The solution is fairly simple in ADF, by leveraging the dynamic declarative component, facet references and JSTL tags (that are supported in either facelets or JSP), you can have a layout that easily adapts to the device.

To create such a site, first create an ADF application. You can do so by downloading a 11.1.2.3.0 JDeveloper from the JDeveloper OTN page. Open JDeveloper and create a new Fusion Web Application (ADF) and accept the defaults. Create a new Page (either JSP or facelets based on your preference). In that page, you will want to add a reference to a page fragment that defines the content for a dynamic declarative component (note that a page template will also work and will allow you to pass an ADF model page template binding if desired). In this component, you will want to pass 2 facets, one for the navigation and one for the body. Should you want the same navigation on every page, you could create a page template and include the declarative component from the page template.

The technique allows you to define the content for the navigation area and the content separately from the layout. By passing these areas as faces to a declarative component, it allows you to dynamically place the content into different locations based on the device.

Example index page showing the usage of the declarative component:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <af:document title="index.jsf" id="d1">
    <af:form id="f1">
      <af:declarativeComponent id="dc" viewId="/dynamicLayout.jsff">
        <f:facet name="navigation">
          <af:navigationPane hint="list" id="np1">
            <af:commandNavigationItem text="Home" id="cni1"/>
            <af:commandNavigationItem text="Manage items 1" id="cni2"/>
            <af:commandNavigationItem text="Manage items 2" id="cni3"/>
            <af:commandNavigationItem text="Manage items 3" id="cni4"/>
            <af:commandNavigationItem text="Manage items 4" id="cni5"/>
            <af:commandNavigationItem text="Manage items 5" id="cni6"/>
            <af:commandNavigationItem text="Manage items 6" id="cni7"/>
            <af:commandNavigationItem text="Manage items 7" id="cni8"/>
            <af:commandNavigationItem text="Manage items 8" id="cni9"/>
            <af:commandNavigationItem text="Manage items 9" id="cni10"/>
            <af:commandNavigationItem text="Manage items 10" id="cni11"/>
            <af:commandNavigationItem text="Manage items 11" id="cni12"/>
            <af:commandNavigationItem text="Manage items 12" id="cni13"/>
            <af:commandNavigationItem text="Manage items 13" id="cni14"/>
            <af:commandNavigationItem text="Manage items 14" id="cni15"/>
            <af:commandNavigationItem text="Manage items 15" id="cni16"/>
            <af:commandNavigationItem text="Manage items 16" id="cni17"/>
            <af:commandNavigationItem text="Manage items 17" id="cni18"/>
          </af:navigationPane>
        </f:facet>
        <f:facet name="content">
          <af:panelGroupLayout layout="scroll" id="pgl2">
            <af:outputText value="lorem ipsum dolor sit amet officia modi anim voluptas studiosius. viribus doloribus tempus, ratione quasi. voluptates laborum quis, unum rerum. quamque cumque dolor explicabo reprehenderit. maiores nostrud ad, mites quidem. honestam quamque placeat, Et propter. ad anim cum acceperat Ut. mollitia iure humani, quaerat acceperat. nec eos culpa reprehenderit dignissimos. aspernatur feris laboriosam dolore.
lorem ipsum dolor sit amet repellat et molestias liberos quia. fugiat enim sint, reddidit earum. magnam eveniet id ut necessitatibus. reprehenderit aliquam commodo viribus dolor. et voluptate et, reprehenderit non. esse reiciendis reddere; culpa.
lorem ipsum dolor sit amet non voluptatem sapiens. officia harum illum ita rerum. iste in ipsa perniciosissimis natus. passim esse abditos animi dolore. quibusdam irure ut, Quis ipsum. tenetur victu ut enim quos. illum orationem se, delectus reddere;. sed velit velit, eaque aliqua. aliquam sapiente lorem voluptatum dolore. aut fugiat totam rerum.
lorem ipsum dolor sit amet nondum voluptatem ab. primo Duis propagabant consequatur non. dolor quia victu quid dolore. dolorem eos Sed odio inscientiam. id aut in, et facilis. quo deserunt sed, sint errorem. studiosius delectus maxime qui ducimus. sapiens soluta alias labore ex. et corporis unam non se. nisi corporis et expedita.
lorem ipsum dolor sit amet facere ex occaecati. magnam quae reprehenderit, culpa acceperat. natus nesciunt quia lorem iste. qui eu aequabile quisquam.
lorem ipsum dolor sit amet quadam earum quidam. perferendis in in, optio dolor. expedita cognovit rerum minus quia. quas qui quanta fuga deleniti. quam labore deinde ducimus materia. praesentium non saepe, si ut. voluptas quia se acceperat.
lorem ipsum dolor sit amet et et rerum quibusdam commodo. aute temeraria nuptias, audientes non. rem voluptate viderat sit irure. alias reddidit aliqua propter pariatur. ad doloribus doloremque, recusandae pariatur. elicere ad ius viribus tempor. dignissimos dolores res, dignissimos optio. consectetur deleniti atque excepturi.
lorem ipsum dolor sit amet est et placeat. religionis et et animi dispersos. modi nulla voluptate, officia esset. consequatur sunt quisquam soluta et. reprehenderit illo et, liberos cupiditate. inesset victu cupiditate magnam.
lorem ipsum dolor sit amet fero se congregavit recusandae ratione. ea ad quamque, nihil nobis. aperiam facilis ea voluptate conpulit. perferendis eveniet suscipit cupidatat ius. cognovit cillum conpulit impedit eligendi. deserunt corporis non aut Et. ut eam rem, non propagabant. molestiae quis commodi nemo.
lorem ipsum dolor sit amet amet aliquip reiciendis esse dolores. et beatae consequatur, unam et. divinae beatae agros, ducimus sint. do provident quo, temeraria qui. omnis Nemo utilem Excepteur minus. qui culpa perspiciatis, ratione in. et exercitationem qui, a libero. minima fero vel quo.
lorem ipsum dolor sit amet et consequatur qui Quis aperiam. voluptatibus nisi ad, viderat corporis. non nihil amet quos in. et numquam earum voluptate totam. voluptatum ad animi, minim et. videlicet perspiciatis nisi, libero propter. repellendus victu propter, rerum officiis. quidam possimus quicquam, dolore nihil. rerum ullamco aliquip, quaerat rerum. asperiores a vitam non.
lorem ipsum dolor sit amet hominum ipsum feris voluptatem ea. eos ad Itaque, dolorem consectetur. qui eligendi voluptatibus distinctio corporis. occaecat eveniet Excepteur, eos nihil. acceperat dolores elicere ea blanditiis. id esse satellitibus vagabantur.
lorem ipsum dolor sit amet consectetur consequatur officia sit nulla. expedita molestiae est nihil deserunt. molestiae adipisci quo iusto reclamantes. sint quia et ea.
lorem ipsum dolor sit amet maiores magnam deleniti. legitimas ex et nulla voluptates. sapiens alias quas victu nulla. veniam fuga eos est dolorum. autem distinctio corrupti, fuga inesset. consequatur autem viribus, officia Ut. propter non reprehenderit et culpa. laboriosam maximas in ipsum repudiandae. dolor et expedita sunt divinae. magnus autem necessitatibus bestiarum.
lorem ipsum dolor sit amet ad tempore opportunitas. aut sit magni, quis elit. sapiente liberos occaecat adipisci delectus. consectetur dignissimos et, inventore officii. impedit nec in, nuptias vitam. in voluptatem rerum minim et. administrabant et et, quo adipisicing. beatae ratione in, pariatur consequatur. quas animis fero si eius. quod rerum molestiae aliqua.
lorem ipsum dolor sit amet qui honestam acceperat animi voluptas. excepturi saepe et, tempus maxime. ut doloremque dolores, illo officiis. eam sed optio, unde Nemo. omnis laborum quis fugiat in. et voluptate aut, inducens in. molestias honestam laboris, mollit facere. voluptatem eos nihil occaecat repellendus. dolor cupiditas pleraque quos mollit. mollit hic conpulit aut.
lorem ipsum dolor sit amet in molestias qui. reclamantes sit reddidit, quis abutebatur. esse deserunt Ut, laboris atque. quas locum At, eaque studiosius. aspexerat elit dispersos, distinctio inesset. quo aut quadam, orationem ullamco. ac inscientiam lorem, amet ut. voluptas ex silvestribus videlicet.
lorem ipsum dolor sit amet aute abutebatur perniciosissimis omnis non. quis ullam sunt, nemo qui. quia id rerum mansuetos nobis. eos quid in si bestiarum. est earum placeat, nulla et. in reddidit sint, homines nostrum. in aut iste, accusamus et. velit in nulla soluta rem. atque honestam At ratio dolorum. natus omnis dolores laborum.
lorem ipsum dolor sit amet ratione aliquid rerum nam assumenda. nihil omnis ut, studiosius id. rem corporis enim quoddam aut. pariatur pleraque est voluptatem Duis. explendam deserunt pleraque numquam qui. commodo dolor sint, studiosius reddere;. Nemo ut voluptas rerum sit. officia sed nec, a est. reprehenderit mites laudantium veniam."
                           id="ot1"/>
          </af:panelGroupLayout>
        </f:facet>
      </af:declarativeComponent>
    </af:form>
  </af:document>
</f:view>

In the declarative component, you will now want to use JSTL to build either a splitter or a button that shows a popup based on the device. ADF has an API for the browser agent that includes capabilities. One of the capabilities that is exposed is if the agent is a touch screen device (Android and iOS devices). By referencing this capability from JSTL, you can change what components are created. Example EL usage:

<c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? true : false}">

Using this you can create two layouts, each using af:facetRef to include the passed in facets. Note that JDev will report errors that the facet is used more than once, but that is only because it does not know the c:if tag will be sure to only render one set. Example code for the declarative component page fragment:

<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:f="http://java.sun.com/jsf/core">
  <af:componentDef var="attrs" componentVar="comp">
    <af:xmlContent>
      <component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
        <description>Layout dynamically adjusted based on user agent</description>
        <facet>
          <facet-name>
            navigation
          </facet-name>
        </facet>
        <facet>
          <facet-name>
            content
          </facet-name>
        </facet>
      </component>
    </af:xmlContent>
    <c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? true : false}">
      <af:panelSplitter id="dc_ps1" dimensionsFrom="parent">
        <f:facet name="first">
          <af:facetRef facetName="navigation" />
        </f:facet>
        <f:facet name="second">
          <af:facetRef facetName="content" />
        </f:facet>
      </af:panelSplitter>
    </c:if>
    <c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? false : true}">
      <af:panelStretchLayout topHeight="2em" bottomHeight="0" id="dc_psl1"
                             dimensionsFrom="children">
        <f:facet name="top">
          <af:panelGroupLayout id="dc_pgl1">
            <af:commandButton id="dc_cil1" text="Open navigation" iconPosition="trailing"
                                 icon="/afr/fusion/dropdown_ena.png">
              <af:showPopupBehavior popupId="dc_navPopup" triggerType="action" align="overlap"/>
            </af:commandButton>
          </af:panelGroupLayout>
        </f:facet>
        <f:facet name="center">
          <af:facetRef facetName="content"/>
        </f:facet>
        <f:facet name="bottom">
          <af:popup id="dc_navPopup">
            <af:facetRef facetName="navigation" />
          </af:popup>
        </f:facet>
      </af:panelStretchLayout>
    </c:if>
  </af:componentDef>
</ui:composition>

Now load the page in a browser and on an iPad and see the layout change. How it looks on the desktop:

desktop image

How it looks on an iPad:

iPad image

How it looks on an iPad with the navigation open:

iPad image

You can download the sample JDeveloper workspace for 11.1.2.3.0 here.

2 comments:

Filip Huysmans said...

Hi,
Instead of the if statement, you can use the switcher component.

HTH

Filip

Andrew Robinson said...

af:switcher would not work. af:switcher is a render-time component, meaning that the components under it are always created, which would cause errors with the af:facetRef tags. Also, af:switcher is not ideal for performance reasons as it would create the components that would not be used even if it were to work. JSTL tags are ideal for this type of work as they ensure that only one set of components are created.