Newer Version Available

This content describes an older version of this product. View Latest

JavaScript Remoting and Static HTML

Combine JavaScript remoting and static HTML to offer the best user experience, with the best performance and user interface match to the Salesforce mobile app. This architecture avoids most Visualforce tags in favor of rendering page elements in JavaScript. This option requires the most developer expertise, and can take a little longer to set up than standard Visualforce or mixed Visualforce and HTML. Use the Salesforce Mobile Packs for a fast start and to work with the very latest in mobile Web application technology.

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 mobile 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 mobile app, follow this general process:

  1. Install your preferred Salesforce Mobile Pack (available on Salesforce) into your organization as a static resource.
  2. 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">
  3. 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    )}"/>
  4. Use HTML5 and your mobile toolkit’s tags and attributes to create a page skeleton.
  5. 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.
  6. 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

The following code sample shows a remoting + HTML Visualforce page that allows a user to edit a warehouse record. The edit feature is provided by a controller extension with @RemoteAction methods that respond to JavaScript remoting requests.
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>
The static HTML provides the shell of the page, including empty form fields. JavaScript functions load the record, fill in the form fields, and send updated form data back to Salesforce.

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

Unlike the other two approaches to creating mobile pages, the remoting + HTML approach doesn’t use standard controller functionality to retrieve data from and save data to Salesforce. Instead, you create a controller extension, or custom controller, to add any @RemoteAction methods your page requires. Here’s a simplified controller extension that supports the above page.
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}