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.

No comments: