Wednesday, May 30, 2012

JS Remoting in Salesforce with Mustache



Js remoting is very useful & interesting feature. By this the page is not posted back to server & you will get result in less than a second...!!!

let's start with an example how this works. I am also going to use Mustache.js template in the VF page. Mustache is a js templates & can be used if you want to generate the html dynamically. It means you will define the template first & the data will be rendered as per the defined templates.

To know more about mustache you can check below url

to implement the js remoting first we need to create a controller class in apex.
Please notice that the apiVersion for controller class must be 23 or above.

global class JSremotingController {
     @RemoteAction
     global static OuterWrapper getOuterWrapperObject(string oppId){
         return null;
     }
   @RemoteAction
   global static OuterWrapper setOuterWrapperObject(String jsonString, string oppId){
      return null
   }

global class InnerWrapper{
   public string name {get;set;}
   public string description {get;set;}
   public InnerWrapper(String strName, String strDesceription){
      this.name = strName;
      this.description = strDesceription;
   }
}
global class OuterWrapper{
  public String oppName {get;set;}
  public String oppDescription {get;set;}
  public List<InnerWrapper> listOfInnerWrapper {get;set;}
  public List<String> listOfStr_SelectOptions {get;set;}
  public OuterWrapper(Opportunity o){
     listOfStr_SelectOptions = SelectOptions_Global;
     listOfInnerWrapper = new List<InnerWrapper>();
     oppName = o.Name;
     oppDescription = o.Description;
   }
  }
}


let's dig the code step by step-

@RemoteAction
global static OuterWrapper getOuterWrapperObject(string oppId){
}

this method is defined as remote action by @RemoteAction tag
it must be declared with global & static keyword because the function is related to class instance. It will be called by js remoting to get the data on VF page.


@RemoteAction
global static OuterWrapper setOuterWrapperObject(string oppId){
}

this method is same as above just the difference is we will use this to update the opportunity description.

There are two wrapper class. One is main class & other class list used in main class. The reason why I took this wrapper class structure to explain how we can deserialize the input (json string) directly to wrapper class.

Now again taking the implementataion part for the methods-



@RemoteAction
global static OuterWrapper getOuterWrapperObject(string oppId){
   Opportunity opp = [Select o.Name, o.Id, o.Description From     
   Opportunity o WHERE Id=:oppId];
    OuterWrapper objOuterWrapper = new OuterWrapper(Opp);
    List<InnerWrapper> WrapperT = new List<InnerWrapper>();
    listOfInnerWrapperT.add(new InnerWrapper('TEST1','DESC1'));
    objOuterWrapper.listOfInnerWrapper = listOfInnerWrapperT;
   return objOuterWrapper;
}





in this method we will pass a opp od from the VF page, the code will get the detail from the database create wrapper & return this.
In JS we will get a js object of OuterWrapper, we can directly access it's field same as we get the value we get in javascript code. You will get it more clearly as I will add the vf page in nect couple of lines.

@RemoteAction
global static OuterWrapper setOuterWrapperObject(String jsonString, string oppId){
   OuterWrapper objOuterWrapper =   
   (OuterWrapper)System.JSON.deserialize(jsonString,OuterWrapper.Class);
  Opportunity opp = new Opportunity(Id=oppId);
   opp.Description = objOuterWrapper.oppDescription;
  update opp;
  return objOuterWrapper;
}
this method simply updateting the opp description. This method will be called from the VF page buttton.

Now let's move on the VF page detail-

first of all you need to include the js files for this. There are two js files-

<apex:includescript value="{!URLFOR($Resource.JS,'jquery-1.4.2.min.js')}" />
<apex:includescript value="{!URLFOR($Resource.JS,'mustache.js')}" />

I have to include these files because I am using the mustache in the page, you can avoid this if not required.

function getOuterWrapperObjectByJSRemoting(oppId) {
  JSremotingController.getOuterWrapperObject(oppId, function(result, event){
  if (event.status) {
   result_Global= result;
   var template = "<b><span>Opportunity Name:</span>  {{oppName}}</b><br/><br/><span>Opportunity Description</span>  {{oppDescription}}";
   var html = Mustache.to_html(template, result);
   $('#SPANOppName').html(html);
  } else if (event.type === 'exception') {
    alert("exception");
    document.getElementById("responseErrors").innerHTML = event.message;
  } else {
   alert("else");
   document.getElementById("responseErrors").innerHTML = event.message;
  }
}, {escape:true});
}

by this js method we will pass a opp id & will get the detail in jsObject result. You can check/run result_Global on the console to check the structure of the js object.






function SetOuterWrapperObjectByJSRemoting(jsonString, oppId){
  JSremotingController.setOuterWrapperObject(jsonString, oppId, function(result, event){
  if (event.status) {
    result_Global2 = result;
    alert('updated..!!');
  } else if (event.type === 'exception') {
    alert("exception");
    document.getElementById("responseErrors").innerHTML = event.message;
  } else {
    alert("else");
    document.getElementById("responseErrors").innerHTML = event.message;
  }
 }, {escape:true});
}

this method simply udate the opp description by js remoting.

function getJSONObject(){
//Outer Wrapper
var objOpportunityWrapper = new Object();
objOpportunityWrapper["oppDescription"] = $("#inputOppDescription").val();
var InnerWrapper = new Object();
var InnerWrapperArray = new Array();
InnerWrapper["name"] = 'TEST Name1';
InnerWrapper["description"] = 'TEST Description1';
InnerWrapperArray[0] = InnerWrapper;
objOpportunityWrapper["listOfInnerWrapper"] = InnerWrapperArray;
JSON_STR = JSON.stringify(objOpportunityWrapper);
SetOuterWrapperObjectByJSRemoting(JSON_STR,Opp_Id);
}

I think this is the most important method on the js. this method is going to create structure same as the wrapper class. it there is any difference in between the object you created on JS & controller class than the json string will not be serialized in controller.


let's put the whole code in one place-

class code will be

global class JSremotingController {
  private static List<String> SelectOptions_Global =    
  getFieldDescibe(Opportunity.LeadSource.getDescribe());
  
public static List getFieldDescibe(Schema.DescribeFieldResult Res){
  List<String> listOfOpt = new List<String>();
  List<Schema.PicklistEntry> pe = Res.getPicklistValues();
  if(pe != NULL && pe.SIZE()>0){
    for(Schema.PicklistEntry p:pe){
       listOfOpt.add(p.getLabel());
    }
  }
  return listOfOpt;
}

@RemoteAction
global static OuterWrapper getOuterWrapperObject(string oppId){
  Opportunity opp = [Select o.Name, o.Id, o.Description From Opportunity o WHERE Id=:oppId];
  OuterWrapper objOuterWrapper = new OuterWrapper(Opp);
  List<InnerWrapper> listOfInnerWrapperT = new List<InnerWrapper>();
  listOfInnerWrapperT.add(new InnerWrapper('TEST1','DESC1'));
  objOuterWrapper.listOfInnerWrapper = listOfInnerWrapperT;
  return objOuterWrapper;

}
@RemoteAction
global static OuterWrapper setOuterWrapperObject(String jsonString, string oppId){
  OuterWrapper objOuterWrapper =  
  (OuterWrapper)System.JSON.deserialize(jsonString,OuterWrapper.Class);
  Opportunity opp = new Opportunity(Id=oppId);
  opp.Description = objOuterWrapper.oppDescription;
  update opp;
  return objOuterWrapper;
}
global class InnerWrapper{
  public string name {get;set;}
  public string description {get;set;}

  public InnerWrapper(String strName, String strDesceription){
    this.name = strName;
    this.description = strDesceription;
 }
}
global class OuterWrapper{
  public String oppName {get;set;}
  public String oppDescription {get;set;}
  public List<InnerWrapper> listOfInnerWrapper {get;set;}
  public List<String> listOfStr_SelectOptions {get;set;}
  public OuterWrapper(Opportunity o){
    listOfStr_SelectOptions = SelectOptions_Global;
    listOfInnerWrapper = new List<InnerWrapper>();
    oppName = o.Name;
    oppDescription = o.Description;
  }
}
}

VF Page

<apex:page controller="JSremotingController" sidebar="false" showHeader="false">
 <apex:form id="form1">
 <apex:includescript value="{!URLFOR($Resource.JS,'jquery-1.4.2.min.js')}" />
 <apex:includescript value="{!URLFOR($Resource.JS,'mustache.js')}" />
 <script type="text/javascript">
   var result_Global;
   var Opp_Id = '006900000071POn';
   function getOuterWrapperObjectByJSRemoting(oppId) {
  JSremotingController.getOuterWrapperObject(oppId, function(result, event){
  if (event.status) {
      result_Global= result;
var template = "<b><span>Opportunity Name:</span>  {{oppName}}</b><br/><br/><span>Opportunity Description</span>  {{oppDescription}}";
       var html = Mustache.to_html(template, result);
       $('#SPANOppName').html(html);
                      
   } else if (event.type === 'exception') { 
       alert("exception");
       document.getElementById("responseErrors").innerHTML = event.message;
   } else {
       alert("else");
       document.getElementById("responseErrors").innerHTML = event.message;
   }
}, {escape:true});
 }

$(document).ready(function(){
   getOuterWrapperObjectByJSRemoting(Opp_Id);
});
var result_Global2;
 function SetOuterWrapperObjectByJSRemoting(jsonString, oppId){
  JSremotingController.setOuterWrapperObject(jsonString, oppId, function(result, event){
  if(event.status) {
    result_Global2 = result;
       alert('updated..!!');
  } else if (event.type === 'exception') {
       alert("exception");
       document.getElementById("responseErrors").innerHTML = event.message;
  } else {
       alert("else");
       document.getElementById("responseErrors").innerHTML = event.message;
  }
 }, {escape:true});
}
var JSON_STR;
function getJSONObject(){
  //Outer Wrapper
  var objOpportunityWrapper = new Object();
  objOpportunityWrapper["oppDescription"] = $("#inputOppDescription").val();
                    
  var InnerWrapper = new Object();
  var InnerWrapperArray = new Array();
  InnerWrapper["name"] = 'TEST Name1';
  InnerWrapper["description"] = 'TEST Description1';
  InnerWrapperArray[0] = InnerWrapper;
  objOpportunityWrapper["listOfInnerWrapper"] = InnerWrapperArray;
  JSON_STR = JSON.stringify(objOpportunityWrapper);
  SetOuterWrapperObjectByJSRemoting(JSON_STR,Opp_Id);
}   
                    
</script>    
  <apex:pageBlock title="GET DATA BY JS REMOTING">
 <apex:pageBlockSection >
  <span id="SPANOppName"></span>
 </apex:pageBlockSection>
  </apex:pageBlock>  
  <apex:pageBlock title="SET/UPDATE DATA BY JS REMOTING">
<apex:pageBlockSection >
  <apex:pageBlockSectionItem >
  <span>Opportunity Description</span>
  <input type="text" id="inputOppDescription" />
  </apex:pageBlockSectionItem>
<apex:pageBlockSectionItem >
<input type="button" onclick="getJSONObject()" value="Update" class="btn"/>
</apex:pageBlockSectionItem>
 </apex:pageBlockSection>
   </apex:pageBlock>
  </apex:form>  
</apex:page>

to run this example you just need to copy & paste & change the Opp id in in the top of the vf page.
 var Opp_Id = '006900000071POn';


the page ui will be as below








Tuesday, February 21, 2012

IE double click on List Select Option


I recently came across a very strange issue in IE. The requirement was adding a action on select option double click of a list. The double click is working fine for all other controls in IE but was not working in case of select option.
In Mozilla & Chrome below code working as expected --

$("#list1").find("option").unbind('dblclick');
$("#list1").find('option').dblclick(function(){
//put your code here whatever needed on double click
});

but IE not able to get the double click on select option & not performing the action as other browsers. I did some Rnd on the issue but haven;t got any solution, even in some post mentioned that this is not supported on IE.
I am thankful to my friend Kapil Choudhary who help me to fix this issue. Here is the script for IE which will work on all browsers perfectly.

$("#list1").unbind('dblclick');
$("#list1").dblclick(function () {
$("#list1").find('option:selected').each(function() {
//put your code here whatever needed on double click
});
});

Saturday, January 21, 2012

Use Apply With Linked In Salesforce Site


I am back after couple of months. Recently I started to look into the Linked in Salesforce integration. I found this very interesting & keep diging & finally implemented as web site page which is using apply with linked-on button. Here are the complete steps by which you can add “Apply with Linked In” button to your VF site.
1-go this below url & create a developer login on linked in-
https://www.linkedin.com/reg/join
2-Login with the linked-in password by below url
http://developer.linkedin.com/user/login
2-go to below url & click on “Create On API Key” link
https://developer.linkedin.com/apply-getting-started

3-You will see a page as below.


Click on “Add New Application” Link, fill all the values in the next form & in the “JavaScript API Domain:” text box enter your site url. For Eg In my case it was “http://kgblog-developer-edition.ap1.force.com/apex/ApplyWithLinkedIn”
Click on save & you will see some information on the page, something like api key, secret key etc. do not close this window, it will be used in next step.




4-Now go to below url 
https://developer.linkedin.com/plugins/apply & click on Get Code button. You will see something link as below-
<script src="http://platform.linkedin.com/in.js" type="text/javascript">
  api_key: YOUR_API_KEY
</script>
<script type="IN/Apply" data-companyid="XXXX" data-jobtitle="test" data-email="XXXXX@yahoo.com"></script>
Copy this js code & put it in your vf page. 
Replace api key in above code which we get step-3.
5-now run the site page, you will see apply with linked button on your page.
If you are building an application in which you need some input something like name, email & some other information from the linked site than you can use “apply with linked in” button .

 <script type="IN/Apply" data-companyid="XXXX" data-jobtitle="Manager" data-joblocation="Delhi" data-callback="onCallBack" ></script>
& the js method will be as below



<script>


        function onCallBack(p1,p2) {
            document.getElementById("txt1").value = p1.person.firstName;
            document.getElementById("txt2").value = p1.person.lastName;
            document.getElementById("txt3").value = p1.person.emailAddress;
            document.getElementById("txt4").value = p1.person.publicProfileUrl;
         
        }
     
</script>


take a look of the demo by below url

Friday, March 11, 2011

Play with bulk data - Apex Batch Class


Wherever you want to perform some operation on bulk data the Batch Class is the right option for that.
Here I am going to explain the steps of how a batch class can be implemented….

declare a batch class

global class TestBatchClass implements Database.Batchable <Sobject>,Database.Stateful{
   public Id accountId{get;set;}
}

The syntex will be same for all batch classes you only need to change the class name “TestBatchClass”. 

Here I am taking an example of account updation, suppose we want to perform some operation on accounts than we need to take a property accountId in the batch class. This property will be used when we create test class for this batch class.

In below lines I am declaring constructor for the batch class
public TestBatchClass(){
}
   
public TestBatchClass (Id accountId){
        this.accountId = accountId;
   }

   One noticeable point is second constructor will be used when we write test 
   class for this batch class. You can avoid the parameterize constructor if does not         
   required in your case.

  //--------------------------------------------------------------//
    //Start Method :->
    //-------------------------------------------------------------//
    global Database.Querylocator start(Database.BatchableContext ctx){
            return null;
    }

   As per the name start method will be called as the class start execution. In this method  
   you need to change the query as per you requirement. For Eg we are using a query to get  
   accounts on which we will perform some operations. 
  
  
 if(test.isrunningTest()) {
             return Database.getQueryLocator([select Id,Name from Account limit 5]);
   }
   else{
        return Database.getQueryLocator([select Id,Name from Account where Id=: accountId]);
    } 
  
   Here one noticeable point is there are two queries to get the accounts. Test.isrunninTest 
  checks and execute the first query if we are calling this batch class from test class, it 
  will useful because we should not perform the batch logic for thousands of account if it is 
  calling from Test class.

 
  //--------------------------------------------------------------//
  //Execute method
  //-------------------------------------------------------------//
  global void execute(Database.BatchableContext ctx, List<Account>){

   }  

 Execute method is the most important method of the batch class, here you need to write the  
 logic on the basis of you want to perform update/insert/delete operation. For Eg if we need 
 to change a field the fetched accounts than the code will be look like as below-


 List<Account> listOfAccounts = (List<Account>)scope;
  List<Account> listOfAccountsUpdated = new List<Account>();
  Account newAccount;
  for(Account acct :listOfAccounts) {
newAccount = new Account(Id = acct.Id);
            newAccount.checked__c = true;
listOfAccountsUpdated.add(newAccount);
  }
  If(listOfAccountsUpdated.Size()>0){
       Update listOfAccountsUpdated;
  }


  At the end of the batch class there is finish method, generally you need not to write any 
  code in this method, you can write some code if there is any specific requirement at the 
  end of batch execution. for eg. send a mail in finish.

 
   //--------------------------------------------------------//
  //Finish method
  //----------------------------------------------------------//
  global void finish(Database.BatchableContext ctx){
       
  }
   The whole class code will be look like as below-


    
  global class TestBatchClass implements     Database.Batchable <Sobject>,                                          Database.Stateful {
   public Id accountId{get;set;}
       public TestBatchClass(){
       
   }
   
   public TestBatchClass (Id accountId){
      this.accountId = accountId;
   }

    //--------------------------------------------------------//
   //Start Method :->
   //----------------------------------------------------------//
   global Database.Querylocator start(Database.BatchableContext ctx){
        if(test.isrunningTest()) {
            return Database.getQueryLocator([
select Id,Name from Account limit 5]);
        }
        else{
            return Database.getQueryLocator([
select Id,Name from Account where Id=: accountId]);
        }            
    }
   
    //--------------------------------------------------------//
    //Execute method
    //---------------------------------------------------------//
   global void execute(Database.BatchableContext ctx, List<Accountscope){
       List<Account> listOfAccounts = (List<Account>)scope;
List<Account> listOfAccountsUpdated = new List<Account>();
Account newAccount;
for(Account acct :listOfAccounts) {
newAccount = new Account(Id = acct.Id);
              newAccount.checked__c = true;
listOfAccountsUpdated.add(newAccount);
}
If(listOfAccountsUpdated.Size()>0){
              Update listOfAccountsUpdated;
}

    } 
    //-------------------------------------------------------//
    //Finish method
    //--------------------------------------------------------//
    global void finish(Database.BatchableContext ctx){
       
    }
 }



 it's done..!!

  
 If you want to test this batch class then open the system log & execute below lines of 
 code 

 TestBatchClass obj = new TestBatchClass ();
  Database.executeBatch(obj,5);

 In execute batch you can specify how many records need to take in a batch, for example we 
 are taking 5. Generally we should not take more than 200 for this.

 Now you can check the implementation flow/bugs in the system log detail & manipulate the  
 logic of the batch class.

After executing this batch class sometimes you will not able to save data in the class if 
 there any error thrown in by batch class & batch class not execute successfully. For this 
 you need to go to Setup->Monitoring->Apex Jobs & then Abort the job. In our case it will be 
 “TestBatchClass”.  


Happy Coding...!!