ForcePad is a free, unofficial, unsupported, open-source native iPad app from Salesforce Labs. It's the easiest way to browse your apps, tabs, and records in any Salesforce environment. Create, edit, clone, and delete standard and custom records. Supports every page layout, every field, every related list, every Group Edition thru Unlimited Edition org. With ForcePad, you're an unstoppable force for the cloud!
by Jonathan Hersh (Email, GitHub, Twitter) with special thanks to Wiebke and Brian in Salesforce UX, Ciara for graphic design and UX, plus Darrell, Eugene, Clarence, Reid, Mike, Simon, Todd, and Kevin.
Author's Note: Account Viewer (which became Salesforce Viewer, which became Salesforce for iPad, which became ForcePad) was my first iOS app, developed over more than a year, so files can show significant variation in quality, conciseness, structure, taste, texture, color, and aroma. The app has also been known to take you out to a nice sushi dinner and then not call you the next day. - JH
In this document:
ForcePad was originally released as 'Account Viewer' in August 2011. Account Viewer is available on GitHub. In November 2011, Account Viewer was updated and re-released as 'Salesforce Viewer'. In February 2012, I updated it again to Salesforce for iPad. In August 2012 it became ForcePad.
v2.4.1, 2.4.2, 2.4.3 - Released 10/6/2012
v2.4 - Released 9/5/2012
v2.3 - Released 4/5/2012
v2.2.3 - Released 3/13/2012
v2.2.2 - Released 3/6/2012
v2.2.1 - Released 2/20/2012
v2.2 - Released 2/15/2012
Copyright (c) 2012, salesforce.com, inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
git clone https://github.com/ForceDotComLabs/Salesforce-for-iPad.git
https://login.salesforce.com/services/oauth2/success
for production. Copy your OAuth Client ID into the OAuthClientID
variable in OAuthViewController.h
.RootViewController.h
under PartnerTokenId
.RecordNewsViewController.h
under NEWS_API_KEY
.ForcePad authenticates to Salesforce with OAuth. The app encrypts your OAuth refresh token in the device's keychain. The app never has access to your username or password.
After authentication, ForcePad's API calls are about 30% to the SOAP (Web Services) API and 70% to the REST APIs. REST is the future for mobile and I'm trying to move as much as possible away from SOAP.
SOAP is used for:
REST is used for:
Remote records are never saved locally. Page layouts, sObject describes, and other metadata are cached in-memory and cleared at every logout and refresh.
Some record data (names and addresses) are sent to third-party APIs to provide app functionality, but always over HTTPS. More details below in the External API section.
App preferences (first-run settings, preferences) are stored in NSUserDefaults
.
When ForcePad first loads, it evaluates whether it has a stored OAuth refresh token from a previous authentication. If so, it attempts to refresh its session with that refresh token. See appFinishedLaunching
in RootViewController.m
. If there is no stored refresh token, or if the refresh fails for any reason, the app destroys all session data and shows the OAuth login screen.
The left-side navigation view (in landscape mode, also visible in portrait mode in a popover when you tap the browse button), a.k.a. the Master view, is powered by the RootViewController
and SubNavViewController
classes. RootViewController
handles most of Login/Logout, while SubNavViewController
powers record browsing, searching, object lists, and displaying favorite objects.
The right-side view is powered by the DetailViewController
. It serves as a container for the rest of the app's content and is responsible for creating, managing, and destroying Flying Windows and the Flying Window stack. It manages dragging operations on Flying Windows.
The interactive, draggable panes that fill the DetailViewController
are termed Flying Windows and each is a subclass of the FlyingWindowController
class. The FlyingWindowController
base class defines some basics about their look and enables them to be dragged about the screen.
Behold ye the Flying Windows:
RecordOverviewController
is responsible for displaying a record overview, page layout, and rendering the record's location on a map.
ListOfRelatedListsViewController
lists all of the related lists on a record. The list ordering as well as which lists appear is determined by your record page layout. This view controller also chains subqueries together to load the number of related records on each list.
RecordNewsViewController
is responsible for querying Google News (over HTTPS) and displaying news stories about a single Account. Only available for Account records.
WebViewController
is a simple UIWebView
wrapper with a few added pieces of functionality, like being able to email the link to the open page, copy its URL, open in Safari, and tweet a link.
RelatedListGridView
displays a related list for a given record. The columns displayed on the related list grid are determined by the record's page layout. Related record grids have tap-to-sort columns and tapping an individual record's name will open its full detail.
RecordEditor
handles creating, editing, cloning, and deleting records, as well as filling values from the local Address Book when editing Contacts and Leads.
RecentRecordsController
is the default 'home' flying window, displaying a list of recent records accessed in ForcePad. It allows sorting by record type and removing recent records from the list.
AboutAppViewController
, SFVEULAAcceptController
, and SFVFirstRunController
are part of the first-run experience and also power the help pages accessed via the settings window.
ChatterPostController
is the main interface for posting an article or URL to chatter.
CloudyLoadingModal
probably doesn't do anything important. Look behind you!
OAuthCustomHostCreator
, OAuthLoginHostPicker
, and OAuthViewController
microwave popcorn. Actually, they microwave burritos. Nah, just kidding, they're the brothers who run that corner convenience store.
ObjectLookupController
is a lookup box launched when you tap the 'Post To' field in the ChatterPostController
. It also handles lookup fields on record editing layouts. It can search via SOQL and SOSL for (almost) any standard or custom object.
PicklistPicker
is a popover tableview for selecting from a picklist, multiselect, or combobox. It also handles picking from lists of record types, for use when editing a record.
FieldPopoverButton
is a generic UIButton
intended to display the value of an sObject field. All FieldPopoverButton
s can be tapped to copy the text value of that field, and depending on the field type, some may have additional actions. For example, a FieldPopoverButton
displaying an address will offer to open the address in Google Maps, phone/email fields will offer to call with Facetime or Skype, and lookups to User will display a full-featured user profile with a photo and other details from the User record.
FollowButton
is a generic UIBarButtonItem
intended to make it easy to create a follow/unfollow toggle between the running user and any other chatter-enabled object (User, Account, etc).
QCContactMenu
is a subclass of the super-nifty QuadCurveMenu component intended to make it easy to Email, Skype, Facetime, or open the website for any record. If a page layout has three fields of type Phone, for example, a QCContactMenu
, when tapped, will allow you to choose Skype, then place a Skype call to any of those three phone numbers.
The app has a caching layer to hold metadata in-memory, allowing the app to read metadata from cache instead of re-querying the server. This is mostly contained in SFVAppCache
except for page layouts, which are cached in SFVUtil
.
Network operations use the block methods in SFVAsync
(for SOAP) and the Mobile SDK's SFRestAPI
blocks (for REST). SFVUtil
contains the app's image loading block method and cache. I added additional REST API blocks under SFRestAPI+SFVAdditions
, mostly for the purpose of intercepting object describes and, if cached, immediately returning the cached value.
SFCrypto
encrypts and decrypts OAuth session tokens for the device's keychain.
SimpleKeychain
is a utility class for reading from and writing to the device's keychain.
ForcePad communicates with these APIs over HTTPS.
ForcePad uses Salesforce components:
And a number of third-party components:
Much is there to do on ForcePad. Some unsolved problems:
Some other things to do: