Jan 24, 2012

Handy Hover Popup CSS for Salesforce Lists/Tables

Hover popups are a quick way to spruce up your salesforce.com interfaces while providing your users valuable information that does not require a new page to load. This simple hover popup is using cascading style sheets, and is very easy to add to lists and tables in your visualforce pages.


Step one:
Add the following styles to a css file. Add the css file to a static resource zip (in this example, I am using crmscience_resources.zip and crmscience.css).
These classes work nicely with the standard pageBlockTable styles as they are set to a height that allows you to see the detail in the popup for each row as you move your mouse down the rows. You can change the look and feel by changing the styles - the last two styles are for the hidden section.
.HoverPopup {
    position: relative;
    z-index: 0;
    }

.HoverPopup:hover {
    background-color: #eeeeee;
    Text-decoration: none;
    z-index: 50;
    }

.HoverPopup div {
    position: absolute;
    visibility: hidden;
    }

.HoverPopup:hover div {
    visibility: visible;
    top: 20px;
    left: 0px;
    border: 1px solid black;
    background-color: #eeeeee;
    padding: 10px;
    color: black;
    Text-decoration: none;
    white-space: pre;
    }

Step two:
In the visualforce page you are going to include the hover popups, add the following right after the <apex:page> (change the resource and css file names to what you created). Note that this should go in the top level page, even if you include the code in step 3 in a separate component.


.....
.....
.....


Step three:
Add the following to your visualforce component or page. The {!c.Name} is the text/link that shows up in the table, and everything inside the div will show up in the hover popup. Be careful though... Salesforce adds a bunch of divs and spans everywhere, but as long as you use a div inside the outputLink you should be fine.

    
        {!c.Name}
            

Jan 12, 2012

Chart Control to Add Charts Without Writing New Code



A couple of years ago, I created a control that took chart data and used the Google Chart API to create a custom chart image inside Salesforce. That worked nicely, but I was happy to see that Salesforce finally added charting elements to visualforce in Winter '12.

Once I was added to the charting pilot, I spent some time exploring the new elements. Generally, it is a good start, but there are several features I would like to see (like customizable tooltips), and there are a few bugs that need to be cleaned up (for example, long labels are chopped when displayed in slice tooltips).

When I finished exploring and started creating some of these charts for a customer, I realized that most of the code behind for gathering and displaying chart data is almost identical. I like modular code, so I saw this as an opportunity to create a wrapper control that can take a few parameters and create the chart for you without having to write new apex/soql.

This code sample creates a pie chart when you give it the following parameters:
  • Field - The field name (standard or custom) that is grouped into pie slices.
  • Object - The object (standard or custom) that includes the above field.
  • Conditions - A SOQL string that contains conditions for the query (if needed).
  • Title - A title to display above the chart.
  • Height - The height of the chart area.
  • Width - The width of the chart area.
This control takes care of the null group (no value set), empty dataset, and allows you to add a good range of conditions. Other than that, it is pretty simple. Once set up though, it is easy to add some more bells and whistles. For example, in a later version, I added the ability to reference lookup fields for labels, and the ability to use the data with different chart types.

Step one:
Create an apex class Chart.cls to hold the code for the visualforce components:

public with sharing class Chart {

    // constructor
    public Chart() { }
    
    // set up properties to get the values from the calling vf page
    public string fld { get; set; }
    public string obj { get; set; }
    public string conditions { get; set; }
    public string title { get; set; }
    public string width { get; set; }
    public string height { get; set; }

    // class to hold data for the display 
    public class ChartData {

        public String label { get; set; }
        public Integer value { get; set; }

        public ChartData(String label, Integer value) {
            this.label = label;
            this.value = value;
        }
    }

    private List Counts;
    public List getCounts() {
        if (Counts == null) {
            if (fld !=null && obj != null) {
                Counts = new List();
             
                // construct the query for the results. add escape chars to conditions
                string strQuary = 'SELECT Count(Id), ' + fld + ' FROM ' + obj;
                if (conditions != null && conditions.length() > 0)
                    strQuary += ' WHERE ' + conditions.replaceall('\'', '\\\'');
                strQuary += ' GROUP BY ' + fld;

                AggregateResult[] groupedResults = Database.query(strQuary);
 
                if (groupedResults == null || groupedResults.IsEmpty())
                    // no results, just create a dummy chart
                    Counts.add(new ChartData('No Data', 1));
                else
                    for (AggregateResult ar : groupedResults)
                        // construct a list of the counts
                        if (string.valueof(ar.get(fld)) == null)
                            // capture the count for no value
                            Counts.add(new ChartData('No Value', integer.valueof(ar.get('expr0'))));
                        else
                            Counts.add(new ChartData(string.valueof(ar.get(fld)), integer.valueof(ar.get('expr0'))));
            }
        }
        return Counts;
    }
}
Step two:
Create a component Chart.component:

<apex:component controller="Chart">
    <apex:attribute name="Field" type="String" required="true" assignTo="{!fld}"
                    description="The field name (standard or custom) that is grouped into pie slices." />
    <apex:attribute name="Object" type="String" required="true" assignTo="{!obj}"
                    description="The object (standard or custom) that includes the above field." />
    <apex:attribute name="LookupField" type="String" required="false" assignTo="{!lookupFld}"
                    description="If the DataField is a lookup, this is the field holds the values that the lookup resolve to." />
    <apex:attribute name="LookupObject" type="String" required="false" assignTo="{!lookupObj}"
                    description="If the Field is a lookup, this is the object that LookupField is in." />
    <apex:attribute name="Conditions" type="String" required="false" assignTo="{!conditions}"
                    description="A SOQL string that contains conditions for the query (if needed)." />
    <apex:attribute name="Title" type="String" required="false" assignTo="{!title}"
                    description="A title to display above the chart." />
    <apex:attribute name="height" type="String" required="false" assignTo="{!height}" default="220"
                    description="The height of the chart area." />
    <apex:attribute name="width" type="String" required="false" assignTo="{!width}" default="300"
                    description="The width of the chart area." />

    <span style="font-size: 14px; font-weight: bold;">{!Title}</span><br/>
    <apex:chart height="{!height}" width="{!width}" data="{!Counts}">
        <apex:pieSeries dataField="value" labelField="label"/>
        <apex:legend position="right"/>
    </apex:chart>

</apex:component>
Step three:
Include the component in your visualforce page like that:


<c:Chart Field="Status__c" Object="CustomObj__c" Conditions="IsArchived__c = False" title="My Status" />


Jan 3, 2012

Contact Rating in Salesforce (Better Version)

A few weeks ago I posted code for a rating control that allows users to easily rate a contact. The finished control looks like the image on the left, where each star is clickable and will set the rating to that many stars.

The control was working great, but it was using two components instead of one, and also needed a page reload to work. It wasn't as elegant as I would like it to be, so I reworked the control (and it only took 10 minutes to rework...) Now the control is using the reRender attribute to refresh without reloading the page, and there is no need for the nested component.

Here are a few other things you should know about this code sample:
  • This code example is using the control with the Contact object although you can adjust it to use with any object.
  • To get the nodes to the visualforce page, I construct and populate a list of Contact objects with the node information. I did that because I am lazy - instead, you can easily create a class to hold the right info and make the field names make more sense.
  • The way I set it up, the rating range is set up by the control as opposed to hard code a one to five range.

Step one:
Create a zip with two images called RatingOn.png and RatingOff.png (I created a colored star and a grey star), and upload as a static resource.

Step two:
Create an apex class Rating.cls to hold the code for the visualforce components:

public with sharing class Rating {
 
    // set up properties to get the values from the calling vf page
    public string contactId { get; set; }
    public integer maxRating { get; set; }
    public integer Location { get; set; }
    public string ImageURL { get; set; }
    
    // Class constructor
    public Rating() { }

    //getter for the current rating of the contact
    private integer CurrentRating;
    public integer getCurrentRating() {
        if (CurrentRating == null) {
         Contact c = [SELECT Id, Rating__c FROM Contact WHERE Id = :contactId LIMIT 1];
         CurrentRating = (integer.valueOf(c.Rating__c) == null) ? 0 : integer.valueOf(c.Rating__c);
        }
        return CurrentRating;
    }

    public List Nodes;
    public Contact[] getNodes() {
        if (Nodes == null) {
            Nodes = new List();
   for (Integer i = 1; i <= maxRating; i++) {
    contact c = new Contact();
    c.FirstName=string.valueOf(i);
       c.LastName = '/resource/crmscience_resources/';
       c.LastName += (i <= getCurrentRating()) ? 'RatingOn' : 'RatingOff';
       c.LastName += '.png';
       c.Title = 'Set rating to ' + string.valueof(i);
       Nodes.add(c);
   }            
        }
        return Nodes;
    }

    // save the new rating and reload the page
    public PageReference SaveRating() {
        Contact c = [SELECT Id, Rating__c FROM Contact WHERE Id = :contactId LIMIT 1];
        c.Rating__c = Location;
        update c;    

        PageReference callPage = new PageReference('/' + contactId);
        callPage.setRedirect(true);
        return callpage;
    }
}

Step three:
Create a visualforce component called Rating.component:

<apex:component controller="Rating" allowDML="True">
    <apex:attribute name="contactId" type="String" required="true" assignTo="{!contactId}"
                    description="This is the contact Id to bind the results to." />
    <apex:attribute name="maxRating" type="integer" required="true" assignTo="{!maxRating}"
                    description="This is the desired maximum rating." />

<apex:form >
    <apex:outputPanel id="Rating">
        <apex:repeat value="{!Nodes}" var="node" id="RatingRepeat2">            
      <apex:commandLink action="{!SaveRating}" rerender="Rating" title="{!node.Title}" >
                <apex:param name="Location" assignTo="{!Location}" value="{!node.FirstName}" />
          <apex:image url="{!node.LastName}" />
      </apex:commandLink>
        </apex:repeat>
    </apex:outputPanel>
</apex:form>
</apex:component>

Step four:
Include the component in your visualforce page like that:


<c:Rating contactId="{!Contact.Id}" maxRating="5" >


Jan 2, 2012

Custom Salesforce Email Service

Email to Salesforce is a nice shrink-wrapped feature, but it lacks some functionality that I think is very necessary. For example, when Highrise CRM users using the Highrise dropbox feature, the email service looks for matching email addresses in the body of the email in addition to the TO and CC lists. To create that functionality in Salesforce, you need to write your own email service.

In this post I am going to concentrate on what I wrote to parse the email addresses out of the TO and CC lists as well as the body of the email. Parsing the TO and CC lists is necessary because these arrays usually include texts that looks like this - "John Doe <john@doe.com>".

I was able to create one method that will do the work for both the lists and the email body. To do that I used the apex classes Pattern and Matcher, which are very useful, but pretty confusing to work with. These classes give you the ability to search through strings and return matches based on a regex (regular expression). I always find it very tedious to write regexes, so I borrowed an email regex from Jan Goyvaerts.

This method is pretty handy. I also used it to find the "from:" email address of a forwarded email so I can create a contact if needed (I didn't include that piece of code as it is using the same stuff - Pattern, Matcher, and my handy method). Note that if you only want to find the first email address in a given string, you only need to change the while to an if.

global class MyEmailService implements Messaging.InboundEmailHandler {

    global Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail email, Messaging.InboundEnvelope env) {
 
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
  
        // declare an array to hold all the email addresses that we find
        String[] emailAddresses = new String[0];

        // get all the to and cc addresses
        emailAddresses.addall(getEmails(email.toAddresses));
        emailAddresses.addall(getEmails(email.ccAddresses));

        // get all email addresses from the body of the email
        emailAddresses.addall(getEmails(new string[]{email.plainTextBody}));


        ....... Do something with the email addresses we found ......


        return result;
    }

    // get email addresses from an array of strings
    public String[] getEmails(String[] strings) { 
 
        // set up an array to hold the emails
        String[] emailAddresses = new String[0];

        if (strings != null) {
            
            // set up a pattern with an email regex (assume lower case)
            Pattern emailPattern = Pattern.compile('\\b[a-z0-9._%-]+@[a-z0-9.-]+\\.[a-z]{2,4}\\b');
            Matcher emailMatcher;

            // go through each string and add any valid email address to the return array
            for (string str : strings) {
             emailMatcher = emailPattern.matcher(str.toLowerCase());
             while (emailMatcher.find())
                 emailAddresses.add(str.substring(emailMatcher.start(), emailMatcher.end()));
            }
        }
        return emailAddresses; 
    }
}