There are only a handful of things you need to know about Salesforce in the beginning. First, you should get an IDE: https://developer.salesforce.com/page/Force.com_IDE. Once you install the IDE and synchronize to your Salesforce organization (or a sandbox copy), you will see that it consists of classes, components, pages, staticresources, and triggers. To build an SPA, you do not need to create any controllers. All you need is a page and staticresources. Static resources contain your Javascript and CSS files and are typically simply ZIP files.
In order to create an SPA in Salesforce that performs only CRUD and search operations, you do not need to create any controller yourself. Just package your static resources into a ZIP file and name it yourDeployable.package
and upload it together with a page like this one to your org:
<apex:page showHeader="true" sidebar="false" standardStylesheets="false">
<html>
<head>
<link rel="stylesheet" href="{!URLFOR($Resource.yourDeployable, 'css/style.css')}"></link>
<apex:stylesheet value="{!URLFOR($Resource.SLDS092, 'assets/styles/salesforce-lightning-design-system-vf.css')}" />
<script src="{!URLFOR($Resource.yourDeployable, 'js/script.js')}"></script>
...
<script src="{!URLFOR($Resource.yourDeployable, 'js/sdk/forcetk.mobilesdk.js')}"></script>
<script src="{!URLFOR($Resource.yourDeployable, 'js/sdk/smartsync.js')}"></script>
<script src="{!URLFOR($Resource.yourDeployable, 'js/sdk/angular-force.js')}"></script>
<script src="{!URLFOR($Resource.yourDeployable, 'js/sdk/forcetk.ui.js')}"></script>
</head>
<body ng-app="myApp">
<div class="container">
<div ng-include="'{!URLFOR($Resource.yourDeployable, 'partials/header.html')}'"/>
<div class="main" ng-view="ng-view"></div>
<div ng-include="'{!URLFOR($Resource.yourDeployable, 'partials/footer.html')}'"/>
</div>
<script type="text/javascript">
// used by angular-force to log in.
var configFromEnv = {};
configFromEnv.sessionId = "{!$API.session_Id}";
// used for templateUrls etc.
var SalesforceURLPrefix = "{!URLFOR($Resource.yourDeployable, '')}/";
</script>
<script src="/soap/ajax/35.0/connection.js" type="text/javascript"></script>
<script type="text/javascript">
// use this to get your user id:
sforce.connection.sessionId = "{!$Api.Session_ID}";
console.log(sforce.connection.getUserInfo().userId);
</script>
<script src="/soap/ajax/35.0/apex.js" type="text/javascript"></script>
</body>
</html>
</apex:page>
You will see that this file includes scripts using URLs created though the syntax {!URLFOR($Resource.yourDeployable, 'js/script.js')}
. This references the files inside your zip file.
Note that we have included four special libraries, forcetk.mobilesdk.js
, smartsync.js
, angular-force.js
, and forcetk.ui.js
. You can get those from some of the excellent samples found here: https://github.com/developerforce/MobilePack-AngularJS/. You can use them to access the standard back-end controllers from your Angular front-end. Walk through the samples and you will quickly see how this works. See https://github.com/developerforce/MobilePack-AngularJS/blob/master/samples/AngularVFBootstrap/src/staticresources/Archive/js/init.js for how to initialize your app (using the session ID we assigned to configFromEnv.sessionId
in the apex page) and https://github.com/developerforce/MobilePack-AngularJS/blob/master/samples/AngularVFBootstrap/src/staticresources/Archive/js/app.js for how to query objects. Do not forget to AngularForce.login()
. When you use the AngularForceObjectFactory
in its raw form, you also have to remember to $scope.$apply()
.
Whereas this approach works in principle, it comes with a couple of problems. First, your views and templates (those that are typically referred to in Angular by templateUrl
, are resolved relatively to the main page. However, if you package them into yourDeployable.package
, URLFOR
will assign an obscure numeric URL which is next to impossible to hardcode into all your template URLs. Because of that, we have added this line of code to the page:
var SalesforceURLPrefix = "{!URLFOR($Resource.yourDeployable, '')}/";
Now, convert that into an Angular constant and prefix all your templateURLs with it.
Ok, now you can get into the real development. But as soon as you do so, you will soon recognize that you have to do a lot of work to get your little changes in JS files and views up into the Cloud and see your changes. This can be automated, e.g. with grunt, but each deploy still can take up to 20 seconds. This can become quite annoying quickly. Note that the obvious solution, to serve all static resources including the main HTML page locally, does not work: We need to inject the session ID, which is only possible by using the APEX directives as in the above page.
This solution has worked well for us: Just create a copy of the above apex page, and replace all the {!URLFOR($Resource.yourDeployable, '...')}
by references to localhost, and deploy it to Salesforce as well. Now serve all static resources (except the main HTML file) locally, and they will be linked and included from your main page on Salesforce. You will now run into two more easily fixable problems:
- CORS and mixed content (HTTP/HTTPS) policies will prevent stuff from loading and your browser will explode with security warnings. Enable loading of unsafe scripts and get a plugin to disable the CORS policy for development purposes.
- We have used absolute URLs in our
templatesUrl
s, so those are not loaded from localhost. Fair enough, all we have to do is replace the SalesforceURLPrefix by localhost as well.
With this setup, we were able to develop a Salesforce SPA quite efficiently, with a minimal number of redeploys and zero server-side controllers.