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...!!

Monday, January 31, 2011

Access Salesforce Content on your VF Page


Recently I came across the Salesforce Content Related requirement, Salesforce only provide limited access to the content (ContentVersion object). It does not allow you to access the Content & show in your custom VF page.
Here Me & my friend Vinod did a lot of Rnd & found a easy solution. This post will save your lot of time if you are doing this kind of task with Content.

To implement its example, take a div
<div class="centerContent" id="myDiv"></div>
There are two simple JS function which will help to get salesforce content –

function OpenDoc(docId){
 
var htmlString = '<div id="chatterFileViewerPanel" class="chatterFileViewerPanel"><br/><br/> <embed height="500px" align="middle" width="500px" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.adobe.com/go/getflashplayer" allowfullscreen="true" allowscriptaccess="sameDomain" ' +
'name="renditionLarge" bgcolor="#f3f3f3" quality="high" id="renditionLarge" '
+'flashvars="shepherd_prefix=/sfc/servlet.shepherd&amp;v='+docId+'&amp;mode=chatterfilepreview&amp;in_tests=false" src="/static/101210/sfc/flex/DocViewer.swf" /> </div> ';
           
        document.getElementById('myDiv').innerHTML =  htmlString;
        PrepareFlexComponent(docId);
       
       
}

function PrepareFlexComponent(docId){
        insertFlexComponent('/static/102010/sfc/flex/DocViewer',
                'shepherd_prefix=/sfc/servlet.shepherd&v='+docId+'&mode=chatterfilepreview&
                amp;in_tests=false','500px', '500px', '#f3f3f3', 'chatterFileViewerPanel', 'renditionLarge', false,
                { adobeFlashVersionLbl : 'You must enable or download Adobe Flash Player version 9.0.115 or 
                   later to use this feature.',
                   downloadAdobeLbl : 'Download Adobe Flash Player', downloadAdobeLinkLbl : 'Click the 
                   link below to download the Adobe Flash Player:' closeDialogLbl : 'Cancel'});
                   
        Ninja.Flex.ContentStaticResourcesBase = '/static/102010/sfc';
        Ninja.Flex.ShepherdController = '/sfc/servlet.shepherd';
}  



Put below script as it is in your page


<script type="text/javascript" src="/static/101210/js/functions.js"></script>
<script  src="/EXT/ext-3.0.0/ext.js" type="text/javascript"></script>
<script  src="/jslibrary/1294273133000/main.js" type="text/javascript"></script>
<script  src="/jslibrary/1289945557000/Chatter.js" type="text/javascript"></script>
<script  src="/jslibrary/1289346548000/ChatterFiles.js" type="text/javascript"></script>
<script  src="/jslibrary/labels/1295420058000/en_US.js" type="text/javascript"></script>
<script type="text/javascript" src="/static/101210/desktop/desktopAjax.js"></script>
             
<script type="text/javascript" src="/static/101210/sfc/javascript/lib/AC_OETags.js"></script>
 
<script>

   function postSubDocument(url, internal) {
      var form = document.getElementById("formSubDocument");
      form.action = url;
      form.internal.value = internal;
      form.submit();
    }
             
    function hideFlex() {
        document.getElementById('IE6Confirm').style.display = "block";
    }
                    
    function showFlex() {
        document.getElementById('IE6Confirm').style.display = "none";
    }
                    
    function cancelDownload(url) {
        showFlex();
    }
                    
    function triggerDownload(url) {
        window.open(url);
    }
                    
    function downloadDocForIE6() {
        showFlex();
        triggerDownload(_url);
    }
                    
    function downloadDoc(url) {
        var isIE6 = (navigator.userAgent.indexOf("MSIE 6") != -1);
        if (isIE6) {
            _url = url;
            hideFlex();
        }
        else {
           triggerDownload(url);
        }
     }


  </script>

It’s done! Now call function OpenDoc by passing a ContentId & you can see your Content on VF page :)
It will show the content same as the salesforce standard way.
Enjoy..!!



Tuesday, January 25, 2011

Monday, January 24, 2011

Half Star Rating in jQuery Plug in

I recently used jQuery Star Rating in a site, in that I faced a issue regarding the half star rating, for Eg. If the rating is 2 than it can be shown easily but if it is 2.5 it is a little bit difficult to show the correct rating on the plug in. Here is the code if you are facing this type of issue-
Here is the VF page code & apex class code. In this the calculate method is most important to show the half star rating.
Apex Class
===============================================
public with sharing class StarRatingController {
public decimal selctedAverage{get;set;}
public decimal averageStar{get;set;}
public List getSkillOptionsStar() {
List skillOptions = new List();
decimal dOption= 1.0;
string val= '';
for(integer i=0; i<>
{
val = string.valueOf(dOption);
skillOptions.add(new Selectoption(val,val)) ;
dOption += 0.5;
}
return skillOptions;
}
public List getSkillOptions() {
List skillOptions = new List();
decimal dOption= 1.0;
string val= '';
for(integer i=0; i<>
{
val = string.valueOf(dOption);
skillOptions.add(new Selectoption(val,val)) ;
dOption += 0.1;
}
return skillOptions;
}
public void calculate(){
string str = string.valueOf(selctedAverage);
decimal tempAvg = 0;
if(str.indexOf('.') > -1)
{
str = str.substring(0, str.indexOf('.') + 2);
}
if(str.indexOf('.5') > -1)
{
tempAvg = Double.valueOf(str);
}
else
{
tempAvg = Double.valueOf(str);
tempAvg = tempAvg.round();
}
averageStar = tempAvg + 0.5;
}
public StarRatingController(){
selctedAverage = 1;
averageStar = 1.5;
}
}

VF Code
=============================================
<table cellspacing="5" cellpadding="0" border="0"><tr>
<td>Select average rating :</td>
<td>
<apex:selectList value="{!selctedAverage}" size="1" id="listSelect" onchange="calculate()">
<apex:selectOptions value="{!SkillOptions}"/>
</apex:selectList>
</td>
</tr>
<tr>
<td>Rating :</td>
<td valign="top">
<apex:outputPanel id="pnlResult">
<div id="divStarRating">
<apex:selectList value="{!averageStar}" size="1">
<apex:selectOptions value="{!SkillOptionsStar}"/>
</apex:selectList>
</div>
<script type="text/javascript">
$(document).ready(function(){
$("#divStarRating").stars({
inputType: "select",
split: 2,
cancelShow: false,
disabled : true,
starWidth: 17
});
});
</script>
<br/>
<br/>
<apex:outputLabel value="selected value==> {!selctedAverage}" /> <br/>
<br/>
<apex:outputLabel value="calculated value==> {!averageStar}" />
</apex:outputPanel>
</td>
</tr>
</table>
<apex:actionFunction name="calculate" action="{!calculate}" rerender="pnlResult" status="calStatus"/>
<apex:actionStatus startText="Please wait..." id="calStatus"/>


Sunday, January 23, 2011

Approval Process on VF Page

Take action approve/reject through custom VF page.
Generally to approve or reject an approval process standard way is used, but it’s quite interesting to access a record on custom VF page & approve/reject the record from this page. Here is the sample for this –
For this two objects are required one is master & other is detail. Let's say these two objects are professional object [Master] & review object [detail].
In the detail object there will be a field "Submitted_For__c" lookup (user) field. This field will be used for mapping to which user this review will be submitted.
Now coming to the code
===============================================
To access all review object for currently logged in user –
List listOfAllReviewObjects = new List([Select r.Proffesional_Info__c, r.Name, r.Id From Review__c r where r.User_SumittedFor__c = : UserInfo.getUserId() order by Id asc limit 1000 ]);
//iterate this list & create a set of listOfAllReviewObjects id’s. Let’s say name is “setOfAllReviewObjects”
now to access all approval process for currently logged in user
List listProcessInstance = new List([Select p.Id, p.Status, p.TargetObjectId, (Select ActorId, Comments, OriginalActorId, StepStatus From Steps order by CreatedDate asc) from ProcessInstance p where p.TargetObjectId in : setOfAllReviewObjects limit 1000]);
for(ProcessInstance pi:listProcessInstance){
objPi = new processInst();//here processInst is a custom class
objPi.apprProcId = pi.Id;
objPi.status = pi.Status;
if(pi.Steps.Size()>0)
{
//if object is approved/rejected, than Process Steps will have two entries
if(pi.Steps.Size()>1)
{ //these are the approved or pending records & size of steps will be greater than 1 in this case, initially there will be one entry in steps if no action taken on the record by approver
objPi.actorUserId = pi.Steps.get(1).ActorId;
objPi.reviewerName = mapReview.get(pi.TargetObjectId).Name;
objPi.reviwerId = pi.TargetObjectId;
objPi.originalActorUserId = pi.Steps.get(1).OriginalActorId;
objPi.comments = pi.Steps.get(1).Comments;
}
else
{ //all records which are submitted but pending to approve.
objPi.actorUserId = pi.Steps.get(0).ActorId;
objPi.reviewerName = mapReview.get(pi.TargetObjectId).Name;
objPi.reviwerId = pi.TargetObjectId;
objPi.originalActorUserId = pi.Steps.get(0).OriginalActorId;
objPi.comments = pi.Steps.get(0).Comments;

}

}
if(objPi.status == 'Approved' || objPi.status == 'Rejected'){
objPi.isApproved = true;
}
else {
objPi.isApproved = false;
}
to show the all records for current user, VF page code is as below-
<apex:pageBlockTable value="{!listProIns}" var="lpi" rendered="{!IF(listProIns.Size>0,true,false)}">
<apex:column headerValue="Approval Id" value="{!lpi.apprProcId}" />
<apex:column headerValue="Reviewer Name" value="{!lpi.reviewerName}" />
<apex:column headerValue="OriginalActorId" value="{!lpi.originalActorUserId}" rendered="false" />
<apex:column headerValue="Assigned Approver Name" value="{!lpi.originalActorUserName}" />
<apex:column headerValue="ActorId" value="{!lpi.actorUserId}" rendered="false" />
<apex:column headerValue="Actual Approver Name" value="{!lpi.actorUserName}" />
<apex:column headerValue="Comments">
<apex:inputTextArea id="txtComments" value="{!lpi.comments}" rendered="{!NOT(lpi.isApproved)}" cols="40" />
<apex:outputLabel value="{!lpi.comments}" rendered="{!lpi.isApproved}" />
</apex:column>>
<apex:column headerValue="Status" >
<apex:selectList value="{!lpi.Status}" multiselect="false" rendered="{!NOT(lpi.isApproved)}" size="1">
<apex:selectOption itemValue="NONE" itemLabel="--Action--" />
<apex:selectOption itemValue="Approve" itemLabel="Approve" />
<apex:selectOption itemValue="Reject" itemLabel="Reject" />
</apex:selectList>
<apex:outputlabel value="{!lpi.Status}" rendered="{!lpi.isApproved}"/>
</apex:column>
</apex:pageBlockTable>
<apex:pageBlockButtons id="blockButton" location="bottom">
<apex:CommandButton id="cmdButton" action="{!approvalProcessAction}" value="Save" rendered="{!IF(listProIns.Size>0,true,false)}"/>
</apex:pageBlockButtons>
On the VF page the records which are in pending state will show a drop down to take action while other records are not editable.
Finally to approve or reject a particular record, user will select a value from drop down & click on save button, the save button apex code will be-
Map mapStepIds = new Map();
List lstReview = new List([Select (Select ID,ProcessInstanceId from ProcessSteps where isPending=true) from Review__c where Id in :setPendingReviewObjects]);
if(lstReview.size()>0)
{
string id = '';
integer i = 0;
for(Review__c rv:lstReview)
{
if(rv.ProcessSteps.size() > 0)
{
id = rv.ProcessSteps[0].ID;
if(id != null && id != '')
{
mapStepIds.put(rv.ProcessSteps[0].ProcessInstanceId, id);
}
}
}

}
List listReq = new List();
for(processInst pi :listProIns)
{
if(!pi.isApproved && pi.status != 'NONE')
{
Approval.Processworkitemrequest objReq = new Approval.Processworkitemrequest();
objReq.setAction(pi.status);
objReq.setComments(pi.comments);
objReq.setWorkitemId(mapStepIds.get(pi.apprProcId));
listReq.add(objReq);

}
}
if(listReq.size()>0)
{
List res = Approval.process(listReq);
}
While submitting the action “setWorkitemId” is the most important part, this is the process instance id.

Enjoy!!!!