Building a hybrid application with Mobile Application Framework and WebCenter Portal

Introduction

With the release of the Mobile Application Framework it makes sense to look into building a hybrid application that integrates assets from WebCenter into a native application. This will reduce the development cost (build once, reuse multiple times) and simplifies management.
WebCenter Portal provides functionality to target specific pages to specific mobile devices. By doing so, we can build pages that are designed to be consumed in a native application.
By doing so, you can build your entire mobile application in WebCenter Portal but still make use of the native features provided by the mobile framework.
It also allows you to reuse the assets and no redevelopment is required.

In this blog post I will show what steps are required to build a hybrid application in both WebCenter Portal and a mobile application.

Main Article

In order to build a hybrid application we need to complete a few steps:

1) Building a template in WebCenter Portal that will be used to display our pages for native integration
2) Build a custom component in your mobile application to consume WebCenter pages
3) Add the custom component to an AMX page

Each of these steps will be described in detail below.

In addition to this I will also briefly explain how to reuse the navigation model from WebCenter to build a dynamic navigation in your mobile application.

Build a page template in WebCenter Portal

Because we are relying on the mobile application to display the page, we have to build a clean template in WebCenter portal that does not contain anything except for the content facet.
A simple template like that will look like this:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:wcshell="http://xmlns.oracle.com/webcenter/shell" xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" xmlns:wcdc="http://xmlns.oracle.com/webcenter/spaces/taglib" xmlns:cust="http://xmlns.oracle.com/adf/faces/customizable" xmlns:trh="http://myfaces.apache.org/trinidad/html" xmlns:rtc="http://xmlns.oracle.com/webcenter/collab/rtc">
   <af:pageTemplateDef var="attrs">
      <af:xmlContent>
         <component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
            <display-name>WCSiteTemplateRRTopNavFlowCollaborative</display-name>
            <facet>
               <facet-name>auxiliary1</facet-name>
            </facet>
            <facet>
               <facet-name>content</facet-name>
            </facet>
            <attribute>
               <attribute-name>auxiliary1Size</attribute-name>
               <attribute-class>int</attribute-class>
               <default-value>200</default-value>
            </attribute>
            <attribute>
               <attribute-name>templateFixedWidth</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{not empty WCAppContext.currentPageTemplate.attributesMap['templateFixedWidth'].value ? WCAppContext.currentPageTemplate.attributesMap['templateFixedWidth'].value : '1000'}</default-value>
            </attribute>
            <attribute>
               <attribute-name>topPanelHeight</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{not empty WCAppContext.currentPageTemplate.attributesMap['topPanelHeight'].value ? WCAppContext.currentPageTemplate.attributesMap['topPanelHeight'].value : '44'}</default-value>
            </attribute>
            <attribute>
               <attribute-name>panelSeparatorHeight</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{not empty WCAppContext.currentPageTemplate.attributesMap['panelSeparatorHeight'].value ? WCAppContext.currentPageTemplate.attributesMap['panelSeparatorHeight'].value : '14'}</default-value>
            </attribute>
            <attribute>
               <attribute-name>panelFooterHeight</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{WCAppContext.currentPageTemplate.attributesMap['panelFooterHeight'].value}</default-value>
            </attribute>
            <attribute>
               <attribute-name>brandingImageUrl</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{not empty WCAppContext.currentPageTemplate.attributesMap['brandingImageUrl'].value ? WCAppContext.currentPageTemplate.attributesMap['brandingImageUrl'].value : ''}</default-value>
            </attribute>
            <attribute>
               <attribute-name>footerNavigationModelPath</attribute-name>
               <attribute-class>java.lang.String</attribute-class>
               <default-value>#{empty WCAppContext.currentPageTemplate.attributesMap['footerNavigationFile'].value ? '' :
                                'modelPath='}#{empty WCAppContext.currentPageTemplate.attributesMap['footerNavigationFile'].value ? '' :
                                 WCAppContext.currentPageTemplate.attributesMap['footerNavigationFile'].value}</default-value>
            </attribute>
         </component>
      </af:xmlContent>
      <af:panelGroupLayout id="pt_pgl3" layout="scroll" styleClass="WCSiteTemplateRoot">
         <af:panelGroupLayout id="pt_pgl5" layout="vertical" styleClass="WCSiteTemplateBackground">
            <af:panelGroupLayout id="pt_pbl1" layout="vertical" inlineStyle="width:#{attrs.templateFixedWidth}px;margin:0px auto;padding-top:8px;">
               <af:panelGroupLayout id="pt_pglc" layout="vertical" inlineStyle="min-height:600px;padding:#{attrs.panelSeparatorHeight}px 0px 12px;" styleClass="WCContent">
                  <af:region value="#{bindings.spacesNavigationPageHeader.regionModel}" id="wcSpcInd"/>
                  <af:skipLinkTarget/>
                  <af:facetRef facetName="content"/>
               </af:panelGroupLayout>
            </af:panelGroupLayout>
         </af:panelGroupLayout>
      </af:panelGroupLayout>
   </af:pageTemplateDef>
</jsp:root>

As you can see the actual content of the template is very small. It only acts as a container for the content facet.
This is because in the native application, we want to use our own template.

If of course, you want to reuse a template from WebCenter, you can do so as well but later on in this post you will see that a WebCenter page will only be a part of native app page and we don’t want the WebCenter template to interfere with the native template.

Once you have such a template, you can assign it to be the template for your mobile devices. In order to do so you need to go the administration of your portal and select Device Settings:

template

Once this is done you can start creating page variants for the devices you have the mobile template assigned to.

After this, the work can start on the mobile application.

Creating a custom component in MAF

The mobile application framework is based upon HTML5 and Javascript. It has been designed in a way that developers can easily create their own components and use them in a native application. These components are build using JavaScript and we can inject HTML into our native application.
With this approach we can build a custom component that renders an iframe in our native application

The custom component also allows us to specify the source for the iframe and the height. This way we can reuse the component in several places.

More information about building custom components can be found here: http://www.ateam-oracle.com/custom-components-with-maf/

The JS for this custom component looks like this:

(function()
{
  try
  {
    var wcpEmbed = adf.mf.api.amx.TypeHandler.register("http://xmlns.example.com/wcp", "wcpEmbed");
    
    wcpEmbed.prototype.render = function(amxNode, id)
    {
     
      var rootElement = document.createElement("div");
      var source = amxNode.getAttribute("source");
      var height = amxNode.getAttribute("height");
      rootElement.innerHTML = "<iframe width='100%' id='f1' height='"+height+"' src='"+source+"'/>";
      return rootElement;
    }
  }
  catch(problem){
      alert("Problem ");
  }
})();

When we have the javascript for our custom component we need to register it with our feature before using it:

feature

After we can register our custom component with our AMX page:

<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt"
          xmlns:wcp="http://xmlns.example.com/wcp">

Notice that the URL for our namespace matches the URL we provide in the register method in our custom component javascript:

var wcpEmbed = adf.mf.api.amx.TypeHandler.register("http://xmlns.example.com/wcp", "wcpEmbed");

Now that we have registered the component we can use it in the AMX page using the wcpEmbed component:

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt"
          xmlns:wcp="http://xmlns.example.com/wcp">
  <amx:panelPage id="pp1" styleClass="AFStretchWidth">
    <amx:facet name="header">
      <amx:outputText value="Hybrid App" id="ot1"/>
    </amx:facet>
    <amx:facet name="secondary">
      <amx:commandButton shortDesc="back" text="&lt;" action="back" id="cb1"/>
    </amx:facet>
    <wcp:wcpEmbed id="em1" height="500px" source="#{viewScope.urlBean.prettyUrl}"/>
  </amx:panelPage>
</amx:view>

When building mobile pages in WebCenter it is always a good practice to build an adaptive or responsive design.
You always have to remind that devices vary in size and people can reorient their device. Because of this some careful considerations need to be taken when designing those pages.

 

Reusing WebCenter Portal navigation model for dynamic navigation

Inside your mobile application you can even reuse the navigation model from WebCenter to build a dynamic navigation.
By using the WebCenter Portal REST API you can have access to the navigation model.

In WebCenter you can also decide about the default behavior for pages that don’t have a page variant for the requesting device.
This can be done in Device page fallback section of the page summary:

fallback

In order for this to work you also have to add the agent parameter to the header because if you call a REST API, ADF will not add the header to the request. This needs to be done manually. If you omit that parameter from the header, WebCenter will assume desktop and return all the pages.

Consuming a REST API has been made easy with the Mobile Persistence Extension for MAF: http://www.ateam-oracle.com/a-team-mobile-persistence-extension-for-oracle-maf/
This framework allows you to easily create a data control based upon the REST endpoint.

More information on the navigation REST API for WebCenter Portal can be found here: http://docs.oracle.com/cd/E29542_01/webcenter.1111/e27739/jpsdg_navigation.htm#BEHBHJDG

 

 


Add Your Comment