Wednesday Mar 04, 2015

Oracle MAF 2.1 Now in Eclipse Too

We are happy to announce the release of a new version of Oracle Enterprise Pack for Eclipse (OEPE)- the Oracle Eclipse tooling offering - with support for the latest Oracle MAF 2.1.

Download the new OEPE 12.1.3.4 with MAF 2.1 Support Here.

This new version of OEPE is based on the latest Eclipse Luna SR2, and adds support for the full set of MAF 2.1 features including support for Java 8 compact profile 2, the new Xcode  and Android SDKs, and Cordova 3 plugin integration.

One unique feature added to the OEPE design time experience is a simplified way to interact with REST/JSON services. REST/JSON services are the preferred way to exchange information between your mobile application and remote server. A new wizard based interface in OEPE allows you to easily test REST URLs and then generate a simple set of POJO Java classes that access those services and working with the data types they use. You can then expose those classes as data controls and simply drag and drop them to the AMX pages to create the mobile UI.

Here is a 5 minutes demo that shows you the new REST features in action:

For more about this new version see the OEPE blog entry.

The OEPE Blog also has new entries describing some of the new features including:

 

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

How-to Access Selected Values in a Multi Select List

MAF Version: 2.1

Problem Statement

Using multi-item-select components, like amx:selectManyCheckbox, in a MAF application does not directly write the values back to the model but saves an array of selected row indexes in the inputValue attribute of the list binding. In addition, when users start selecting items in a muli-select list, each time they click on an item, the component valueChangeListener fires notifying the event listener in the managed bean about the selection. Both leads to the question of how to access the underlying data object (the row) for each selected item to read its properties.

For example, the assumed requirement for the sample application you can download at the end of this blog entry is to display the name of selected customers in a text box below the list component on the AMX page.

The Solution

The best part of a trip often is the journey because its where you see things and learn new skills. The same is true for the solution to the above challenge, as you will see when going through the implementation. For this solution, the decision was made to use the valueChangeListener and - for each selection - print the actual list values.

As shown in the image below, each selection in the list of customers is immediately mirrored in the text field below the list.

Multi Select List Sample Screen Shot

Note: If you don't need this information on each selection you would read the selected index values from the attribute binding (list binding) inputValue property upon e.g. a press on a button. In this case the code shown in this blog will guide you the way.

The implementation in the sample performs the following steps

  1. A valueChangeListener tracks all user item selection and de-selection
  2. For the selected items, the managed bean code reads the indexes from the component event
  3. The indexes are then changed from an array of String to List of Integer
  4. The list binding in the pageDef file (the binding) is accessed to obtain access to the list iterator
  5. For each index in the list of selected items, the iterator current row is set and then read

Note: Keep in mind that multiple binding artifacts may point to the same iterator. If you change the current row of a shared iterator, make sure to first read the current row key or index so it can be set back at the end of your action. In the sample the iterator is not shared and therefore the current row doesn't need to be set back.

Implementation Steps

The implementation is based on a POJO model, as this model is the most common in MAF. POJO models are usually created for REST services (using the REST Service Adapter in MAF), or when accessing the SOAP DC from Java, or when exposing the local SQLite database to the UI. In each of the cases mentioned before, you create a Java bean that then is exposed as a Data Control for use in MAF. From the data control you then create the UI using drag and drop from the Data Controls panel in JDeveloper or Oracle Enterprise Package for Eclipse (OEPE).

Note: This sample has customer data contained in the POJO data control (so to not require any dependency to a service, database or SQLite in MAF)

AMX view

The sample uses a single AMX view in the feature (which is good for demos and sample. For real MAF applications my personal preference is to use bounded task flows as the feature content). The page source of the list and the item mirroring the selections is shown below

<amx:selectManyCheckbox value="#{bindings.customers.inputValue}" 
         label="#{bindings.customers.label}" id="smc1"
         valueChangeListener="#{viewScope.SelectHelper.onValueChange}">
    <amx:selectItems value="#{bindings.customers.items}" id="si1"/>
</amx:selectManyCheckbox>
<amx:inputText label="Selected Values" id="it1" rows="7" inputType="text"
                   value="#{viewScope.SelectHelper.selectedValues}"/> 

To explain the important bits:

#{viewScope.SelectHelper.onValueChange} 
" valign="top"> Managed bean reference to the value change listener.  The managed bean doesn't hold state and thus is in the smallest scope available for MAF, which is viewScope
#{bindings.customers.items}
The customers reference is the list binding in the AMX pageDef file. Items gives you access to a list of items that then are iterated over by the component. This part doesn't need to be manually created by you and is generated by JDevelper or OEPE
#{bindings.customers.inputValue}
The inputValue property exists on the list binding and holds the value of the selected items (for the use case in which you need to access the selected row indexes in response to a command button or link action. As you see, the value is EL accessible and as such can be referenced from Java too using the AdfmfJavaUtilities class in MAF
#{viewScope.SelectHelper.selectedValues}

This EL is added to the input text field that shows the selected customer name and references a set/get method pair (bean properties) in the managed bean. The value change listener associated with the select component calls the set-method to post changes to the text field. A property change event is used notify the UI field to refresh


Note: most of the AMX page configuration is created for you when dragging and dropping a collection from the data control panel to the page. The only manual step is the creation of the value change listener and the input text field referencing the managed bean properties.

Managed Bean

The value change listener method - onValueChange(...) - in the managed bean is doing the real work. This is where the journey starts becoming exciting. The important parts of the code - in addition to the code comments, are explained further at the end

public void onValueChange(ValueChangeEvent valueChangeEvent) {

 //the newValue property of the valueChangeEvent shows an array of Strings 
 //with the selected values. This array is updated with each new selection 
 //or de-select
 Object[] newVal = (Object[]) valueChangeEvent.getNewValue();

 //the oldValue property of the valueChangeEvent shows an array of Strings 
 //with the values of the previous selection. This property allows you to 
 //tell what exactly has been changed within a new selection
 Object[]  oldVal = (Object[]) valueChangeEvent.getOldValue();

 //converting array of Strings to list of Integer to read the real 
 //objects from the list binding iterator
 ArrayList<Integer> selectedCustomers = new ArrayList<Integer>();      
 for (int i = 0; i < newVal.length; i++) {
     selectedCustomers.add(new Integer((String)newVal[i]));
 }

 //lists are represented by the  AmxAttributeBinding
 AmxAttributeBinding customerList = (AmxAttributeBinding) AdfmfJavaUtilities
                               .evaluateELExpression("#{bindings.customers}");

 StringBuffer selectedCustomerNames = new StringBuffer();

 //access the list iterator to first set the current row by the indexes obtained 
 //from the value change event and then read the row object, which in this sample
 //represents a customer
 AmxIteratorBinding amxListIterator =  customerList.getIteratorBinding();
 
 //the basic iterator in the AmxIteratorBinding is what we need to work with
  BasicIterator      basicIterator = amxListIterator.getIterator();
        
  for (Integer customerIndx : selectedCustomers) {
     //set new current row in list iterator
     basicIterator.setCurrentIndex(customerIndx.intValue());
     //get current row. Note that for POJO models you call getDataProvider and cast to 
     //the entity. For SOAP models you call getCurrentRow and cast it to GenericType 
     Customer customer = (Customer) basicIterator.getDataProvider();
     //for this sample, print selected customers into a text field on the page
     selectedCustomerNames.append("Customer on index  " + (customerIndx.intValue() + 1) + ": " + customer.getName()+"\n");
  }
  
  //update the input text field with the selection. The set-method of the 
  //selectedValue property uses the PropertyChane listener in MAF to trigger
  //the field to refresh
  this.setSelectedValues(selectedCustomerNames.toString());

}

Following table contains further explanations for parts of the code

valueChangeEvent.getNewValue() The list component has a "ValueChange" property that can be linked to a managed bean that defines a public method with the ValueChangeEvent as the only argument. The event argument gives developers access to the current selected items as well as the previously selected items, allowing them to tell the change. In the sample, the selected list is used only to lookup the associated binding object (aka. model objects) for more details. The getNewValue() method returns an array of String, where each string represents the index of a selected item in the list. The index is the same as in the binding layer, which makes it easy to lookup the data object.
AmxAttributeBinding There is no list binding in MAF (and I used teh term for simplicity in the article). List binds are attribute bindings and allow access to the list of items and the iterator. The iterator is created from the collection that in this example displays customer information. The attribute binding however does not display the full customer object and only knows of a value property (usually you select this as the PK of a object) and one to any display properties.
AmxIteratorBinding The iterator is an instance of AmcIteratorBinding, which is a wrapper for BasicIterator, which is the object instance that gives access to the data provider, which in the POJO case represents the data object (entity) representing a row.
basicIterator.getDataProvider(); The getDataProvider() method works for POJO models (PojoDC) wheras getCurrentRow provides equivalent access to the current row object for the SOAP DC. The code in the sample sets the current row for each selected item (using the item index) before accessing the row data object (entity)

Summary

This blog article and its sample shows one possible solution to work with multi select  lists in MAF. The use case covered is where the application requirement is to respond to any selections users make in the list. The use case in which the selected list is read after all items are selected is not covered. The handling for this is to access the list binding (which now you know is an attribute binding) to access the inputValue property for the submitted values. You then iterate over the values and set the current row in the BasicIterator using the keys you find in the array you obtain from the inputValue. The rest then remains the same

Download the Sample

The sample for this blog entry has been written for MAF 2.1 using JDeveloper. The sample code can be download from http://www.oracle.com//technetwork/developer-tools/adf/learnmore/multiselectlist-2423622.zip . In JDeveloper, open the MAF workspace and run the AMX page contained within. When the application shows up in the simulator, emulator or on-device, select items in the list to see the customer name - which is not part of the list - displaying in the text field. 

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

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


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.

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
« August 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
25
26
27
29
30
31
     
Today