Monday, August 16, 2010

Using IMAGE function in formula fields

Things that are "visible" are more easy to understand than things that are meant to "read".

In salesforce standard pages, IMAGE function is the answer to above line. You can show images to the user instead of just having TEXT. And your images can be very dynamic, the way you want them to be. This is the very core objective of salesforce IMAGE formula.

Let us take an example of a very simple IMAGE field. Suppose we want to create an IMAGE field which shows a RED flag when your Lead score is less than 2, yellow when score is between 2 and 4 and green when Lead score is 5.

Create a formula field by going through Setup --> Customize -->Lead --> Fields --> Create  New Custom field and the return data type as TEXT.
Select the field type as formula field. in the formula editor type in the following:

IMAGE(IF(Lead_Score__c <= 2, "/img/samples/flag_red.gif", IF(AND(Lead_Score__c > 2,Lead_Score__c <= 4), "/img/samples/flag_yellow.gif",IF(Lead_Score__c = 5, "/img/samples/flag_green.gif"))))

Now you can put this field in your page layout and try changing your Lead Score field (I am assuming you have Lead Score field on your Lead object, else create one) and see the flag changing. The images that you see here are standard icons provided by salesforce. You can get more icons at mgsmith's blog post here.

One more formula field that I used in one of my project implementation, is below. It just illustrates how you can "concatenate" your image formula field the same way you do it with TEXT.

IF(OR(NumberofLocations__c=1,NumberofLocations__c=2,NumberofLocations__c=3,NumberofLocations__c=4),IMAGE("/img/samples/color_green.gif", "green", 15, 49),IMAGE("/img/samples/color_red.gif", "red", 15, 49))
& IMAGE("/s.gif", "green", 15, 1)
& IF(OR(NumberofLocations__c=2,NumberofLocations__c=3,NumberofLocations__c=4),IMAGE("/img/samples/color_green.gif", "green", 15, 49),IMAGE("/img/samples/color_red.gif", "red", 15, 49))
& IMAGE("/s.gif", "green", 15, 1)
& IF(OR(NumberofLocations__c=3,NumberofLocations__c=4),IMAGE("/img/samples/color_green.gif", "green", 15, 49),IMAGE("/img/samples/color_red.gif", "red", 15, 49))
& IMAGE("/s.gif", "green", 15, 1)
& IF(OR(NumberofLocations__c=4),IMAGE("/img/samples/color_green.gif", "green", 15, 49),IMAGE("/img/samples/color_red.gif", "red", 15, 49)).

Here I have taken refrence of a field NumberofLocations__c on say Account object. It displays a series of images concatenated. So this is kind of Progress Bar that you can show to your user.

Did you note the '&', the numbers 15 and 49, and /s.gif location?
 The '&'  is used to concatenate two strings or texts. In salesforce image fields are treated as string so you can 'add' them.
The number 15 is the height of your image and 49 is the width or span.
One very good image provided by salesforce is "blank" image i.e. '/s.gif'. So when you do not want to show any image in your condition, simply put the url of image as '/s.gif'. It shows a blank. In case you do not want to show an image and leave the url part as blank, it shows a red cross instead of blank. Which definetely doesn't look good.

You can find more about image formulas at salesforce documentation here.

Hope this was useful.

Happy Clouding :)

Thursday, August 5, 2010

Creating Visualforce page code against your standard page layout using AJAX toolkit

I was wondering if we somehow, could automate this and I came across a post by Jeff Douglas. This was simply amazing. I got the base. he had done it using web service API in .NET and this was somewhat disappointing because I never worked on it. I decided to run it, got environment setup using some tutorials on .NET and the result.. wow..

My next step was to simplify it without using the WSDL and all that stuff. But this is not possible in Apex (please correct me, if I am wrong), so I had to look for an alternate way. My search ended up with AJAX Toolkit. And I learned AJAX for the first time then only. (Credit goes to Jeff, he made me learn two new technologies). I must tell you this is simply powerfullll. I used it in a visualforce page along with a small controller class (I had to use the class, its a different story). Later on I found that it is possible even without using the class. Here is how it looks like.

Input Screen

And here is the generated visualforce code



The logic is almost the same. You can even see variable names not very different. Below is the code for all this. (Please excuse me, code with no comments :)).

Page Code

<apex:page controller="OutputController">
<apex:form >

<script type="text/javascript">
var __sfdcSessionId = '{!GETSESSIONID()}';
</script>

<script src="../../soap/ajax/19.0/connection.js" type="text/javascript"></script>

<script type="text/javascript">
//var output='';
function setupPage() {
    var obj = document.getElementById("obj");
    var opt = document.getElementById("opt");
    var rct = document.getElementById("rct");
    var result;
    try{
        if(rct.value != '' && rct.value != null){
            result = sforce.connection.describeLayout(obj.value, new Array(rct.value));        
        }else {
            result = sforce.connection.describeLayout(obj.value);
            alert(result);
            alert('please note that you have not provided record type id. if the object has more than one record type and different page layout assignments, you will get the code with all the page layouts');
        }
        //call methods edit or details
        if(opt.value == 'Edit')editLayoutResults(result, obj);
        else detailLayoutResults(result, obj);
    }catch(error){       
        var er = new String(error);
        if(er.indexOf('INVALID_TYPE') != -1)alert('please check object api name');
        document.getElementById("{!$Component.hide}").value = '';
    }
}


//edit*****************************************layout
function editLayoutResults(result, obj) {
    output = '';
    var layouts = result.getArray("layouts");

    output += '<' + 'apex' + ':' + 'page standardController=' + '"' + obj.value + '"' + '>';
    output += '\n';
    output += '<' + 'apex' + ':' + 'sectionHeader' + ' title=' + '"' + obj.value + ' Edit' + '"' + ' subtitle=' + '"' + '{' + '!' + obj.value + '.name}' + '"' + '/>';
    output += '\n';
    output += '<' + 'apex' + ':' + 'form' + '>';
    output += '\n';
    output += '<' + 'apex' + ':' + 'pageBlock title=' + '"' + obj.value + ' Edit' + '"' + ' mode=' + '"edit">';
    output += '\n';
    output += '\n';
    output += '<' + 'apex' + ':' + 'pageBlockButtons location=' + '"top">';
    output += '\n';
    output += '<' + 'apex:commandButton value=' + '"' + 'Save' + '" ' + 'action=' + '"' + '{' + '!' + 'save' + '}"' + '/>';
    output += '\n';
    output += '<' + 'apex:commandButton value=' + '"' + 'Save & New' + '"' + ' action=' + '"' + '{' + '!save}" />';
    output += '\n';
    output += '<' + 'apex:commandButton value="Cancel" action=' + '"' + '{' + '!cancel}' + '"/>';
    output += '\n';
    output += '<' + '/apex:pageBlockButtons>';
    output += '\n';
    output += '\n';
    output += '<' + 'apex' + ':' + 'pageBlockButtons location=' + '"bottom">';
    output += '\n';
    output += '<' + 'apex:commandButton value=' + '"' + 'Save' + '" ' + 'action=' + '"' + '{' + '!' + 'save' + '}"' + '/>';
    output += '\n';
    output += '<' + 'apex:commandButton value=' + '"' + 'Save & New' + '"' + ' action=' + '"' + '{' + '!save}" />';
    output += '\n';
    output += '<' + 'apex:commandButton value="Cancel" action=' + '"' + '{' + '!cancel}' + '"/>';
    output += '\n';
    output += '<' + '/apex:pageBlockButtons>';

   
    //adding fields and sections
    var allTheLayouts = result.getArray("layouts");
    for (var i = 0; i < allTheLayouts.length; i++){
        var layout = allTheLayouts[i];
        if (layout.editLayoutSections != null){
            var elSections = layout.getArray("editLayoutSections");
            for (var j = 0; j < elSections.length; j++){
                var els = elSections[j];
                
                output += '\n';
                output += '\n';
                output += '<' + 'apex:pageBlockSection title=' + '"' + els.heading + '" ' +  'columns=' + '"' + els.columns + '"' + '>';
                output += '\n';

                var allTheLayoutRows = els.getArray("layoutRows");
                for (var k = 0; k < allTheLayoutRows.length; k++){
                    var lr = allTheLayoutRows[k];
                    var lis = lr.getArray("layoutItems");
                    for (var h = 0; h < lis.length; h++){
                        var li = lis[h];
                        //only in case of Lead and Contact First Name, which includes Salutation also
                        if (li.layoutComponents != null && li.layoutComponents.length == 2){                           
                            output += '<' + 'apex:inputField value=' + '"' + '{' + '!' + obj.value + '.' + li.layoutComponents[1].value + '}' + '" ' + 'required=' + '"' + li.required.toString() + '"' + '/>';
                            output += '\n';
                        }
                        //for all other fields
                        else if (li.layoutComponents != null){
                            output += '<' + 'apex:inputField value=' + '"' + '{' + '!' + obj.value + '.' + li.layoutComponents.value + '}' + '" ' + 'required=' + '"' + li.required.toString() + '"' + '/>';
                            output += '\n';
                        }
                    }
                }
                output += '<' + '/apex:pageBlockSection>';
                output += '\n';
            }
        }
    }
    output += '\n';
    output += '<' + '/apex:pageBlock>';
    output += '\n';
    output += '<' + '/apex:form>';
    output += '\n';
    output += '<' + '/apex:page>';
 
    document.getElementById("{!$Component.hide}").value = output; 
}


//details**********************************layout
function detailLayoutResults(result, obj) {
    var layouts = result.getArray("layouts");
    var output = '';

    output += '<' + 'apex' + ':' + 'page standardController=' + '"' + obj.value + '"' + '>';
    output += '\n';
    output += '<' + 'apex' + ':' + 'sectionHeader' + ' title=' + '"' + obj.value + '"' + ' subtitle=' + '"' + '{' + '!' + obj.value + '.name}' + '"' + '/>';
    output += '\n';
    output += '<' + 'apex' + ':' + 'pageBlock title=' + '"' + obj.value + '"' + '>';
    output += '\n';
   
    //adding fields and sections
    var allTheLayouts = result.getArray("layouts");
    for (var i = 0; i < allTheLayouts.length; i++){
        var layout = allTheLayouts[i];
        if (layout.editLayoutSections != null){
            var elSections = layout.getArray("editLayoutSections");
            for (var j = 0; j < elSections.length; j++){
                var els = elSections[j];
                
                output += '\n';
                output += '<' + 'apex:pageBlockSection title=' + '"' + els.heading + '" ' +  'columns=' + '"' + els.columns + '"' + '>';
                output += '\n';

                var allTheLayoutRows = els.getArray("layoutRows");
                for (var k = 0; k < allTheLayoutRows.length; k++){
                    var lr = allTheLayoutRows[k];
                    var lis = lr.getArray("layoutItems");
                    for (var h = 0; h < lis.length; h++){
                        var li = lis[h];
                        //only in case of Lead and Contact First Name, which includes Salutation also
                        if (li.layoutComponents != null && li.layoutComponents.length == 2){                           
                            output += '<' + 'apex:outputField title=' + '"' + li.label + '" value="' + '{' + '!' + obj.value + '.' + li.layoutComponents[1].value + '}' + '"' + '/>';
                            output += '\n';
                        }
                        //for all other fields
                        else if (li.layoutComponents != null){
                            output += '<' + 'apex:outputField title=' + '"' + li.label + '" value="' + '{' + '!' + obj.value + '.' + li.layoutComponents.value + '}' + '"' + '/>';
                            output += '\n';
                        }
                    }
                }
                output += '<' + '/apex:pageBlockSection>';
                output += '\n';
            }
        }
    }
    output += '\n';
    output += '<' + '/apex:pageBlock>';   
    output += '\n';
    output += '<' + '/apex:page>';

    document.getElementById("{!$Component.hide}").value = output;
}
</script>

<p><b>Enter the object API Name and record type Id associated to create visualforce code of the page layout for the selected page type.</b></p>

<table>
<tr >
<td><b>Object API Name</b></td>
<td><input type="text" id="obj"/></td>
</tr>

<tr >
<td><b>Record Type Id</b></td>
<td><input type="text" id="rct"/></td>
</tr>

<tr >
<td><b>Page Type</b></td>
<td><select id="opt">
  <option value="Edit">Edit</option>
  <option value="Detail">Detail</option>
</select></td>
</tr>
</table>

<center><apex:commandButton id="but" value="Create Page Code" onclick="setupPage()" action="{!create}" rerender="pan" status="pageStatus"/></center>
<apex:inputHidden id="hide" value="{!hide}"/>

<br><br></br></br>
<apex:actionStatus id="pageStatus" startText="Getting page code..." stopText="Page Code"/>
<table >
<tr>
<td>
<apex:outputPanel id="pan">
{!output}
</apex:outputPanel>
</td>
</tr>
</table>

</apex:form>
</apex:page>

Controller Class

public class OutputController {       
    //variables and getter/setter
    public String output {get;set;}
    public String hide {get;set;}   
   
    //action method of the button
    public PageReference create() {
        output = hide;
        return null;
    }        
   
    //test method
    public static testMethod void testPage(){
        Test.setCurrentPage(Page.VisualforceCodeCreator);
        Test.startTest();
        OutputController oc = new OutputController();
        oc.create();
        Test.stopTest();
    }
}

An This Is It.

The controller, I had to use for a reason. We cannot just print some text with HTML encoding in it in a visualforce page. The option I came across later on is to use HTMLENCODE() function in the page and you'll get rid of the controller.


Happy Clouding.
And its not my dialog but I love it...Human Knowledge belongs to the world.