Tell Me More: About the Code

There are two code objects behind our Visaulforce custom action: the Apex class QuickOrderController and the Visualforce page QuickOrderPage.

The Apex class is the controller for the Visualforce page, and uses the @RemoteAction annotation on the methods. When you use this annotation, the Visualforce page wraps the logic in a JavaScript-friendly way. This is known as Visualforce remoting.

Visualforce remoting allows for quick and tight integration between Apex and JavaScript. This communication model works asynchronously as opposed to the synchronous model in the traditional Visualforce/Apex MVC paradigm. So after passing parameters into your controller, you can get the result from a response handler function, and write any additional client-side logic before doing any DOM manipulation or building your page with mobile templates or frameworks.

Visualforce remoting is ideal for mobile developers on the Salesforce Platform because it simplifies the direct server-side access to Salesforce objects and allows you to use Apex tools such as SOQL, Apex methods, and so on for rapid platform development. And, you don’t have to deal with view state, which makes pages perform better.

Apex Class QuickOrderController

This class uses Visualforce remoting and contains the logic called by the Visualforce page to find warehouses and create orders and line items.

1global class QuickOrderController{
2    public static List<Merchandise__c> merchandise;
3    public static Line_Item__c quickOrder;
4    
5    
6    public QuickOrderController(ApexPages.
7        StandardController controller){  
8    }
9    
10    @RemoteAction
11    global static List<Merchandise__c> findWarehouses(String accId,
12        String merchName, String warehouseDist){
13         merchandise = new List<Merchandise__c>();
14         String queryString = '';
15         String queryName = '%' + merchName + '%';
16         
17         Account acc = [Select Location__Longitude__s, 
18         Location__Latitude__s, Name, Id 
19         from Account where Id =: accId];
20         
21         //Finds warehouses nearby if you have location 
22         //specified on the Account
23         if(acc.Location__Latitude__s != null && 
24             acc.Location__Longitude__s != null){
25             queryString = 'SELECT Id, (SELECT Id, Name, Quantity__c,
26                 Warehouse__r.Name, Warehouse__r.Id, 
27                 Warehouse__r.Street_Address__c, 
28                 Warehouse__r.City__c '+
29                 'FROM Merchandise__r WHERE Name 
30                 like :queryName) '
31                 +'FROM Warehouse__c WHERE '
32                 +'DISTANCE(Location__c, GEOLOCATION('
33                 +acc.Location__Latitude__s+','
34                 +acc.Location__Longitude__s+'), \'mi\')';
35             if(warehouseDist != null){
36                 queryString += ' <'+ warehouseDist;
37             }
38             
39         }
40         //If no location defined on the Account, this will run 
41         //query against the merchandise name only
42         else {
43             queryString = 'SELECT Id, Name, 
44                 Location__Longitude__s, 
45                 Location__Latitude__s, '
46                 +'(SELECT Id, Name, Warehouse__r.Name, 
47                 Quantity__c 
48                 FROM Merchandise__r WHERE Name 
49                 like :queryName) '
50                 +'FROM Warehouse__c limit 25';
51             
52         }
53         
54         //This creates a list of merchandise 
55         //to display in the search results
56         Warehouse__c[] warehouses = Database.Query(queryString);
57         for(Warehouse__c warehouse : warehouses){
58             Merchandise__c[] merch = 
59                 warehouse.getSObjects('Merchandise__r');
60                 if (merch != null) {
61                    for (Merchandise__c m : merch){ 
62                        merchandise.add(m);
63                    }
64                 }    
65         }
66         return merchandise;
67         
68    }
69    
70    //This remote action creates the invoice for the quick order
71    @RemoteAction
72    global static Line_Item__c createQuickOrder(
73        String accId, String merchandiseId){
74        Invoice__c newInvoice = new Invoice__c();
75        newInvoice.Account__c = accId;
76        insert newInvoice;
77        
78        quickOrder = new Line_Item__c();
79        Merchandise__c m = [Select Id, Name from Merchandise__c 
80            where Id=: merchandiseId limit 1];
81        quickOrder.Merchandise__c = m.Id;
82        quickOrder.Invoice__c = newInvoice.Id;
83        
84        return quickOrder;
85    }
86    
87    //This remote action creates the line item related to the 
88    //invoice for the quick order
89    @RemoteAction
90    global static Boolean insertQuickOrder(String o, String q){
91        try {
92            Integer quantity = integer.valueof(q);
93        
94        Line_Item__c order = new Line_Item__c();
95        /* The order variable being passed in as a param is being 
96        passed in the form of a JSON object. You need to use 
97        the JSON deserialize method in Apex to convert it 
98        into a SObject */
99        order = (Line_Item__c)JSON.deserialize(
100            o, Line_Item__c.class);
101        
102        order.Quantity__c = quantity;
103        insert order;
104
105    //Need to requery for the name for the post to chatter 
106    //since it wasn't explicitly specified
107    Line_Item__c li = [Select Name, Merchandise__r.Name, Id, 
108        Quantity__c, Invoice__c from Line_Item__c 
109        where Id =: order.Id];
110            
111        FeedItem post = new FeedItem();
112        post.ParentId = aId;
113        post.Body = UserInfo.getName() + ' just created a quick order';
114        post.type = 'LinkPost'; 
115        post.LinkUrl = '/' + li.Invoice__c;
116        post.Title = li.Merchandise__r.Name + ': ' + li.quantity__c;
117        insert post; 
118    } catch(System.Exception ex) {
119        system.debug(ex.getMessage());
120    }
121        return true;
122    }
123    
124    //This remote action handles deleting the invoice if 
125    //the user doesn't want to insert the line item
126    @RemoteAction
127    global static Boolean goBack(String invoiceId){
128        // Delete created invoice and return to original 
129        //search screen
130        Invoice__c cancelledInvoice = [select Id from Invoice__c 
131            where Id=: invoiceId];
132        delete cancelledInvoice;
133        
134        return true;
135    }
136    
137}

Use static queries and binding variables to prevent a SOQL injection attack. For example, use :queryName instead of \'%'+merchName+'%\'. For more information, see “Security Guidelines for Apex and Visualforce Development“ in the Salesforce Help.

Note

The Apex controller also has an insertQuickOrder method that creates a feed item about the new order in the account feed as shown in this code snippet. The feed item is a link post that links to the invoice.
1FeedItem post = new FeedItem();
2    post.ParentId = aId;
3    post.Body = UserInfo.getName() + ' just created a quick order';
4    post.type = 'LinkPost'; 
5    post.LinkUrl = '/' + li.Invoice__c;
6    post.Title = li.Merchandise__r.Name + ': ' + li.quantity__c;
7    insert post;

Visualforce Page QuickOrderPage

This page calls the controller with the user input and then displays the merchandise and warehouse information to the user. If the user wants to create an order, this page also calls the controller to create the order associated with the customer account and add a line item. At the beginning of the page, the code also does some styling of the page using the Salesforce mobile design templates.

1<apex:page standardController="Account" 
2    extensions="QuickOrderController" docType="html-5.0"
3    standardStylesheets="false"     showheader="false" sidebar="false">
4     
5    <!--Include stylesheets for the mobile look and feel -->
6    <apex:stylesheet value="{!URLFOR(
7        $Resource.Mobile_Design_Templates, 
8        'Mobile-Design-Templates-master/
9            common/css/app.min.css')}"/>
10    <apex:includeScript value="{!URLFOR(
11        $Resource.Mobile_Design_Templates, 
12        'Mobile-Design-Templates-master/common/js/
13            jQuery2.0.2.min.js')}"/>
14    <apex:includeScript value="{!URLFOR(
15        $Resource.Mobile_Design_Templates, 
16        'Mobile-Design-Templates-master/common/js/
17            jquery.touchwipe.min.js')}"/>
18    <apex:includeScript value="{!URLFOR(
19        $Resource.Mobile_Design_Templates, 
20        'Mobile-Design-Templates-master/common/
21            js/main.min.js')}"/>
22      
23      <style>
24          /* Default S1 color styles */
25          .list-view-header, .data-capture-buttons a {
26                background: -webkit-linear-gradient(
27                    #2a93d5,#107abb);
28                background: linear-gradient(#2a93d5,#107abb);
29                box-shadow: 0 1px 3px rgba(0,0,0,.2),
30                    inset 0 1px 0 rgba(255,255,255,.21);
31                color: white;
32                font-weight: bold;
33          }
34          
35          #resultPage, #searchPage {
36              padding-bottom: 50px;
37          }
38      </style>

The QuickOrderPage also calls the Canvas SDK to enable the publisher Submit button and close the publisher window.

First, it includes a reference to the SDK:
1<!-- This needs to be included so the publisher can be used 
2    to submit the action -->
3<script type='text/javascript' src='/canvas/sdk/js/publisher.js'></script>
Then it calls the setValidForSubmit method to enable the publisher Submit button:
1//This method will activate the publish button 
2//so the form can be submitted 
3Sfdc.canvas.publisher.publish({
4    name: "publisher.setValidForSubmit", 
5    payload:"true"});
After the setValidForSubmit is called and the user clicks Submit, this subscribe method fires. This method invokes the final JavaScript function which uses JavaScript remoting to insert the line item (thus completing the quick order) and then post a feed item to the account:
1<script type='text/javascript'>  
2    Sfdc.canvas.publisher.subscribe({name: "publisher.post", 
3        onData:function(e) {
4    //This subscribe fires when the user hits 
5    //Submit in the publisher
6    insertQuickOrder();
7    }});
8</script>
Finally, after the callback from the remoting method returns successfully, this method closes the publisher window:
1// Success - close the publisher and refresh the feed
2Sfdc.canvas.publisher.publish({name: "publisher.close", 
3    payload:{ refresh:"true"}});