Introduction
JET is Oracle’s new mobile toolkit specifically written for developers to help them build client slide applications using JavaScript. Oracle Fusion Applications implementers are often given the requirement to create mobile, or desktop browser, based custom screens for Fusion Applications. There are many options available to the developer for example Oracle ADF (Java Based) and Oracle JET (JavaScript based). This blog article gives the reader a tutorial style document on how to build a hybrid application using data from Oracle Fusion Sales Cloud. It is worth highlighting that although this tutorial is using Sales Cloud, the technique below is equally applicable to HCM cloud, or any other Oracle SaaS cloud product which exposes a REST API.
Main Article
Pre-Requisites
It is assumed that you’ve already read the getting started guide on the Oracle Jet website and installed all the pre-requisites. In addition if you are to create a mobile application then you will also need to install the mobile SDKs from either Apple (XCode) or Android (Android SDK).
You must have a Apple Mac to be able to install the Apple IOS developer kit (XCode), it is not possible to run XCode on a Windows PC
Dealing with SaaS Security
Before building the application itself we need to start executing the REST calls and getting our data and security is going to be the first hurdle we need to cross.Most Sales Cloud installations allow “Basic Authentication” to their APIs, so in REST this involves creating a HTTP Header called “Authorization” with the value “Basic <your username:password>” , with the <username:password> section encoded as Base64. An alternative approach used when embedding the application within Oracle SaaS is to use a generated JWT token. This token is generated by Oracle SaaS using either groovy or expression language. When embedding the application in Oracle SaaS you have the option of passing parameters, the JWT token would be one of these parameters and can subsequently be used instead of the <username:password>. When using JWT token the Authorization string changes slightly so that instead of “Basic” it become “Bearer”,
Usage | Header Name | Header Value |
---|---|---|
Basic Authentication | Authorization | Basic <your username:password base64 encoded> |
JWT Authentication | Authorization | Bearer <JWT Token> |
Groovy Script in SalesCloud to generate a JWT Token
def thirdpartyapplicationurl = oracle.topologyManager.client.deployedInfo.DeployedInfoProvider.getEndPoint("My3rdPartyApplication" ) def crmkey= (new oracle.apps.fnd.applcore.common.SecuredTokenBean().getTrustToken()) def url = thirdpartyapplicationurl +"?jwt ="+crmkey return (url)
Expression Language in Fusion SaaS (HCM, Sales, ERP etc) to generate a JWT Token
#{EndPointProvider.externalEndpointByModuleShortName['My3rdPartApplication']}?jwt=#{applCoreSecuredToken.trustToken}
Getting the data out of Fusion Applications using the REST API
When retrieving data from Sales Cloud we need to make sure we get the right data, not too much and not too little. Oracle Sales Cloud, like many other Oracle SaaS products, now supports the REST API for inbound and outbound data access. Oracle HCM also has a REST API but at the time of writing this article, the API is in controlled availability.
Looking at the documentation hosted at Oracle Help Center :http//docs.oracle.com/cloud/latest/salescs_gs/FAAPS/
The REST call to get all Sales Cloud Opportunities looks like this :
https://yourCRMServer/salesApi/resources/latest/opportunities
If you executed the above REST call you will notice that the resulting payload is large, some would say huge. There are good reasons for this, namely that the Sales Cloud Opportunity object contains a large number fields, secondly the result not only contains data but also contains metadata and finally the request above is a select all query. The metadata includes links to child collections, links to List of Values, what tabs are visible in Sales Cloud , custom objects, flexfields etc. Additionally the query we just executed is a the equivalent of a select * from table, i.e. it brings back everything so we’ll also need to fix that.
Example snippet of a SalesCloud Opportunity REST Response showing custom fields,tabs visible, child collections etc
"Opportunity_NewQuote_14047462719341_Layout6": "https://mybigm.bigmachines.com/sso/saml_request.jsp?RelayState=/commerce/buyside/document.jsp?process=quickstart_commerce_process_bmClone_4%26formaction=create%26_partnerOpportunityId=3000000xxx44105%26_partnerIdentifier=fusion%26_partnerAccountId=100000001941037", "Opportunity_NewQuote_14047462719341_Layout6_Layout7": "https://mybigMmachine.bigmachines.com/sso/saml_request.jsp?RelayState=/commerce/buyside/document.jsp?process=quickstart_commerce_process_bmClone_4%26formaction=create%26_partnerOpportunityId=300000060xxxx5%26_partnerIdentifier=fusion%26_partnerAccountId=100000001941037", "ExtnFuseOpportunityEditLayout7Expr": "false", "ExtnFuseOpportunityEditLayout6Expr": "false", "ExtnFuseOpportunityCreateLayout3Expr": "false", "Opportunity_NewQuote_14047462719341_Layout8": "https://mybigm-demo.bigmachines.com/sso/saml_request.jsp?RelayState=/commerce/buyside/document.jsp?process=quickstart_commerce_process_bmClone_4%26formaction=create%26_partnerOpportunityId=300000060744105%26_partnerIdentifier=fusion%26_partnerAccountId=100000001941037", "ExtnFuseOpportunityEditLayout8Expr": "false", "CreateProject_c": null, "Opportunity_DocumentsCloud_14399346021091": "https://mydoccloud.documents.us2.oraclecloud.com/documents/embed/link/LF6F00719BA6xxxxxx8FBEFEC24286/folder/FE3D00BBxxxxxxxxxxEC24286/lyt=grid", "Opportunity_DocsCloud_14552023624601": "https://mydocscserver.domain.com:7002/SalesCloudDocCloudServlet/doccloud?objectnumber=2169&objecttype=OPPORTUNITY&jwt=eyJhxxxxxy1pqzv2JK0DX-xxxvAn5r9aQixtpxhNBNG9AljMLfOsxlLiCgE5L0bAI", "links": [ { "rel": "self", "href": "https://mycrmserver-crm.oracledemos.com:443/salesApi/resources/11.1.10/opportunities/2169", "name": "opportunities", "kind": "item", "properties": { "changeIndicator": "ACED0005737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A65787000000002770400000010737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787200106A6176612E6C616E672E4F626A65637400000000000000000000007870000000017371007E00020000000178" } }, { "rel": "canonical", "href": "https://mycrmserver-crm.oracledemos.com:443/salesApi/resources/11.1.10/opportunities/2169", "name": "opportunities", "kind": "item" }, { "rel": "lov", "href": "https://mycrmserver-crm.oracledemos.com:443/salesApi/resources/11.1.10/opportunities/2169/lov/SalesStageLOV", "name": "SalesStageLOV", "kind": "collection" },
Thankfully we can tell the REST API that we :
- Only want to see the data, achieved by adding onlyData=true parameter
- Only want to see the following fields OpportunityNumber,Name,CustomerName (TargetPartyName), achieved by adding a fields=<fieldName,fieldname> parameter
- Only want to see a max of 10 rows, achieved by adding the limit=<value> parameter
- Only want to see open opportunities, achieved by adding the q= parameter with a query string, in our case StatusCode=OPEN
If we want to get the data in pages/blocks we can use the offset parameter. The offset parameter tells the REST service to get the data “from” this offset. Using offset and limit we can effectively page through the data returned by Oracle Fusion Applications REST Service.
Our final REST request URL would look like :
https://myCRMServeroracledemos.com/salesApi/resources/latest/opportunities?onlyData=true&fields=OptyNumber,Name,Revenue,TargetPartyName,StatusCode&q=StatusCode=OPEN&offset=0&limit=10
The Oracle Fusion Applications REST API is documented in the relevant Oracle Fusion Applications Documentation, e.g. for Sales Cloud, http://docs.oracle.com/cloud/latest/salescs_gs/FAAPS/ but it is also worth noting that the Oracle Fusion Applications REST Services are simply an implementation of the Oracle ADF Business Components REST Services, these are very well documented here https://docs.oracle.com/middleware/1221/adf/develop/GUID-8F85F6FA-1A13-4111-BBDB-1195445CB630.htm#ADFFD53992
Our final tuned JSON result from the REST service will look something like this (truncated) :
{ "items": [ { "Name": "Custom Sentinel Power Server @ Eagle", "OptyNumber": "147790", "StatusCode": "OPEN", "TargetPartyName": "Eagle Software Inc", "Revenue": 104000 }, { "Name": "Ultra Servers @ Beutelschies & Company", "OptyNumber": "150790", "StatusCode": "OPEN", "TargetPartyName": "Beutelschies & Company", "Revenue": 175000 }, { "Name": "Diablo Technologies 1012", "OptyNumber": "176800", "StatusCode": "OPEN", "TargetPartyName": "Diablo Technologies", "Revenue": 23650 } }
Creating the Hybrid Application
Now we have our datasource defined we can start to build the application. We want this application to be available on a mobile device and therefore we will create a “Mobile Hybrid” application using Oracle JET, using the –NavDrawer template.
yo oraclejet:hybrid OSCOptyList --template=navDrawer --platforms=android
Once the yeoman script has built your application, you can test the (basic) application using the following two commands.
grunt build --platform=android grunt serve --platform=android --web=true
The second grunt serve command has a web=true parameter at the end, this is telling the script that we’re going to be testing this in our browser and not on the device itself. When this is run you should see basic shell [empty] application in your browser window.
For
Building Our JavaScript UI
Now that we have our data source defined we can now get onto to task of building the JET User Interface. Previously you executed the yo oraclejet:hybrid command, this created you a hybrid application using a template. Opening the resulting project in an IDE, like NetBeans, we can see that the project template has created a collection of files and that one of them is “dashboard.html” (marked 1 in the image), edit this file using your editor.
Within the file delete everything and replace it with this snippet of html code
<div class="oj-hybrid-padding"> <div class="oj-flex"> <div class="oj-flex-item"> <button id= "prevButton" data-bind="click: previousPage, ojComponent: { component: 'ojButton', label: 'Previous' }"> </button> <button id= "nextButton" data-bind="click: nextPage, ojComponent: { component: 'ojButton', label: 'Next' }"> </button> </div> </div> <div class="oj-flex-item"> <div class="oj-panel oj-panel-alt1 oj-margin"> <table id="table" summary="Opportunity List" aria-label="Opportunity List" data-bind="ojComponent: {component: 'ojTable', data: opportunityDataSource, columnsDefault: {sortable: 'none'}, columns: [{headerText: 'Opty Number', field: 'OptyNumber'}, {headerText: 'Name', field: 'Name'}, {headerText: 'Revenue', field: 'Revenue'}, {headerText: 'Customer Name', field: 'TargetPartyName'}, {headerText: 'Status Code', field: 'StatusCode'} ]}"> </table> </div> </div> </div>
The above piece of html adds a JET table to the page, for prettiness we’ve wrapped the table in a decorative panel and added a next and previous buttons. The table definition tells Oracle JET that the data is coming from a JavaScript object called “opportunityDataSource“, it also defines defines the columns, column header text and that the columns are not sortable. The button definitions reference two functions in our JavaScript (to follow) which will paginate the data.
Building The logic
We can now move onto the JavaScript side of things, that is the part where we get the data from Sales Cloud and makes it available to the table object in the html file. For this simplistic example we’ll get the data direct from Sales Cloud and display it in the table, with no caching and nothing fancy like collection models for pagination .
Edit the dashboard.js file, this is marked as 2 in the above image. This file is a RequiresJS AMD (Application Module Definition File) and is pre-populated to support the dashboard.html page.
Within this file, cut-n-paste the following JavaScript snippet.
define(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojtable', 'ojs/ojbutton'], function (oj, ko, $) { function DashboardViewModel() { var self = this; var offset = 0; var limit = 10; var pageSize = 10; var nextButtonActive = ko.observable(true); var prevButtonActive = ko.observable(true); // self.optyList = ko.observableArray([{Name: "Fetching data"}]); console.log('Data=' + self.optyList); self.opportunityDataSource = new oj.ArrayTableDataSource(self.optyList, {idAttribute: 'Name'}); self.refresh = function () { console.log("fetching data"); var hostname = "https://yourCRMServer.domain.com"; var queryString = "/salesApi/resources/latest/opportunities?onlyData=true&fields=OptyNumber,Name,Revenue,TargetPartyName,StatusCode&q=StatusCode=OPEN&limit=10&offset=" + offset; console.log(queryString); $.ajax(hostname + queryString, { method: "GET", dataType: "json", headers: {"Authorization": "Basic " + btoa("username:password")}, // Alternative Headers if using JWT Token // headers : {"Authorization" : "Bearer "+ jwttoken; success: function (data) { self.optyList(data.items); console.log('Data returned ' + JSON.stringify(data.items)); console.log("Rows Returned"+self.optyList().length); // Enable / Disable the next/prev button based on results of query if (self.optyList().length < limit) { $('#nextButton').attr("disabled", true); } else { $('#nextButton').attr("disabled", false); } if (self.offset === 0) $('#prevButton').attr("disabled", true); }, error: function (jqXHR, textStatus, errorThrown) { console.log(textStatus, errorThrown); } } ); }; // Handlers for buttons self.nextPage = function () { offset = offset + pageSize; console.log("off set=" + offset); self.refresh(); }; self.previousPage = function () { offset = offset - pageSize; if (offset < 0) offset = 0; self.refresh(); }; // Initial Refresh self.refresh(); } return new DashboardViewModel; } );
Lets examine the code
Line 1: Here we’ve modified the standard define so that it includes a ojs/table reference. This is telling RequiresJS , which the JET toolkit uses, that this piece of JavaScript uses a JET Table object
Line 8 & 9 : These lines maintain variables to indicate if the button should be enabled or not
Line 11: Here we created a variable called optyList, this is importantly created as a knockout observableArray.
Line 13: Here we create another variable called “opportunityDataSource“, which is the variable the HTML page will reference. The main difference here is that this variable is of type oj.ArrayTableDataSource and that the primary key is OptyNumber
Lines 14-47 : Here we define a function called “refresh”. When this javascript function is called we execute a REST Call back to SalesCloud using jquery’s ajax call. This call retrieves the data and then populates the optyList knockout data source with data from the REST call. Specifically here note that we don’t assign the results to the optyData variable directly but we purposely pass a child array called “items”. If you execute the REST call, we previously discussed, you’ll note that the data is actually stored in an array called items
Line 23 : This line is defining the headers, specifically in this case we’re defining a header called “Authorization” , with the username & password formatted as “username:password” and then the base64 encoded.
Line 24-25 :These lines define an alternative header which would be appropriate if a JWT token was being used. This token would be passed in as a parameter rather than being hardcoded
Lines 31-40 : These query the results of the query and determine if the next and previous buttons should be enabled or not using jQuery to toggle the disabled attribute
Lines 50-63 : These manage the next/previous button events
Finally on line 65 we execute the refresh() method when the module is initiated.
Running the example on your mobile
To run the example on your mobile device execute the follow commands
grunt build --platform=android grunt serve --platform=android
or if you want to test on a device
grunt serve --platform=android -destination=[device or emulator name]
If all is well you should see a table of data populated from Oracle Sales Cloud
For more information on building JavaScript applications with the Oracle JET tool make sure to check out our other blog articles on JET here , the Oracle JET Website here and the excellent Oracle JET You Tube channel here
Running the example on the browser and CORS
If you try and run the example on your browser you’ll find it probably won’twork. If you look at the browser console (control+shift+I on most browsers) you’ll probably see that the error was something like “XMLHttpRequest cannot load…” etc
This is because the code has violated “Cross Origin Scripting” rules. In a nut shell “A JavaScript application cannot access a resource which was not served up by the server which itself was served up from”.. In my case the application was served up by Netbeans on http://localhost:8090, whereas the REST Service from Sales Cloud is on a different server, thankfully there is a solution called “CORS”. CORS stands for Cross Origin Resource Sharing and is a standard for solving this problem, for more information on CORS see this wikipedia article, or other articles on the internet.
Configuring CORS in Fusion Applications
For our application to work on a web browser we need to enable CORS in Fusion Applications, we do this by the following steps :
- 1. Log into Fusion Applications (SalesCloud, HCM etc) using a user who has access to “Setup and Maintenance”
- 2. Access setup and Maintenance screens
- 3. Search for Manage Administrator Profile Values and then navigate to that task
- 4. Search for “Allowed Domains” profile name (case sensitive!!).
- 5. Within this profile name you see a profile option called “site“, this profile option has a profile value
- 6. Within the profile value add the hostname, and port number, of the application hosting your JavaScript application. If you want to allow “ALL” domains set this value to “*” (a single asterisk )
- WARNING : Ensure you understand the security implication of allowing ALL Domains using the asterisk notation!
- 7. Save and Close and then retry running your JET Application in your browser.
If all is good when you run the application on your browser, or mobile device, you’ll now see the application running correctly.
Final Note on Security
To keep this example simple the security username/password was hard-coded in the mobile application, not suitable for a real world application. For a real application you would create a configuration screen, or use system preferences, to collect and store the username , password and the SalesCloud Server url which would then be used in the application.
If the JET Application is to be embedded inside a Fusion Applications Page then you will want to use JWT Token authentication. Modify the example so that the JWT token is passed into the application URL as a parameter and then use that in the JavaScript (lines 24-25) accordingly.
For more information on JWT Tokens in Fusion Applications see these blog entries (Link 1, Link 2) and of course the documentation
Conclusion
As we’ve seen above its quite straightforward to create mobile, and browser, applications using the Oracle JET Framework. The above example was quite simple and only queried data, a real application would also have some write/delete/update operations and therefore you would want to start to look at the JET Common Model and Collection Framework (DocLink) instead. Additionally in the above example we queried data direct from a single SalesCloud instance and did no processing on it.. Its very likely that a single mobile application will need to get its data from multiple data sources and require some backend services to “preprocess” and probably post process the data, in essence provide an API.. We call this backend a “MBaaS”, ie Mobile Backend As A Service, Oracle also provides a MBaaS in its PaaS suite of products and it is called Oracle Product is called “Mobile Cloud Service”..
In a future article we will explore how to use Oracle Mobile Cloud Service (Oracle MCS) to query SalesCloud and Service cloud and provide an API to the client which would be using the more advanced technique of using the JET Common Model/Collection framework.
All content listed on this page is the property of Oracle Corp. Redistribution not allowed without written permission