Sunday, May 17, 2009

Formating Page Created using <apex:page RenderAs="PDF">

VisualForce allows you to create PDF documents, but did you know those files can be easily manipulated with CSS, for example you can break a document on different pages, you can set footers and headers, you can set the page size, you can add page numbering…

You can find a very good document on how to use CSS to format PDF files here

This page creates a PDF file that:

  • Users Letter as the paper size
  • Has margins of 1/4 centimeters
  • Has a title on every page
  • Every page shows the page number in this format (page # of #)
  • Control what content goes on each page.
VisualForce Page:
<apex:page renderAs="PDF">
    <style>
        @page {
            size: letter;
            margin: 25mm;
            @top-center {
                content: "Sample";
            }
            @bottom-center {
                content: "Page " counter(page) " of " counter(pages);
            }
        }
        .page-break {
            display:block;
            page-break-after:always;
        }
        body {
            font-family: Arial Unicode MS;
        }
    </style>
    <div class="page-break">Page A</div>
    <div class="page-break">Page B</div>
    <div>Page C</div>
</apex:page>

How to set up a VisualForce page to edit multiple rows at the same time?

This code explains how to build a simple page that allows you to edit several records at the same time. The code builds this table:

You can edit any email, but only those marked with the checkbox will be saved on the database once the “Save” button is clicked, additionally there is a link on each row that will navigate to the edit page and fully edit the record. This is not a very good user interface, but allows me to demonstrate some techniques.

VisualForce Page:

<apex:page controller="MultiRow" >
    <apex:form >
        <apex:pageblock >
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!Save}" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection columns="1">
                <apex:dataTable value="{!Contacts}" var="c" border="1" rowClasses="odd,even" styleClass="tableClass" cellpadding="3">
                    <apex:column >
                        <apex:facet name="header">Actions</apex:facet>
                        <apex:inputCheckbox value="{!c.Checked}"/>
                        <apex:commandLink value="Full Edit" action="{!URLFOR($action.Contact.Edit, c.ID)}" target="_blank" />
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">Name</apex:facet>
                        <apex:outputText value="{!c.Name}"/>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">Email</apex:facet>
                        <apex:inputText value="{!c.Email}" />
                    </apex:column>
                </apex:dataTable>
            </apex:pageBlockSection>
        </apex:pageblock>
    </apex:form>
</apex:page>
Controller:
public class MultiRow {
    public List<multiRowContact> Contacts { get; set; }

    public MultiRow() {
        LoadData();        
    }

    public PageReference Save() {
        for (multiRowContact MRC : Contacts) {
            MRC.Save();
        }
        LoadData();
        return null;
    }
    private void LoadData() {
        Contacts = new List<multiRowContact>();
        for (List<Contact> cs : [SELECT c.ID, c.Name, c.Email FROM Contact c WHERE c.AccountID = '0018000000OdcOt']) {
            for (Contact c : cs) {
                multiRowContact MRC = new multiRowContact();
                MRC.ID = c.ID;
                MRC.Checked = false;
                MRC.Name = c.Name;
                MRC.Email = c.Email;
                Contacts.add(MRC);
            }
        }
    }

    private class multiRowContact {
        public String ID { get; set; }
        public String Name { get; set; }
        public String Email { get; set; }
        public Boolean Checked { get; set; }
        public void Save() {
            if (Checked) {
                System.debug('Saving...ID: ' + ID);
                Contact c = [SELECT c.Email FROM Contact c WHERE c.ID = :ID LIMIT 1];
                c.Email = Email;
                update c;
            }
        }
    }
}

URLFOR() explained

Although this is a very useful function in Salesforce.com, it is not properly documented.

Few days ago, I found a very good blog that explains this function, I have decided to summarize Sam Arjmandi’s article, but for full details please visit his article here.

URLFOR function returns a relative URL using this syntax:

{!URLFOR(target, id, [inputs], [no override])}
Target: Action, s-control or static resource.
Id: Resource name (string type) or record ID (depends on the “target”).
Inputs: Additional parameters passed. Format: [param1="value1", param2="value2"]
No override: A Boolean flag. Set to true if to display a standard Salesforce page regardless of whether you have defined an override for it elsewhere. (default false)

So, how to use this function for files and folders ?

Resource file: URLFOR($Resource.LogoImg)
Resource zip file or folder: URLFOR($Resource.CorpZip, 'images/logo.gif')

But equally important, is that the URL for actions that can be performed on custom and standard objects can be easily found using this function. These actions are implemented by all objects:

View record: URLFOR($Action.Account.View, account.id)
Create new record: URLFOR($Action.Account.New)
Edit record: URLFOR($Action.Account.Edit, account.id)
Delete record: URLFOR($Action.Account.Delete, account.id)
View List: URLFOR($Action.Account.Tab, $ObjectType.Account)

Note that some objects support other additional actions; for example Contact also supports "Clone" and Case also supports "CloseCase" action. To find out which action can be invoked on a particular object, check the buttons and links which can be overridden for that object. For standard objects go to Setup > App Setup > Customize > Desired Standrd Object > Buttons and Links and you will see a page like this:

Just use the name of the function like this: URLFOR($Action.Object.Name, Object.ID)

Saturday, May 16, 2009

<apex:inputCheckBox> OnChange vs. OnClick Events

VisualForce allows most of its tags to react to JavaScript events, and they are usually quite straightforward and easy to implement. But, there are few events that can be tricky…

One of them, is the OnChange event for the <apex:inputCheckbox> tag. When a checkbox is checked or uncheck it generates the OnChange event, but only after the user selects a different input element in the page, after the checkbox loses its focus.

A better user experience could be achieved if the checkbox fired its event without waiting for the focus to be lost. This can be easily achieved using the OnClick event:
VisualForce code:

<apex:inputCheckbox onClick="{!SomeMethod}" />

Governor Limits for multi-tenant communities

I don’t like rules, especially when I am caught by the cops on the highway going over the speed limit, on the other hand I’m glad people stop on red lights and drive on the correct side of the road.

There are some laws that are required to prevent chaos. Can you imagine if people could drive on the roads as fast, in any direction and in any vehicle they pleased? Imagine being run over in a red light by a “monster truck” just because they did not want to wait? Or having to avoid oncoming traffic in the lane you are driving? There has to be some order and some rules to protect our rights in the communities we live.

Fortunately, there are rules, but usually those who do not follow them (even due to ignorance) are heavily fined. In a perfect society, we would be politely reminded if any of our actions could affect other community members and possibly prevent us from doing those actions that could affect others.

The applications we develop for salesforce.com run on a multi-tenant community, and we have rules that protect us from other community member’s actions, and to protect them from our actions. These rules are collectively known as Governor Limits!

Different Document Types

You already know that Salesforce.com allows you to easily create web pages to display information from your objects, but did you know you can also create other types of documents quite easily?

Two formats are currently supported: Microsoft Excel and Acrobat Reader (PDF).

These properties for the <apex:page> tag will create an Excel document called “Cases.XLS” with the contents of your page:

<apex:page contentType="application/vnd.ms-excel#Cases.xls" cache="true">
The cache=”true” property is quite important, especially when the user browses your ORG with Microsoft Internet Explorer.

These properties will create an Acrobat Reader document called “Cases.pdf” with the contents of your page:

<apex:page contentType="application/x-download#Casessomedocument.pdf" renderas=”PDF” >
Note: Your page should not have <apex:input*> tags. First of all, it does not make sense to have these tags if the forms are not to be posted back to Salesforce.com. Second, the rendering of these tags may not be as expected. But, it is quite valid to use links.

Assignment Rules Using APEX

Did you know you can use the assignment rules functionality that is provided to you with the standard Salesforce.com interface with Apex code?

This code creates a Case, sets the owner using the standard assignment rules, and sends the email to the new Case owner. It could also be used for any other object that support assignment rules.

Apex Code:

// Instantiate a new Case
Case newCase = new Case();

// Put here the code to initialize the fields with the information...

// Instantiate the database options
Database.DMLOptions dlo = new Database.DMLOptions();

// This line assigns the ID for the assignment rule you want to use
// dlo.assignmentRuleHeader.assignmentRuleId = getAssignmentRuleId();

// This line assigns the active assignment rule
dlo.assignmentRuleHeader. useDefaultRule = true;

// This line sends email to the related contact.
dlo.EmailHeader.triggerAutoResponseEmail = true;

// This line sends email to the new case owner
dlo.EmailHeader.triggerUserEmail = true;

// Set the options just defined
newCase.setOptions(dlo);

// Create case.
insert newCase;

API access with no password expiration

When you create an API integration package, you may want to have it run by an account whose password never expires.

By default the applications that connect to Salesforce.com via the API require a username, a password and a token. How would you set this up, so that the credentials do not expire?

  1. Create a profile for your API user
    1. Ensure the API checkbox is checked.
    2. Ensure password not expire checkbox is checked.
    3. Set other security options required by your application.
  2. Create a user you will use for the API logins and assign it to this profile.
  3. The application should run on a computer in a trusted IP address range (it could be located inside your network, on a trusted ISP, or it may connect to the network using a VPN).
    1. Login in via the API from the computer where the application is installed
    2. Check the IP address used here (Setup > Personal Setup > My Personal Information > Personal Information > Login History (Related List))
    3. Ensure that IP address is in a range defined here (Setup > Administration Setup > Security Controls> Network Access > Trusted IP Range Edit)

By executing the step #3, we are avoiding the use of a security token. This is still secure, because we are specifying the IP range of the computer where we know the application will always execute.

Saturday, May 9, 2009

What should go in the List parameter for a <apex:relatedList List=""> tag?

To answer this question, I decided to build a VisualForce page that would be as close as possible to the pages created using the standard page layouts.

For this task, I created two custom objects to represent a flight. One custom object would contain information about the flight and the second custom object would have information for the city. This picture ilustrates how I set up the relationships between them.

This is how I defined each of my objects:

This is the definition for "City":

Nothing special here, but note there is a field called "ABC" (it will be important when I explain the rules for naming child relationships)

This is the definition for "Flight":

There are some basic fields ("Arrival","Departure", ...) and there are two lookup fields to "City". One for the "Origin" and one for the "Destination".

This is how I defined the relationships:

This image illustrates how the "Origin" field is defined:

Note there is an error in the definition of the "Child Relationship Name".

This entry is very important to answer this post's question. These are the rules to the values you can enter:

  1. Only alphanumeric characters (Letters, numbers and underscore) are allowed
  2. Must begin with a letter
  3. Can’t end with an underscore
  4. Can’t contain two consecutive underscore characters.
  5. Must be unique across all city fields
    1. Can not be "ABC", because there is one field named "ABC" in City. I defined this field, to explain this point.
    2. Can not be the name of a different relationship… Although it may not be technically accurate, it helps if you think these relationships create a field in the destination object (City in this case).
With these rules, I used these "child relationship names":

  • Origin_Flights
  • Destination_Flights

Now I can answer this blog's question:

<apex:relatedList list="Origin_Flights__r" />
<apex:relatedList list="Destination_Flights__r" />
Simulating a standard page layout in VisualForce

My next step, create a page that looked as closed as possible to the ones using standard page layouts. Basically, this was my goal:

As you can see, there are a lot of standard related lists. How could I get their names? Eclipse will tell you:

With that information, I created a page that looked as close to the standard page layout as I could.

Before I show you the results, let me tell you there are some differences, but I will leave them for a future post. For example, I did not created the JavaScript to show the related lists Divs on mouse over. The section header is not identical (some links are missing)

This is what I got:

Finally, here is the code...

VisualForce Page:

<apex:page standardController="City__c" showHeader="true" tabStyle="City__c">
    <apex:sectionHeader title="City" subtitle="{!City__c.Name}" help="/help/doc/user_ed.jsp?loc=help&target=getstart_help.htm§ion=Getting_Started" />
    This page is made in visualFroce simulating a Page Layout Page... This is the description... blah... blah... blah<br/><br/>
    <apex:detail relatedList="false" title="false" />
    <apex:relatedList list="OpenActivities"/>
    <apex:relatedList list="ActivityHistories"/>
    <apex:relatedList list="NotesAndAttachments"/>
    <apex:relatedList list="Origin_Flights__r" />
    <apex:relatedList list="Destination_Flights__r" />
    <apex:relatedList list="ProcessSteps"/>
    
<H1>Other Related Lists Available from VisualForce</H1>

    <apex:pageblock title="City History">
        <apex:pageBlockTable value="{!City__c.Histories}" var="h">
            <!-- h.ID, h.ParentId -->
            <apex:column headerValue="Date">
                <apex:outputField value="{!h.CreatedDate}"/>
            </apex:column>
            <apex:column headerValue="User">
                <apex:outputField value="{!h.CreatedById}"/>
            </apex:column>
            <apex:column headerValue="Field">
                <apex:outputText value="{!h.Field}"/> <!-- This is a picklist -->
            </apex:column>
            <apex:column headerValue="From">
                <apex:outputField value="{!h.OldValue}"/>
            </apex:column>
            <apex:column headerValue="To">
                <apex:outputField value="{!h.NewValue}"/>
            </apex:column>
            <apex:column headerValue="IsDeleted">
                <apex:outputField value="{!h.IsDeleted}"/>
            </apex:column>
        </apex:pageBlockTable>
    </apex:pageblock>
    <apex:pageblock title="City Tags">
        <apex:pageBlockTable value="{!City__c.Tags}" var="t">
            <!-- t.Id, t.ItemId-->
            <apex:column headerValue="TagDefinitionId">
                <apex:outputField value="{!t.TagDefinitionId}"/>
            </apex:column>
            <apex:column headerValue="CreatedDate">
                <apex:outputField value="{!t.CreatedDate}"/>
            </apex:column>
            <apex:column headerValue="SystemModstamp">
                <apex:outputField value="{!t.SystemModstamp}"/>
            </apex:column>
            <apex:column headerValue="IsDeleted">
                <apex:outputField value="{!t.IsDeleted}"/>
            </apex:column>
            <apex:column headerValue="Name">
                <apex:outputField value="{!t.Name}"/>
            </apex:column>
            <apex:column headerValue="Type">
                <apex:outputField value="{!t.Type}"/>
            </apex:column>
        </apex:pageBlockTable>
    </apex:pageblock>
</apex:page>

Thursday, January 29, 2009

Inter-window communication (Pop-up & IFrames)

This post explains a Javascript technique called "Inter-window communication". You could use this technique in VisualForce pages to open a pop-up window and have both windows talk among them.

For this post I'll have two HTML windows: A opener window and a pop-up window.

Opener.html:

<html>
<head>
    <title>*** OPENER ***</title>
</head>
<body>
    <form id="form1" runat="server">
    <table border="1">
        <tr>
            <th>
                Counter:
            </th>
            <td>
                <input type="button" onclick="Count();" value="Count" />
                <input type="text" id="ShowCounter" value="0" />
            </td>
        </tr>
        <tr>
            <th>
                Message:
            </th>
            <td>
                <input type="button" onclick="Write();" value="Write" />
                <span id="OutMsg">...</span>
                <input type="text" id="InMsg" value="..." />
            </td>
        </tr>
        <tr>
            <th>
                Other Window:
            </th>
            <td>
                <input type="button" onclick="OpenWindow();" value="Open Window" />
                <input type="button" onclick="UpdatePopPup();" value="Update PopUp" />
                <input type="button" onclick="UpdateIFrame();" value="Update IFrame" />
            </td>
        </tr>
    </table>
    </form>
    <iframe id="MyFrame" name="MyFrame" src="pop-up.html" width="600px" height="150px">
    </iframe>
</body>
</html>

<script type="text/javascript" language="javascript">
    // Local Scripts
    function Count() {
        var objCounter = document.getElementById('showCounter')
        objCounter.value = ++objCounter.value;
    }
    function Write() {
        WriteMessage(document.getElementById('InMsg').value);
    }
    function WriteMessage(inMsg) {
        var objMsg = document.getElementById('OutMsg');
        objMsg.innerHTML = inMsg;
    }
</script>

<script type="text/javascript" language="javascript">
    // Other window scripts
    var newWindow = null;
    function OpenWindow() {
        newWindow = window.open('pop-up.html');
    }
    function UpdateOpener(txtMsg, txtCounter) {
        document.getElementById('InMsg').value = txtMsg;
        document.getElementById('ShowCounter').value = txtCounter;
        Write();
    }
    function UpdatePopPup() {
        if (newWindow != null) {
            Write();
            var txtMsg = document.getElementById('InMsg').value;
            var txtCounter = document.getElementById('ShowCounter').value;
            newWindow.UpdatePopPup(txtMsg, txtCounter);
        }
    }
    function UpdateIFrame() {
        if (document.getElementById("myFrame") != null) {
            Write();
            var txtMsg = document.getElementById('InMsg').value;
            var txtCounter = document.getElementById('ShowCounter').value;
            document.getElementById("myFrame").contentWindow.UpdatePopPup(txtMsg, txtCounter);
        }
    }
</script>
pop-up.html:
<html>  
<head>  
    <title>*** POP-UP ***</title>  
</head>  
<body>  
    <form id="form1" runat="server">  
    <table border="1">  
        <tr>  
            <th>  
                Counter:   
            </th>  
            <td>  
                <input type="button" onclick="Count();" value="Count" />  
  
                <input type="text" id="ShowCounter" value="0" />  
            </td>  
        </tr>  
        <tr>  
            <th>  
                Message:   
            </th>  
            <td>  
                <input type="button" onclick="Write();" value="Write" />  
                <span id="OutMsg">...</span>  
  
                <input type="text" id="InMsg" value="..." />  
            </td>  
        </tr>  
        <tr>  
            <th>  
                Other Window:   
            </th>  
            <td>  
                <input type="button" onclick="CloseWindow();" value="Close Window" />  
  
                <input type="button" onclick="UpdateOpener();" value="Update Opener" />  
            </td>  
        </tr>  
    </table>  
    </form>  
</body>  
</html>  
  
<script type="text/javascript" language="javascript">  
    // Local Scripts   
    function Count() {   
        var objCounter = document.getElementById('showCounter')   
        objCounter.value = ++objCounter.value;   
    }   
    function Write() {   
        WriteMessage(document.getElementById('InMsg').value);   
    }   
    function WriteMessage(inMsg) {   
        var objMsg = document.getElementById('OutMsg');   
        objMsg.innerHTML = inMsg;   
    }   
</script>  
  
<script type="text/javascript" language="javascript">  
    // Other window scripts   
    function CloseWindow() {   
        window.top.close();   
        UpdateOpener();   
    }   
    function UpdateOpener() {   
        if (window.opener != null) {   
            Write();   
            var txtMsg = document.getElementById('InMsg').value;   
            var txtCounter = document.getElementById('ShowCounter').value;   
            window.opener.UpdateOpener(txtMsg, txtCounter);   
        }
        if (window.parent.frames.length>0) {
            Write();   
            var txtMsg = document.getElementById('InMsg').value;   
            var txtCounter = document.getElementById('ShowCounter').value;   
            window.parent.UpdateOpener(txtMsg, txtCounter);   
        }
    }   
    function UpdatePopPup(txtMsg, txtCounter) {   
        document.getElementById('InMsg').value = txtMsg;   
        document.getElementById('ShowCounter').value = txtCounter;   
        Write();   
    }   
</script>  
The pop-up works because:
  • When the window is opened (opener.html, line 63), the window is assigned to a variable "newWindow".
  • On the pop-up window, the javascript refers to the "window.opener" object (pop-up.html, line 70).
The IFrame works on a similar concept, but the syntax is a bit different.

How do I prevent duplicates based on more than one field?

Recently, I had the need to create a unique index based on more than one field.

I required a sObject whose name field is unique for each user. A user can not have two or more records with the same "name", but it is possible for different users to have records with the same name.

Let's suppose I have an sObject with this data:

Owner Name
user1 name1
user2 name2

If "user1" tries to add a row named "name1", the system should prevent him from doing so because "user1" already has a row with "name1". On the other hand if "user2" tries to add a row named "name1" the system should allow this, because "user2" does not have a row named "name1".

My first idea, was to have a formula field that merged the two values and make that formula field be a primary key. But formula fields can not keys.

So I decided on a trigger, and this worked fine...

trigger PreventDuplicateNameForUser on TPName__c (before insert, before update) {
    for (TPName__c TPName : Trigger.new) {
        List<TPName__c> listFound = [SELECT name
                                       FROM TPName__c
                                      WHERE ownerid = :UserInfo.getUserID()
                                        AND name = :TPName.name];
        if (listFound.size() > 0) {
            Trigger.new[0].addError('You already defined a Parameter Name called ['
                                     + TPName.name +']. Please use another name and try again');
        }
    }
}

How can the controller read the page?

Let's suppose you have a page (or part of a page) that you want to send via email. How can you get the contents of the page in the controller?

Since the contents of the page cannot be obtained by the controller, why not let the page send its contents to the controller?

VisualForce page:

<apex:page controller="MyEmail">
    <div ID="renderedPage">
        ... Put here the HTML content you want to get (it could be the entire page) ...
    </div>
    <apex:form >
        <apex:actionFunction name="setApexHTML" action="{!sendEmail}" rerender="AJAXSection">
            <apex:param name="strHTML" value="" />
        </apex:actionFunction>
    </apex:form>
    <apex:outputPanel id="AJAXSection"></apex:outputPanel>
    <script language="javascript">
        function getJSHTMLData() {
            var divPage = element = document.getElementById('renderedPage');
            var strHTML = divPage.innerHTML;
            setApexHTML(strHTML);
        }

        // Execute on load
        var previousOnload = window.onload;
        window.onload = function() {
            if (previousOnload) {
                previousOnload();
            }
            getJSHTMLData();
            alert('Email sent');
        }
    </script>
</apex:page>
Controller:
public class MyEmail {
    public void sendEmail() {
        List<String> emailAddress;
        String htmlContent = ApexPages.currentPage().getParameters().get('strHTML');

        // Send email
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

        // To
        emailAddress = new List<String>();
        emailAddress.add('user@acme.com');
        mail.setToAddresses(emailAddress);

        // Cc
        emailAddress = new List<String>();
        emailAddress.add('smith@gmail.com');
        mail.setCcAddresses(emailAddress);

        // Reply to
        mail.setReplyTo('support@acme.com');
        mail.setSenderDisplayName('Acme Support');

        // Bcc
        mail.setBccSender(true); // Set to True if you want to BCC yourself on the email.

        // Contents
        mail.setSubject('You have mail!');
        mail.setHtmlBody(htmlContent);
        mail.setUseSignature(false);

        // Send email
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }
}
How does this code works?
  1. When the VisualForce page loads, part of the JavaScript code (lines 19-25) gets executed.
  2. This calls the getJSHTMLData() method (lines 12-16) which
    1. Gets the contents of the <DIV> (lines 2-4)
    2. Calls the setApexHTML(strHTML) method with that information
  3. The setApexHTML(strHTML) method (lines 6-8) sends the data in the strHTML parameter to the SendEmail() method in the controller.
  4. The SendEmail() method
    1. Gets the data from the parameter (line 4)
    2. Creates and sends the email (line 6-32).
Althogh this code calls the Javascript when it loads, it could easily be changed to have the method called by JavaScript when an event happens (for example the onClick event of a HTML button)

Building a multi-page wizards

I recently tried to develop a set of related pages, something like a multi-page wizard where the context of the data would be preserved from one page to the next.

I started out with these ideas:

  • I tried sending the object as a parameter, but only strings (or numbers in string format) can be sent out as parameters.
  • I tried static objects but the state was not preserved.

I tried a few other tricks, but the system did not preserve the state... All the objects were being instatiated, rather than using the previous instance. I knew it could be done, because I had seen the documentation... And re-reading it I found the answer...

There is a comment for the wizzard's controller code sample in the Visualforce Developer's Guide, that reads:

... Note that the redirect attribute does not need to be set on the PageReference because the URL does not need to change when users move from page to page.

This information should be re-worded to something like this:

Note that the redirect attribute on the PageReference must not be set to true (set it to false or do not set it at all) because the URL must not change when users move from page to page.

Anyways, this is how it should be set up: VisualForce Page:

<apex:page controller="theData">
    <H1>Page1</H1>
    <apex:form >
        Data value: <apex:inputText value="{!DataText}" />
        <apex:commandButton action="{!Page1}" value="Page1" />
        <apex:commandButton action="{!Page2}" value="Page2" />
        <apex:commandButton action="{!Page3}" value="Page3" />
    </apex:form>
</apex:page>

Controller:

public class aaData {
// Data
    private String strText = null;
    public String getDataText() {
        return strText;
    }
    public void setDataText(String value) {
        strText = value;
    }

// Navigation
    public PageReference Page1() {
        return getPage('/apex/aaPage1');
    }
    public PageReference Page2() {
        return getPage('/apex/aaPage2');
    }
    public PageReference Page3() {
        return getPage('/apex/aaPage3');
    }
    private PageReference getPage(String URL) {
        PageReference page = new PageReference(URL);
        page.setRedirect(false);
        return page;
    }
}

I noticed that if the URL does not change, the state is preserved, but if the URL changes the state is lost. In addition to the "page.setRedirect(false)" (line 23 on the previous code), you must be careful as to the use of controllers and extensions... The only way I was able to not change the URL (and preserve the state) was when all the pages use exactly the same set of controllers and extensions... You may use a standard or custom controller and zero or more extensions, but every page in the wizard must use the same set of classes.

All the pages must have the same heading. Something like this:

<apex:page controller="theData">
Or this:
<apex:page controller="theData" extensions="ext1">
Or this:
<apex:page controller="theData" extensions="ext1,ext2">
Or this:
<apex:page standardController="Account" extensions="theData">
Or this:
<apex:page standardController="Account" extensions="theData,ext1">
The important thing is that all the pages in the wizard must use the same set of controllers and extensions, otherwise the state will be lost! I noticed that if I had something like this: Page 2:
<apex:page controller="theData" extensions="step2,step3">
Page 3:
<apex:page controller="theData" extensions="step3">
I was able to go from page 2 to page 3 preserving the state, but if I went back from page 3 to page 2, the state would be lost. Something simpler that you could try is not using extensions, but rather having an instance of the "extension class" in the controller "theData"... This would probably simplify your life :-)

Wednesday, January 28, 2009

Sorting any List<sObject>.

I recently developed an utility class that sorts any List retrieved using SOQL. This blog explains how you can use the class and how I developed it (maybe you can grab some ideas for similar classes you are building).

Using this class is quite simple, and because I have written unit tests that validates 100% of the code you can easily use it in production sytems.

This class can can sort any List on any field. It can sort the list either in ascending or descending order. The class does not care what type of sObjects are in the list, so you could use it for custom and/or standard sObjects.

Performance

  • The class performs quite well because the sorting is done in memory (using Maps, Sets and Lists).
  • The class also caches previous sorts, so if it detects that you are re-sorting on a previously sorted field, it uses the information from the cache and does not re-sort.
  • If you sort in ascending order and then request a sort in descending order for the same field, the class uses the data in the cache.
How to use the class?

Before going into details of the Appex class, let me show you how the class can be used. For this example, I'm going to build a VisualForce page that shows a datatable with information from the Contact sObject. The table has three columns with three columns (Contact name, Contact phone, Account related to this contact).

The VisualForce page:

<apex:page controller="SorterContact">
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockSection columns="1" ID="AjaxTable">
                <apex:datatable value="{!List}" var="acc" Border="1" cellspacing="1" cellpadding="5">
                    <apex:column >
                        <apex:facet name="header">
                            <apex:commandButton action="{!SortByName}" value="Sort By Name" rerender="AjaxTable" />
                        </apex:facet>
                        <apex:outputText value="{!acc.Name}"></apex:outputText>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:commandButton action="{!SortByPhone}" value="Sort By Phone" rerender="AjaxTable" />
                        </apex:facet>
                        <apex:outputText value="{!acc.Phone}"></apex:outputText>
                    </apex:column>
                    <apex:column >
                        <apex:facet name="header">
                            <apex:commandButton action="{!SortByAccount}" value="Sort By Account" rerender="AjaxTable" />
                        </apex:facet>
                        <apex:outputText value="{!acc.Account.Name}"></apex:outputText>
                    </apex:column>
                </apex:datatable>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>
Nothing fancy here... Just building a page like this:

When the buttons on the header are clicked, the table is sorted either ascending or descending. If the user selects a different column from the previous one, then the table gest sorted ascending by that column. But when the user clicks on the button for the same column, then the sort order gets reversed from ascending to descending and viceversa.

The controller:

Couple things going in here, but that is just to make the page look nice... Nothing really to do with the sorting.

public class SorterContact {
    private String sortedBy = null;
    private Boolean sortAscending = null;
    private AP_SortHelper sorter = new AP_SortHelper();
    private List<Contact> sortedList = null;

    public SorterContact() {
        sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact];
    }
    public PageReference SortByName() {
        setSortedBy('NAME');
        sortedList = (List<Contact>) sorter.getSortedList('Name', sortAscending);
        return null;
    }
    public PageReference SortByAccount() {
        setSortedBy('ACCOUNT');
        sortedList = (List<Contact>) sorter.getSortedList('Account.Name', sortAscending);
        return null;
    }
    public PageReference SortByPhone() {
        setSortedBy('PHONE');
        sortedList = (List<Contact>) sorter.getSortedList('Phone', sortAscending);
        return null;
    }
    public List<Contact> getList() {
        if (sortedList == null) {
            SortByName();
        }
        return sortedList;
    }
    private void setSortedBy(String value) {
        if (sortedBy == value) {
             sortAscending = !sortAscending;
        } else {
            sortAscending = true;
        }
        sortedBy = value;
    }
}
Let me talk about the easy part first...

There are methods that answer the calls from the commandbuttons on the page:

  • SortByName()
  • SortByAccount()
  • SortByPhone()
These methods follow the same structure:

public PageReference SortByName() {
   setSortedBy('NAME');
   sortedList = (List<contact>) sorter.getSortedList('Name', sortAscending);
   return null;
}
First, it calls a method setSortedBy() to find out the ascending or descending order. If the user clicks on a different button, the table is sorted ascending by that column, ortherwise the order is inverted from Ascending to descending and viceversa.

Second, it calls the method in the Appex class that does the sorting. (I will explain on detail how to use the Appex class, keep reading)

Finally, the controller's method returns a null value to the page.

The controller's constructor gets the list from the database.

public SorterContact() {
   sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact];
}
Since the buttons use the rerendered propery, and therefore a partial page refresh is performed using AJAX, the class constructor is only called at the initial page load rather than every time the buttons are clicked. This means the SOQL statement gets called only once regardless of how many times the data table gets sorted.

AP_SortHelper:

Finally, the more interesting part... This is the Appex class that does the sorting. Please note that you do not need to understand how this class is built in order to be able to use it (just grab the source code below and paste it into salesforce.com).

public class AP_SortHelper {
    private Map<String, Integer> listPosition = null; // <ID, Position>
    private Map<String, List<String>> sortedFieldValuesPerFieldName = null; // <FieldName, <FieldValues>>
    private Map<String, Map<String, List<String>>> sObjectIDsPerFieldNames = null; // <FieldName, <FieldValue, <IDs>>>

// Properties
    public List<sObject> originalList {get; set;}

// Constructor
    public AP_SortHelper() {
        originalList = null;
    }

// Public Method
    public List<sObject> getSortedList(String fieldName, Boolean ascending) {
        if (originalList == null) {
            // Assume that originalList has a not NULL value.
            // If the class who uses this method has not assigned a value it will get an Exception which
            //    needs to be handled by the calling class.

            // Force the exception...
            originalList.clear();
        }

        // Make field name uppercase
        fieldName = fieldName.toUpperCase();

        // Get sorted list
        return makeSortedList(fieldName, ascending);
    }
    public List<sObject> getSortedList(List<sObject> originalList, String fieldName, Boolean ascending) {
        this.originalList = originalList;
        sortedFieldValuesPerFieldName = null;
        return getSortedList(fieldName, ascending);
    }
    public static String getValue(sObject sObj, String fieldName) {
        // This returns the sObject desired in case the fieldName refers to a linked object.
        Integer pieceCount;
        String[] fieldNamePieces;
   
        fieldNamePieces = fieldName.split('\\.');
        pieceCount = fieldNamePieces.size();
        for (Integer i = 0; i < (pieceCount-1); i++) {
            sObj = sObj.getSObject(fieldNamePieces[i]);
        }
        return String.valueOf(sObj.get(fieldNamePieces[pieceCount-1]));
    }


// Private Methods
    private void InitializeFieldName(String fieldName) {
        String sObjectID;
        Integer position;
        String fieldValue;
        List<String> sObjectIDs = null;
        Set<String> valuesForFieldSet = null;    // Sets automatically omit duplicate values
        List<String> valuesForFieldList = null;
        Map<String, List<String>> sObjectIDsPerFieldValues = null;
   
        // Make sortedFieldValuesPerFieldName
        if (sortedFieldValuesPerFieldName == null) {
            listPosition = new Map<String, Integer>();
            sortedFieldValuesPerFieldName = new Map<String, List<String>>();
            sObjectIDsPerFieldNames = new Map<String, Map<String, List<String>>>();
        }
   
        // Get (or create) map of sObjectIDsPerFieldValues
        sObjectIDsPerFieldValues = sObjectIDsPerFieldNames.get(fieldName);
        if (sObjectIDsPerFieldValues == null) {
            sObjectIDsPerFieldValues = new Map<String, List<String>>();
            sObjectIDsPerFieldNames.put(fieldName, sObjectIDsPerFieldValues);
        }
        if (!sortedFieldValuesPerFieldName.keySet().contains(fieldName)) {
            // Objects need to be initialized
            position = 0;
            valuesForFieldSet = new Set<String>();
            listPosition = new Map<String, Integer>();
       
            for (sObject sObj : originalList) {
                sObjectID = sObj.ID;
                fieldValue = getValue(sObj, fieldName);
           
                // Add position to list
                listPosition.put(sObjectID, position++);
           
                // Add the value to the set (sets rather than lists to prevent duplicates)
                valuesForFieldSet.add(fieldValue);
           
                // Get (or create) map of sObjectIDs
                sObjectIDs = sObjectIDsPerFieldValues.get(fieldValue);
                if (sObjectIDs == null) {
                    sObjectIDs = new List<String>();
                    sObjectIDsPerFieldValues.put(fieldValue, sObjectIDs);
                }
           
                // Add ID to sObjectIDs
                sObjectIDs.add(sObjectID);
            }
       
            // Sort set items (Need to convert to list)
            valuesForFieldList = new List<String>();
            valuesForFieldList.addAll(valuesForFieldSet);
            valuesForFieldList.sort();
       
            // Now add it to the map.
            sortedFieldValuesPerFieldName.put(fieldName, valuesForFieldList);
        }
    }
    private List<sObject> makeSortedList(String fieldName, Boolean ascending) {
        Integer position;
        List<String> sObjectIDs = null;
        List<String> valuesForFieldList = null;

        // Initialize objects
        InitializeFieldName(fieldName);

        // Get a list of the same type as the "originalList"
        List<sObject> outputList = originalList.clone();
        outputList.clear();

        // Get a list of sorted values
        valuesForFieldList = sortedFieldValuesPerFieldName.get(fieldName);
   
        // for each sorted value
        for (String fieldValue : valuesForFieldList) {
            // Get lisft of IDs
            sObjectIDs = sObjectIDsPerFieldNames.get(fieldName).get(fieldValue);
       
            // for each ID
            for (String ID : sObjectIDs) {
                // Get position in originalList
                position = listPosition.get(ID);

                // Add each sObject to the list.
                if ((ascending)  (outputList.size()==0)) {
                    outputList.add(originalList[position]);
                } else {
                    outputList.add(0, originalList[position]);
                }
            }
        }
        return outputList;
    }

// Unit testing
/*
    static testMethod void testSortCustomObject() {
        List<TPValue__c> TPValues;
        AP_SortHelper sorter = new AP_SortHelper();
        String fieldName;
   
        TPValues = [SELECT TPName__r.TPName__c, Value__c FROM TPValue__c LIMIT 50];
        fieldName = 'Value__c';
        testOrderedList(sorter.getSortedList(TPValues, fieldName, true), fieldName, true);
   
        fieldName = 'TPName__r.TPName__c';
        testOrderedList(sorter.getSortedList(TPValues, fieldName, true), fieldName, true);
    }
*/
    static testMethod void testSimpleField_Ascending() {
        testSortingContacts('Name', true);
    }
    static testMethod void testSimpleField_Descending() {
        testSortingContacts('Name', False);
    }
    static testMethod void testLookupField_Ascending() {
        testSortingContacts('Account.Name', True);
    }
    static testMethod void testLookupField_Decending() {
        testSortingContacts('Account.Name', False);
    }
    static testMethod void testMultipleCalls() {
        AP_SortHelper sorter;
        sorter = testSortingContacts(null, 'Name', true);
        testSortingContacts(sorter, 'Name', False);
        testSortingContacts(sorter, 'Account.Name', True);
        testSortingContacts(sorter, 'Account.Name', False);
    }
    static testMethod void testForgotOriginalList() {
        Boolean exceptionDetected = false;
        AP_SortHelper sorter = new AP_SortHelper();
        try {
            sorter.getSortedList('Name', true);
        } catch (NullPointerException e) {
            exceptionDetected = true;
        }
        System.assert(exceptionDetected);
    }
    static testMethod void testPassingList() {
        AP_SortHelper sorter = new AP_SortHelper();
        List<Contact> contacts = [SELECT Name, Phone, Account.Name FROM Contact LIMIT 50];
        List<Contact> sortedList = (List<Contact>) sorter.getSortedList(contacts, 'Name', true);
        testOrderedList(sortedList, 'Name', true);
    }
    private static void testSortingContacts(string fieldName, Boolean isAscending) {
        testSortingContacts(null, fieldName, isAscending);
    }
    private static AP_SortHelper testSortingContacts(AP_SortHelper sorter, string fieldName, Boolean isAscending) {
        // If sorted is null,create it.   
        if (sorter == null) {
            sorter = new AP_SortHelper();
            sorter.originalList = [SELECT Name, Phone, Account.Name FROM Contact LIMIT 50];
        }
   
        // Sort list
        List<Contact> sortedList = (List<Contact>) sorter.getSortedList(fieldName, isAscending);

        // Test sort order
        testOrderedList(sortedList, fieldName, isAscending);
   
        return sorter;   
    }
    private static void testOrderedList(List<sObject> sortedList, string fieldName, Boolean isAscending) {
        String lastValue = null;
        String currentValue = null;

        for (sObject sObj : sortedList) {
            currentValue = getValue(sObj, fieldName);
            if ((lastValue != null) && (currentValue != null)) {

                String strDebug = '';
                strDebug += '\n--------------------------------------------------------------';
                strDebug += '\nSTART';
                strDebug += '\n--------------------------------------------------------------';
                strDebug += '\n[Ascending:'+isAscending+']';
                strDebug += '\n[Previous:'+lastValue+'] [IsNull():'+(lastValue==null)+']';
                strDebug += '\n[Current:'+currentValue+'] [IsNull():'+(currentValue==null)+']';
                strDebug += '\n[CompareTo:'+(currentValue.compareTo(lastValue))+']';
                strDebug += '\n--------------------------------------------------------------';
                strDebug += '\nEND';
                strDebug += '\n--------------------------------------------------------------';
                System.debug(strDebug);

                if (isAscending) {
                    System.assertEquals(currentValue.compareTo(lastValue)>=0, true);
                } else {
                    System.assertEquals(currentValue.compareTo(lastValue)<=0, true);
                }
            }
            lastValue = currentValue;
        }
    }
}
Summary: How to use this class?

  1. Copy the source code above into Salesforce.com
  2. Create an instance of this class AP_SortHelper()
  3. Assign the list to sort (this list is created when you execute a valid SOQL statement)
  4. Call the getSortedList() method which takes two parameters:
    1. The name of the field as it was used in the SOQL
    2. The order (true for ascending, false for descending)
Please let me know if there are people interested in the explanation of how the sorter class works. If there are enough people interested, I'll revisit this post and explain how I did it.

Tuesday, January 27, 2009

Salesforce Very Useful Documents

I keep this documents open all the time

Force.com Sites Developer Challange

While learning about salesforce.com and developing software for the cloud, I found the "Force.com Sites Developer Challenge". One of the things that attracted my attention was that any site submitted to them would receive a T-Shirt.

I developed this site on December 2008 while I was looking for work, to solve a problem I was having:

  • There are many jobs posted daily on the job boards that a person may want to apply, but it is so time consuming to personalize each cover letter (which Human Resource departments love to receive).
  • Additionally, there needs to be a way to keep track of the networking activities that a person looking for a job needs to do.

It started being a simple cover letter creator, but it turned out to be a really good "mail merge" application which uses special functions (NOT_EMPTY(), IF(), ...) to do the merging and the merged letter can use system parameters or custorm parameters with default values or values specific per document.

Use this information to check out the site: URL: http://aperez-developer-edition.na6.force.com/ResumeHome Username: test2@apc.com Password: testpwd2a I created an users guide document that explains the site. You can download the document here.

Let me know if you want to see the code for this site and I will be glad to expand this post.

Who am I?

I am a Salesforce.com consultant in Markham, Ontario and I love helping other salesforce.com developers with their tough questions.

I learned about salesforce.com in December 2008, and I was immediatly captured by the revolutionary idea of Developement-as-a-service. While learning about the technology and the tools used to develop for the cloud, I learned about the "Force.com Sites Developer Challenge" which were giving away a t-shirt for creating a site using their technology. I decided to join the challenge and get a t-shirt, although I did not win any of the prizes... I did get my T-shirt.

Once I had become familiar with the technology, I decided to start helping users in the community boards. One day, I designed a Apex class that Sorts Any List and I had no place to post it so that users like you would be able to read about it, download it and use it. This is when I decided to create this blog.

Enjoy.