Pages

Tuesday, December 23, 2008

URLFOR function finally explained!

While developing your Visualforce pages you may need to be able to obtain the URL of certain actions, s-controls or your static resources.

I found it personally a challenge since the documentation for "URLFOR" function is not included in "Visualforce Developer Guide" itself and instead included in the general help area of Salesforce.

Generally you can use the "URLFOR" function for three purposes:
  1. Obtain URL of a s-control
  2. Obtain URL of a static resource
  3. Obtain URL of an object's action
In this article I will demonstrate usages of the three above.

Generally, URLFOR function returns a relative URL for an action, s-control, or a file in a static resource archive in a Visualforce page. Following the syntax of the function:

{!URLFOR(target, id, [inputs], [no override])}

Parameters shown in brackets ([]) are optional.

  • target: You can replace target with a URL or action, s-control or static resource.
  • id: This is id of the object or resource name (string type) in support of the provided target.
  • inputs: Any additional URL parameters you need to pass you can use this parameter.
    you will to put the URL parameters in brackets and separate them with commas
    ex: [param1="value1", param2="value2"]
  • no override: A Boolean value which defaults to false, it applies to targets for standard Salesforce pages. Replace "no override" with "true" when you want to display a standard Salesforce page regardless of whether you have defined an override for it elsewhere.

Obtaining URL of a s-control:

<!-- Use $SControl global veriable to reference your s-control and pass it to the URLFOR function -->
<apex:outputLink value="{!URLFOR($SControl.MySControlName)}">Link to my S-Control</apex:outputLink>



Obtaining URL of a Static Resource

<!-- Use $Resource global veriable to reference your resource file -->
<apex:image url="{!URLFOR($Resource.LogoImg)}" width="50" height="50" />

<!-- If your file is in another ZIP file, then pass the path of the file as id to URLFOR -->
<apex:image url="{!URLFOR($Resource.CorpZip, 'images/logo.gif')}" width="50" height="50" />




Obtaining URLs of an Object's Actions:
In order to get URL of the an object's actions you need to know what actions that object supports. Below are some of the common actions most Objects support:
  • View: Shows the detail page of an object
  • Edit: Shows the object in Edit mode
  • Delete: URL for deleting an object
  • New: URL to create a new record of an object
  • Tab: URL to the home page of an object
However, each object may support additional actions for example Contact also supports "Clone" action and Case supports "CloseCase" action.

<!-- Use $Action global varialble to access the New action reference -->
<apex:outputLink value="{!URLFOR($Action.Account.New)}">New</apex:outputLink>
<br/>
<!-- View action requires the id parameter, a standard controller can be used to obtain the id -->
<apex:outputLink value="{!URLFOR($Action.Account.view, account.id)}">View</apex:outputLink>
<br/>
<!-- Edit action requires the id parameter, id is taken from standard controller in this example -->
<apex:outputLink value="{!URLFOR($Action.Account.Edit, account.id)}">Edit</apex:outputLink>
<br/>
<!-- Delete action requires the id parameter, also a confirm message is added to prevent deleting the record when clicked by mistake -->
<apex:outputLink value="{!URLFOR($Action.Account.delete, account.id)}" onclick="return window.confirm('Are you sure?');">Delete</apex:outputLink>
<br/>
<!-- From all custom buttons, links, s-controls and visualforce pages you can use the following to get the link of the object's homepage -->
<apex:outputLink value="{!URLFOR($Action.Account.Tab, $ObjectType.Account)}">Home</apex:outputLink>



Thursday, December 4, 2008

Modifying the Default Force.com Site Template to Build am Excellent Customer Portal

Force.com Sites were released on December 2, 2008 (just two days ago) and we are all so excited about this new feature.

Now you can potentially create Customer/Partner portals that are integrated with your CRM readily, this is big advantage plus that with ease of Visualforce's technology and shorter development life-cycles now you can deliver with a much faster pace.

In this article I will demonstrate how fast we can modify our sites' template to customize the looked and feel our customer, partner, etc portals.

With each new site that you create there are a few new Visualforce pages and components added to your box. some of those are:

User Management Pages:
  • SiteLogin
  • SiteRegister
  • SiteRegisterConfirm
  • ForgotPassword
  • ForgotPasswordConfirm
  • Unauthorized

Template:
  • SiteTemplate

Components:
  • SiteLogin
  • SiteHeader
  • SiteFooter
Also a few more pages for handling errors and such.

Now let's take a closer look at the SiteTemplate:



<!-- showHeader is turned off so we won't see any Salesforce standard tabs or sidebar -->
<apex:page showHeader="false" id="SiteTemplate" standardStylesheets="true">
<!-- A section is reserved for header of the site -->
<apex:insert name="header">
<c:SiteHeader />
<hr/>
</apex:insert>
<!-- This section is where you can include the content of each page of your site-->
<apex:insert name="body"/>
<!-- A section is reserved for footer of the site -->
<apex:insert name="footer">
<hr/>
<c:SiteFooter />
</apex:insert>
</apex:page>



Generally it's a good practice that you follow the same pattern in developing your sites.

Furthermore, it's best to include your logo, log in and out links, any headline that remains the same throughout your site and above all your site navigation menu to be included in the "SiteHeader" component.

The same way, you can modify the "SiteFooter" component to add your site's pages footer template. It's good practice to include your site's shortcuts links and your company copy right message in this section.

The shortcuts links in this section particularly is good for search engine purpose, in any case you may be using dynamic (javascript enabled) menus, so this part will compensate for that.

Now let's see how we can benefits from all this. At this point you can start adding pages into your site. For your new pages to be able to utilize your template you need to do the following:




<apex:page showHeader="false" standardStylesheets="true">
<apex:composition template="{!$Site.Template}">
<apex:define name="body">
<!-- Page's Content go here -->
</apex:define>
</apex:composition>
</apex:page>





The above code, the Composite component enables the page to use our site template.

Also if you need to overwrite the existing template's section (Insert Component), you will need to add a "apex:define" component with the same name attribute as the "insert" component in the template.

So in order to overwrite the "body" section of the template, you will need write this line:

<apex:define name="body">
... You page's body...
<apex:define>

And one more thing, in order to be able to use your Static Resources in your force.com Sites, you are required to set the Cache mode of those resources to public. By default all them are in private mode.

Click here to see a usage sample of such template in action:
http://zagrus-developer-edition.na5.force.com/Customer

Wednesday, November 12, 2008

Adding Sorting Capability to PageBlockTable Component

In one of my previous posts I demonstrated how to add paging feature to your Visualforce Pages, in this article we will explore how we can add sorting option to the PageBlockTable component.

Challenges:
  • How to replace standard header text with a link that calls a controller method for sorting the data
  • How to implement the link in such a way to support sort direction Ascending and Descending features
  • How to view Ascending and Descending state of the columns
And this is one way of meeting the above challenges:
I first replace the standard header of the Column tag with a facet tag to include a commandLink component in it.

Then I select the column name to be pasted as parameter to the controller once the commandLink header is clicked on.

Then I add some condition to the Label of header to show "Ascending" and "Descending" icons.



<apex:page controller="PageBlockTableSortingCon" tabStyle="Account">
<apex:sectionHeader title="Accounts List with Sorting"></apex:sectionHeader>
<apex:form >
<apex:pageBlock title="" id="pageBlock">
<apex:pageBlockButtons location="top">
<apex:commandButton value="View" action="{!ViewData}" id="theButton" rerender="pageBlock"></apex:commandButton>
</apex:pageBlockButtons>
<apex:pageMessages ></apex:pageMessages>
<apex:pageBlockTable value="{!accounts}" var="a" rendered="{!NOT(ISNULL(accounts))}">
<apex:column>
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Account Name{!IF(sortExpression=='name',IF(sortDirection='ASC','▼','▲'),'')}" id="cmdSort">
<apex:param value="name" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
<apex:outputLink value="/{!a.Id}" target="_blank">{!a.Name}</apex:outputLink>
</apex:column>
<apex:column value="{!a.Phone}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Phone{!IF(sortExpression=='Phone',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="Phone" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>
<apex:column value="{!a.BillingCity}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Billing City{!IF(sortExpression=='BillingCity',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="BillingCity" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>
<apex:column value="{!a.BillingCountry}">
<apex:facet name="header">
<apex:commandLink action="{!ViewData}" value="Billing Country{!IF(sortExpression=='BillingCountry',IF(sortDirection='ASC','▼','▲'),'')}">
<apex:param value="BillingCountry" name="column" assignTo="{!sortExpression}" ></apex:param>
</apex:commandLink>
</apex:facet>
</apex:column>

</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>





And this how the controller look like:




public class PageBlockTableSortingCon {

private List<Account> accounts;
private String sortDirection = 'ASC';
private String sortExp = 'name';

public String sortExpression
{
get
{
return sortExp;
}
set
{
//if the column is clicked on then switch between Ascending and Descending modes
if (value == sortExp)
sortDirection = (sortDirection == 'ASC')? 'DESC' : 'ASC';
else
sortDirection = 'ASC';
sortExp = value;
}
}

public String getSortDirection()
{
//if not column is selected
if (sortExpression == null || sortExpression == '')
return 'ASC';
else
return sortDirection;
}

public void setSortDirection(String value)
{
sortDirection = value;
}

public List<Account> getAccounts() {
return accounts;
}


public PageReference ViewData() {
//build the full sort expression
string sortFullExp = sortExpression + ' ' + sortDirection;

//query the database based on the sort expression
accounts = Database.query('Select id, Name, BillingCity, BillingCountry, Phone from Account order by ' + sortFullExp + ' limit 1000');
return null;
}

}



Friday, November 7, 2008

A Utility Apex Class to Convert All Types Into String

If you have been in the programming world long enough, you already know that casting types from one to another specially string representation of the variables is always part the job.

Whether you want to show the data to user or form the variables in different formats, incorporate them in messages, etc.

In this article I will present you one of the Apex classes I have written to help me faster develop Salesforce Applications.

This class helps me in number of ways, for example I do not need to worry about the underlying Apex code to convert a primitive type to string anymore, for all types I just need to call one method "ToString" and it will take care of for me.
Also the methods provide me with formatting capabilities, so I can not only convert to string but also format the string in many ways I need.

Look at the below code and see how the results are:



ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Integer: '+ zConvert.ToString(13434)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Double: '+ zConvert.ToString(1.23)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Boolean: '+ zConvert.ToString(true)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Date: ' + zConvert.ToString(date.newinstance(1960, 2, 17))));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Date time: ' + zConvert.ToString(Datetime.now(),'MMM, dd yyyy')));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'File Size: '+ zConvert.FileSizeToString(6766767)));
 ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Money: '+ zConvert.CurrencyToString(Decimal.valueOf(34.99),'$'))




This is the result of running the above code:



Well, very nice, now let's see how the actual class is developed:
Because I would like to directly call my Coverter's class methods without creating a new instance of the class, I have defined all the methods as "static".

The methods for this class are:
  • ToString(Integer)
  • ToString(Double)
  • ToString(Long)
  • ToString(Boolean)
  • ToString(Date)
  • ToString(Date,format)
    sample: zConvert.ToString(mydate,'MM-dd-yy')
  • ToString(Time)
  • ToString(Time,format)
    sample: zConvert.ToString(myTime,'hh:mm:ss')
  • ToString(Datetime)
  • ToString(Datetime,format)
  • ToString(Decimal)
  • ToString(Decimal, ScientificNotaion)
    ScientificNotaion is a Boolean value and if false is passed then the string will not have scientific notations.
  • FileSizeToString(Long)
    Returns values such as "5.5 KB", "8 MB", etc. Parameter passed is in bytes.
  • CurrencyToString(Decimal, CurrencyChar)
    CurrencyChar can be "$", "£", etc




public class zConvert
{
 /* The Initial Developer of the Original Code is Sam Arjmandi.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved. 
 * 
 * This Code is provided "As Is" without warranty of any kind.
 */
 
  public static String ToString(integer Value)
  {
      /* string representation if an Integer value */
      return Value.format();
  }
 
  public static String ToString(Double Value)
  {
    /* string representation if a Double value */
     return Value.format();
  }
 
  public static String ToString(Boolean Value)
  {
     /* string representation if a Boolean value */
     if (Value)
       return 'true';
     else
       return 'false';
  }
 
  public static String ToString(Long Value)
  {
    /* string representation if a Long value */
    return Value.format();
  }
 
  public static String ToString(Date Value)
  {
     /* string representation if a Date value */
     return Value.format();
  }
 
  public static String ToString(Date Value,String format)
  {
    /* string representation if a Date value with formatting */
    Datetime temp = Datetime.newInstance(Value.year(), Value.month(), Value.day());
    return temp.format(format);
  }
 
  public static String ToString(Datetime Value)
  {
     /* string representation if a Datetime value */
     return Value.format();
  }
 
  public static String ToString(Datetime Value,String format)
  {
     /* string representation if a Datetime value with formatting */
     return Value.format(format);
  }
 
  public static String ToString(Time Value)
  {
    /* string representation if a Time value */
    return String.valueOf(Value);
  }
 
  public static String ToString(Time Value, String format)
  {
    /* string representation if a Time value with formating */
    Datetime temp = Datetime.newInstance(1970, 1, 1, Value.hour(), Value.minute(), Value.second());
    return temp.format(format);
  }

  public static String ToString(Decimal Value)
  {
    /* string representation if a Decimal value */
    return Value.format();
  }
 
  public static String ToString(Decimal Value, Boolean ScientificNotation)
  {
    /* string representation if a Decimal value with or without Scientific Notation */
    if (ScientificNotation)
     return Value.format();
    else
     return Value.toPlainString();
  }
 
  public static String FileSizeToString(Long Value)
  {
     /* string representation if a file's size, such as 2 KB, 4.1 MB, etc */
     if (Value < 1024)
       return ToString(Value) + ' Bytes';
     else
     if (Value >= 1024 && Value < (1024*1024))
     {
       //KB
       Decimal kb = Decimal.valueOf(Value);
       kb = kb.divide(1024,2);
       return ToString(kb) + ' KB';
     }
     else
     if (Value >= (1024*1024) && Value < (1024*1024*1024))
     {
       //MB
       Decimal mb = Decimal.valueOf(Value);
       mb = mb.divide((1024*1024),2);
       return ToString(mb) + ' MB';
     }
     else
     {
       //GB
       Decimal gb = Decimal.valueOf(Value);
       gb = gb.divide((1024*1024*1024),2);
      
       return ToString(gb) + ' GB';
     }
    
  }
 
  public static String CurrencyToString(Decimal Value, String CurrencyChar)
  {
     return CurrencyChar + ToString(Value);
  }
 
}



Tuesday, October 28, 2008

How to Easily Bring Graphical Charts into your CRM?

There is no doubt that nothing communicates better and faster than graphics with users. It has been always the case that users fail to utilize software solutions merely because the information is not delivered to them intuitively, comprehensively and graphically.

Managers often require summarized business critical information quick and easy to grasp. Utilizing charts in Web Applications has been a challenge for many of us over the past two decade and now it is all much easier than ever!

Google Chart is an excellent tool available for free! It is actually very easy to work with and practical.

In this article I will demonstrate how you can utilize this outstanding component to bring new life to your Salesforce pages!

Example: I have a requirement to create a Visualforce page to view a pie chart that shows number of Accounts in our organization divided by their Types.


Here is how it works:
The Google Chart API requires me to pass the chart information via URL parameters and then in return Google will send me an image in PNG format. Then all I need to do is to view the image using the HTML img tag!

Ingredients:
Chart Server URL: http://chart.apis.google.com/chart
Parameters List:
  • chs: is in fact the chart size in pixels ex: 300x200
  • chd: you will need to pass the chart data using this parameter. ex: t:60,40
  • cht: chart type, ex: p3
  • chl: chart items' label, ex: Data 1|Data 2
The above are all basic required parameters in order to create charts. So there is more to it if you are interested.

So in order to utilize this component I create a Visualforce page as follows:



<apex:page controller="googleChartCon" tabStyle="Account">
<apex:sectionHeader title="Accounts by Type"></apex:sectionHeader>
<apex:image url="{!chartData}"></apex:image>
</apex:page>


This page only contains an apex:image component which talks to the controller to obtain it's image URL.

The URL is where the magic happens so we will need to focus on that.

In order to do this I first need to get a list of Account's "Type" picklist values and then
in my custom controller, I create an internal Apex class to save the chart's data while I am running through Account's records and find out how many of which type we have in our database.




public class googleChartCon {
private String chartData;

public String getChartData()inter
{
return chartData;
}

public googleChartCon()
{
//obtain a list of picklist values
Schema.DescribeFieldResult F = Account.Type.getDescribe();
List<Schema.PicklistEntry> P = F.getPicklistValues();
//where chart data should be stored.
List<ChartDataItem> items = new List<ChartDataItem>();

//iterate through each picklist value and get number of accounts
// I wish we could do GROUP BY in SOQL!
for(Schema.PicklistEntry pValue : P)
{
integer Count = [select count() from Account where Type = :pValue.getValue() limit 10000];
if (Count > 0)
items.add(new ChartDataItem(pValue.getValue()+ '-['+ Count.format() + ']' , Count.format()));
}

//Prepare the chart URL
String chartPath = 'http://chart.apis.google.com/chart?chs=600x200&cht=p3';
chartData = chartPath + getChartData(items);
}

private String getChartData(List<ChartDataItem> items)
{
String chd = ''; //23,34,56
String chl = ''; //Hello|World

for(ChartDataItem citem : items)
{
chd += citem.ItemValue + ',';
chl += citem.Label + '|';
}
//remove the last comma or pipe
chd = chd.substring(0, chd.length() -1);
chl = chl.substring(0, chl.length() -1);

String result = '&chd=t:' + chd + '&chl=' + chl;
return result;
}

public class ChartDataItem
{
public String ItemValue
{
get;
set;
}

public String Label
{
get;
set;
}

public ChartDataItem(String Label, String Value)
{
this.Label = Label;
this.ItemValue = Value;
}


}

}


For some security might be a concern sending the data via URL parameters.
Up to this point, I have not been able to confirm if Google is officially supporting SSL connections to rectify this problem. However the following seems to be fine:

https://www.google.com/chart?cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World


In this article I have barely scratched the surface of Google Charts API, if you are interested to learn more about it go to http://code.google.com/apis/chart/

Thursday, October 23, 2008

Working With Multiselect Picklists

Working with Picklists essentially is rewarded since it limits the options of the user and standards what is valid as a value for a field in the system. As good as they are however they bring their own complexity with them.

This is especially true when you are working with multi-select Picklists. Mostly Visualforce’s inputField is used to view multi-select Picklists as Salesforce team is working on it to be more effective and error-free.

In this article we will see how to use the multi-select Picklists.

First off, let’s see how we can load values into our Picklist and view it on our Visualforce page.
Let’s say I have a custom multi-select Picklist field on my User object to define sales regions my users operate on.

The picklist has values such as:
  • North America
  • Latin America
  • Europe
  • Asia
  • Middle East
  • Africa
and so on

In my controller class I add the following property:




public class multiselectPicklist {
private User salesRegions;

public multiselectPicklist()
{
salesRegions = new User();
}
public User getSalesRegions() {
return salesRegions;
}
public void setSalesRegions(User Value) {
salesRegions = Value;
}
}




As you can see it is enough to create a new User object and later on use the picklist field to show the values on your page. Unfortunately there is no other way through which you would more control over what values are shown on your multi-select control. May be in future Salesforce will supply a Component for multi-select operations.

Please note that I have created a setter property as well that receives a User object. This setter allows us to later on read the selected values by user.

Now that we have our controller ready we can program our page to view our multi-select picklist.
in this example what I am trying to achieve is that to allow the user choose as many regions as he/she wants and them filter the Users of the system and view only those who are assigned to those regions.




<apex:page controller="multiselectPicklist" tabStyle="User" sideBar="false">
<apex:form >
<apex:sectionHeader title="Users by Region Report"></apex:sectionHeader>

<apex:pageBlock id="pageBlock" title="Search Filter">
<apex:pageMessages ></apex:pageMessages>
<apex:pageBlockSection title="Filters" columns="2" collapsible="true">
<apex:inputField id="salesRegions" value="{!salesRegions.Sales_Regions__c}" ></apex:inputField>
</apex:pageBlockSection>
</apex:pageBlock>
<apex:pageBlock id="searchResults" title="Results">
<apex:pageBlockButtons >
<apex:commandButton value="Run Report" action="{!runReport}" status="status" rerender="searchResults"></apex:commandButton>
</apex:pageBlockButtons>
<apex:pageMessages ></apex:pageMessages>
<apex:pageBlockTable value="{!users}" var="u" rendered="{!NOT(ISNULL(users))}" rules="cols">
<apex:column value="{!u.UserName}"></apex:column>
<apex:column value="{!u.IsActive}"/>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:Page>




Something that you should bear in mind is that even though multi-select option suggests that you should be dealing with a list of Strings as a result of user’s interaction with your control on the page all you will receive will just a semicolon separated string. It is your job to separate the values and use then in whatever endeavor you intend to have.



Below on my button action I have demonstrated how you can read the selected values back and use them in your query to get a list of users in those sales regions:





public PageReference runReport() {

if (salesRegions.Sales_Regions__c == null || salesRegions.Sales_Regions__c == '') {
apexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO, 'Please select one or more regions first.'));
return null;
}
// read the values into an array
string[] regions = salesRegions.Sales_Regions__c.split(';',0);
if (regions != null && regions.size() > 0)
{
//query the database based on the user's selection
users = [select Username, IsActive, Id from user where Sales_Regions__c in :regions and IsActive = true order by Username];
}

return null;
}

private List<User> users;

public List<User> getUsers()
{
return users;
}
}





Something funny that occurred to me was that I tried to hook the Multi-select Component (inputField) with actionSupport to capture onChange event and load values of another Picklist item on my project which was totally a disappointment! It seems that the Salesforce team has been able to get that one to work yet.

Wednesday, October 22, 2008

How to Create Custom Related-List on Visualforce Pages

In the past few weeks in many cases I came across the requirement to add a custom list to the Object's detail page.

The list should be just like related-lists such as "Open Activities" or "Google Docs, Notes, & Attachments", however it is suppose to view a list of data which is somehow relevant to the object but not necessarily from an object directly related to the current object's page.

Let's consider this example, imagine we need to view a list of opportunities related to a Contact through it's Account relationship (not the ones that are directly linked to the Contact).



This is to say that if Contact A is linked to Account X and Account X has 5 opportunities linked to it, by viewing Contact A's details we would be able to view those opportunities as well.

Ok, now action, in order to do this we will need to create a new Visualforce Page and Controller extension.

So I add this text in front of http://yourOrg.salesforce.com/apex/SampleDetailPage and create the new page instantly (This is only possible when you profile is set to work in developer mode).


I set the page's Controller attribute to "Contact" and call my extension "SampleDetailPageCon".
On the page all I need to do is to show the existing Contact details based on whatever active layout target user has chosen and then add my related-list to the bottom of the page. The page code goes like this:


<apex:page standardController="Contact" extensions="sampleDetailPageCon">
<style>
.fewerMore { display: none;}
</style>
<apex:form >
<apex:pageMessages />
<apex:detail relatedList="true"></apex:detail>
<apex:pageblock id="CustomList" title="Related Opportunities" >
<apex:pageBlockTable value="{!oppz}" var="o" rendered="{!NOT(ISNULL(oppz))}">
<apex:column value="{!o.Name}"/>
<apex:column value="{!o.Account.Name}"/>
<apex:column value="{!o.Type}"/>
<apex:column value="{!o.Amount}"></apex:column>
<apex:column value="{!o.CloseDate}"/>
</apex:pageBlockTable>
<apex:outputLabel value="No records to display" rendered="{!(ISNULL(oppz))}" styleClass="noRowsHeader"></apex:outputLabel>
</apex:pageblock>
</apex:form>
</apex:page>



A few tips, normally at the end of detail page Salesforce publishes a component for viewing a few more or a few less records to adjust the size of the page.

Since our related list will be rendered under this component it might not be a bad idea to get rid of it. If you pay attention to be page's code you will see that a style tag has been added to the page that will just do that.

Apart from this everything else is very straight forward. Now all we need to do is to extend the Contact's standard controller and make it able to generate a list of related Opportunities as well.

Here is the Controller's Code:



public class sampleDetailPageCon {
private List<Opportunity> oppz;
private Contact cntact;
public sampleDetailPageCon(ApexPages.StandardController controller) {
this.cntact= (Contact)controller.getRecord();
}
public List<Opportunity> getOppz()
{
Contact con = [Select id, Account.id FROM Contact where id = :cntact.id];
if (con.Account == null)
return null;
oppz = [Select id, Name, Account.Name, CloseDate, Amount, Type from Opportunity where Account.id = :con.Account.id];
return oppz;
}
}




As you can see this also very simple. In the above code, I have just added a method called "getOppz()" in which I first try to see if the Contact is linked to any accounts and if yes then use the Account id to get a list of its Opportunities.



That's it! Now if you need your page to be used by Salesforce as default detail page all you need to do is:
  • Go to: Setup -> Customize -> Contacts -> Buttons and Links
  • Click on "override" next to "View" label
  • Select "Visualforce Page" and then your page from the drop down list

Now there is only one thing left, as you all have noticed every related-list has a hover feature on the top of the page where user can quickly view all the related-lists without scrolling down the whole page.



In my next article I will show you how you can go through the backdoor to add your related-list on the top of the page!


Friday, October 10, 2008

Bringing Google Calendar into Salesforce CRM pages


Since the very first time I saw Google Calendar' API, I wondered if it can be used for any bizarre or genius applications (having a sense of a-tool-for-public) or not. For example I really liked to be able to load Google Calendar into my page and then using the API add as many events as I desire on it from my own database without any authentication requirement and just for the sake of showing the data to user, but I have not been able to get it work in that order so far.

Yet there are a lot of other benefits that Google Calendar can bring along, in terms of sharing events, publishing, flexibility and integration with other portals/applications in the cloud.

One of things you might find useful is the capability of sharing a Google public calendar with your Salesforce CRM users.

At first I tried to publish my own personal calendar into Salesforce, and I was successful to some extent. I could see the calendar is being shown properly and I can switch between the weekly, monthly, etc views. All nice, but I noticed that the Calendar is empty and my events are missing.

In Google Calendar's documentation is not mentioned that you should use a public calendar to be able to share it in other websites or maybe that goes without saying! So I initially thought that any Calendar could be shared and as long as my event is set as public people should be able to see them.

It was more like one of those scenarios where you show a button on your page to a user but say "Do not click on it, ok my boy?"

Anyway, in order to publish the calendar on your Salesforce CRM, you should first think about where you want to place it and you may require to create a new Visualforce page before copying the calendar HTML code into it.

In order to get the calendar's HTML code go to your Google Calendar page and either create a new Calendar to publish it as public calendar or if you already have one then click on settings (on the left side of the screen).

Once you have your public calendar ready go to its "details" page and look for a spot that says "Embed This Calendar", copy the code and paste it into your Visualforce page. That's all!



<apex:page tabStyle="Campaign">
<apex:form >
</apex:form>
<iframe src="http://www.google.com/calendar/embed?src=6lectl1l76jrmul8st527rbq6c%40group.calendar.google.com&ctz=America/New_York&color=%23336699;" style="border: 0" width="800" height="600" frameborder="0" scrolling="no"></iframe>
</apex:page>



And you will see something like below in your page:

Thursday, October 9, 2008

Utilizing Dynamic Apex Feature in Winter 09 Release!


Well for some of us yet the developer edition is not supporting the all glorious Salesforce CRM winter 09 features such as Dynamic Apex.

However, those who have been able to get their hands on it already know that this release has opened up a lot of new possibilities for developing Visualforce pages. Even though here and there I still stumble over some glitches or nice-to-haves, I have decided to devote some of my time each day to learning the new stuff and incorporate them into my apps.

In this article we will see how we can build Dynamic SOQL queries, execute them, get exactly the records we want and moreover we will see how we can now easier than ever hook our selectLists to object's picklists.

For this example, consider filtering Account records based on Name, Type, Ownership and also we want the user to choose how many records they want to get back on the screen.

Points:
I have used a PanelGrid to put my filter Components in and PageBlockTable to view the results.
The page is pretty standrad, what Winter 09 features are about in this article are all in the Controller's Code.




<apex:page controller="myPageController" tabStyle="Account">
<apex:form >
<apex:sectionHeader title="Customer Search" />
<apex:PanelGrid columns="2" id="panel" cellpadding="2">

<apex:outputLabel value="Account Name: "></apex:outputLabel>
<apex:inputText id="textbox" value="{!searchText}" size="40" style="height:13px;font-size:11px;"></apex:inputText>

<apex:outputLabel value="Type: "></apex:outputLabel>
<apex:selectList size="1" id="accountType" value="{!accountType}">
<apex:selectOptions value="{!accountTypes}"></apex:selectOptions>
</apex:selectList>

<apex:outputLabel value="Ownership:"></apex:outputLabel>
<apex:selectList size="1" id="accountOwnership" value="{!accountOwnership}">
<apex:selectOptions value="{!accountOwnerships}"></apex:selectOptions>
</apex:selectList>

<apex:outputLabel value="Result:"></apex:outputLabel>
<apex:selectList size="1" id="resultNo" value="{!resultNo}">
<apex:selectOption itemValue="50" itemLabel="50"></apex:selectOption>
<apex:selectOption itemValue="100" itemLabel="100"></apex:selectOption>
<apex:selectOption itemValue="150" itemLabel="150"></apex:selectOption>
</apex:selectList>

<div></div>
<apex:commandButton action="{!search}" value="Search" rerender="pageblock"></apex:commandButton>
</apex:PanelGrid>



</apex:form>
<br/>
<apex:pageblock id="pageblock" >
<apex:messages></apex:messages>
<apex:pageBlockSection title="Results" id="results" columns="1" collapsible="true">
<apex:pageBlockTable value="{!accountResults}" var="a" rendered="{!NOT(ISNULL(accountResults))}">
<apex:column value="{!a.Name}"></apex:column>
<apex:column value="{!a.Type}"></apex:column>
<apex:column value="{!a.Ownership}"></apex:column>
<apex:column value="{!a.Industry}"></apex:column>
</apex:pageBlockTable>
</apex:pageBlockSection>
</apex:pageblock>
</apex:page>

In this Controller I have developed a procedure "getObjPicklistValues" which receives a DescribeFieldResult object as parameter, corresponding to the picklist I want to show in the filter components' area. DescribeFieldResult methods provide you with most reflection capabilities you require to be able to programmatically write reusable codes (as you can see in this example).

The other benefit that previously was not an option in Visualforce is Dynamic Apex. Now you can build your query based on user's choices on the fly, then run it against Salesforce database and view the customized data to the user.

In this example, if the user is not entering any value for Account Name or has not chosen any Account type, then simply those fields will not be queried and will be excluded from the SOQL statement.

And the controller's code:


public class myPageController {

private List accountResults;

public List getAccountResults() {

return accountResults;

}

public String searchText

{

get;

set;

}

public String accountType

{

get;

set;

}

public String accountOwnership

{

get;

set;

}

public String resultNo

{

get;

set;

}

public List getAccountTypes()

{

return getObjPicklistValues(Account.Type.getDescribe());

}

public List getAccountOwnerships()

{

return getObjPicklistValues(Account.Ownership.getDescribe());

}

private List getObjPicklistValues(Schema.DescribeFieldResult picklistDesc)

{

List pList = picklistDesc.getPicklistValues();

List items = new List();

items.add(new SelectOption('','--None--'));

for (Schema.PicklistEntry pl : pList) {

items.add(new SelectOption(pl.getValue(),pl.getLabel()));

}

return items;

}

public PageReference search()

{

String query = 'Select name, type, ownership, industry from account ';

String queryLimit = ' limit ' + resultNo;

String conditions = '';

if (searchText != null && searchText != '')

conditions += ' name LIKE \'' + searchText + '%\'';

if (accountType != null)

{

if (conditions != '') conditions+= ' and ';

conditions += ' Type = \'' + accountType + '\'';

}

if (accountOwnership != null)

{

if (conditions != '') conditions+= ' and ';

conditions += ' Ownership = \'' + accountOwnership + '\'';

}

if (conditions != '')

query += ' where ' + conditions;

query += ' order by CreatedDate DESC ';

query += queryLimit;

//apexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO, query));

accountResults = Database.query(query);

return null;

}

}






Tuesday, October 7, 2008

A Picklist Component For Your Visualforce Pages

The last couple of months, numerous times in order to view picklist values in a form either for filter the views or creating custom data entry forms, I had to come up with ways to get around the limitations and somehow hook the inputField with the picklist to show what I needed.

Now, with the Salesforce Winter 09 improvements now it is much easier to tackle this issue.

In this article we will design an Apex component that gets the picklist name as attribute and renders the picklist values in form of a dropdown list.

This component will be really useful for non-multi-select picklists. I will post another article on multiselect picklists in near future.

Let's see how the Component will be added to the Page:

<c:picklist id="industryPicklist" value="{!accountIndustry}" picklistField="Industry" systemEntity="Account" usedefaultvalue="true"/>

What you need to define for the component is what object, which picklist field of that Object and a property of your page's Controller allowing you to read what user's choice was.

Now let's dive into the code and see how this component is developed. Like most other Visualforce page developments our component has two parts. The User Interface elements and the controller behind it.


<apex:component controller="picklistController">
<apex:attribute name="SystemEntity" description="" type="String" required="true" default="Account" assignTo="{!systemObject}"></apex:attribute>
<apex:attribute name="picklistField" description="" type="String" required="true" default="Type" assignTo="{!picklist_Field}"></apex:attribute>
<apex:attribute name="value" description="" type="String" required="true"></apex:attribute>
<apex:attribute name="defaultLabel" description="default value to be shown to user" type="String" assignTo="{!listDefaultLabel}" default="--None--"></apex:attribute>
<apex:attribute name="defaultValue" description="default value to be shown to user" type="String" assignTo="{!listDefaultValue}" default=""></apex:attribute>
<apex:attribute name="useDefaultValue" description="whether a default value to be shown or not" type="Boolean" assignTo="{!haveDefaultValue}" default="false"></apex:attribute>

<apex:selectList value="{!value}" size="1">
<apex:selectOptions value="{!items}"></apex:selectOptions>
</apex:selectList>
</apex:component>


The Component has only a selectList and a few attributes. Below are a list of attributes used for this component:

  • systemEntity: API name of the Object which has a picklist field
  • picklistField: API name of the Object's picklist field
  • value: This field should be bound to a property in parent Page's Controller
  • defaultLabel: Label of the default value shown to user, only visible when useDefaultValue is set to true.
  • defaultValue: Value of the default item shown to user, only visible when useDefaultValue is set to true.
  • useDefaultValue: A Boolean value tells the component whether to show a default value or not.
Now let's see how the Controller is look like:


public class picklistController {

//System object name, such as Account

private String systemObject;

public String getSystemObject() {

return systemObject;

}

public void setSystemObject(String value)

{

systemObject = value;

}

//picklist name such as "Type" of account object

private String picklist_Field;

public String getPicklist_Field() {

return picklist_Field;

}

public void setPicklist_Field(String value) {

picklist_Field= value;

}

//default item's value

public String listDefaultValue

{

get;

set;

}

//default item's label

public String listDefaultLabel

{

get;

set;

}

// whether the default item should be shown

public Boolean haveDefaultValue

{

get;

set;

}

public List getItems()

{

List entries_local;

if (systemObject != null)

{

//use GlobalDecribe to get a list of all available Objects

Map gd = Schema.getGlobalDescribe();

Set objectKeys = gd.keySet();

for(String objectKey: objectKeys)

{

//Iterate through all objects to locate selected Object

if (objectKey == systemObject.toLowerCase())

{

Schema.SObjectType systemObjectType = gd.get(objectKey);

Schema.DescribeSObjectResult r = systemObjectType.getDescribe();

Map M = r.fields.getMap();

Set fieldNames = M.keySet();

if (picklist_Field == null)

{

break;

}

//iterate through all fields of the object to locate the picklist field

for(String fieldName: fieldNames)

{

if (fieldName == picklist_Field.toLowerCase())

{

Schema.SObjectField field = M.get(fieldName);

Schema.DescribeFieldResult fieldDesc = field.getDescribe();

//extract the picklist values

entries_local = fieldDesc.getPicklistValues();

break;

}

}

}

}//end for

}

//Loading the picklist values and default item for our selectList

List options = new List();

//take care of Default value

if (haveDefaultValue == true)

{

if (listDefaultValue == null)

listDefaultValue = '';

if (listDefaultLabel== null)

listDefaultLabel = '--None--';

options.add(new SelectOption(listDefaultValue,listDefaultLabel));

}

//take care of picklist values

if (entries_local != null)

{

for(Schema.PicklistEntry picklistItem : entries_local)

{

options.add(new SelectOption(picklistItem.getValue(),picklistItem.getLabel()));

}

}

return options;

}

}