Field Sets and Dynamic Visualforce

by Patrick Connelly posted on April 25, 2016

One of the downsides of the code I posted a couple of weeks ago to generate PDFs is that if you want to add new fields to your invoices, you have to edit the Visualforce page. By updating our Visualforce and using field sets, we can dynamically add fields to our invoices

Field Sets

Field sets allow for addition of fields to an ordered list that can be used dynamically inside of Visualforce. They are most useful for managed packages since they can be deployed with them and then customized by the end user without having to have access to the Visualforce code in the package.

Creating our field sets

In order to create our PDF we will need three field sets. One for the Battle Station object, one for the Resource object and one for the Supply object. To create the field set, navigate to the object definition page and click the New button under the field set section.

On the next page, you define the available fields as well as which ones are currently in the field set. For a non-managed package scenario, like what we are dealing with, there may not be a need to have a field that is available but not in the set. For ease of use, I’ve named all of the field sets “Battle Station Invoice” across all three objects.

Battle station field set

Resource field set

Supply Field Set

Updating Visualforce

To now take use of these new field sets, we need to update the Visualforce. Instead of having each row / column specifically defined in our Visualforce, we will need to pull the field set data down and use that in our code.

<apex:component layout="none" access="global">
    <apex:attribute name="station" description="The battle station." type="Battle_Station__c" />
    <head>
        <style type="text/css" media="print">
          // CSS GOES HERE
        </style>
    </head>
    <div class="header"></div>
    <div class="content">
        <h1 class="stationName">
            {!station.Name}
        </h1>

        <table id="status">
            <apex:repeat value="{!$ObjectType.Battle_Station__c.FieldSets.Battle_Station_Invoice}" var="f">
                <tr>
                    <td class="label">{!$ObjectType.Battle_Station__c.fields[f].Label}</td>
                    <td><apex:outputText value="{!station[f]}" /></td>
                </tr>
            </apex:repeat>
        </table>

        <div class="sectionHeader">{!$ObjectType.Resource__c.labelPlural}</div>
        <table id="resources">
            <tr>
                <apex:repeat value="{!$ObjectType.Resource__c.FieldSets.Battle_Station_Invoice}" var="f">
                    <th class="tableHeader">{!$ObjectType.Resource__c.fields[f].Label}</th>
                </apex:repeat>
            </tr>
            <apex:repeat value="{!station.Resources__r}" var="resource">
                <tr>
                    <apex:repeat value="{!$ObjectType.Resource__c.FieldSets.Battle_Station_Invoice}" var="f">
                        <td><apex:outputField value="{!resource[f]}"/></td>
                    </apex:repeat>
                </tr>
            </apex:repeat>
        </table>

        <div class="sectionHeader">{!$ObjectType.Supply__c.labelPlural}</div>
        <table id="resources">
            <tr>
                <apex:repeat value="{!$ObjectType.Supply__c.FieldSets.Battle_Station_Invoice}" var="f">
                    <th class="tableHeader">{!$ObjectType.Supply__c.fields[f].Label}</th>
                </apex:repeat>
            </tr>
            <apex:repeat value="{!station.Supplies__r}" var="supply">
                <tr>
                    <apex:repeat value="{!$ObjectType.Supply__c.FieldSets.Battle_Station_Invoice}" var="f">
                        <td><apex:outputField value="{!supply[f]}"/></td>
                    </apex:repeat>
                </tr>
            </apex:repeat>
        </table>
        <br class="clearboth" />
        <div id="totalCost">
            <span id="totalCostLabel">{!$ObjectType.Battle_Station__c.Fields.Total_Cost__c.Label}:</span>
            <apex:outputField value="{!station.Total_Cost__c}"/>
        </div>
    </div>
	<div class="footer">
        <div class="centered">Generated by {!$User.FirstName} {!$User.LastName}</div>
        <div>
            <div class="subfooter">{!NOW()}</div>
            <div class="subfooter right">Page <span class="pagenumber"/> of <span class="pagecount"/></div>
        </div>
    </div>
</apex:component>

Above is a truncated version that does not have the CSS. If you would like full code with the CSS you can find that here.

Make it prettier

Now, this PDF is great because we can dynamically add and rearrange fields to our heart’s content without ever having to touch our Visualforce. However by making this dynamic, we lose our ability to specifically stylize our fields. For example, before our unit cost and total cost fields were right justified. Now, everything is left justified. With a little bit of additions to our visualforce and our CSS, we can bring it back to the way we had it before

Let’s look at our supply table generation. By defining a class on it based on the field name, we have a CSS selector we can use to target that field specifically.

<table id="supplies">
    <tr>
        <apex:repeat value="{!$ObjectType.Supply__c.FieldSets.Battle_Station_Invoice}" var="f">
            <th class="tableHeader supply_{!$ObjectType.Supply__c.fields[f].Name}">{!$ObjectType.Supply__c.fields[f].Label}</th>
        </apex:repeat>
    </tr>
    <apex:repeat value="{!station.Supplies__r}" var="supply">
        <tr>
            <apex:repeat value="{!$ObjectType.Supply__c.FieldSets.Battle_Station_Invoice}" var="f">
                <td class="supply_{!$ObjectType.Supply__c.fields[f].Name}"><apex:outputField value="{!supply[f]}"/></td>
            </apex:repeat>
        </tr>
    </apex:repeat>
</table>

Now we can add the following to our CSS and we can get the correct text justification on those fields

.supply_Unit_Cost__c, .supply_Total_Cost__c {
    text-align: right;
}

.supply_Quantity__c {
    text-align: center;
}

This can then be repeated for the resources table as well. The only downside of this approach is that if you add new fields that need a different justification, then you will have to alter the Visualforce page to change them. However editing a single line of CSS is way easier than adding new stuff to a repeat deep inside a Visualforce page.

The completed Visualforce page with all the updates can be seen here.