X

An Oracle blog about Application UX

Integrating Google Maps, IFTTT, and OpenWeatherMap APIs into the Oracle Cloud UX Rapid Development Kit

The Oracle Applications Cloud User Experience Rapid Development Kit (RDK) enables Oracle partners and developers to learn faster, design simpler, and build better. The RDK contains a set of free resources for the design and build phases and includes sample Oracle ADF applications that illustrate how to develop sleek, modern SaaS integrations and custom PaaS applications that have the same look and feel as the Oracle Applications Cloud.

You can find more resources for the RDK at tinyurl.com/PaaS4SaaS, including the code download link. If you prefer to hear about the RDK, tune in to our Oracle HCM Talk Radio podcast to hear how you can tap into the Oracle Applications Cloud User Experience.

In this blog, I explain how to enhance the Oracle ADF application samples in the RDK with integration to third-party services, such as Google Maps, IFTTT (If This Then That), and OpenWeatherMap, using simple HTTP and JavaScript.

What You Need

  • Oracle JDeveloper (11.1.1.9.0 or 12c) available as a free download from OTN.
  • Oracle UX Rapid Development Kit available as a free download from OTN and GitHub.

Where To Start

Download the RDK zip file, and then unzip it to your Oracle JDeveloper working folder. You should see a folder structure resembling the following.

RDK Code Folder Structure

AppsCloudUIKit folder structure

Open the application (AppsCloudUIKit.jws) in Oracle JDeveloper to show the projects that make up the RDK.

RDK Project Structure in JDeveloper

Oracle JDeveloper view of projects in the RDK

At this point, you can run the page Welcome.jspx in the DemoMaster project and interact with the out-of-the-box runnable sample application.

Integrating Google Maps

Google Maps come integrated in the Contacts Map page fragment. You can find this page under the DemoCRM project, which corresponds to the Oracle Sales Cloud, with the code organized under logical business objects - contacts, opportunities, and so on.

Location of Contacts Map page in JDeveloper

DemoCRM project: Page fragment ContactsMap.jsff 

When you run the RDK, dismiss the one-time welcome banner, and then click the Map my Contacts icon to navigate to the Contacts Map page. The finished page has a searchable list of contacts on the left with the map rendered on the right. When you click a contact record, the map renders a pin to mark the geographical location of the contact. Clicking the same contact again removes the pin. You can click more than one contact to see location pins together.

Screen shot of running Contacts Map page

Map My Contacts page

Let us see how to build this.

The Google Maps API is a JavaScript Library. We can add JavaScript to an Oracle ADF Faces page by either adding the JavaScript code to the page source or by adding a reference to an external JavaScript library file. In this case, we will use both techniques. We first add a reference to the Google Maps JavaScript Library using the <af:resource> tag.

  <af:resource type=”javascript” source=”https://maps.googleapis.com/maps/api/js”/>

We then add JavaScript code blocks directly into the page source using the same <af:resource> tag. The following code block initializes the map object on the page when the page loads.

  <af:resource> 
     if (window.addEventListener) {
        /* Modern browsers */
        window.addEventListener("load", onLoad, false)
     } else if (window.attachEvent) {
        /* IE */
        window.detachEvent("onload", onLoad)
        window.attachEvent("onload", onLoad)
     } else {
        window.onload = onLoad
     }
    function onLoad() {
        doMap();
     }
  </af:resource>
  

The doMap function anchors the map container object to a seeded element in the page with the ID mapdiv. It sets a default value for latitude, longitude, and zoom level to the map object in the container for initial rendering. This code block also prepares an array to hold multiple marker references in case of multiple contacts selection.

  <af:resource type="javascript">
     var map;
     var geocoder = new google.maps.Geocoder();
     markers = [];
    function doMap() {
        var mapelem = document.getElementById('mapdiv');
        var newLatLng = new google.maps.LatLng(37.75, - 122.42);
        var myOptions = {
           center : newLatLng, zoom : 8
        };
        map = new google.maps.Map(mapelem, myOptions);
     }
  </af:resource>
  

When the user clicks a contact, a client listener triggers the geocodeAndMark() function in the following code block. This function obtains the contact address information, generates a unique marker ID, and then calls the geocodeAddress() function.

  <af:resource type="javascript">
     function geocodeAndMark(evt) {
        var comp = evt.getSource();
        var combo = comp.getShortDesc();
        var comboArr = combo.split( "+" );
       var id = comboArr[0];
        var addr = comboArr[1];
        geocodeAddress(id, addr, geocoder, map);
     }
  </af:resource>
  

The geocodeAddress function uses the Google Maps geocoder to geocode the address, save the marker in the marker array, and add a pin to the map.

  <af:resource type="javascript">
     function geocodeAddress(id, address, geocoder, resultsMap) {
        if (markers[id] == undefined) {
           markers[id] = new google.maps.Marker(null, null);
        }
        if (markers[id].getMap() == null) {
           geocoder.geocode(
              {‘address' : address},
              function (results, status) {
                 if (status === google.maps.GeocoderStatus.OK) {
                    resultsMap.setCenter(results[0].geometry.location);
                    var marker = new google.maps.Marker({
                       map : resultsMap, position : results[0].geometry.location
                    });
                    markers[id] = marker;
                 } else {
                    alert('Unable to find address. Geocoding returned ' + status);
                 }
              }
            );
        } else {
           markers[id].setMap(null);
        }
     }
  </af:resource>
  

Because this processing is done on the client side, the page does not require full or partial refresh to redraw the map to toggle markers. That is all there is to it!

Integrating IFTTT (If This Then That)

IFTTT is a free web-based service that allows you to conditionally trigger events by writing statements in the formatIf Condition Then Actionreferred to as Recipes. A large number of Channels are available to specify the Condition and Action part of the Recipe. It is a fun, yet powerful, integration or automation service for all things internet-connected. I will assume that you have an IFTTT.com account and that you know how to build IFTTT Recipes.

We will use the IFTTT Maker Channel to trigger a Recipe based on a user event in the RDK. The Maker Channel lets you connect a Web Request as the Condition or Action of a Recipe, and this will be the basis for connecting the RDK to IFTTT. You will need to set up your Recipe with the Maker Channel as trigger and give a specific name to the Trigger Event. You will need to add this in the RDK code, so make it meaningful.

Trigger event screenshot

Trigger event name example

You will also need to note the secret "key" string generated by IFTTT for your Maker Channel instance. (Because it is a secret key, I have hidden mine.)

IFTTT Channel screen shot

Maker Channel page

In the RDK, we will trigger the Recipe every time the Sales Opportunity amount is changed. This can be done quite easily by adding a few lines of code to the managed bean associated with the Sales Opportunity page. Open OpportunityBean.java in the DemoCRM project under package oracle.apps.uikit.crm.opportunities.bean, and then add the event name you created and your secret key.

  private static final String IFTTT_MAKER_EVENT = “RDK_Opportunity_Amount_Changed”;
  private static final String IFTTT_SECRET_KEY = “################”;
  

We now invoke the following method when the user saves the changes they make on a page. The method continues processing only if the amount attribute has changed. It effectively sends a HTTP POST to IFTTT that triggers the Recipe associated with the Maker Channel instance identified by event name and secret key. At the time of writing, you can send up to three parameters to the event.

  //Alert opportunity amount change
  private void _alertOpportunityAmountChanged(String name, String oldAmount, String newAmount){
     if (!oldAmount.equals(newAmount)){
        try {
           CloseableHttpClient httpclient = HttpClients.createDefault();
           try {
              String HttpPostString = "http://maker.ifttt.com/trigger/" + IFTTT_MAKER_EVENT + "/with/key/" + IFTTT_SECRET_KEY;
              HttpPost httppost = new HttpPost(HttpPostString);
              String inputStr = "{\"value1\" : \"" + name + "\", \"value2\" : \"" + oldAmount + "\", \"value3\" : \"" + newAmount + "\" }";
              StringEntity input = new StringEntity(inputStr);
              input.setContentType( "application/json" );
              httppost.setEntity(input);
              String responseBody = httpclient.execute(httppost, responseHandler);
           } catch (ClientProtocolException e) {
           } catch (IOException e) {
           } finally {
              httpclient.close();
           }//try-catch
        } catch (IOException e) {
        }//try-catch
     }//amount has changed
  }//_alertOpportunityAmountChanged

  // Create a custom response handler
  ResponseHandler responseHandler = new ResponseHandler() {
     public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
        int status = response.getStatusLine().getStatusCode();
        if (status >= 200 && status < 300) {
           HttpEntity entity = response.getEntity();
           return entity != null ? EntityUtils.toString(entity) : null;
        } else {
           throw new ClientProtocolException("Unexpected response status: " + status);
        }
     }//handleResponse
  };
  

As part of the Recipe action, you now have a world of exciting things you can do every time the amount attribute of a Sales Opportunity is updated in the RDKblink your lights, send an email, write to a cloud document, tweet. Now go play!

Integrating OpenWeatherMap

OpenWeatherMap is an online service that provides free API access to weather data, including current weather, forecasts, and historical data. To use this service, you will need to register for a free account and obtain your unique APP-ID. I will assume that you have already done this. We will now use this service to display an icon for the current weather at the location for Sales Contacts.

Contacts List View screenshot

Contacts page

The technique involves a simple HTTP request to a specific URL with necessary parameters passed as part of the URL. While the services can be invoked for several cities at a time, I will keep it simple by fetching the current weather for one city. You can use the following URL patterns for your HTTP request.

  api.openweathermap.org/data/2.5/weather?q={city name}
  api.openweathermap.org/data/2.5/weather?q={city name},{ISO 3166 country code}
  E.g.:
  api.openweathermap.org/data/2.5/weather?q=london
  api.openweathermap.org/data/2.5/weather?q=london,uk
  

The following piece of code can be used to invoke the OpenWeatherMap service and extract the weather icon reference from the returned JSON payload.

  String location = <city>;
  String wicon = “”;
  if (location != null && location.length() > 0){
     location = location.replaceAll(" ", "+" );
     try {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        JSONParser parser = new JSONParser();
        try {
           HttpGet httpget = new HttpGet( "http://api.openweathermap.org/data/2.5/weather?q=" + location + "&APPID=" + APPID + "&mode=json&units=metric" );
           //Create custom response handler
           ResponseHandler responseHandler = new ResponseHandler() {
              @Override
              public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                 int status = response.getStatusLine().getStatusCode();
                 if (status >= 200 && status < 300) {
                    HttpEntity entity = response.getEntity();
                    return entity != null ? EntityUtils.toString(entity) : null;
                 } else {
                    throw new ClientProtocolException("Unexpected response status: " + status);
                 }//if
              }
           };
           String responseBody = httpclient.execute(httpget, responseHandler);
           JSONObject root = (JSONObject)parser.parse(responseBody);
           JSONArray weather = (JSONArray)root.get( "weather" );
           JSONObject wobj = (JSONObject)weather.get(0);
           wicon = "http://openweathermap.org/img/w/" + wobj.get( "icon" ) + ".png";
        } catch(ClientProtocolException e) {
           //Handle as appropriate
        } catch(IOException e) {
           //Handle as appropriate
        } catch(ParseException e) {
           //Handle as appropriate
        } finally {
           httpclient.close();
        }//try-catch
    } catch(IOException e) {
        //Handle as appropriate
     }//try-catch
  }//check location is available
  

You can display the weather icon in the Sales Contacts list view as shown in the Contacts page above, or anywhere else in the RDK that suits your requirement.

Conclusion

I have shown at a very elementary level, how to call a Web Service (or REST) API by constructing a URL string by using HTTP, receive a JSON payload in response, and parse this to extract the relevant parts. I have also shown how such processing can be easily incorporated into an ADF application by using Java or JavaScript. This gives you a flavor of how to rapidly enhance Oracle Applications Cloud with standalone PaaS applications or PaaS4SaaS integrations, enabling you to offer more UX value to your customers.

Do feel free to share your thoughts in comments, and stay tuned for more. Enjoy!

Resources

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.