Thursday, 28 July 2016

Using Salesforce standard modal in a Detail page button


Javascript button to open an iframe using Salesforce standard modal.


Create a detail page button, set it to javascript and add the following code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function() {
    var box = new SimpleDialog("spoon" + Math.random(), true);
    box.setTitle("Dialog title");
    box.displayX = true;
    box.isMovable = false;
    box.createDialog();
    box.setWidth(750);
    box.setContentInnerHTML(
        "<iframe src='REPLACE WITH YOUR URL' frameborder='0' width='100%' height='380px' />"
    );
    //set the height of the modal
    box.dialog.children[1].style.height = '400px';
    box.show();
    //if displayX is true, then override standard close event by this code
    box.dialog.getElementsByClassName('dialogClose')[0].onclick = function() {
        box.hide();
    };
    //optional : add this to close the modal onclick of the overlay background
    box.background.addEventListener('click', function() {
        box.hide();
    });
})();

Output:

Sunday, 24 May 2015

DataTables in Visualforce Part 1

Introduction

One day a client asked us to display the Opportunities of an Account. We thought a nice Visualforce PageBlockTable would do the job. Then the client added functionalities like:

  • Pagination (as there would be around 100 records),
  • Sorting of columns,
  • Filtering of opportunities.
Instead of scratching our head thinking how to do this in Visualforce, we laughed out loud and told the client that the solution will be available in 5 minutes :)

DataTables our Saviour

DataTables is a jQuery plugin which we will use to make the dream of our client come true. DataTables focuses on enhancing the user-experience on standard HTML tables.

Code

Below is a small code used to generate our table of Opportunities. Notice how we import our scripts and css for DataTables; you should normally put them in a Static Resource. At line 15, we called the dataTable function to initialise our table.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<apex:page standardController="Account">
    <apex:includescript value="//code.jquery.com/jquery-1.11.1.min.js" / >
    <apex:includescript value="//cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js" />
    <apex:stylesheet value="//cdn.datatables.net/1.10.4/css/jquery.dataTables.css" />
    
    <apex:dataTable value="{!Account.Opportunities}" var="opp" styleClass="opp-table">
        <apex:column value="{!opp.Name}" headerValue="Name"/>
        <apex:column value="{!opp.StageName}" headerValue="Stage"/>
        <apex:column value="{!opp.Amount}" headerValue="Amount"/>
        <apex:column value="{!opp.CloseDate}" headerValue="Close Date"/>
    </apex:dataTable>
    
    <script>
        $(function(){
            $('table.opp-table').dataTable();
        });
    </script>
</apex:page>

Result



  1. Pagination controls
  2. Pagination size controls
  3. Sorting by clicking on column header. (currently sorted on Amount)
  4. Instant filtering capability over whole table.
Final words

This is a basic example of using DataTables in Visualforce. Part 2 coming soon...


Cheers & Happy Coding !!
Gulshan.

Thursday, 16 October 2014

Apex Map into JSON for use in Javascript / jQuery

Introduction

Ever wondered how to use your apex map into javascript / jQuery for your VisualForce development?
Please find below a way to do it.

Note: jQuery is required in this tutorial.

Apex Class (Visualforce Controller)

Suppose we populate a map with key Id and value as a list of Ids, say mapParentIdListChildrenId in our controller.
We then declare a getter to access this map as JSON in our VF page.
 public Map <Id, List <Id>> mapParentIdListChildrenId{get;set;}
 public String mapParentChildrenJSON {get{return JSON.serialize(mapParentIdListChildrenId);}}
Visualforce Page

The JSON String mapParentChildrenJSON is converted into javascript object as shown below:
 <script> 
  $(function() {
     var $mapParentChildren = jQuery.parseJSON('{!JSENCODE(mapParentChildrenJSON)}');
     console.log($mapParentChildren);
     // var value = $mapParentChildren[key]; // can be used to access values of the map
  }
 </script>
Use the Console of your browser to inspect the elements in your map ;)

Cheers & Happy Coding !!
Gulshan.

Wednesday, 6 August 2014

Dependent picklist mapping data matrix in apex



Reference  :  http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/

The Background:

According to the API Docs for the DescribeSObjectResult, a PicklistEntry object has a validFor property which is not available in Apex, but is available through the API.  To Nethaneel Edwards, this implied that if one were to serialize a PicklistEntry to JSON, this validFor property should become available. If such a scenario held true, then a deserialization of that content into a custom type which provided a property for validFor should allow us to access it in Apex.  That’s part one of the problem hypothetically solved.

The second problem is that Apex doesn’t make it simple to manipulate a byte array or a base64 string.  However, most of the time, we never really need to manipulate a byte array.  A proper understanding of decimal should suffice.  For this reason, I reference the MSDN library for a base64 mapping table [ http://msdn.microsoft.com/en-us/library/cc422512.aspx ] which will act as a reference for what I want to do.

/*
// TStringUtils
----------------------------------------------------------------------
**********************************************************************
Reference:
Author :  Nethaneel Edwards
URL    :  http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/
************************************************************************/
public class TStringUtils{
    public static Map<String,List<String>> GetDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){
        Map<String,List<String>> objResults = new Map<String,List<String>>();
        //get the string to sobject global map
        Map<String,Schema.SObjectType> objGlobalMap = Schema.getGlobalDescribe();
        if (!objGlobalMap.containsKey(pObjName))
            return objResults;
        //get the type being dealt with
        Schema.SObjectType pType = objGlobalMap.get(pObjName);
        Map<String, Schema.SObjectField> objFieldMap = pType.getDescribe().fields.getMap();
        //verify field names
        if (!objFieldMap.containsKey(pControllingFieldName) || !objFieldMap.containsKey(pDependentFieldName))
            return objResults;     
        //get the control values   
        List<Schema.PicklistEntry> ctrl_ple = objFieldMap.get(pControllingFieldName).getDescribe().getPicklistValues();
        //get the dependent values
        List<Schema.PicklistEntry> dep_ple = objFieldMap.get(pDependentFieldName).getDescribe().getPicklistValues();
        //iterate through the values and get the ones valid for the controlling field name
        TStringUtils.Bitset objBitSet = new TStringUtils.Bitset();
        //set up the results
        for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){           
            //get the pointer to the entry
            Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
            /*//get the label
            String pControllingLabel = ctrl_entry.getLabel();
            */
            //get the label
            String pControllingLabel = ctrl_entry.getValue();
            //create the entry with the label
            objResults.put(pControllingLabel,new List<String>());
        }
        //cater for null and empty
         objResults.put('',new List<String>());
         objResults.put(null,new List<String>());
        //check the dependent values
        for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++){          
            //get the pointer to the dependent index
            Schema.PicklistEntry dep_entry = dep_ple[pDependentIndex];
            //get the valid for
            String pEntryStructure = JSON.serialize(dep_entry);                
            TStringUtils.TPicklistEntry objDepPLE = (TStringUtils.TPicklistEntry)JSON.deserialize(pEntryStructure, TStringUtils.TPicklistEntry.class);
            //if valid for is empty, skip
            if (objDepPLE.validFor==null || objDepPLE.validFor==''){
                continue;
            }
            //iterate through the controlling values
            for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){    
                if (objBitSet.testBit(objDepPLE.validFor,pControllingIndex)){                   
                    /*//get the label
                    String pControllingLabel = ctrl_ple[pControllingIndex].getLabel();
                    */
                    String pControllingLabel = ctrl_ple[pControllingIndex].getValue();
                    /*objResults.get(pControllingLabel).add(objDepPLE.label);
                    */
                    objResults.get(pControllingLabel).add(objDepPLE.Value);
                }
            }
        } 
        return objResults;
    }
    
    //HDU
     public static Map<String,String> GetReverseDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){
         Map<String,String> mapChildToParent = new Map<String,String>();
         Map<String,List<String>> objResults  = TStringUtils.GetDependentOptions(pObjName, pControllingFieldName, pDependentFieldName);
         for(String parentValue :objResults.KeySet() ){
             for(string childValue :objResults.get(parentValue )){
                 mapChildToParent.put(childValue ,parentValue );
             }
         }
         return mapChildToParent;
     }
    
    
 /*
     * @Summary: Entity to represent a json version of a picklist entry
     * so that the validFor property becomes exposed
    */
    public class TPicklistEntry{
        public string active {get;set;}
        public string defaultValue {get;set;}
        public string label {get;set;}
        public string value {get;set;}
        public string validFor {get;set;}
        public TPicklistEntry(){
            
        }
    }
public class Bitset{
        public Map<String,Integer> AlphaNumCharCodes {get;set;}
        public Map<String, Integer> Base64CharCodes { get; set; }
        public Bitset(){
            LoadCharCodes();
        }
        //Method loads the char codes
        private void LoadCharCodes(){
            AlphaNumCharCodes = new Map<String,Integer>{
                'A'=>65,'B'=>66,'C'=>67,'D'=>68,'E'=>69,'F'=>70,'G'=>71,'H'=>72,'I'=>73,'J'=>74,
                'K'=>75,'L'=>76,'M'=>77,'N'=>78,'O'=>79,'P'=>80,'Q'=>81,'R'=>82,'S'=>83,'T'=>84,
                'U'=>85,'V'=> 86,'W'=>87,'X'=>88,'Y'=>89,'Z'=>90    
            };
            Base64CharCodes = new Map<String, Integer>();
            //lower case
            Set<String> pUpperCase = AlphaNumCharCodes.keySet();
            for(String pKey : pUpperCase){
                //the difference between upper case and lower case is 32
                AlphaNumCharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey)+32);
                //Base 64 alpha starts from 0 (The ascii charcodes started from 65)
                Base64CharCodes.put(pKey,AlphaNumCharCodes.get(pKey) - 65);
                Base64CharCodes.put(pKey.toLowerCase(),AlphaNumCharCodes.get(pKey) - (65) + 26);
            }
            //numerics
            for (Integer i=0; i<=9; i++){
                AlphaNumCharCodes.put(string.valueOf(i),i+48);
                //base 64 numeric starts from 52
                Base64CharCodes.put(string.valueOf(i), i + 52);
            }
        }
        public Boolean testBit(String pValidFor,Integer n){
            //the list of bytes
            List<Integer> pBytes = new List<Integer>();
            //multiply by 6 since base 64 uses 6 bits
            Integer bytesBeingUsed = (pValidFor.length() * 6)/8;
            //will be used to hold the full decimal value
            Integer pFullValue = 0;
            //must be more than 1 byte
            if (bytesBeingUsed <= 1)
                return false;
            //calculate the target bit for comparison
            Integer bit = 7 - (Math.mod(n,8)); 
            //calculate the octet that has in the target bit
            Integer targetOctet = (bytesBeingUsed - 1) - (n >> bytesBeingUsed); 
            //the number of bits to shift by until we find the bit to compare for true or false
            Integer shiftBits = (targetOctet * 8) + bit;
            //get the base64bytes
            for(Integer i=0;i<pValidFor.length();i++){
                //get current character value
                pBytes.Add((Base64CharCodes.get((pValidFor.Substring(i, i+1)))));
            }
            //calculate the full decimal value
            for (Integer i = 0; i < pBytes.size(); i++)
            {
                Integer pShiftAmount = (pBytes.size()-(i+1))*6;//used to shift by a factor 6 bits to get the value
                pFullValue = pFullValue + (pBytes[i] << (pShiftAmount));
            }
            //& is to set the same set of bits for testing
            //shift to the bit which will dictate true or false
            Integer tBitVal = ((Integer)(Math.Pow(2, shiftBits)) & pFullValue) >> shiftBits;
            return  tBitVal == 1;
        }
    }
}

Thursday, 15 May 2014

Padding Text in Salesforce / Visualforce

Introduction

Last time I got a request from a client to limit a number to 6 digits and if the number has less than 6 digits, prepend 0 in front of it.

Example: 7007 must become 007007

I had to do this in 
  1. Javascript (for display in a disabled textbox) , 
  2. Visualforce (for display in a PDF), and 
  3. Apex (to send to a Web Service).

Javascript

For the Javascript version, I wrote a small function that would do the left padding of zeroes. This function is called when the page is loaded.
 function addZeroes(eId, size){  
      var elem = document.getElementById(eId)  
      while(elem.value.length < size)  
           elem.value = "0" + elem.value  
 }

The function takes the element Id and size as parameter to limit left padding. In my case the element was an input of type text:
 <input id="agent_number" type="text" value="{!$User.Agent__c}" disabled="true"/>  

The addZeroes() function was called as follows in a <script> tag after the form was loaded.
 <script>  
      addZeroes("agent_number", 6);  
 </script>  

Visualforce

The visualforce solution was hard to find but very simple to implement. I used the formula LPAD.
 {!LPAD($User.Agent_No__c,6,'0')}  

Apex

I could not find a solution that resembles the visualforce LPAD in Apex. I used two methods from the String Class to achieve the same result.
 String agentNum = '7007';  
 String agentNumber = agentNum.leftPad(6).replace(' ', '0');  
 system.debug('## agentNumber : ' + agentNumber);  
leftPad() method is used to pad spaces in front without exceeding the length parameter.
replace() method is used to replace all the spaces with 0.

Thnx & Cheers,
Gulshan.

Sunday, 4 May 2014

Twitter Application-only authentication in Salesforce Apex

Hello all,

Today I will be posting an example on how to implement the Twitter Application-only authentication using Salesforce Apex.

Overview


The Twitter Application-only authentication is used when no user sign-in with Twitter is required.
When using this type of authentication, we are allowed to call limited Twitter functionalities, namely :


  • Pull user timelines;
  • Access friends and followers of any account;
  • Access lists resources;
  • Search in tweets;
  • Retrieve any user information;

  • In our example, we will be implementing the Authentication flow described in Twitter Application-only authentication which involves two HTTP requests.

    Code


     public TwitterContr(){  
          HttpRequest req = new HttpRequest();  
          req.setMethod('POST');  
          req.setEndpoint('https://api.twitter.com/oauth2/token');  
            
          //// Step 1: Encode consumer key and secret  
            
          String consumerKey = 'xvz1evFS4wEEPTGEFPHBog';  
          String consumerSecret = 'L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg';  
            
          Blob headerValue = Blob.valueOf(consumerKey + ':' + consumerSecret);  
          String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);  
         
          //// Step 2: Obtain a bearer token  
                 
          req.setHeader('Authorization', authorizationHeader);  
          req.setHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8');  
          req.setBody('grant_type=client_credentials');  
         
          // Create a new http object to send the request object  
          // A response object is generated as a result of the request  
          // the server will respond with a JSON-encoded payload  
         
          Http http = new Http();  
          HTTPResponse res = http.send(req);  
          
          String jsonStr = res.getbody();  
          JSONParser parser = JSON.createParser(jsonStr);  
            
          // extract access token from JSON response  
          string accessToken;  
          while (parser.nextToken() != null) {  
               if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getText() == 'access_token')) {  
                    // Get the value.  
                    parser.nextToken();  
                    accessToken = parser.getText();  
               }  
          }  
         
          // access token can now be used to perform other requests  
          //// Step 3: Authenticate API requests with the bearer/access token  
            
          // e.g. searching for tweets   
            
          HttpRequest req1 = new HttpRequest();  
          req1.setMethod('GET');  
          req1.setEndpoint('https://api.twitter.com/1.1/search/tweets.json?q=%23freebandnames');  
         
          String authorizationHeader1 = 'Bearer ' + accessToken;  
          req1.setHeader('Authorization', authorizationHeader1);  
         
          Http http1 = new Http();  
          HTTPResponse searchResponse = http.send(req1);  
          system.debug('## body1 : ' + searchResponse.getBody());  
     }  
    

    Cheers!!

    Saturday, 16 November 2013

    Insufficient Privileges

    Insufficient Privileges
    You do not have the level of access necessary to perform the operation you requested. Please contact the owner of the record or your administrator if access is necessary.

    Ever got the error above!!

    Even if you are an administrator!!

    Check that all your components (Apex Classes, Visualforce pages, Visualforce components,...) are using the same API version.

    Cheers!!