Wednesday Feb 25, 2015

How-to create a select one choice list-of-values bound to the AMX binding layer

MAF Version 2.1

Problem Statement

Oracle Mobile Application Framework (MAF) allows you to declaratively build list of values. To create a list of values, you need a list binding that provides the list of selectable values and a iterator that holds the object to update. Though the list is created and configured declaratively in MAF using collections exposed on the MAF data control, there is something you need to know before adding the select list component: How to expose the list source collection through the binding layer?

Solution

To create a list of values in MAF using the AMX binding layer you need to create an Accessor Iterator for the list source collection before dragging the attribute to update as a select one choice to the AMX page.

Implementation

The image below shows the data control panel of the sample workspace you can download. The CustomerDC data control, which is a JavaBean data control, exposes two collections: countries and customers. The countries collection is the list source. The customer collection exposes customer row objects to update in a MAF input form. 

Data Control Panel

To build an input form from a collection, customers in this case, you

  1. Select the collection in the data controls panel
  2. Drag the collection to the AMX page
  3. Drop the collection as a MAF input form
The sample workspace that is available for download has the edit form added in its EditCustomer.amx page. A previous page, SelectOneChoice.amx contains a list view created from the same customer collection. At runtime, selecting a customer on the first page automatically navigates to the edit form page. When you create a MAF input form, the MAF binding layer (PageDef file) is created and populated with bindings for each property of the customer object. To access the bindings you click the "Bindings" tab at the bottom of the AMX page editor.  

Switch the MAF editor view to the "Bindings" tab to create a new Accessor Iterator binding. In the binding editor, click the green plus icon above the "Executable" section and select the "accessorIterator" binding item. Then click OK.

Create Accessor Iterator

Select the data control that holds the collection with the values you want to show in the list of values. In the sample, the data control is the same as for the customer collection, but the collection is "countries". Click OK to create the iterator and switch back to the source view of the MAF page editor.

Configure Accessor Iterator with List Source collection

In the MAF page source editor, select the complete markup (af:inputText) for the customer property that you want to create a list of values for ("countryId" in the sample) and hit the delete key. Then, expand the collection (customers in the sample) and drag the property for which you deleted the markup to the AMX page (countryId in this example). The context aware popup shows the "Single Selection" menu item, which hosts the "MAF Select One Choice" sub item. Select the MAF Select One Choice item in the menu to bring up the MAF list binding dialog.

Add amx:selectOneChoice to AMX page

In the "Edit List Binding" dialog, keep the default settings for the Base Data Source and the Dynamic List and change the "List Data Source"  configuration to <data control>.root.<collection name>, which in the sample is CustomersDc.root.countries. The target attribute that will be updated through the list of value is selected automatically and all you need to do is to select the list attribute that provides the value for it.

Configure List

Note the "Display Attribute" property that can be set to any of the list collection attribute or a combination of attribute. In the sample, the display was changed to show the countryId and the country name as you can see in the following screen shots.

Note: the list edit dialog allows you to map multiple properties for update. This however is not yet supported in MAF and only the first mapping will be used at runtime. If you need to update multiple form properties then you do this programmatically. Have a look at this previous blog entry to get an idea of how to do this.

After you downloaded the sample application and opened it in JDeveloper 12.1.3, select the SelectOneChoice.amx page and choose "Run" from the  context menu. This will bring up the iOS simulator or Android Emulator (you may have to configure the deployment profile as it is set to iOS). On the first page, shown below, select a customer entry to edit.

Select Customer at Runtime

This leads you to the form page where you now can change the country ID field using the select one choice component (the screen shot shows the iOS simulator rendering, if you run this on Android the list of values may look different)

Select Country in ChoiceList

Summary

This blog article stepped you through creating a single select choice list of values based on the AMX binding layer in MAF. To build such a list declaratively you need to create the accessor iterator manually in the binding, which needs to be done before you create the list component. This article showed you how.

Sample Download

The sample is pretty simple and is focussed on the list only. The data it displayed is saved in a JavaBean so that there is no need for you to configure a web service connection or use the local SQLite database. 

MAF 2.1 Workspace

--------------

Disclaimer

The sample explained in the blog and provided for this blog entry is provided as is. The sample will not be upgraded automatically to newer versions of MAF and is also not supported by Oracle world wide customer support. Samples are community efforts that we support in a community effort: If you experience problems or have questions, please post a note to the MAF community forum on OTN: https://community.oracle.com/community/oracle-mobile/oraclemaf/content?customTheme=otn

Thursday Feb 19, 2015

Integrating a custom Cordova plugin into a MAF app

In this earlier post, I demonstrated how to create a simple Cordova plugin for each of Android and iOS.  In this post, I’ll describe how to include that plugin into a MAF 2.1.0 app and how to invoke it from Java, from AMX and from local HTML.

You can follow these same instructions to integrate any 3rd party or custom Cordova plugin into your MAF app.

How do I include a custom Cordova plugin into my MAF app?

To include a custom Cordova plugin into a MAF app, you specify the location of the plugin’s top-level folder (on your local hard drive) in the app’s maf-application.xml > Plugins UI in JDeveloper, as described in the MAF 2.1.0 Documentation.

The path to the plugin will be stored in maf-plugins.xml and will be relative to that file’s location. To avoid any deploy-time issues, make sure the plugin’s top-level folder is on the same drive as your app and that there are no spaces in its path. If your plugin is specific to your MAF app, you probably want to include it within the app’s source tree.

The plugin will appear in the maf-application.xml > Plugins UI as follows:

Note: If you want to use a 3rd party plugin, you must first download it (as a zip file) and extract it to your local hard drive as described in Step 7 of this blog post.

How do I invoke the plugin’s methods?

As described in the earlier post, a Cordova plugin includes a JavaScript interface that defines its available functions. You must write your own JavaScript in your MAF application to call the plugin’s JavaScript interface so your MAF application can interact with the plugin.

In the example “Alert” plugin created in the earlier post, we defined the following JavaScript interface in the alert.js file:

module.exports = {
  alert: function(title, message, buttonLabel, successCallback) {
    cordova.exec(successCallback,
                 null, // No failure callback
                 "Alert",
                 "alert",
                 [title, message, buttonLabel]);
  }
};

Our plugin’s manifest file, plugin.xml, defined the JavaScript module as follows:

  <js-module src="www/alert.js" name="Alert">
    <clobbers
target="Alert" />
  </js-module>

This means that the alert function will be exported as part of the Alert JavaScript module within the consuming MAF application, which enables you to call this function from JavaScript as follows:

  Alert.alert(“My title”, “My message”, “My button label”, myCallbackFunction);

As per the plugin design, this code would cause a native popup dialog to be displayed with “My title” as the title, “My message” as the message and a single button with the label “My button label”. When the user taps the button to dismiss the dialog, the JavaScript callback function myCallbackFunction will be executed with an input parameter of 0.

For more information on using JavaScript callback functions, refer to this helpful blog post by Michael Vollmer.

Note: If you want to use a 3rd party plugin, the JavaScript interface and how to call it should be described in the plugin’s README.md file, typically found in the plugin’s top-level folder.

How do I invoke the plugin from a Local HTML page?

You could invoke the plugin’s JavaScript interface methods directly from your HTML code, but it’s more common to provide a wrapper function that simplifies your code and abstracts you from possible changes to the plugin’s interface.

Let’s take a look at a local HTML page that calls our “Alert” plugin:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport"

          content="width=device-width, initial-scale=1, maximum-scale=1"/>
    <title>Plugin Demo</title>
    <script type="text/javascript">if (!window.adf) window.adf = {};
                                   adf.wwwPath = 
"../../../../www/";</script>
    <script type="text/javascript" src="../../../../www/js/base.js"></script>
    <script type="text/javascript">
      function showAlert(frm) {
        Alert.alert(frm.title.value,
                    frm.message.value,
                    frm.buttonLabel.value,
                    function(button) {
                      console.log("User tapped button:" + button);
                    });
      }
    </script>
  </head>
  <body>
    <form>
      <br>Title: <input type="text" name="title" value="Title">
      <br>Message: <input type="text" name="message" value="Message">
      <br>Button Label: <input type="text" name="buttonLabel" value="Button">
      <br><input type="button" value="Alert" onclick="showAlert(this.form);">
    </form>
  </body>
</html>

The <meta> tag is included here to avoid the user seeing the components on the page increase or decrease in size as she interacts with it.

You must include the two lines of JavaScript that define the window.adf object and include base.js. There is no need to explicitly include the plugin’s JavaScript file here, since at deploy-time it will be injected into base.js.

A simple showAlert function has been defined to wrap the call to Alert.alert, which in turn simplifies the calling code within the HTML form.

The showAlert function is called when the user taps on the “Alert” button. The “title”, “message” and “button label” input text values are read from the form and passed to Alert.alert, along with an anonymous callback function that will be called when the user taps on the dialog’s button. This callback function will be passed a value for the input parameter button, which we know will be 0, since that was hard-coded in our plugin’s native code.

How do I invoke the plugin from an AMX page?

AMX components cannot call JavaScript methods directly, so you must implement a Java method that is called from an AMX component, which in turn executes some JavaScript that calls the plugin’s JavaScript interface.

Let’s take a look at an AMX page that is similar to the local HTML page described above:

<?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">
  <amx:panelPage id="pp1">
    <amx:facet name="header">
      <amx:outputText value="Plugin Demo" id="ot1"/>
    </amx:facet>
    <amx:inputText label="Title" id="it1"
                   value="#{applicationScope.PluginBean.alertTitle}"/>
    <amx:inputText label="Message" id="it2"
                   value="#{applicationScope.PluginBean.alertMessage}"/>
    <amx:inputText label="Button Label" id="it3"
                   value="#{applicationScope.PluginBean.alertButtonLabel}"/>
    <amx:commandButton text="Alert" id="cb1"
                       actionListener="#{PluginBean.popupAlertDialog}"/>
  </amx:panelPage>
</amx:view>

The three inputText components store their values in three attributes of an bean called PluginBean. When the user taps the commandButton component, it calls the popupAlertDialog method of PluginBean.

Let’s take a look at the PluginBean:

package mobile;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class PluginBean {
  private String alertTitle"Title";
  private String alertMessage"Message";
  private String alertButtonLabel"Button";

  public PluginBean() {
  }

  public void popupAlertDialog(ActionEvent actionEvent) {
    AdfContainerUtilities.invokeContainerJavaScriptFunction(
      AdfmfJavaUtilities.getFeatureId(),
      "showAlert",
      new Object[] {
        alertTitle,
        alertMessage,
        alertButtonLabel
      });
  }

  public void setAlertTitle(String alertTitle) {
    this.alertTitle = alertTitle;
  }

  public String getAlertTitle() {
    return alertTitle;
  }

  public void setAlertMessage(String alertMessage) {
    this.alertMessage = alertMessage;
  }

  public String getAlertMessage() {
    return alertMessage;
  }

  public void setAlertButtonLabel(String alertButtonLabel) {
    this.alertButtonLabel = alertButtonLabel;
  }

  public String getAlertButtonLabel() {
    return alertButtonLabel;
  }
}

The popupAlertDialog method calls AdfmfContainerUtilities.invokeContainerJavaScriptFunction to invoke a JavaScript function called showAlert, passing the alertTitle, alertMessage and alertButtonLabel attributes as input parameters to the JavaScript function.

You may wonder why the plugin’s alert JavaScript method is not invoked directly and we call the showAlert function instead. The reason is that the plugin’s alert method requires a JavaScript function callback to be passed as an input parameter and there is no way to do this from Java. Passing a null value may have unwanted side effects, so that is not recommended.

For this reason, we need to write a JavaScript function that wraps the call to the plugin’s alert method. There are two options for this.

One option is to create a JavaScript file and include it into each feature that wishes to invoke the plugin via the app’s maf-feature.xml UI as follows:


The JavaScript file would look similar to the following:

showAlert = function(title, message, buttonLabel) {
  Alert.alert(title,
              message,
              buttonLabel,
              function(button) {
                console.log("User tapped button: " + button);
              });
};

This JavaScript file defines a global function variable called showAlert, which accepts 3 input parameters and calls the plugin’s alert method with those 3 parameters, along with an anonymous callback function that will be executed when the user taps on the dialog’s button. The native code will pass a return value of 0 to the anonymous callback function as the input parameter button.

The other option, if the plugin is to be invoked from a single AMX page, is to include the wrapper function as verbatim JavaScript within the AMX page, similar to the following:

<?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">
  <amx:panelPage id="pp1">
    <amx:verbatim id="v1">
      <![CDATA[
        <script type="text/javascript">
          function showAlert(title, message, buttonLabel) {
            Alert.alert(title,
                        message,
                        buttonLabel,
                        function(button) {
                          console.log("User tapped button: " + button);
                        });
          }
        </script>
      ]]>
    </amx:verbatim>
    <amx:facet name="header">
      <amx:outputText value="Plugin Demo" id="ot1"/>
    </amx:facet>
    <amx:inputText label="Title" id="it1"
                   value="#{applicationScope.PluginBean.alertTitle}"/>
    <amx:inputText label="Message" id="it2"
                   value="#{applicationScope.PluginBean.alertMessage}"/>
    <amx:inputText label="Button Label" id="it3"
                   value="#{applicationScope.PluginBean.alertButtonLabel}"/>
    <amx:commandButton text="Alert" id="cb1"
                       actionListener="#{PluginBean.popupAlertDialog}"/>
  </amx:panelPage>
</amx:view>

In either case, there is no need to include the plugin’s JavaScript file in the calling feature(s), since at deploy-time it will be injected into base.js.

Conclusion

You now have the knowledge required to create your own custom Cordova plugin and integrate it into your MAF 2.1.0 app, invoking it from local HTML, an AMX page, or from Java code.

Once you’ve tested your code successfully, you might consider publishing the plugin to make it available to the greater Cordova community. This can be achieved by calling plugman as follows:

$ plugman publish path-to-plugin-top-level-folder

Thursday Feb 12, 2015

How-to open and close a popup dialog from Java in MAF

MAF Version: 2.1

Problem Statement

In MAF, application developers use two component behavior tags, amx:showPopupBehavior and amx:closePopupBehavior, to show or hide popup dialogs in response to user interaction, pressing a command button or link. What however if you need to show or hide a popup from Java in a value change listener or similar component event? And what if the dialog should close itself after a period of time? By the time of writing, no API for this exists in MAF.

The Solution

Since there is no framework API to close and open popup dialogs in MAF, a custom implementation must do. The custom implementation uses JavaScript, as well as the amx:showPopupBehavior and amx:closePopupBehavior tags on hidden buttons (visible buttons would be possible too).

To show or hide a popup dialog you use Java in a managed bean to call out to JavaScript,using the
AdfmfContainerUtilities.invokeContainerJavaScriptFunction() framework method.

Section Q&A:

Q1: Could you call the same from a POJO Data Control?
A1:Yes you can.

Q2: Does it make sense?
A2: No. The data control is part pf the MAF model and thus needs to be generic and not specifically written for a view in MAF. 

About the Example Code

The MAF 2.1 sample code shown in this blog entry (and available for download) keeps it simple. The AMX page displays single command button that, when pressed, triggers a component action event. This action event is calls a Java method in a managed bean that in turn invokes JavaScript to "virtually" press the hidden button with the amx:showPopupBehavior tag attached to it for you. A timer in the same managed bean then waits for 5 seconds before it closes the dialog, pressing the other hidden button (it actually raises a tap event on teh button). Its a simple example that however showcases nicely how you can open and clode dialogs in MAF.

The Sample Implementation

Though you can download the sample ZIP file from a link provided at the end of this article, its worth showing the code in the blog entry as well, so you you can quickly scan it. The JavaScript source is saved in a stand-alone JS file so it can be reused. In general I recommend using external JavaScript files and force yourself to write generic code than to stuff scripts into an AMX page directly.

Step 1) Creating the JavaScript file

First you need to create JavaScript file - or extend an existing file - with the JavaScript code shown below. In my sample (MAF 2.1) this the JS file is named "PopupUtils.js". The two functions in the file popupUtilsShowPopup and popupUtilsHidePopup become globally available for the feature they are configured for (explained next). The two functions require a singel argument to be passed, which is the id of the popup dialog to close

(function () {
  //register 
  popupUtilsShowPopup = function() {
        //the argument is required and cannot be missing
        if (arguments.length > 0) {
            var popupOpener = document.getElementById(arguments[0]);
            if (popupOpener != null && popupOpener != undefined) {
                adf.mf.api.amx.triggerBubbleEventListener(popupOpener,"tap");
            }
            else {
                adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
                  "PopupUtils", "showPopup", "hidden button to launch
                   popup not found");
            }
        }
        else {
            adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
              "PopupUtils", "showPopup", "Missing input argument");
        }
    }

    popupUtilsHidePopup = function() {
        //the argument is required and cannot be missing
        if (arguments.length > 0) {
            var popupCloser = document.getElementById(arguments[0]);

            if (popupCloser != null && popupCloser != undefined) {
                adf.mf.api.amx.triggerBubbleEventListener(popupCloser,"tap");
            }
            else {
                adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
                "PopupUtils","showPopup", "hidden button to close popup
                 not found");
            }
        }
        else {
            adf.mf.log.Application.logp(adf.mf.log.level.WARNING,
             "PopupUtils", "hidePopup", "Missing input argument");
        }
    }

})();

Step 2) Registering the JS file with a MAF feature

To register the JavaScript file with a MAF feature, open the maf-feature.xml file (the file resides in the View Controller| Application Sources || META-INF directory). In the opened maf-feature.xml file, select the feature that needs programmatic access to a popup and select the "Content" tab. Click the green plus icon next to the "Includes" label and search for the JavaScript file in the project. The file type should be set to "JavaScript".

With this configuration, the custom JavaScript functions become available so they can be called from AdfmfContainerUtilities.invokeContainerJavaScriptFunction()

Step 3) About the Managed Bean (Java)

A managed bean is useed to hold the Java code that either launches or closes the popup. The managed bean doesn't hold state and therefore can be defined in any scope (shorter scopes are preferred). The sample actually shows and hides the popup in response to an action performed on another button. However, you can call the same code from any other component event, e.g. from a value change listener.

public class PopupHelper {

    boolean isPopupOpen = false;

    public PopupHelper() {

    }

    public void onPopupShowHideAction(ActionEvent actionEvent) {

        if (!isPopupOpen) {

         //the error object is filled with the reason for a JS exception.
         //If the variable is empty, no exception has occured. Note that
         //usually you use the return object to pass information from the
         //JS function back to MAF. In this case, the function doesn't
         //return a value and so its only used here to allow debugging
         //of exceptions

            Object errorMsg =
                AdfmfContainerUtilities.invokeContainerJavaScriptFunction
                (FeatureContext.getCurrentFeatureId(),"popupUtilsShowPopup",
                new Object[] {"_popShowId" });


            isPopupOpen = true;

            //wait a few seconds and then close the popup
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                  closePopup();
                  this.cancel();
                }

            },5000);

        }
            }


    private void closePopup() {

        if (isPopupOpen) {

            Object errorMsg =
               AdfmfContainerUtilities.invokeContainerJavaScriptFunction(
                FeatureContext.getCurrentFeatureId(),"popupUtilsHidePopup",
                new Object[] {"_popCloseId" });
            isPopupOpen = false;

        }

    }
}

Step 4) Configuring the hidden buttons

The hidden buttons (in this example they are hidden, but can be visible too) have the two behavior tags, amx:showPopupBehavior and amx:closePopupBehavior defined as shown in the AMX page source shown below

<amx:panelPage>
 ...
  <amx:commandButton text="_hiddenShow" id="_popShowId"
                     inlineStyle="visibility: hidden;">
      <amx:showPopupBehavior id="spb1" popupId="p1"
                        type="action" decoration="simple" alignId="pp1"
                        align="overlapMiddleCenter"/>
  </amx:commandButton>
  <amx:commandButton text="_hiddenClose" id="_popCloseId"
                      inlineStyle="visibility: hidden;">
      <amx:closePopupBehavior id="cpb1" popupId="p1"
                                     type="action"/>
  </amx:commandButton>



  <amx:commandButton text="Show / Hide Popup" id="cb3"
                     actionListener="#{PopupHelper.onPopupShowHideAction}"/>

</amx:panelPage>
  <amx:popup id="p1" autoDismiss="false"
          inlineStyle="font-size:large;">
          <amx:outputText value="This Popup is launched from Java in a
                         managed bean. Wait 5 seconds for Java to close
                          this dialog." id="ot2"/>
</amx:popup> 

Download Sample

The sample files can be downloaded in a MAF 2.1 workspace from here: http://www.oracle.com/technetwork/developer-tools/adf/learnmore/programmaticcloseofpopup-2420222.zip Run the AMX page within the sample workspace and press the command button in the opened mobile application. A dialog shows and stay up for 5 seconds before it closes down again.

Note:  If you experience problems, or like to comment on the solution, please post to the MAF forum on OTN: https://community.oracle.com/community/oracle-mobile/oraclemaf/content?customTheme=otn

Introduction to custom Cordova plugin development

Oracle Mobile Application Framework (MAF) v2.1.0 uses Cordova version 3.6.3 on Android and 3.7.0 on iOS to provide access to native device functionality.  The MAF 2.1.0 extension to JDeveloper 12.1.3 enables you to easily include any of the hundreds of published 3rd party Cordova plugins into your MAF app.

But what if you can’t find a suitable 3rd party plugin? You could elect to write your own, which, depending on the functionality required, may not be as difficult as you think.

In this post I’ll provide an introduction to developing a Cordova plugin by creating a very simple plugin for each of Android and iOS.

How does a Cordova plugin work?

In a nutshell, Cordova provides a bridge between JavaScript and native code, enabling you to write native code that gets exposed to your app via a common JavaScript interface.

Each method exposed by a plugin’s JavaScript interface is mapped to a method in the plugin’s native code via the Cordova bridge, which also enables you to pass parameters back and forth between the JavaScript and native methods.

What comprises a Cordova plugin?

A Cordova plugin typically consists of:

  • Native code for each supported platform
  • A common JavaScript interface
  • A manifest file called plugin.xml

The conventional structure for a Cordova plugin supporting both Android and iOS is:

  - Plugin top-level folder
     - plugin.xml
     - src/
        - android/
           - <Java source code>
        - ios/
           - <Objective-C source code>
     - www/
        - <JavaScript interface>

Ideally, community-published plugins also include release notes, author and license information, and a README file.  

A plugin may also include additional native resources and these are identified in the plugin.xml manifest file. This manifest file is read by the plugman command-line tool, which is used by the Cordova command-line interface and also by the MAF extension for JDeveloper.

In some rare cases, a plugin may be created that simply executes some native code on initialization and requires no JavaScript interface.

For more detailed information about the plugin.xml manifest file, refer to the Cordova Plugin Specification.

How do I create my own custom Cordova plugin?

To create your own custom Cordova plugin, you must write:

  • JavaScript that provides the interface for calling your plugin from within a Cordova-based app, such as a MAF app.
  • Native code that provides the functionality you need.  Since MAF supports both Android and iOS, you should write native code for both platforms.
  • A plugin.xml manifest file that defines your plugin and how plugman should incorporate it into a MAF app (or any Cordova-based app).

What tools do I need to create my own custom Cordova plugin?

You really only need a text editor to create your own custom Cordova plugin, which is all I’ve used to create the custom plugin described in this post.

For more complex plugins, you may wish to develop and test the plugin’s native code in each platform’s native IDE, which means downloading and installing the Android Studio and/or Apple’s Xcode.

You don’t need Cordova installed to develop a Cordova plugin.  Once you have developed your Cordova plugin, you can incorporate it directly into your MAF app for testing. However, if you wish to test your plugin within a Cordova app, you must download and install Cordova using the Cordova Command-Line Interface.

How do I write the plugin’s JavaScript interface?

We start with the JavaScript interface since it provides a common interface to both the Android and iOS native code.  This interface effectively defines what is required in the native code.

The JavaScript interface must call the cordova.exec method to invoke a native plugin method, as follows: 

cordova.exec(successCallback, failureCallback, service, action, [args]);

This call invokes the action method on the service class on the native side, passing the arguments in the optional args array.  If the native code completes successfully, the successCallback function is executed, along with any return parameters.  If the native code fails, the failureCallback function is executed, with an optional error parameter.  For more information on JavaScript callback functions, refer to this helpful blog post by Michael Vollmer.

Let’s create a very simple plugin that displays a native popup dialog.  Thus plugin shall present one method that takes 3 parameters – title, message and button label – and shall return the result in the ‘success’ callback. No ‘failure’ callback will be implemented. The service class shall be named “Alert” and we shall call the sole method “alert”. 

The following JavaScript defines the interface:

module.exports = {
  alert: function(title, message, buttonLabel, successCallback) {
    cordova.exec(successCallback,
                 null, // No failure callback
                 "Alert",
                 "alert",
                 [title, message, buttonLabel]);
  }
};

The call to module.exports exports the JavaScript function alert as part of the JavaScript module that will be defined in the plugin manifest file, plugin.xml

Your app will call this alert JavaScript function, which will invoke the alert method on the Alert class in the plugin’s native code.

Save this JavaScript into a file called alert.js, within a www subfolder of your plugin’s top-level folder.

How do I write the plugin’s native code?

Android

Based on the JavaScript interface, we must define a class called Alert in a Java source file called Alert.java.  Let’s take a look at the Alert.java source file:

package com.acme.plugin.alert;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Alert extends CordovaPlugin {
  protected void pluginInitialize() {
  }

  public boolean execute(String action, JSONArray args, CallbackContext callbackContext)
      throws JSONException {
    if (action.equals("alert")) {
      alert(args.getString(
0), args.getString(1), args.getString(2), callbackContext);
      return true;
    }
    return false;
  }

  private synchronized void alert(final String title,
                                  final String message,
                                  final String buttonLabel,
                                  final CallbackContext callbackContext) {
    new AlertDialog.Builder(cordova.getActivity())
    .setTitle(title)
    .setMessage(message)
    .setCancelable(
false)
    .setNeutralButton(buttonLabel,
new AlertDialog.OnClickListener() {
      public void onClick(DialogInterface dialogInterface, int which) {
        dialogInterface.dismiss();
        callbackContext.sendPluginResult(
new PluginResult(PluginResult.Status.OK, 0));
      }
    })
    .create()
    .show();
  }
}

The Alert class should be part of a package that will be referenced in the manifest file, plugin.xml.

The Alert class must extend the CordovaPlugin class, the definition of which you can find here.

The Alert class must override the execute method, since this will be called each time the JavaScript cordova.exec method is called, providing the name of the plugin method, the input parameters and a callback context.  The execute method should return true if a valid action was passed in, otherwise false.

Once the code has completed, it should return a result and optional return parameters to the calling JavaScript by invoking the sendPluginResult method on the callbackContext object.   Returning a result of PluginResult.Status.OK will cause the JavaScript ‘success’ callback to be executed.  Any other result (apart from PluginResult.Status.NO_RESULT) will cause the JavaScript ‘failure’ callback to be executed.

In our plugin, a ‘success’ result is returned when the user taps the button on the popup dialog and a value of 0 is returned.

The Alert class may override the pluginInitialize method if any initialization logic is required when the plugin is first constructed.

Save this code into a file called Alert.java, within a src/android subfolder of your plugin’s top-level folder.

For more detailed information on aspects such a threading and event handling, refer to the Cordova page Android Plugins.

iOS

For our example, we must define a class called Alert in an Objective-C source file called Alert.m and corresponding header file Alert.h

Let’s take a look at the Alert.h header file:

#import <Cordova/CDV.h>

@interface Alert : CDVPlugin <UIAlertViewDelegate> {}
- (void)alert:(CDVInvokedUrlCommand*)command;
@end

@interface
MyAlertView : UIAlertView {}
@property (nonatomic, copy) NSString* callbackId;
@end

The Alert class must be a sub-class of CDVPlugin, the definition of which you can find here.

The Alert class must provide an alert method, since this will be called each time the plugin’s JavaScript alert method executes the cordova.exec method.

Save this code into a file called Alert.h, within a src/ios subfolder of your plugin’s top-level folder.

Let’s take a look at the Alert.m source file:

#import "Alert.h"

@implementation Alert
- (void)pluginInitialize
{
}

- (void)alert:(CDVInvokedUrlCommand*)command
{
  NSString* callbackId = command.callbackId;
  NSString* title = [command argumentAtIndex:
0];
  NSString* message = [command argumentAtIndex:
1];
  NSString* button = [command argumentAtIndex:
2];

  MyAlertView *alert = [[MyAlertView alloc]
                        initWithTitle:title
                        message:message
                        delegate:
self
                        cancelButtonTitle:button
                        otherButtonTitles:
nil];
                        alert.callbackId = callbackId;
  [alert show];
}

- (void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
  MyAlertView* myAlertView = (MyAlertView*)alertView;
  CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                             messageAsInt:
0];
  [
self.commandDelegate sendPluginResult:result callbackId:myAlertView.callbackId];
}
@end

@implementation
MyAlertView
@synthesize callbackId;
@end

The alert method receives the input parameters and a callback id.

Once the code has completed, it should return a result and optional return parameters to the calling JavaScript by invoking the sendPluginResult method on the commandDelegate object. Returning a result of CDVCommandStatus_OK will cause the JavaScript ‘success’ callback to be executed. Any other result will cause the JavaScript ‘failure’ callback to be executed.

In our plugin, a ‘success’ result is returned when the user taps the button on the popup dialog and a value of 0 is returned.

The Alert class may implement the pluginInitialize method if any initialization logic is required when the plugin is first constructed.

Save this code into a file called Alert.m, within a src/ios subfolder of your plugin’s top-level folder.

For more detailed information on aspects such a threading and event handling, refer to the Cordova page iOS Plugins.

How do I write the plugin’s manifest file (plugin.xml)?

The manifest file, called plugin.xml, is an XML document that defines the plugin and tells plugman how to incorporate the plugin into your MAF app (or any Cordova-based app) for each platform it supports.

The Cordova Plugin Specification is comprehensive, but we will focus on the manifest file used for our “Alert” plugin:

<?xml version="1.0" encoding="UTF-8"?>
<plugin 
xmlns="http://apache.org/cordova/ns/plugins/1.0"
        id="com.acme.plugin.alert"
        version="0.0.1">

  <name>
Alert</name>
  <description>
A Cordova plugin that displays an alert popup dialog</description>

  <engines>
    <engine
name="cordova" version=">=3.6.0" />
  </engines>

  <js-module
src="www/alert.js" name="Alert">
    <clobbers
target="Alert" />
  </js-module>

  <!-- android -->
  <platform name="android">
    <config-file
target="res/xml/config.xml"
parent="/*">
      <feature name="Alert"/>
        <param name="android-package" value="com.acme.plugin.alert.Alert" />
      </feature>
    </config-file>
    <source-file src="src/android/Alert.java" target-dir="src/com/acme/plugin/alert" />
  </platform>

  <!-- ios -->
  <platform name="ios">
    <config-file target="config.xml" parent="/*">
      <feature name="Alert"/>
        <param name="ios-package" value="Alert" />
      </feature>
    </config-file>
    <header-file src="src/ios/Alert.h" />
    <source-file src="src/ios/Alert.m" />
  </platform>

</plugin>

The plugin element must contain the plugin’s XML namespace (xmlns), id and version.

The name and description elements should always be defined.  If you intend to publish your plugin for public use, you should include additional elements such as those identifying the author, license, keywords and repository.

The child elements of the engines element define which version(s) of the Cordova framework the plugin supports.  Since this plugin has been developed based on Cordova 3.6.0 documentation and will be tested within a MAF 2.1.0 app, we should set the minimum required Cordova version to 3.6.0.

The JavaScript interface is defined by including a js-module tag for each JavaScript file.  Any file defined here is automatically copied into your MAF app and injected into any HTML using a <script> tag so that you don’t have to specifically add this tag yourself, or include the JavaScript as content in your AMX features.  The clobbers tag indicates that the module shall be inserted into the window object as Alert, so your MAF app code should call the JavaScript method Alert.alert to execute the plugin’s alert method.

If your plugin has a dependency on another Cordova plugin, you can define this using a dependency tag.  Since our plugin does not have any dependencies, there is no such tag defined.

The platform tag is used to define the platforms that are supported by the plugin.  Within each platform tag, you specify the native source files, changes required to configuration files, any additional native resources or frameworks and any platform-specific JavaScript files.

For Android, it is important to specify the full package name of the plugin as the value for the android-package parameter, for example “com.acme.plugin.alert.Alert”, and to specify the target-dir for any source files (since this indicates the location to which the source file should be copied and it must match the package name), for example “src/com/acme/plugin/alert”.

Save this XML into a file called plugin.xml, within your plugin’s top-level folder.

Conclusion

We have now developed a custom Cordova plugin that can be incorporated into a MAF 2.1.0 app, or any app based on Cordova 3.6.0 or above.  In a follow-up post [Edit: here], I’ll describe how to integrate this plugin into a MAF app.

For developers looking for more detailed information about how to create a Cordova plugin, refer to the Cordova Plugin Development Guide, which contains specific guides for each native platform.

Monday Feb 09, 2015

Oracle Mobile Development Certification Exam - Beta Testing

Get your Oracle Mobile Development 2015 Implementation Specialist Certification!

Oracle Mobile Development 2015 Essentials Exam (1Z1-441) is now available in beta testing.

This certification covers topics such as: Mobile Application Framework (MAF), Mobile Application Framework (MAF) Data Layer, User Interface (UI) Development, Device Services Integration, and App Security. Up-to-date training and field experience are recommended.

This certification is available to all candidates, but is geared toward members of the Oracle PartnerNetwork. OPN members earning this certification will be recognized as OPN Certified Specialists. This certification qualifies as competency criteria for the Oracle Mobile Development specialization.

Access the exam study guide in order to get pointers to resources meant to help you prepare for the exam!

Take the exam for free while in beta testing! Request a discounted beta voucher via the OPN Beta Certified Specialist Exam Voucher Request Form!

Usedful Links:

  • Oracle Mobile Development 2015 Essentials Exam Study Guide
  • Oracle Mobile Development Specialization
  • Oracle Enterprise Mobility Knowledge Zone
  • Wednesday Feb 04, 2015

    Migrating MAF apps with 3rd party Cordova plugins

    Cordova is used in Oracle Mobile Application Framework (MAF) to provide access to device features.   MAF 2.1.0 uses Cordova version 3.6.3 on Android and 3.7.0 on iOS, dramatically increasing the number of available plugins that developers can leverage.

    MAF 2.1.0 greatly simplifies the process of adding 3rd party Cordova plugins into a MAF app by internally using the plugman command-line interface to build the plugin and incorporate all the required artifacts into your MAF app.

    So how do you migrate an existing MAF app that uses a 3rd party Cordova plugin to MAF 2.1.0?

    The following steps and screenshots show the process of migrating the BarcodeDemo sample app from MAF 2.0.1 to MAF 2.1.0 using JDeveloper.

    Step 1 – Download JDK 8 and update MAF extension

    MAF 2.1.0 requires JDK 8. Download JDK 8 before updating the MAF extension to 2.1.0 via Check for Updates in JDeveloper. Following a restart, you will be prompted to enter the JDK 8 location on your local machine and to reconfigure the Android and iOS Platform Preferences in JDeveloper. Please refer to the Installation Guide for more detailed information.

    Step 2 – Automatic app migration

    Open your MAF app within JDeveloper and you will be prompted to allow JDeveloper to automatically migrate your app. The Installation Guide describes this process in more detail. 



    Step 3 – Remove the old plugin code

    In a later step, you will point JDeveloper to the location of the plugin’s source code on your local machine and JDeveloper will use plugman to build the plugin and incorporate it into your MAF app.

    Before doing that, you should remove the plugin artifacts that you had manually added to your MAF app previously.  This includes:

    • Android native code found in Projects > ApplicationController > Application Sources > plugins.BarcodeScanner.Android
    • iOS native code found in Projects > ApplicationController > ApplicationSources > plugins.BarcodeScanner.iOS
    • JavaScript files found in Projects > ViewController > Web Content > plugins



    Step 4 – Remove unnecessary iOS app icons and launch images

    This is an optional step to reduce the size of your app. You can remove the following iOS app icons and launch images for unsupported devices and operating system versions, found in Application Resources > Resources > ios:

    • Default-1135h@2x.png
    • Default-Land.png
    • Default-Landscape-Ipad.png
    • Default-Landscape.png
    • Default-Landscape@2x.png
    • Default-Landscape@2x~Ipad.png
    • Default-Portrait-Ipad.png
    • Default-Portrait.png
    • Default-Portrait @2x.png
    • Default-Portrait @2x~Ipad.png
    • Default.png
    • Default@2x-Landscape.png
    • Icon-50.png
    • Icon-50@2x.png
    • Icon-57.png
    • Icon-57@2x.png
    • Icon-72@2x.png
    • Icon-80.png
    • Icon-144.png
    • icon-72.png
    • icon.png
    • icon@2x.png


    Step 5 – Remove old plugin details from the Plugins UI

    To complete the removal of the old plugin, you should remove the references to it from within the maf-application.xml Plugins UI, by selecting each reference and clicking the red ‘x’ icon.


    Step 6 – Save all, Clean all

    At this point, save all modifications and then do a ‘clean all’.

    Step 7 – Download a new version of the plugin

    MAF 2.1.0 requires you to have the plugin source code residing on your build machine, located on the same drive as your app source code, in a path that contains no spaces.

    It is likely that there is a later version of the plugin that you have used previously which is compatible with the Cordova versions used by MAF.  You may even find that more recently someone has written a plugin that provides a better match to your requirements.

    Anyone can build a Cordova plugin and they can choose to keep it to themselves or to publish it for use by others. The most common way to publish a Cordova plugin is to store it in a Git repository and to register it with the Cordova plugin registry.  Launch your browser and open http://plugins.cordova.io.


    To find a barcode scanner plugin, type in “barcode” and search.


    Click on com.phonegap.plugins.barcodescanner to view the details of this plugin.


    You can see that this plugin requires Cordova version 3.0.0 or greater, works on Android and iOS, and was last updated 3 months ago to version 2.0.1. This page also provides a description and API documentation.

    To access this plugin, click on the link to its Git repository.


    To download this plugin, click on the link to Download Zip.

    Once the plugin has been downloaded, extract the zip file contents into a location relative to your app’s source code which does not contain spaces. For example, you wish to extract the plugin into your app’s src folder as follows:


    Step 8 – Add the plugin via the Plugins UI

    To import the downloaded version of the plugin into your MAF app, return to the maf-application.xml Plugin UI in JDeveloper and click the green ‘+’ icon. Navigate to the location of the plugin’s top-level folder and click the Select button.


    The details of the plugin, which are read from the plugin.xml file, will be displayed in the Plugins UI.


    Step 9 – Update the JS that calls the plugin API

    In most cases, you will need to update the JavaScript that calls the plugin API. Firstly, you no longer need to include the plugin’s JavaScript file because plugman will include this into your application at deploy-time. Secondly, the manner in which you invoke the plugin’s JS API may have changed.

    For the BarcodeDemo sample app, this means editing the scanner.amx file to remove the inclusion of barcodescanner.js and to change window.plugins.barcodeScanner.scan to cordova.plugins.barcodeScanner.scan as described in the plugin’s README.md file.


    Step 10 – Save all, deploy and test

    All that’s left now is to save all modifications, deploy your app (most likely to a device, since many plugins won’t work on a simulator or emulator) and test it.

    A tip for anyone using plugins with dependencies

    If a 3rd party Cordova plugin has a dependency on another plugin, this will be defined in the plugin.xml file. At deploy-time, JDeveloper calls plugman to build and incorporate the 3rd party plugins defined in the maf-application.xml Plugins UI. If one of these plugins has a dependency, plugman will automatically attempt to download the required plugin's source code so it can be built and incorporated into your MAF app.

    If your build machine is behind a firewall, this attempted download may cause the JDeveloper deployment to hang for a while before reporting error messages similar to the following:

    [10:21:35 AM] Fetching plugin "https://github.com/apache/cordova-plugin-device" via git clone

    [10:21:35 AM] Deployment Successful

    [10:21:35 AM] Failed to install 'de.appplant.cordova.plugin.local-notification':Error: Command failed: Cloning into '/var/folders/h9/84x8qcsj391c355v3v137n580000gn/T/plugman/git/1423102819927'...

    [10:21:35 AM] fatal: unable to access 'https://github.com/apache/cordova-plugin-device/': Failed to connect to github.com port 443: Operation timed out

    [10:21:35 AM] at ChildProcess.exithandler (child_process.js:648:15)

    [10:21:35 AM] at ChildProcess.emit (events.js:98:17)

    [10:21:35 AM] at maybeClose (child_process.js:756:16)

    [10:21:35 AM] at Socket.<anonymous> (child_process.js:969:11)

    [10:21:35 AM] at Socket.emit (events.js:95:17)

    [10:21:35 AM] at Pipe.close (net.js:465:12)

    [10:21:35 AM] Command failed: Cloning into '/var/folders/h9/84x8qcsj391c355v3v137n580000gn/T/plugman/git/1423102819927'...

    [10:21:35 AM] fatal: unable to access 'https://github.com/apache/cordova-plugin-device/': Failed to connect to github.com port 443: Operation timed out

    The solution for this is:

    • Download and extract the required plugin as per Step 7 above.
    • Remove the dependent plugin from the Plugins UI.
    • Add the required plugin to the Plugins UI.
    • Add the dependent plugin to the Plugins UI. 

    This will ensure that each plugin is built and incorporated into your MAF app in the correct order.

    About

    This blog is is dedicated to announcements,tips and tricks and other items related to developing, integrating, securing, and managing mobile applications using Oracle's Mobile Platform. It is created and maintained by the Oracle Mobile product development team.

    Archive of past entries

    Even More Mobile Development Blogs

    Oracle A-Team Site - Mobile Related Entries

    Code samples from the Community

    Fusion Middleware Blogs

    Search

    Archives
    « March 2015
    SunMonTueWedThuFriSat
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
        
           
    Today