Showing posts with label ADF Mobile. Show all posts
Showing posts with label ADF Mobile. Show all posts

Monday, October 21, 2013

ADF Mobile: Working with iframes

Oracle ADF Mobile, as of version 11.1.2.4.0, does not provide a web browser control that you can embed and use. There is the notion of implementing an application feature as a remote url, but that is tied in to a connection that cannot be modified at runtime.

I happened to have a need for modifying the context parameters of the target location at runtime.

Here is what I was trying to do:
  1. Fetch some data using a REST service
  2. On a certain action, show the web site inside the application itself
For the second operation, all I wanted to do was construct the target URL and display it in an IFRAME. Turns out there is no easy way to do it!

So I ended up developing a hack that looks like this:



The App Browser Proxy

I added a new feature - "App Browser Proxy" - whose sole purpose was to display a URL (which has been white listed). Since remote connections are out of the question, I decided to implement this feature as a Local HTML file instead.

The good thing about implementing a feature as a local HTML file is that you can add HTML elements like scripts & iframes. The only problem is how do you pass data from your business controller to the page?

The developer guide talks about how one can use JavaScript to get hold of the business logic code, but it is classified under "Using ADF Mobile APIs to Create a Custom HTML Springboard Application Feature". Not exactly the set of keywords I am looking for, but nonetheless in here lies the solution for this hack.

Putting these concepts together, yielded the following code for my local HTML file:


/Web Content/app-browser-proxy/pages/app-browser-proxy.xhtml
<script type="text/javascript">
  if (!window.adf) { window.adf = {}; }
  adf.wwwPath = "../../../../../www/";
</script>
<!-- Take note about the number of folders that you need to skip -->
<script type="text/javascript" src="../../../../../www/js/base.js"></script>
<script type="text/javascript" src="../../../../../www/js/adf.el.js"></script>
<script type="text/javascript">
  function onEvaluateSuccess(req, res) {
    try {
      if (res) {
        if (res[0]) {
          var targetURL = res[0].value;
          if (targetURL) {
            var ifrm = document.getElementById("app-browser-iframe");
            if (ifrm) {
              ifrm.src = targetURL;
            }
          }
        }
      }
    }
    catch (e) {
      alert("Load Target: error occurred " + e);
    }
  }

  function onError(req, resp) {
    alert("Error occurred " + adf.mf.util.stringify(res));
  }

  function loadTargetURL() {
    try {
      adf.mf.el.getValue("#{applicationScope.appBrowserTargetUrl}",
                          onEvaluateSuccess, onError);
    }
    catch (e) {
      alert("Caught exception: " + e);
    }
  }
  document.addEventListener("showpagecomplete", loadTargetURL, false);
</script>
<iframe id="app-browser-iframe" width="100%" height="100%"
           style="background-color:#ffffff; border: none;"></iframe>


In the above fragment, I have achieved the following:
  1. Create an IFRAME that occupies the entire height & width of the screen
  2. Using JavaScript, fetch the target URL by evaluating an EL - applicationScope.appBrowserTargetUrl
  3. Set the IFRAME's source to the evaluated EL

The App Browser Proxy Feature Listener

The thing about ADF Mobile is that once it loads a feature, it does not necessarily reload it every time you navigate between features. So, the above code will work perfectly for the first time. Second time you navigate to this page, you will continue to see the last loaded page.

In order to work around this issue, you need to create a Feature Listener, that invokes the JavaScript function that we have defined - loadTargetURL().

In the activate method of the feature listener, adding the following snippet will do the trick:

AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureName(), "loadTargetURL",new Object[0]);


The Final Piece

Finally, invoking this app browser proxy, from any other feature, is a matter of adding the following code snippet:

// Ensure that the target URL has been set in the following EL
// #{applicationScope.appBrowserTargetUrl}
// e.g.,
// AdfmfJavaUtilities.setELValue(
//       "#{applicationScope.appBrowserTargetUrl}", targetURL)

// Use the correct feature ID
AdfmfContainerUtilities.resetFeature("demo.AppBrowserProxy");
AdfmfContainerUtilities.gotoFeature("demo.AppBrowserProxy");

Note: This hack works because I store the target URL in the application scope. Any other scope will not be available across features.

Hope this was useful to you.

Thursday, October 17, 2013

ADF Mobile: Listening for changes to preferences

Lately I've been doing quite a bit of prototyping and, as is the case with such activities, I kept coming up with weird requirements that didn't have any apparent answers available .... some can be solved with a bit of ingenuity, the others leave me a few hair strands less.

Oracle ADF Mobile is a pretty neat framework for designing cross platform mobile apps. I was tasked with creating a prototype that communicates with a REST service to fetch some data.

As I went about with the exercise, I decided that I wanted to store the server configuration and user credentials in the application preferences.


Done. Next I wanted to refresh my controllers every time the preferences changed ... well this is where I got stumped! There is no preference-change-listener that I could implement.

Fortunately, there is an alternative - a feature level LifeCycleListener - which has only two methods activate() & deactivate() which are invoked when the feature is displayed and hidden. Every time you access the app's settings page (on your android phone) you are forcing the "Feature" to "deactivate" and then "activate" when you come back.

It is in this listener that you could employ a nifty hack to check if your preferences have changed or not. I chose to verify the hash codes to determine if the preferences had changed; and accordingly invalidate the required content.

So I ended with the following code for my listener:


public class PortalFeatureListener
  implements LifeCycleListener
{
  private long prefsHashCode = -1;

  public void activate()
  {
    // Verify if the hashcode of the preferences has changed or not.
    String prefs = "" +
     AdfmfJavaUtilities.evaluateELExpression(
        "#{preferenceScope.application.serverConfig.connUrl}") +
     AdfmfJavaUtilities.evaluateELExpression(
        "#{preferenceScope.application.userCredentials.username}") +
     AdfmfJavaUtilities.evaluateELExpression(
        "#{preferenceScope.application.userCredentials.password}");
    
    if(prefs.hashCode() != prefsHashCode) 
    {
      prefsHashCode = prefs.hashCode();
      refreshControllers();
    }
  }

  public void deactivate()
  {
    // Do nothing for now.
  }
  
  private void refreshControllers() 
  {
    // Do something here.
  }
}


, which gets configured in the adfmf-feature.xml file.



Hope this was useful to you.