Newer Version Available
JavaScript Remoting and Static HTML
Visualforce pages designed this way eschew many of the automatic, simplified features of standard Visualforce, in favor of taking more control over the request-response cycle, and performing page updates using JavaScript instead of page reloads. This can substantially improve the performance of the page, especially over the lower bandwidth, higher latency wireless network connections that make mobile devices so, well, mobile. The downside is that there is more code to write, and you need expertise in JavaScript, JavaScript remoting, HTML5, your mobile toolkit, and CSS, in addition to Apex and Visualforce. The upside of the downside is that you’re working with the latest, most advanced tools for mobile development, and the pages you can build are the best, most complete way to “snap in” custom functionality that fully integrates with the app.
You can build desktop Visualforce pages using this approach as well as pages for the Salesforce app. It’s even possible to share such pages between the two environments by customizing the styling, though it’s a challenge to closely match the full Salesforce site look and feel. Most importantly, the pages you design can be fully responsive, adapting and working across a range of devices and form factors.
Applying this Approach to Your Visualforce Pages
To use this approach for creating pages for the Salesforce app, follow this general process:
- Install your preferred Salesforce Mobile Pack (available on Salesforce) into your organization as a static resource.
- Set your page’s docType to html-5.0.
Strongly consider disabling the standard stylesheets and header. For example:
1<apex:page standardController="Warehouse__c" 2 extensions="WarehouseEditor" 3 showHeader="false" standardStylesheets="false" 4 docType="html-5.0"> - Add scripts and styles from your chosen mobile toolkit to the page using
Visualforce resource tags. For
example:
1<apex:includeScript 2 value="{!URLFOR( 3 $Resource.Mobile_Design_Templates, 4 'Mobile-Design-Templates-master/common/js/ 5 jQuery2.0.2.min.js' 6 )}"/> - Use HTML5 and your mobile toolkit’s tags and attributes to create a page skeleton.
- Add JavaScript functions to the page as handlers to respond to user interaction. Use JavaScript remoting to call Apex @RemoteAction methods that retrieve records, perform DML, and so on.
- Add additional JavaScript functions to handle user actions and page updates. Perform page updates by constructing HTML elements in JavaScript, and then adding or appending them to the page skeleton.
Example of a JavaScript Remoting and Static HTML Page
1<apex:page standardController="Warehouse__c" extensions="WarehouseEditor"
2 showHeader="false" standardStylesheets="false"
3 docType="html-5.0" applyHtmlTag="false" applyBodyTag="false">
4
5 <!-- Include Mobile Toolkit styles and JavaScript -->
6 <apex:stylesheet
7 value="{!URLFOR($Resource.Mobile_Design_Templates,
8 'Mobile-Design-Templates-master/common/css/app.min.css')}"/>
9 <apex:includeScript
10 value="{!URLFOR($Resource.Mobile_Design_Templates,
11 'Mobile-Design-Templates-master/common/js/jQuery2.0.2.min.js')}"/>
12 <apex:includeScript
13 value="{!URLFOR($Resource.Mobile_Design_Templates,
14 'Mobile-Design-Templates-master/common/js/jquery.touchwipe.min.js')}"/>
15 <apex:includeScript
16 value="{!URLFOR($Resource.Mobile_Design_Templates,
17 'Mobile-Design-Templates-master/common/js/main.min.js')}"/>
18
19<head>
20<style>
21 html, body, p { font-family: sans-serif; }
22 input { display: block; }
23</style>
24
25<script>
26 $(document).ready(function(){
27 // Load the record
28 loadWarehouse();
29 });
30
31 // Utility; parse out parameter by name from URL query string
32 $.urlParam = function(name){
33 var results = new RegExp('[\\?&]' + name + '=([^&#]*)')
34 .exec(window.location.href);
35 return results[1] || 0;
36 }
37
38 function loadWarehouse() {
39 // Get the record Id from the GET query string
40 warehouseId = $.urlParam('id');
41
42 // Call the remote action to retrieve the record data
43 Visualforce.remoting.Manager.invokeAction(
44 '{!$RemoteAction.WarehouseEditor.getWarehouse}',
45 warehouseId,
46 function(result, event){;
47 if(event.status){
48 console.log(warehouseId);
49 $('#warehouse_name').text(result.Name);
50 $('#warehouse_address').val(
51 result.Street_Address__c);
52 $('#warehouse_city').val(result.City__c);
53 $('#warehouse_phone').val(result.Phone__c);
54 } else if (event.type === 'exception'){
55 console.log(result);
56 } else {
57 // unexpected problem...
58 }
59 });
60 }
61
62 function updateWarehouse() {
63 // Get the record Id from the GET query string
64 warehouseId = $.urlParam('id');
65
66 // Call the remote action to save the record data
67 Visualforce.remoting.Manager.invokeAction(
68 '{!$RemoteAction.WarehouseEditor.setWarehouse}',
69 warehouseId, $('#warehouse_address').val(),
70 $('#warehouse_city').val(),
71 $('#warehouse_phone').val(),
72 function(result, event){;
73 if(event.status){
74 console.log(warehouseId);
75 $('#action_status').text('Record updated.');
76 } else if (event.type === 'exception'){
77 console.log(result);
78 $('#action_status').text(
79 'Problem saving record.');
80 } else {
81 // unexpected problem...
82 }
83 });
84 }
85
86</script>
87</head>
88
89<body>
90
91<div id="detailPage">
92 <div class="list-view-header" id="warehouse_name"></div>
93 <div id="action_status"></div>
94
95 <section>
96 <div class="content">
97 <h3>Warehouse Details</h3>
98 <div class="form-control-group">
99 <div class="form-control form-control-text">
100 <label for="warehouse_address">
101 Street Address</label>
102 <input type="text" id="warehouse_address" />
103 </div>
104 <div class="form-control form-control-text">
105 <label for="warehouse_city">City</label>
106 <input type="text" id="warehouse_city" />
107 </div>
108 <div class="form-control form-control-text">
109 <label for="warehouse_phone">Phone</label>
110 <input type="text" id="warehouse_phone" />
111 </div>
112 </div>
113 </div>
114 </section>
115
116 <section class="data-capture-buttons one-buttons">
117 <div class="content">
118 <section class="data-capture-buttons one-buttons">
119 <a href="#" id="updateWarehouse"
120 onClick="updateWarehouse();">save</a>
121 </section>
122 </div>
123 </section>
124</div> <!-- end detail page -->
125
126</body>
127
128</apex:page>Although this page can be used in the full Salesforce site, it’s designed as a Salesforce app page and looks very different than a normal Visualforce page.
Example of a JavaScript Remoting and Static HTML Controller
1global with sharing class WarehouseEditor {
2
3 // Stub controller
4 // We're only using RemoteActions, so this never runs
5 public WarehouseEditor(ApexPages.StandardController ctl){ }
6
7 @RemoteAction
8 global static Warehouse__c getWarehouse(String warehouseId) {
9
10 // Clean up the Id parameter, in case there are spaces
11 warehouseId = warehouseId.trim();
12
13 // Simple SOQL query to get the warehouse data we need
14 Warehouse__c wh = [
15 SELECT Id, Name, Street_Address__c, City__c, Phone__c
16 FROM Warehouse__c
17 WHERE Id = :warehouseId];
18
19 return(wh);
20 }
21
22 @RemoteAction
23 global static Boolean setWarehouse(
24 String whId, String street, String city, String phone) {
25
26 // Get the warehouse record for the Id
27 Warehouse__c wh = WarehouseEditor.getWarehouse(whId);
28
29 // Update fields
30 // Note that we're not validating / sanitizing, for simplicity
31 wh.Street_Address__c = street.trim();
32 wh.City__c = city.trim();
33 wh.Phone__c = phone.trim();
34
35 // Save the updated record
36 // This should be wrapped in an exception handler
37 update wh;
38
39 return true;
40 }
41}