Blog

Salesforce Reports provide a variety of formats and options to enable us to build reports from our data. However, sometimes we cannot achieve exactly what we need either in terms of the way the data is presented, or having a requirement to report on data that is not related in a way that can be consolidated in a report. That is when we may consider a Visualforce report. A Visualforce report is Visualforce page, that allows us to enter selection criteria and options, and use APEX to retrieve the data and present it back to us in a Visualforce page. Although this requires development skills to create and maintain, it is an option worth investigating if the standard reporting tools do not satisfy your requirements.

The following example explains how a Visualforce report can be built to report on Opportunities and the related Opportunity team. It has been kept relatively simple, and yes the same data could be extracted from a standard report, but just not presented this way, with the Opportunity Team data in the same row as each Opportunity. It also demonstrates how filters can be applied to the data.

Step 1: Create the page and controller

We need to start by creating a Visualforce page and controller. This needs to be a custom controller as this page doesn’t relate to any specific record.

We need two list of opportunities, one will be our master list that we query in the constructor and this is the list we base our filter from. The second list is the filtered opportunities, these are the ones we want displayed on our page. When the page loads, we want all the opportunities to be displayed so in the constructor we initialize this list as a copy of the list of all opportunities.

public class OpportunityReportCont {
	public List allOpportunities {get;set;}
	public List displayOpportunities {get;set;}

	public OpportunityReportCont () {
		allOpportunities = new List();
		allOpportunities = [SELECT Id, Name, Type, Amount, CloseDate, Description, ExpectedRevenue, ForecastCategoryName, LeadSource, NextStep, Probability, TotalOpportunityQuantity, StageName FROM Opportunity];

		displayOpportunities = new List(allOpportunities);
	}
}

Our Visualforce page has a table that displays the list of Opportunities in the display Opportunities list. We put all the fields we want displayed as columns in apex:column tags within a page block table.

<apex:page <apex:page controller="OpportunityReportCont" tabstyle="Opportunity">
	<apex:sectionheader title="Opportunity Report" subtitle="Opportunities" />
	<apex:pageblock title="Opportunities" id="opportunityTable">
		<apex:pageblocktable value="{!displayOpportunities}" var="o">
			<apex:column value="{!o.Name}" />
			<apex:column value="{!o.Type}" />
			<apex:column value="{!o.Amount}" />
			<apex:column value="{!o.CloseDate}" />
			<apex:column value="{!o.ExpectedRevenue}" />
			<apex:column value="{!o.ForecastCategoryName}" />
			<apex:column value="{!o.LeadSource}" />
			<apex:column value="{!o.Probability}" />
			<apex:column value="{!o.StageName}" />
		</apex:pageblocktable>
	</apex:pageblock>
</apex:page>

Step 2: Create the filter

To create the filter we create a dummy opportunity object that we pass from the controller to the page. We use this to capture the input values and take advantage of the Salesforce standard way of displaying fields. Doing this means we only have to specify the field we want, and Salesforce manages the type of control including any picklist values. As we are using this object to pass data back to the controller the fields need to be wrapped in apex:form tags.

public class OpportunityReportCont {
	public List<Opportunity> allOpportunities {get;set;}
	public List<Opportunity> displayOpportunities {get;set;}
	public Opportunity filterOpportunity {get;set;}

	public OpportunityReportCont () {
		allOpportunities = new List<Opportunity>();
		allOpportunities = [SELECT Id, Name, Type, Amount, CloseDate, Description, ExpectedRevenue, ForecastCategoryName, LeadSource, NextStep, Probability, TotalOpportunityQuantity, StageName FROM Opportunity];

		filterOpportunity = new Opportunity();
	}
}

<apex:page controller="OpportunityReportCont" tabstyle="Opportunity">
	<apex:sectionheader title="Opportunity Report" subtitle="Opportunities" />
	<apex:form >
		<apex:pageblock title="Filter">
			<apex:pageBlockSection >
				<apex:inputfield value="{!filterOpportunity.Type}">
				</apex:inputfield>
				<apex:inputfield value="{!filterOpportunity.LeadSource}">
				</apex:inputfield>
			</apex:pageBlockSection>
		</apex:pageblock>
	</apex:form>
</apex:page>

Now for the filter. We want to display only those opportunities that match the filter Opportunity dummy record that we create. To do this we loop through our master record of opportunities and compare them to each field we want from the dummy filter Opportunity. We want to check first of all that the field is not null (or an empty string) this means that nothing has been selected as the filter. If they match we add it to the list of display opportunities.

public void filter(){
	displayOpportunities = new List();
    for(Opportunity o : allOpportunities){
	    if (filterOpportunity.Type == o.Type){
		    if (filterOpportunity.LeadSource == o.LeadSource){
			    displayOpportunities.add(o);
		    }
		}
	}
}

On the Visualforce page we add action support to the filter fields, this tells the controller that we want the filter method to be called when the field changes, and then we want to rerender the table of our Opportunities.

<apex:inputfield value="{!filterOpportunity.Type}">
	<apex:actionsupport event="onchange" action="{!filter}" rerender="opportunityTable" />
</apex:inputfield>

Step 3: Getting the related OpportunityTeamMember records

In the controller we need to query all the opportunity team member records related to the opportunities we have and create a map from Opportunity Id to a list of Opportunity Team Members.

public class OpportunityReportCont {
	public List allOpportunities {get;set;}
	public List displayOpportunities {get;set;}
	public Opportunity filterOpportunity {get;set;}
	public Map> opportunityTeamMemberMap {get;set;}

	public OpportunityReportCont () {
		allOpportunities = new List([SELECT Id, Name, Type, Amount, CloseDate, Description, ExpectedRevenue, ForecastCategoryName, LeadSource, NextStep, Probability, TotalOpportunityQuantity, StageName FROM Opportunity]);

		filterOpportunity = new Opportunity();

		displayOpportunities = new List(allOpportunities);

        opportunityTeamMemberMap = new Map>();
		for(Opportunity o : allOpportunities){
		    opportunityTeamMemberMap.put(o.Id, new
            List());
		}
		List oppTeamMembers = new List([SELECT Id, OpportunityId, UserId, User.Name, TeamMemberRole FROM OpportunityTeamMember WHERE OpportunityId IN :opportunityTeamMemberMap.keyset()]);
		for(OpportunityTeamMember otm : oppTeamMembers){
			opportunityTeamMemberMap.get(otm.OpportunityId).add(otm);
		}
	}
}

Then in the Visualforce page we can put another page block table within one of the columns. This page block table iterates over the list Opportunity Team Members that we get from the map using the key that is the Opportunity Id. Within this table we can add whatever columns we choose.

<apex:column headervalue="Opportunity Team Members">
	<apex:pageblocktable value="{!opportunityTeamMemberMap[o.Id]}" var="otm">
		<apex:column value="{!otm.User.Name}" />
		<apex:column value="{!otm.TeamMemberRole}" />
		</apex:pageblocktable>
</apex:column>

Putting it all together:

public class OpportunityReportCont {
	public List allOpportunities {get;set;}
	public List displayOpportunities {get;set;}
	public Opportunity filterOpportunity {get;set;}
	public Map> opportunityTeamMemberMap {get;set;}

	public OpportunityReportCont () {
		allOpportunities = new List([SELECT Id, Name, Type, Amount, CloseDate, Description, ExpectedRevenue, ForecastCategoryName, LeadSource, NextStep, Probability, TotalOpportunityQuantity, StageName FROM Opportunity]);

		filterOpportunity = new Opportunity();

		displayOpportunities = new List(allOpportunities);

		opportunityTeamMemberMap = new Map>();
		for(Opportunity o : allOpportunities){
			opportunityTeamMemberMap.put(o.Id, new List());
		}
		List oppTeamMembers = new List([SELECT Id, OpportunityId, UserId, User.Name, TeamMemberRole FROM OpportunityTeamMember WHERE OpportunityId IN :opportunityTeamMemberMap.keyset()]);
		for(OpportunityTeamMember otm : oppTeamMembers){
			opportunityTeamMemberMap.get(otm.OpportunityId).add(otm);
		}
	}

	public void filter(){
	    displayOpportunities = new List();
		for(Opportunity o : allOpportunities){
			if (String.isEmpty(filterOpportunity.Type) || filterOpportunity.Type == o.Type){
				if (String.isEmpty(filterOpportunity.LeadSource) || filterOpportunity.LeadSource == o.LeadSource){
					displayOpportunities.add(o);
				}
			}
		}
	}

}
<apex:page controller="OpportunityReportCont" tabstyle="Opportunity">

    <apex:sectionheader title="Opportunity Report" subtitle="Opportunities" />
    <apex:form>

        <apex:pageblock title="Filter">
            <apex:pageblocksection>

                <apex:inputfield value="{!filterOpportunity.Type}">
                    <apex:actionsupport event="onchange" action="{!filter}" rerender="opportunityTable" />
                </apex:inputfield>

                <apex:inputfield value="{!filterOpportunity.LeadSource}">
                    <apex:actionsupport event="onchange" action="{!filter}" rerender="opportunityTable" />
                </apex:inputfield>

            </apex:pageblocksection>
        </apex:pageblock>

        <apex:pageblock title="Opportunities" id="opportunityTable">
            <apex:pageblocktable value="{!displayOpportunities}" var="o">

                <apex:column headervalue="Housing Application ID" style="width:30px">
                    <apex:outputlink value="/{!o.Id}" on-click="window.top.location.href='/{!o.Id}'; return false;">{!o.Name}</apex:outputlink>
                </apex:column>

                <apex:column value="{!o.Type}" />
                <apex:column value="{!o.Amount}" />
                <apex:column value="{!o.CloseDate}" />
                <apex:column value="{!o.ExpectedRevenue}" />
                <apex:column value="{!o.ForecastCategoryName}" />
                <apex:column value="{!o.LeadSource}" />
                <apex:column value="{!o.Probability}" />
                <apex:column value="{!o.StageName}" />

                <apex:column headervalue="Opportunity Team Members">
                    <apex:pageblocktable value="{!opportunityTeamMemberMap[o.Id]}" var="otm">
                        <apex:column value="{!otm.User.Name}" />
                        <apex:column value="{!otm.TeamMemberRole}" />
                    </apex:pageblocktable>
                </apex:column>

            </apex:pageblocktable>
        </apex:pageblock>
    </apex:form>
</apex:page>

Here are the results:

In this example, the filters are used to filter on Customer Type, to only show New Customers and then to filter on where the Lead Source is Web.

Filtered_Visualforce_Table

In this example, no filters are used, and the entire set of Opportunities along with the Opportunity Team Members are displayed.

Filtered_Visualforce_Table1

What Certification are you studying for now?

Focus on Force currently provides practice exams and study guides for sixteen certifications

VisualForce Reports

Salesforce Reports provide a variety of formats and options to enable us to build reports from our data. However, sometimes we cannot achieve exactly what we need either in terms of the way the data is presented, or having a requirement to report on data that is not related in a way that can be consolidated in a report. That is when we may consider a Visualforce report. A Visualforce… Read More

VisualForce Reports

Macros in Salesforce

Have you ever had to perform the same action on multiple records in Salesforce? Wished there was a better way to save time? You may have thought about the possibilities in Microsoft Word or Excel to use macros to perform repetitive actions and wished there was something similar in Salesforce. Well, there is! There is a macro capability and, it is available for all standard and custom feed based objects…. Read More

Macros in Salesforce

Target Reports using Custom Summary Formula Fields

Have you ever come across the need to do actual vs target reporting in Salesforce? It could be to report on if Salespeople are reaching their targets for the value of closed opportunities. It could be whether the number of orders has reached the goal for the month. Another example could be the number of qualified leads created. While it is easy to create reports that summarize actual data, how… Read More

Target Reports using Custom Summary Formula Fields

Passion + Non Stop Learning = Salesforce MVP in 2 Years

Salesforce has been in the market for 17 years now, and it has not only helped companies grow but it has also provided countless job opportunities to IT professionals. Vamsi Krishna is one of those who have benefited from the competitive advantage offered by Salesforce in terms of skills enhancement and professional development. He shares how he got started with Salesforce, his successes, learnings, challenges encountered, and his career journey… Read More

Passion + Non Stop Learning = Salesforce MVP in 2 Years

Merge Fields in Salesforce Tasks

Using Workflow to create followup tasks or reminders is a common use case. It is also simple to setup, just create a workflow rule with a rule trigger date a number of days in the future. Say we wanted to have a followup task generated 5 days before the Opportunity close date. This is easy, but standard workflow comes with a limitation, merge fields cannot be used for field values…. Read More

Merge Fields in Salesforce Tasks

Single vs Multiple Sales Processes in Salesforce

  How Many Salesforce Sales Processes Do You Need?Are you contemplating whether to have one or set up multiple sales processes in your Salesforce Org? Prior to determining whether you need a single Sales Process or multiple Sales Process it is worthwhile to look at the definition of a Sales Process without reference to Salesforce:“A defined set of repeatable, interrelated activities from market awareness through servicing customers that allows communication of progress… Read More

Single vs Multiple Sales Processes in Salesforce

Getting a Salesforce Job

Salesforce Jobs I recently sat down and had a chat with Stuart Smith and Bradley Bilinsky, two business managers at Talentforce, an Australian recruitment company that specialize in Software as a Service roles, including Salesforce. One point of difference that Talentforce has from other recruiters is that they also run the Sydney Salesforce User Group, so they are active in the community. Stuart and Brad shared their thoughts on the… Read More

Getting a Salesforce Job

Powering your Salesforce reports using the PARENTGROUPVAL function

  How to Use the PARENTGROUPVAL functionThe ParentGroupVal function is one of the lesser known features of Salesforce reporting. In this article you will learn how to create some key metrics for summary reports by using the PARENTGROUPVAL function within a summary, joined or a matrix report. The function obtains the summary value from a parent group, which is a grouping that is a level above the current grouping. The syntax… Read More

Powering your Salesforce reports using the PARENTGROUPVAL function

Salesforce Custom Related List using jQuery

In the previous article we created a custom account interface by using Visualforce, in this article we’ll extend the functionality by creating a fully custom Opportunity related list. The Opportunity related list will be replaced with a custom table, and it will support: ✓ Pagination ✓ Filtering ✓ Sorting ✓ Quick filtering open Opportunities In order to implement this we need four steps: STEP 1: Create an Apex controller extension… Read More

Salesforce Custom Related List using jQuery

Focus on People: Danielle Tonkin — From Admin to Consultant

Although most Salesforce Administrators are quite happy with their job, I’m sure there are a number who have an interest and aspiration to become a Salesforce consultant. Danielle Tonkin is one of those that made the transition from Admin to functional consultant. Here she shares with us what it is like to work as a Salesforce consultant, the challenges, successes and her tips for those interested in following her Salesforce… Read More

Focus on People: Danielle Tonkin — From Admin to Consultant

Visualforce Tabbed Interface

Using Visualforce to build a tabbed user interface In this example we will override a standard view with a few lines of Visualforce to build a tabbed interface. Use case: Customize the interface for the Account object using Visualforce tabs. The following tabs should be available: Detail – Display general information about record. Inline edit feature should be supported; Contacts – Related Contacts list should be displayed in this section;… Read More

Visualforce Tabbed Interface

Salesforce Global Picklists

Global Picklists One of the most voted ideas in the Salesforce IdeaExchange forum was to be able to create a picklist that can be shared across objects. Shared Picklist Idea Salesforce delivered the idea in pilot as of Winter ’16, and is now in Beta for Spring ’16. If you look at the release notes, you would find it by the name ‘Global Picklists”. It is also referred to as… Read More

Salesforce Global Picklists

Adding CPQ and Billing Functionality to Salesforce by acquiring SteelBrick

On December 23 2015, just before the holidays, Salesforce and SteelBrick announced an agreement for Salesforce to acquire Steelbrick for $360 million. Salesforce has a long history of acquiring companies that extend and complement Salesforce functionality and this is in the mid range of the scale, with ExactTarget ($2.5 billion in 2013) and Buddy Media ($745 million in 2012) at the high end and comparable with the more recent purchase… Read More

Adding CPQ and Billing Functionality to Salesforce by acquiring SteelBrick

Winter 16 Enhancements for Process Builder

Process Builder was one of the most awaited features released by Salesforce as an upgrade to the possibilties of the Workflow functionality and as part of its effort to strengthen what can be done declaratively. In Winter 16 Salesforce continues to add further enhancements to the Process Builder. Schedule Multiple Actions Prior to the Winter ’16 release, it was only possible to define 1 scheduled action per criteria node or… Read More

Winter 16 Enhancements for Process Builder