Dec 30, 2011

Can't assign a default value to apex:attribute of type boolean

A couple of weeks ago I wrote a component that included several apex attribute tags. One of them was a flag that tells the control if it should be shown in a "disabled" mode. The normal mode for the control is enabled so I set the Type attribute to "boolean" and the Default attribute to "false". When I saved the control, I got the error "Literal value is required for attribute". At first, I didn't know what it was talking about, but after some experimentation, I realized that the Default attribute was causing the problem. I tried 0/1 and several true/false capitalizations, but nothing worked. That led me to the conclusion that in the case of boolean apex:attribute tags, SF does not cast the defaule value it gets into a boolean correctly (it expects a boolean value but tries to put a literal in it).

I still needed that to work, so instead of using the Default attribute, I set up a property in the class to handle the default value, and it worked like a charm...
Visualforce page:

<apex:attribute name="enabled" type="boolean" required="false" assignTo="{!controlEnabled}" />


Class:

public boolean controlEnabled { get { if(controlEnabled == null) controlEnabled = true; return controlEnabled; } set; }

Dec 23, 2011

Graphic Contact Ratings in Salesforce

Stop reading!
I improved this control in a later post... Find it here.


As part of my ongoing effort to spruce up the look and feel of salesforce pages, I created a rating control that allows users to easily rate a contact. The finished control looks like the following image, where each star is clickable and will set the rating to that many stars.

A few 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 Contacts 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.
  • The page reloads after a user selection. That's not great, and at some point I'll try to make this dynamic and repost.

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; }   // used by Rating.component and RatingNode.component
    public integer maxRating { get; set; }  // used by Rating.component
    public integer Location { get; set; }   // used by RatingNode.component
    public string ImageURL { get; set; }   // used by RatingNode.component
    
    // Calss constructors
    public Rating() { }
    public Rating(ApexPages.StandardController stdController) { }

////////////////////////////////////////
//  Methods for Rating.component
////////////////////////////////////////

    //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<Contact> Nodes;
    public Contact[] getNodes() {
        if (Nodes == null) {
            Nodes = new List<Contact>();
     for (Integer i = 1; i <= maxRating; i++) {
  contact c = new Contact(LastName=string.valueOf(i));
  c.FirstName = '/resource/MyReatingResource/';
  c.FirstName += (i <= getCurrentRating()) ? 'RatingOn' : 'RatingOff';
  c.FirstName += '.png';
  Nodes.add(c);
     }            
        }
        return Nodes;
    }

////////////////////////////////////////
//  Methods for RatingNodes.component
////////////////////////////////////////

    public string getTooltip() {
        return 'Set rating to ' + string.valueof(location);
    }
    
    // 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 RatingNode.component (used for each node in the rating):

<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="location" type="integer" required="true" assignTo="{!Location}"
                    description="The location of this node in the rating." />
    <apex:attribute name="ImageURL" type="string" required="true" assignTo="{!ImageURL}"
                    description="The url for this node image (already set to off/on)" />

 <apex:commandLink action="{!SaveRating}" title="{!Tooltip}" >
     <apex:image url="{!ImageURL}" />
 </apex:commandLink>

</apex:component>

Step four:
Create a visualforce component called Rating.component (used to assemble all the nodes together):

<apex:component controller="Rating">
    <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:repeat value="{!Nodes}" var="node" id="RatingRepeat">
         <c:RatingNode contactId="{!contactId}" Location="{!node.LastName}" ImageURL="{!node.FirstName}" />
     </apex:repeat>
    </apex:form>
    
</apex:component>

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


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