The Cordova Calendar Plugin allows your MAF application to create, update and delete entries in the mobile device’s calendar. To be clear it does not render a calendar, it simply allows you to call the plugin with the data to make entries in the device’s calendar.
This blog post will cover 6 broad tasks. We’ll purely focus on the steps to be undertaken for this specific plugin, we wont focus on the "why" for this article in explaining why you need to undertake all the steps, or a more generic description of how to install all plugins beyond the Calendar plugin. Rather we are just focusing on giving you all the steps you need to get the job done:
1) Download & install the required
2) Prepare Cordova via XCode
3) Prepare the plugin via XCode
4) Configure your MAF application for the iOS plugin
5) Copy the plugin files to your MAF application
6) Build your app
This article covers advanced topics within MAF and Cordova and is not suitable for MAF beginners to undertake. Before attempting to implement the steps covered in this article you need to be reasonable familiar with the JDeveloper IDE, the overall MAF framework and how to build a MAF application, including features, managed beans, AMX pages and similar.
We do recognize that the combination of MAF 2.0.0 and Cordova 2.2.0 plugins is a configuration heavy implementation and will be looking to simplify this in future versions of MAF to take the burden off developers.
Download & install the required software
As this post is regarding integrating the Cordova "Calendar" plugin on iOS, it assumes you will have installed JDeveloper on Mac OSX, have downloaded and configured Apple’s XCode, as well as configured JDeveloper’s preferences to correctly deploy to iOS. In other words, this post has nothing to do with Android or Windows, it's Mac all the way.
Besides JDeveloper and XCode, for MAF 2.0.0 you must also download:
a) Cordova (previously known as Phonegap) SDK 2.2.0 – it can be obtained via the following archive site: https://github.com/phonegap/phonegap/zipball/2.2.0
b) Cordova Calendar Plugin for iOS supported by Cordova 2.2.0 – select the Download ZIP button when accessing the following URL: https://github.com/felixactv8/Phonegap-Calendar-Plugin-ios
Note the Calendar Plugin code that is available as of 5th September 2014 from Github when this blog entry was written is compatible with MAF’s Cordova 2.2.0 requirement (with 1 code line change we will make later). However it’s possible the plugin will be updated beyond this article, which implies the plugin may become incompatible. It really depends on the change to the plugin's code. Some changes might just be logic fixes that we do want. Other changes may be to how the plugin integrates with Cordova that will break our solution, and we don’t want these.
For the purposes of this blog, if you want to be 100% sure you have the same plugin code that was available when this blog entry was written, you should check out a copy of the plugin code at the last commit point 9th March 2013. The associated URL for this commit point is as follows, and like previous you must select the Download Zip button to get the complete code: http://bit.ly/1lAWWbZ
From here we’ll assume for this blog post that you’ll be working with the following directories:
a. Your base working directory is your home directory. e.g. /Users/chriscmuir. We’ll use the tilde (~) convention for representing this directory here after.
b. You’ve unzipped Cordova to ~/CordovaSDK
c. And the Calendar plugin to ~/CalendarPlugin
At a later point you will also be working with two additional directories that we’ll list here for completeness, but do not create them yet:
d. ~/CalendarXCode for the compiled XCode CalendarPlugin
e. ~/MyMafApp which will contain your MAF application
Prepare Cordova via XCode
Via XCode we first must build the Cordova code into a usable iOS library such that the plugin can make use of it.
You’ll be working with the ~/CordovaSDK directory for this section.
1) Open the Cordova XCode project located at ~/CordovaSDK/lib/ios/ CordovaLib/CordovaLib.xcodeproj
2) In the XCode IDE select File -> Project Settings
3) Change the "Derived Data Location" option from "Default" to "Project-relative" then accept the changes by selecting Done.
4) Select the Product -> Destination -> iOS Device menu options
5) Select Product ->Build – the build should end with the message Build Successful and you can ignore any warnings or informational messages.
6) Via the OSX Finder locate the libCordova.a file that was just created under ~/CordovaSDK /lib /ios /CordovaLib /DerivedData /CordovaLib /Build /Products /Debug-iphoneos
7) Copy the file to ~/CordovaSDK
Prepare the plugin via XCode
The downloaded plugin is just raw Objective C source code. To make it available as a Cordova plugin we must also build a library for the plugin via XCode.
In this section you’ll be working with the ~/CalendarPlugin directory for the source library code, and the ~/CalendarXCode directory for where you will actually build the library.
8) Open XCode
9) In the Welcome to XCode dialog select "Create a new XCode Project" (alternatively if the dialog is skipped, invoke the New menu option then Project) then Okay
10) In the "Choose a template for your new project" dialog, in the left hand side navigator, select the "Framework & Library" node. Then select "Cocoa Touch Static Library" and click Next
11) In the "Choose options for your new project" dialog enter the following details then select Next:
Product Name: CalendarXCode
Organization Name: <leave default>
Company Identifier: com.acme
12) Create the XCode project under your home directory. This will create a subdirectory under your home directory called CalendarXCode
13) On the IDE opening Select File -> Project Settings
14) Change the “Derived Data Location” option from "Default" to “Project-relative” and then select Done to accept the changes.
15) Returning to the XCode IDE in the file/project structure in the left, under the CalendarXCode project -> CalendarXCode folder, delete the generated CalendarPlugin.h and CalendarPlugin.m files. When prompted select Move to Trash.
16) In the OSX Finder locate and select the similar named files calendarPlugin.h and calendarPlugin.m under the ~/CalendarPlugin directory.
17) From the OSX Finder to the XCode IDE window, literally drag and drop both of these files under the CalendarXCode project -> CalendarXCode folder
18) When prompted in the "Choose options for adding these files" select the "Copy items into destination group’s folder (if needed)" option, leave the other options as default, then select Finish.
19) Double click the calendarPlugin.m file
20) Locate the createEvent method as follows:
(void)createEvent:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
21) Change the following line at the top of the createEvent method:
NSString *callbackId = [arguments pop];
to
NSString *callbackId = [arguments objectAtIndex:0];
22) For the following lines of code:
NSString* title = [arguments objectAtIndex:0];
NSString* location = [arguments objectAtIndex:1];
NSString* message = [arguments objectAtIndex:2];
NSString *startDate = [arguments objectAtIndex:3];
NSString *endDate = [arguments objectAtIndex:4];
Resequence the index count by +1 for each line as follows:
NSString* title = [arguments objectAtIndex:1];
NSString* location = [arguments objectAtIndex:2];
NSString* message = [arguments objectAtIndex:3];
NSString *startDate = [arguments objectAtIndex:4];
NSString *endDate = [arguments objectAtIndex:5];
23) Save your work.
We will now change the XCode project settings to allow you to build the Calendar plugin as an iOS library.
24) In the XCode IDE select the CalendarXCode top level project on the left hand side. This will display all the settings for the project.
25) In the Build Settings page that shows, select the All option next to the Basic option.
26) Use the search field to find the "Header Search Paths" item.
This will display the Header Search Paths option. Note it is expandable, and if expanded will reveal Debug and Release options with their own settings. The way the XCode project preferences work is if the settings for these sub-options are exactly the same, then the parent option, Header Search Paths in this case will reflect the exact same setting as the sub-options. For our purposes we want to change settings for all the sub-options, so we will modify the setting next to the Header Search Paths entry, not individually edit the Debug and Release settings.
27) Double click the setting (the path) next to the Header Search Paths. This will open a popup with 2 existing lines.
28) Select the plus (+) button which will create a new entry at the end, and enter the full path as follows all on one line with no spaces, substituting your username where appropriate. This will include the required Cordova source code header files in an upcoming build step:
/Users/<yourOSXusername>/CordovaSDK
/lib/ios/CordovaLib
/DerivedData/CordovaLib/Build
/Products/Debug-iphoneos/include
29) Once done select outside of the popup to close the options for the Header Search Paths.
30) Again with the All button highlighted use the search option to now locate the "Valid Architectures" option.
31) Select the entry next to the Valid Architecture option, and in the resulting popup eliminate the other options such that only "armv7" is left.
32) Again use the search option to locate the "iOS Deployment Target" and ensure this is set to "iOS 7.1".
33) Again use the search option to locate the "Objective-C Automatic Reference Counting" option and set this to No.
34) Select the Product -> Destination -> iOS Device menu option
35) Select the Product -> Build menu option to build the project. The build should end with the message Build Successful
36) Confirm in the ~/CalendarXCode /DerivedData /CalendarXCode /Build /Products /Debug-iphoneos directory a file libCalendarPlugin.a was created – this might take a minute or two
37) Back in the XCode IDE, select the Product -> Destination -> "iPhone Retina (4-inch)" menu option
38) Select the Product -> Build menu option again. The build should end with the message Build Successful
39) Confirm in the ~/CalendarXCode /DerivedData /CalendarXcode /Build /Products /Debug-iphonesimulator directory a similarly named file libCalendarPlugin.a was created
40) Combine the libraries by issuing the following 2 commands from the terminal (the lipo command is all one line):
cd ~/CalendarXCode/DerivedData/CalendarXCode/Build/Products
lipo -create Debug-iphoneos/libCalendarXCode.a
Debug-iphonesimulator/libCalendarXCode.a
-output libCalendarPlugin.a
41) This will create a combined library libCalendarPlugin.a in the current directory, check that is has been created.
42) In Finder copy this file to ~/CalendarXCode
With the above options you can see we’re building not just for deploying to the device, but various simulator options too. Obviously if you’re testing across a range of simulators you need to build for the others. We’ve assumed you’re just testing on the iPhone Retina 4-inch simulator option.
Configure your MAF application for the iOS plugin
From here we need to configure your MAF application to use the iOS plugin. We will assume you’ve already created your application, and we will instead start describing what files you need to modify in your MAF application to accept the plugin. We’ll also assume you’re relatively familiar with the JDeveloper IDE and wont bother to cover every detail or step-by-step instructions. You need to know how to create features, AMX pages, managed beans and task flows to continue from this point.
For the following steps we’ll assume your MAF application is located at ~/MyMafApp
43) In JDeveloper open the maf-application.xml file.
44) Select the Cordova plugin node in the maf-appliction.xml overview editor.
45) Select the green plus (+) button.
46) In the Insert Plugin dialog enter the following details ensuring you copy the exact same case for the values, then Ok:
Fully Qualified Name: calendarPlugin
Implementation Class: calendarPlugin
Name: CalendarPlugin
Platform: iOS
47) In the Linker Flags enter “-l CalendarPlugin” without the quotes (use the letter “l” not the numeral “1”). This will link the libCalendarPlugin.a file to the app build process when the MAF application is built. Save All.
(Note: If you’re using this blog entry for the purposes of configuring a separate plugin to the one described here, it’s important to understand the Linker Flags described here are particular for the Calendar Plugin. Other plugins may require not just the –l flag and the library name, but also further linker flags as directed by the plugin author. Unfortunately the documentation on 3rd party plugins can be lacking at times, so this can be a challenging exercise)
48) Open the application’s maf-feature.xml file
49) Select the feature you wish to use the Calendar plugin from, then the Content tab.
50) In the Includes option select the green plus (+) button
51) In the resulting dialog select JavaScript and enter the following path, again ensuring you copy the case of this entry exactly:
plugins/CalendarPlugin/iOS/js/calendar.js
52) Click OK and save your work
This file doesn’t yet exist under your application, we’re just setting the configuration for the moment. The next section moves all the required files into the right locations.
Copy the plugin files to your MAF application
53) In Finder under ~/MyMafApp, create the following directory structure under your ApplicationController project, ensuring you copy the exact same case:
ApplicationController/src/plugins/CalendarPlugin/iOS/bin
54) Copy the ~/CalendarXCode/libCalendarPlugin.a file into the new bin directory you just created in your MAF application
55) Again in Finder under ~/MyMaf, this time in your ViewController project, create the following directory structure, ensuring you copy the exact same case:
ViewController/public_html/plugins/CalendarPlugin/iOS/js
56) Copy the ~/CalendarPlugin/calendar.js file into the js directory you just created in your MAF application
Build your app
At this point the plugin is installed, and you are now in a position to build your MAF application around this. We’ll assume you’ve already created a feature within your MAF application you want to add this functionality.
57) The first step is to create a ViewController Java Bean that will call the calendar services:
package mobile;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import oracle.adf.model.datacontrols.device.DeviceManager;
import oracle.adf.model.datacontrols.device.DeviceManagerFactory;
import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
public class MyBean {
public MyBean() {}
private String title;
private String location;
private String notes;
private Date startDate;
private Date endDate;
public void createCalendarEntry(ActionEvent actionEvent) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startStr = df.format(startDate);
String endStr = df.format(endDate);
DeviceManager dm = DeviceManagerFactory.getDeviceManager();
String os = dm.getOs();
if (os.equals("iOS")) {
AdfmfContainerUtilities.
.invokeContainerJavaScriptFunction(
"<featureId>", "calendarEventiOS", new Object[] {
title, location, notes, startStr, endStr});
} else {
// For the equivalent Android call, see other blog post
}
}
}
58) In the above managed bean code I’ve removed the accessor methods for brevity in the example, you’ll need to add these with the appropriate change API calls (i.e. the propertyChangeSupport. firePropertyChange() method).
59) In the call to AdfmfContainerUtilities. invokeContainerJavaScriptFunction() call you substitute the first parameter with the ID of your feature, including any package prefix. Typically this will just be your feature name (e.g. MyFeature), but if you’ve created the feature under a package structure you need to include the package prefix (e.g. com.acme.MyFeature)
60) Depending on if you’re creating a single AMX page for your feature, or a task flow, you must configure the Java Bean as a managed bean in the relating adfc-mobile-config.xml file or <your-task-flow>.xml file. We’ll assume the Java Bean is called MyBean, and the managed bean EL name is myBean (lower case m).
61) Then in your AMX page we add the following code:
<?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 calendarEventiOS(title, location, notes, startDateString, endDateString)
{
var success = function() { alert("Event Created!"); };
var error = function(msg) { alert("Error " + msg); };
window.plugins.calendarPlugin.createEvent(title, location, notes, startDateString, endDateString, success, error);
}
</script>
]]>
</amx:verbatim>
<amx:validationGroup id="vg1">
<amx:panelFormLayout id="pfl1">
<amx:inputText id="it1" label="Title"
value="#{myBean.title}"
required="true"/>
<amx:inputText id="it2" label="Location"
value="#{myBean.location}"
required="true"/>
<amx:inputText id="it3" label="Notes"
value="#{myBean.notes}"
required="true"/>
<amx:inputDate id="id1" label="Start date"
value="#{myBean.startDate}"
inputType="datetime"
required="true"/>
<amx:inputDate id="id2" label="End date"
value="#{myBean.endDate}"
inputType="datetime"
required="true"/>
</amx:panelFormLayout>
</amx:validationGroup>
<amx:commandButton id="cb1"
text="Create calendar entry"
actionListener="#{myBean.createCalendarEntry}">
<amx:validationBehavior id="vb1" group="vg1"/>
</amx:commandButton>
</amx:panelPage>
</amx:view>
That’s the required steps. You can now deploy the application and test it. Remember! This is for the iOS plugin only. Testing this on Android will not work, you need to separately build in the Android Calendar Plugin to do this.
How Does This Work?
If you’ve survived implementing all the steps to this point and the application actually deploys and runs correctly, let’s describe how this works:
a.When the application is deployed, the iOS Cordova Plugin entry you added to maf-application.xml file has the compiler-linker directive to include the ApplicationController /src / plugins /CalendarPlugin /iOS /bin /libCalendarPlugin.a file by the “-l CalendarPlugin.a” entry (notice the “lib” prefix and “.a” suffix are omitted). This essentially bundles the Objective C code, that is calendarPlugin.h + calendarPlugin.m for the Cordova Calendar plugin you downloaded into your application to be used. It does this bundling at deployment time.
b. When the application is run, not only is this library added at runtime to use, but when you access the specific feature the ViewController /public_html /plugins /iOS /js /calendarPlugin.js file is also loaded for your feature to use thanks to the entry you created in the Content tab for the specific feature in the maf-feature.xml file. This JavaScript file is the Cordova wrapper to call the bundled library.
At this point all you’ve done is loaded the library and JavaScript file, you haven’t actually called them from your own AMX page. So basically they’re libraries to be used, it’s up to you to use them.
c. In order for the AMX page to call the calendarPlugin.js, it needs its own JavaScript code to do this. Thus in the AMX page you included the amx:verbatim tag with the JS calendarEventiOS method. If you look inside this method, you can see a call to window.plugins.calendarPlugin.createEvent which is a method in the calendarPlugin.js file.
d. Next in the page we need to grab the calendar title, location, notes, start date and end dates, and store them so we can use them to create the calendar entry. You configured the ViewController managed bean to store these values via EL expressions on the various amx:inputText and amx:inputDate fields on the AMX page.
e. Also on the same page you included a button to fire the calendar creation process off. This included an actionListener to the same bean to call a method createCalendarEntry. Inside that method it grabs the stored values from the previous step, that is title, location, notes, start date and end date, and then makes a call to AdfmfContainerUtilities. invokeContainerJavaScriptFunction(). This function when executed in context of the page, calls the JavaScript function calendarEventiOS in your page within the amx:verbatim tag.
f. From the calendarEventiOS method it calls the Cordova calendarPlugin.js createEvent method, which via Cordova calls calendarPlugin.h + calendarPlugin.m createEvent method. That in turn invokes the iOS functions for finally creating an entry in your calendar!
It’s worth noting in that last step, the user will be prompted the first time they invoke this function if they want to grant the application privileges to access the device’s calendar. If they say no, obviously the whole process fails. Curse those users with a mind of their own! ๐
We’ll leave you to explore calling the other functions to modify/update/delete calendar entries, our focus here has been hooking it all together. To do this you will need to extend the JavaScript calls in the AMX page to call the other functions in the calendar.js file you included from the CalendarPlugin directory.
Please note due to Oracle licensing policies, we are unable to supply a downloadable sample application which bundles the Calendar Cordova plugin. This is due to the fact the plugin has it’s own licensing conditions specific on GitHub which we can’t accept on your behalf.
It Doesn’t Deploy!
The settings required for the plugin are very sensitive to mistakes, just like any compiler-linker activity in a 3GL language. If you’ve placed a file in the wrong location, named a file wrong, have the wrong case in your directories and so on, this all comes to a screaming halt at deployment time.
As such here’s a list to allow you to double check your setup:
I. The file ApplicationController /src /plugins /CalendarPlugin /iOS /bin /libCalendarPlugin.a exists.
II. The file ViewController /public_html /plugins /CalendarPlugin /iOS /js /calendar.js exists
III. Your maf-application.xml file contains the following XML for the Cordova plugin:
<adfmf:cordovaPlugins>
<adfmf:plugin fullyQualifiedName="calendarPlugin"
implementationClass="calendarPlugin"
name="CalendarPlugin" platform="iOS">
<adfmf:iosPluginInfo>
<adfmf:linkerFlags>-l CalendarPlugin</adfmf:linkerFlags>
</adfmf:iosPluginInfo>
</adfmf:plugin>
</adfmf:cordovaPlugins>
IV. The feature for your maf-feature.xml file should have the following entry loading the Cordova JavaScript file for the associated content (assumption in the following example: your feature is called MyFeature, and it uses a task flow):
<adfmf:feature id="MyFeature" name="MyFeature">
<adfmf:content id="MyFeature.1">
<adfmf:amx file="MyFeature/mytaskflow.xml#mytaskflow">
<adfmf:includes>
<adfmf:include type="JavaScript" id="i1"
file="plugins/CalendarPlugin/iOS/js/calendar.js"/>
</adfmf:includes>
</adfmf:amx>
</adfmf:content>
</adfmf:feature>
Though not relating to the configuration of your app, also ensure:
V. You’ve added the amx:verbatim tag to your AMX page with the calendarEventiOS JavaScript function.
VI. And your page via a button invokes a managed bean method which then calls AdfmfContainerUtilities. invokeContainerJavaScriptFunction().
It Deploys but it Doesn’t Work!
Well you got this far, well done! ๐
What typically breaks from here is the call chain we described in “How Does This Work?” That is the call chain from your bean code calling the AMX page JavaScript function calling the calendar.js functions calling Cordova calling the plugin!
Luckily any issues normally occur in the first set of calls.
As the first call to activate the code is from your AMX page to #{myBean.createCalendarEntry} method, you can verify this step is running by running your app in debug mode and placing a breakpoint in that method to see that it’s called.
From there the MyBean.createCalendarEntry() bean method via a call to AdfmfContainerUtilities. invokeContainerJavaScriptFunction() calls the calendarEventiOS JavaScript function in your AMX page. You can’t place a debug breakpoint in the JavaScript, but you can add a JavaScript alert('hello'); message inside the function to see that it’s called at runtime after the call to your bean.
If you can see this happening, but it’s not working after this, this goes back to a configuration issue, consult the previous section.
One final issue is if it does work, but the calendar entries are wrong, ensure you made the changes to the Calendar Plugin’s calendarPlugin.m file.
Beyond this point if you are not able to resolve any issue with the plugin, please post questions to the OTN MAF Forums.