This recipe demonstrates how it’s possible to use Salesforce REST services, from within a MobileCaddy application.
Versions
- Based off starter-app shell-ionic / shell-ionic-sidemenu v1.6
- Requires MC_Resource v1.1.0
Context/Concept/Why
In some scenarios it may be possible to enrich a users experience by providing extra functionality, or intelligence, into your application through the consumption of the REST services that are served from Salesforce.com. An example might be providing a recent chatter feed, or enabling an online SFDC contact search.
In all instances, though, the designer/developer should be fully aware that online consumption of REST services should be approached with a progressive enhancement methodology in mind, as all MobileCaddy applications should fulfil all critical duties even when offline. This is the Offline First mantra.
How To
We can access the REST services using the McRestService angular service. As well as providing a simple promise-based wrapper for us to call, it will also take care of aligning our REST calls with our existing authenticated session with Salesforce. This allows the developer to call the REST services without worrying about setting up and managing authentication to Salesforce.com.
1) Include the McRestService
- To access Salesforce REST service from a MobileCaddy application, the McRestService angular service can be used. This service is available in the shell applications (v1.6+), and can be retrofitted or upgraded by copying the latest file from the MobileCaddy Ionic Shell repository into your project’s www/js/services directory.
- At the time of writing the McRestService makes use of a MobileCaddy module called appDataUtils. For this to be available the following needs to be in the www/js/services/service.module.js file, prior to the final line of the file. This appDataUtils is also needed to be added to the final line.
This file can be used as a reference.
123456angular.module('appDataUtils', []).factory('appDataUtils', function() {return mobileCaddy.require('mobileCaddy/appDataUtils');});
2) Using the McRestService
In our example we will implement a view and controller to query the chatter feeds on our Salesforce org, and also to enable a SOQL query of the contacts on Salesforce.com.
Our template is very basic, and consists of 2 buttons and one text input, that we can put our contact name search string into. We also have an ion-list to list out the contact results.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<ion-view title="REST Calls"> <ion-nav-buttons side="right"> <mc-sync-spinner></mc-sync-spinner> </ion-nav-buttons> <ion-content padding="true"> <h3>Chatter</h3> <button ng-click="vm.getLatestChatter()"> Get latest Chatter Post </button> <h3>SOQL</h3> <input type="text" ng-model="vm.contactSearchStr" placeholder="Contact name"> <button ng-click="vm.searchRemoteContacts()"> <b>Search remote contacts</b> </button> <ion-list> <ion-item class="item-icon-right" ng-repeat="contact in vm.contacts"> <h3>{{contact.Name}}</h3> <p><stong>ID:</stong>{{contact.Id}}</p> </ion-item> </ion-list> </ion-content> </ion-view> |
The controller for this view has two functions exposed. This is the first and is called to display the latest chatter post. This function uses the McRestService’s request method, and we pass in the HTTP method, URL to call, and Content Type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function getLatestChatter(){ // Setup config for our HTTP call var obj = { method: 'GET', contentType: 'application/json', path: '/services/data/v36.0/chatter/feeds/news/me/feed-elements' }; // Call our McRestService McRestService.request(obj).then(function(result){ // We have a positive result, so show the latest chatter post in an Ionic // popup, showing the name of who posted and the body text. var alertPopup = $ionicPopup.alert({ title: 'Last Chatter Post!', template: result.elements[0].actor.displayName + ': ' + result.elements[0].body.text }); alertPopup.then(function(res) { // Show our popup }); }).catch(function(e){ // Something went wrong, so log to the Mobile_Log__mc MobileCaddy table. logger.error("getLatestChatter error",e); }); } |
We have a second function that uses the query method of McRestService. This method can be used to pass in SOQL queries. Our example will pass in a LIKE query against our Contact object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function searchRemoteContacts(){ // vm.contactSearchStr is the binded value from our template's input box. var soql = "SELECT name, id FROM Contact WHERE name LIKE '%" + vm.contactSearchStr + "%'"; McRestService.query(soql).then(function(result){ // Positive result, so update our view model's vm.contacts value. vm.contacts = result.records; }).catch(function(e){ // Something went wrong, so log to the Mobile_Log__mc MobileCaddy table. logger.error("searchRemoteContacts error",e); }); } |
These are examples are cut-down to highlight the interaction to the McRestService, and in reality we’d likely want to have further UX handling for failure scenarios, and the case where no matching contacts are found.
A full working implementation of this controller is as follows;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
/** * Rest Controller * * @description description */ (function() { 'use strict'; angular .module('starter.controllers') .controller('RestCtrl', RestCtrl); RestCtrl.$inject = ['$ionicLoading', '$ionicPopup', 'McRestService', 'logger']; function RestCtrl($ionicLoading, $ionicPopup, McRestService, logger) { var logModule = 'app.RestCtrl'; var vm = this; vm.getLatestChatter = getLatestChatter; vm.searchRemoteContacts = searchRemoteContacts; /** * Request the latest chatter post. Uses the McRestService, which itself takes * care of running correctly on device or on CodeFlow. McRestService also * takes care of aligning auth sessions. */ function getLatestChatter(){ $ionicLoading.show({ template: 'Getting chatter post', animation: 'fade-in', showBackdrop: true, duration: 15000 }); var obj = { method: 'GET', contentType: 'application/json', path: '/services/data/v36.0/chatter/feeds/news/me/feed-elements' }; McRestService.request(obj).then(function(result){ console.log("getLatestChatter result", result); $ionicLoading.hide(); var alertPopup = $ionicPopup.alert({ title: 'Last Chatter Post!', template: result.elements[0].actor.displayName + ': ' + result.elements[0].body.text }); alertPopup.then(function(res) { // Do Nothing }); }).catch(function(e){ logger.error("getLatestChatter error",e); $ionicLoading.hide(); var alertPopup = $ionicPopup.alert({ title: 'Sorry', template: 'Could not get the feed at this time.' }); alertPopup.then(function(res) { // Do Nothing }); }); } /** * Use the McRestService to request an online "LIKE" search for contacts, based * upon input from the UI */ function searchRemoteContacts(){ $ionicLoading.show({ template: 'Searching Salesforce', animation: 'fade-in', showBackdrop: true, duration: 15000 }); var soql = "SELECT name, id FROM Contact WHERE name LIKE '%" + vm.contactSearchStr + "%'"; McRestService.query(soql).then(function(result){ console.log("searchRemoteContacts result", result); $ionicLoading.hide(); if ( result.records.length == 0 ) { var alertPopup = $ionicPopup.alert({ title: 'No contacts found!', template: 'No match for "' + vm.contactSearchStr + '"' }); alertPopup.then(function(res) { // Do Nothing }); } else { vm.contacts = result.records; } }).catch(function(e){ logger.error("searchRemoteContacts error",e); $ionicLoading.hide(); var alertPopup = $ionicPopup.alert({ title: 'Sorry', template: 'Could not search the contacts at this time.' }); alertPopup.then(function(res) { // Do Nothing }); }); } } })(); |
3) Configure the Platform
Although the REST calls will now work fine within CodeFlow, they will fail when running on the devices without some platform configuration.
Obtain the Correct Whitelist Pattern
- With your application running on device, use the Mobile Table Inspector (in the Settings –> Admin Functions area) to view the appSoup.
- Search for the startPageURL, and make a note of this value
Add the Whitelisted CORS Origin
- Go to Settings –> Security Controls –> Manage whitelisted CORS origins
- Add a new whitelist pattern using the value noted above for the Origin URL Pattern.
Limitations
At time of writing the following call types are supported via the McRestService contained in the shell applications. Note, these are not updated during the lifetime of your apps, and to gain any extra support you may need to update the version in your project with the one at https://github.com/MobileCaddy/shell-ionic/blob/master/www/js/services/mcrest.service.js
Troubleshooting
If your application appears to be failing to call the platform successfully when deployed to a device, when it worked OK in CodeFlow, then run the app up with remote debugging enabled. Doing this will enable access to the Network tab in the Developer Tools. Doing this should enable you to see the raw HTTP call/response.
Warnings/Notes
REST calls will fail on device (not in CodeFlow) if the CORS setting has not been correctly setup. This can be diagnosed and confirmed via remote debugging, and the presence of the following type of error;
XMLHttpRequest cannot load https://eu11.salesforce.com/services/data/v36.0/chatter/feeds/news/me/feed-elements. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://c.eu11.visual.force.com’ is therefore not allowed access.
References
- Salesforce Developer’s API Explorer – Salesforce.com
- Remote Debugging – Google