Sunday, January 27, 2013

Salesforce.com - Optimization of View State

Most of the time we did not consider the view state during development, but it is one of the most important aspect to keep in mind during VisualForce development. it will help you to increase the performance of Visualforce and improve the user experience. We should know how can we optimize the VF page and view state, but first something on view state...

What is view state?

You know that HTTP is a stateless protocol, it means request for any new page (called GET request) or clicking on any button with form's data ( called POST request) will be treated as  new independent requests.



What will the impact if there is no view state?


Assume that you are filling a registration form and there are 10-15 fields on the page, after filling all information you click on submit button and you will get a invalid date error message on your page and you will loose all form data,because HTML forms does not have capability to store input data, you need to fill it again. Now view state comes in action, In general, its encrypted data to store fields values of any form.


How do Visualforce maintain the view state?


In Visualforce, page state is persisted as a hidden form field that is automatically inserted into a form when the page gets generated. It can be called as page's view state and it captures the state of the page. The state of its associated controllers and extensions and the component tree on the page. 
The view state is posted back along with the other form data, which gives the server enough information to recreate the page state to which new changes can be applied. 

Visualforce pages that contain a form component also contain an encrypted, hidden form field that encapsulates the view state of the page. This view state is automatically created, and as its name suggests, it holds the state of the page - state that includes the components, field values and controller state.In short,

If there is a <apex:form> in your page, there must be a hidden form field for view state.

View state required for few components:
<apex:action*>
<apex:command*>
<apex:inlineEditSupport>
<apex:input*>
<apex:select*>


How can we optimize the view state?

  • You should reduce the number of components
  • You should use transient whenever possible
  • You should JavaScript Remoting (if possible)
  • Use of Streaming API

Reduce the number of components:


You should minimize the number of Forms on a page as well as number of components which require view state. You should use;
 <apex:outputPanel>
 <apex:outputLink>
 <apex:outputText>
 <apex:panelGrid>


whenever possible and instead of using multiple Forms on a page, you should you use <apex:actionRegion> to submit a portion of the form.This practice will ensure that only a single copy of the view state is associated with that page.
// Using two forms
<apex:page controller="YourController">
<apex:form>
   <apex:commandButton action="{!saveAccount}" value="Update Account"/>
    <!--Account attributes available for editing -->
</apex:form>
<apex:form>
    <apex:commandButton action="{!saveContacts}" value="Update Contacts"/>
     <!--Contact attributes available for editing -->
</apex:form>
</apex:page>
// Combining into single form and leveraging <apex:actionRegion>
<apex:page controller="YourController">
  <apex:form>
     <apex:commandButton action="{!saveAccount}" value="Update Account"/>
     <!--Account attributes available for editing -->
     <apex:actionRegion>
       <apex:commandButton action="{!saveContacts}" value="Update Contacts"/>
     <!--Contact attributes available for editing -->
    </apex:actionRegion>
  </apex:form>
</apex:page>


Use Transient keyword:


To reduce the view state size, you should you the Transient before LIST,SET,MAP and Properties if values are no longer required in duration of page request. An instance variable declared as transient is not saved and is not transmitted as part of the view state.

transient public List<Contact> lstContacts {set;get;}


JavaScript Remoting:


It is a stateless way to call Apex controller methods from JavaScript. If you call a method through JS It will never save its state and increase the page performance.

For ex: Account Search with JavaScript Remoting, it will increase the performance 35% compare to commandbutton or commandlink etc. 
global with sharing class AccountRemoter { public String accountName { get; set; } public static Account account { get; set; } public AccountRemoter() { } // empty constructor @RemoteAction global static Account getAccount(String accountName) { account = [SELECT Id, Name, Phone,
Type, NumberOfEmployees FROM Account WHERE Name = :accountName]; return account; } }
<script type="text/javascript">
   function getRemoteAccount() {
      var accountName = document.getElementById('acctSearch').value;
       Visualforce.remoting.Manager.invokeAction(
             '{!$RemoteAction.AccountRemoter.getAccount}',
            accountName, 
            function(result, event){
             if (event.status) {
               document.getElementById('remoteAcctId').innerHTML = result.Id
               document.getElementById(
             "{!$Component.block.blockSection.secondItem.acctNumEmployees}"
            ).innerHTML = result.NumberOfEmployees;
      } else if (event.type === 'exception') {
         document.getElementById("responseErrors").innerHTML = 
         event.message + "<br/>\n<pre>" + event.where + "</pre>";
       } else {
       document.getElementById("responseErrors").innerHTML = event.message;
       }
        {escape: true}, 
       }
       );
      }
</script>
  <input id="acctSearch" type="text"/>
   <button onclick="getRemoteAccount()">Get Account</button>
   <div id="responseErrors"></div>
  <apex:pageBlock id="block">
       <apex:pageBlockSection id="blockSection" columns="2">
           <apex:pageBlockSectionItem id="firstItem">
               <span id="remoteAcctId"/>
           </apex:pageBlockSectionItem>
           <apex:pageBlockSectionItem id="secondItem">
               <apex:outputText id="acctNumEmployees"/>
           </apex:pageBlockSectionItem>
       </apex:pageBlockSection>
   </apex:pageBlock> 
</apex:page> 


Refine Your SOQL to Retrieve Only the Data Needed by the Page


Only retrieve (and store) the fields you need and also filter the data to only retrieve data needed by the page.

Streaming API:


A highly performed  way to get information from Salesforce.com instance without polling.
If your application is currently polling the SOAP or REST API to detect changes, you should consider refactoring it to use the Streaming API; you will see better performance with lower resource consumption. For more information on Streaming API you can refer the salesforce.com documentation; http://wiki.developerforce.com/page/Getting_Started_with_the_Force.com_Streaming_API

Reference document : Salesforce.com help and training, advance visual force etc.

Salesforce.com - Custom Currency Conversion Code

I would like to explain this issue through a scenario, suppose we need to do integration between SAP and Salesforce.com. Salesforce.com partner portal will used to create opportunity for partners and SAP will be used to maintain the placed orders. 

We need to update the amount of opportunity products which will be rolled up at opportunity level to display actual amount. 

What will the actual amount? if Salesforce.com Opportunity's Currency in USD and SAP Opportunity's Currency in INR.

There is a limitation; Salesforce.com system does not allow you to change the currency of Opportunity if there is any product line item associated with it.

In this case we need to add custom apex code in salesforce.com web service which will identify  the CurrencyCode of Opportunities to covert the amount.

You can use this method for currency conversion:
   1 parameter - Old Currency code which will be used a base/incoming Currency and amount
   2 parameter - New Currency code in which you want to actual amount. 
   3 parameter - Amount to convert 


public Double convertCurrencyWithApexCode(String oCurrency, String nCurrency, Double amount){
        Set<String> isoCodes = new Set<String>();
        Map<String,Double> conversion_rates = new Map<String,Double>();
        
        isoCodes.add(oCurrency);
        isoCodes.add(nCurrency);
        
        for(CurrencyType curr: [SELECT IsoCode,ConversionRate 
                                             FROM CurrencyType 
                                             WHERE IsoCode in: isoCodes]){          
            conversion_rates.put(curr.IsoCode,curr.ConversionRate);        
        }
        //Convert incoming Opportuntiy Amount into USD...
        Double conversionRate = conversion_rates.get(oCurrency);
        amount = amount / v;
        
        //convert amount as per new currency. 
        if(nCurrency != 'USD'){
            conversionRate = conversion_rates.get(nCurrency);
            amount = amount * conversionRate;
        }
        return amount;
    }

Double amt = converCurrencyWithApexCode ('INR','USD',5403.00);

Using this code we can avoid the inconsistency between two systems, which is most important for any business.

Monday, January 21, 2013

Integration of Salesforce.com and Google Maps JavaScript API V3

Google maps JavaScript API can be used to create visual force pages with map to enhance the business experience. We can display partners, contacts and leads on map to identify their locations, find the routes and display the contacts within the specific range.

JavaScript API V3 is the latest version and all maps API applications should load the map API using an API key which can be used to monitor the API usages. We can purchase additional quota if application maps API exceeds the usage limits.

JavaScript API V3 limits: 25000 maps loads per day, a map load is counted when a map is initialized on web page. User interaction with a map after it has loaded does not have any impact on usages limits. 

Let’s start with an example to search Accounts by Name or Postal code and display all results on Google Maps. 

First Step: To get Google Map API key

1)  Go to APIs console https://code.google.com/apis/console and log in with your Google account.

2)  Click on create project button

3)  Left hand side, click on Services

4)  Select the Google Maps API v3 and Activate it by clicking on “Off” button it becomes green “On”.

5)  Left hand side, Click on API Access link, In Simple API Access section you will find the API key.

     6) By default, a key can be used on any site. You can restrict the use of key to domains to         prevent use on unauthorized sites. You can specify which domains are allowed to use your API key by clicking the Edit allowed referrers... 

Create a controller to search Accounts:

public class AccountSearchController {
    public String selectedAccName {set;get;}
    public String selectedZip {set;get;}
    public String qry {set;get;}
    public List<innerCls> lstAcc {set;get;}
    static Integer globalCounter;
   
    public PFinderController(){
        lstAcc = new List<innerCls>();
        globalCounter = 0;
    }
    public void searchAcc(){
        globalCounter = 1;
        lstAcc = new List<innerCls>();
        String qry = ' SELECT Id,Name,BillingStreet, BillingState,Longitude__c,Latitude__c, BillingPostalCode, ' ;
                  qry += ' BillingCountry, BillingCity From Account WHERE Longitude__c!=NULL AND Latitude__c != NULL ';
        if(selectedAccName != NULL && selectedAccName != ''){
                 qry += ' AND Name like \''+ selectedAccName + '%\'';
          }
          if(selectedZip != NULL && selectedZip != ''){
                qry += ' AND BillingPostalCode like \''+ selectedZip + '%\'';
          }
          qry += ' LIMIT 1000 ';
          for(Account acc:Database.query(qry))
                lstAcc.add(new innerCls(acc));
    }
    public class innerCls{
        public Integer count{set;get;}
        public Account acc {set;get;}
        public innerCls(Account acc){
                this.count = globalCounter++;
                this.acc = acc;
        }
    }
}

Visual force page to display search result on Google Map

<apex:page showHeader="true" sidebar="false" controller=" AccountSearchController">
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDjIY5HKM1dAlao6yUU8EzwPzdUh2ZNj0g&sensor=false"> </script>
<script>
var gAccount = [];
var map = null;
var counter = 0;
var delay = 500;

function load() {
    map = null;
    mapDiv = document.getElementById("map");
    var mapOptions = {
          center: new google.maps.LatLng(-34.397, 150.644),
          zoom: 4,
          mapTypeId: google.maps.MapTypeId.ROADMAP,
          mapMaker: true
    };
    map = new google.maps.Map(mapDiv,mapOptions);
    finishSearch();
}

function finishSearch() {
   if(counter < gAccount.length){
      showAddress(gAccount[counter++],finishSearch);
  }
}

function showAddress(record,finishSearch) {
var html='';
try{
 if(record.Longs != '' && record.Lats != ''){
        var points = new google.maps.Point(record.Lats,record.Longs);
        var latLongs = new google.maps.LatLng(record.Lats,record.Longs);
       
        html ='<hr>' + counter +'.&nbsp;'+ record.Name + '<br/>';
        html += record.City +'&nbsp;'+ record.State + '&nbsp;'+ record.Country + ',&nbsp;' + record.Zip + '<br/>';
        html +="<hr><b>URL:<b> <a href=https://ap1.salesforce.com/" + record.Id + " target=_blank >account details</a>";
           
        var infowindow = new google.maps.InfoWindow({
            content: html
        });
        var marker = new google.maps.Marker({
            position: latLongs,
            map: map,
            title: record.Name
        });
       
        google.maps.event.addListener(marker, 'click', function() {
          infowindow.open(map,marker);
        });
       
        //set the center for last flag
        if((gAccount.length - 1) == counter)
            map.setCenter(latLongs, 3);
       
  }
 }catch(error){}   
    setTimeout(finishSearch,delay);
}

function Group(id,name,lats,longs,displayNumber,city,state,country,zip) {
    this.Id = id;
    this.Name = name;
    this.Lats = lats;
    this.Longs = longs;
    this.DisplayNumber = displayNumber;
    this.City = city;
    this.State = state;
    this.Country = country;
    this.Zip = zip;
}

function setFocusOnMap(Lats,Longs){
    map.setCenter(new google.maps.LatLng(Lats,Longs), 3);
}
</script>
<apex:form >
<apex:actionStatus id="status" startText='Loading....' />
<apex:outputPanel id="outMap">
     <script type="text/javascript">
      function populateMap(){
         gAccount=[];
        <apex:repeat value="{!lstAcc}" var="act" id="TmpLoop" >
           gAccount.push(new Group("{!act.acc.id}","{!act.acc.Name}","{!act.acc.Latitude__c}","{!act.acc.Longitude__c}",true,'{!act.acc.BillingCity}','{!act.acc.BillingState}','{!act.acc.BillingCountry}','{!act.acc.BillingPostalCode}'));
        </apex:repeat>
         load();
       }
      </script>
  </apex:outputPanel>
<apex:pageBlock title="Partner Finder">
<apex:pageBlockSection columns="1">
<apex:pageBlockSectionItem >
 <apex:outputLabel value="Name:"></apex:outputLabel>
 <apex:outputPanel >
    <apex:inputText value="{!selectedAccName}"/>
 </apex:outputPanel>
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
 <apex:outputLabel value="Postal Code:"></apex:outputLabel>
 <apex:outputPanel >
<apex:inputText value="{!selectedZip}"/>
<apex:commandButton value="Search" action="{!searchAcc}" status="status" reRender="secMap,outMap" oncomplete="populateMap();" />
 </apex:outputPanel>
</apex:pageBlockSectionItem>
</apex:pageBlockSection>
<apex:pageBlockSection id="secMap" title="Search result" columns="1">
    <apex:outputPanel >
      <table width="100%">
        <tr>
            <td width="100%">
               <div id="map" style="height: 600px;"></div>
           </td>
        </tr>
      </table>
   </apex:outputPanel>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>


 How the given code will work? Lets go one by one..

Script to load the Google Map API:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&sensor=false"> </script>


The SRC contained in the script tag is the location of a JavaScript file that loads all of the symbols and definitions you need for using the Google Maps API. This script tag is required.
The key parameter contains your application's API key. The sensor parameter of the URL must be included and indicates whether this application uses a sensor (such as a GPS locator) to determine the user's location. You must set this value to either true or false explicitly.

<apex:pageBlockSection columns="1">
<apex:pageBlockSectionItem >
 <apex:outputLabel value="Name:"></apex:outputLabel>
 <apex:outputPanel >
    <apex:inputText value="{!selectedAccName}"/>
 </apex:outputPanel>
</apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
 <apex:outputLabel value="Postal Code:"></apex:outputLabel>
 <apex:outputPanel >
<apex:inputText value="{!selectedZip}"/>
<apex:commandButton value="Search" action="{!searchAcc}" status="status" reRender="secMap,outMap" oncomplete="populateMap();" />
 </apex:outputPanel>
</apex:pageBlockSectionItem>
</apex:pageBlockSection>

This section will be used to display input fields for name and postal code which are used to filter accounts, commandButton  action {!searchAcc}  is used to call searchAcc method of controller which returns a list of accounts.

There is a JS method call on commendButton’s “oncomplete” event which will create an Array of account records in JavaScript. <apex:repeat> can be used to push records into Group Object, you can put any name instead of “Group”.

At the end of <apex:repeat>, we can call the JS method load();

<apex:outputPanel id="outMap">
     <script type="text/javascript">
      function populateMap(){
         gAccount=[];
        <apex:repeat value="{!lstAcc}" var="act" id="TmpLoop" >
           gAccount.push(new Group("{!act.acc.id}","{!act.acc.Name}","{!act.acc.Latitude__c}","{!act.acc.Longitude__c}",true,'{!act.acc.BillingCity}','{!act.acc.BillingState}','{!act.acc.BillingCountry}','{!act.acc.BillingPostalCode}'));
        </apex:repeat>
         load();
       }
      </script>
  </apex:outputPanel>

Note: Please make sure that data does not contain the single quote. We can use the JSENCODE().

Group object define in the script section, properties should be same and must have some value if you want to display on info window of markers.

We can define the a Object in JavaScript, for ex: here is the code for Group Object.

function Group(id,name,lats,longs,displayNumber,city,state,country,zip) {
    this.Id = id;
    this.Name = name;
    this.Lats = lats;
    this.Longs = longs;
    this.DisplayNumber = displayNumber;
    this.City = city;
    this.State = state;
    this.Country = country;
    this.Zip = zip;
}

Latitudes and Longitudes values must have some values to display marker on Google Map, We can store these values on Account object using two custom fields. Salesforce.com is planning to put these two fields as a standard field for account, contacts, opportunity and lead. We have another option to create markers using address but Lat and Long is better to use.

Load Google Map on Visual force:

var gAccount = [];
var map = null;
var counter = 0;
var delay = 500;

At the top of the page we have define few Global variables, gAccount = [] will be used to create a JavaScript array for markers, which contains the information to point the markers on maps and display additional info on click of markers, delay will be used to put some gap between two markers requests.

function load() {
    map = null;
    mapDiv = document.getElementById("map");
    var mapOptions = {
          center: new google.maps.LatLng(-34.397, 150.644),
          zoom: 4,
          mapTypeId: google.maps.MapTypeId.ROADMAP,
          mapMaker: true
    };
    map = new google.maps.Map(mapDiv,mapOptions);
    finishSearch();
}

function finishSearch() {
   if(counter < gAccount.length){
      showAddress(gAccount[counter++],finishSearch);
  }
}

Once Google Map API script tag has been added at top of the page, we can start JS code to load map on Visual force page with Map (mapotions: mapotions) method. Create a method load() in VF page using this code.  
Map Options:
Center: display the default center location,
zoom: map zoom level,
mapTypeId: define the type of map, it can be roadmap, satellite etc.  
finishSearch() will be used to call method showAddress(Account object,finishSearch); it is a recursive call to display markers on map for all record of search result.
A <div> should be used to display map in Visual Force pages as a container, we need to define a <div> section before loading map through JavaScript.

<apex:pageBlockSection id="secMap" title="Search result" columns="1">
    <apex:outputPanel >
      <table width="100%">
        <tr>
            <td width="100%">
               <div id="map" style="height: 600px;"></div>
           </td>
        </tr>
      </table>
   </apex:outputPanel>
</apex:pageBlockSection>

We can set the height and width as per our display area on visual force page.

To display marker with info window we can use the code below:

function showAddress(record,finishSearch) {
var html='';
 if(record.Longs != '' && record.Lats != ''){
        var points = new google.maps.Point(record.Lats,record.Longs);
        var latLongs = new google.maps.LatLng(record.Lats,record.Longs);
       
        html ='<hr>' + counter +'.&nbsp;'+ record.Name + '<br/>';
        html += record.City +'&nbsp;'+ record.State + '&nbsp;'+ record.Country + ',&nbsp;' + record.Zip + '<br/>';
        html +="<hr><b>URL:<b> <a href=https://ap1.salesforce.com/" + record.Id + " target=_blank >account details</a>";
           
        var infowindow = new google.maps.InfoWindow({
            content: html
        });
        var marker = new google.maps.Marker({
            position: latLongs,
            map: map,
            title: record.Name
        });
       google.maps.event.addListener(marker, 'click', function() {
          infowindow.open(map,marker);
        });
        //set the center for last flag
        if((gAccount.length - 1) == counter)
            map.setCenter(latLongs, 3);
  }
  setTimeout(finishSearch,delay);
}

google.maps.infowindow will display information onclick on markers. Content can be formatted using HTML tags in string binding with Group Object properties.

Add content to info window:

html ='<hr>' + counter +'.&nbsp;'+ record.Name + '<br/>';
        html += record.City +'&nbsp;'+ record.State + '&nbsp;'+ record.Country + ',&nbsp;' + record.Zip + '<br/>';
        html +="<hr><b>URL:<b> <a href=https://ap1.salesforce.com/" + record.Id + " target=_blank >account details</a>";
           
        var infowindow = new google.maps.InfoWindow({
            content: html
        });
Create a marker with position and assign it to map:

var marker = new google.maps.Marker({
            position: latLongs,
            map: map,
            title: record.Name
  });

Add “click” event to marker to display info window onclick:

       google.maps.event.addListener(marker, 'click', function() {
          infowindow.open(map,marker);
        });


For more information: https://developers.google.com/maps/documentation/javascript/reference#event


You can copy and paste the controller class and visual force page code if you want to use it but remember to use API KEY in script tag.