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.LastName = '/resource/crmscience_resources/';
       c.LastName += (i <= getCurrentRating()) ? 'RatingOn' : 'RatingOff';
       c.LastName += '.png';
       c.Title = 'Set rating to ' + string.valueof(i);
        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);
        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}" />

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

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