Use dynamic Visualforce bindings to construct simple, reusable pages with a known set of fields you want to access. This approach has the advantage of easily customizing which fields are pertinent for a user to work with.
The next two examples are deliberately simple for instructional purposes. See Using Dynamic References for a User-Customizable Page for a more advanced example that makes fuller use of dynamic Visualforce.
The following example demonstrates the simplest way to construct a Visualforce page that uses dynamic references.
public class DynamicAccountFieldsLister { public DynamicAccountFieldsLister(ApexPages.StandardController controller) { controller.addFields(editableFields); } public List<String> editableFields { get { if (editableFields == null) { editableFields = new List<String>(); editableFields.add('Industry'); editableFields.add('AnnualRevenue'); editableFields.add('BillingCity'); } return editableFields ; } private set; } }
<apex:page standardController="Account" extensions="DynamicAccountFieldsLister"> <apex:pageMessages /><br/> <apex:form> <apex:pageBlock title="Edit Account" mode="edit"> <apex:pageBlockSection columns="1"> <apex:inputField value="{!Account.Name}"/> <apex:repeat value="{!editableFields}" var="f"> <apex:inputField value="{!Account[f]}"/> </apex:repeat> </apex:pageBlockSection> </apex:pageBlock> </apex:form> </apex:page>
Visualforce automatically optimizes the SOQL query performed by a page’s StandardController (or StandardSetController), loading only the fields which are actually used on a page. When you create a Visualforce page with static references to objects and fields, the fields and objects can be known in advance. When the page is saved, Visualforce is able to determine and save which objects and fields need to be added to the SOQL query that the StandardController will perform later, when the page is requested.
Dynamic references are evaluated at runtime, after the SOQL query is run by the StandardController. If a field is only used via a dynamic reference, it won’t be automatically loaded. When that dynamic reference is later evaluated, it will resolve to data which is missing, the result of which is a SOQL error. You must provide some extra information to the controller, so that it knows what fields and related objects to load.
public DynamicAccountFieldsLister(ApexPages.StandardController controller) {
controller.addFields(editableFields);
}
This works well for pages when the complete list of fields to load can be known when the controller extension is instantiated. If the list of fields can’t be determined until later in the request processing, you can call reset() on the controller and then add the fields. This will cause the controller to send the revised query. Using Dynamic References for a User-Customizable Page provides an example of this technique.
For more information on these methods, seeThis example creates a Visualforce page for a case record, with certain fields that are editable. Some of the fields displayed are from a related object, showing how you can use dynamic references to traverse relationships.
public class DynamicCaseLoader { public final Case caseDetails { get; private set; } // SOQL query loads the case, with Case fields and related Contact fields public DynamicCaseLoader(ApexPages.StandardController controller) { String qid = ApexPages.currentPage().getParameters().get('id'); String theQuery = 'SELECT Id, ' + joinList(caseFieldList, ', ') + ' FROM Case WHERE Id = :qid'; this.caseDetails = Database.query(theQuery); } // A list of fields to show on the Visualforce page public List<String> caseFieldList { get { if (caseFieldList == null) { caseFieldList = new List<String>(); caseFieldList.add('CaseNumber'); caseFieldList.add('Origin'); caseFieldList.add('Status'); caseFieldList.add('Contact.Name'); // related field caseFieldList.add('Contact.Email'); // related field caseFieldList.add('Contact.Phone'); // related field } return caseFieldList; } private set; } // Join an Apex list of fields into a SELECT fields list string private static String joinList(List<String> theList, String separator) { if (theList == null) { return null; } if (separator == null) { separator = ''; } String joined = ''; Boolean firstItem = true; for (String item : theList) { if(null != item) { if(firstItem){ firstItem = false; } else { joined += separator; } joined += item; } } return joined; } }
<apex:page standardController="Case" extensions="DynamicCaseLoader"> <br/> <apex:form > <apex:repeat value="{!caseFieldList}" var="cf"> <h2>{!cf}</h2> <br/> <!-- The only editable information should be contact information --> <apex:inputText value="{!caseDetails[cf]}" rendered="{!IF(contains(cf, "Contact"), true, false)}"/> <apex:outputText value="{!caseDetails[cf]}" rendered="{!IF(contains(cf, "Contact"), false, true)}"/> <br/><br/> </apex:repeat> </apex:form> </apex:page>
The full potential of Visualforce dynamic bindings is in building pages without knowing which fields are available on an object. The following example demonstrates this capability with a list of accounts that can be customized without knowing any of the fields on the Account object, except for the Name field required on all objects. This is made possible by using the Schema.SobjectType.Account.fields.getMap() to retrieve the list of fields that exist on the object, and Visualforce dynamic references.
public class DynamicCustomizableListHandler { // Resources we need to hold on to across requests private ApexPages.StandardSetController controller; private PageReference savePage; // This is the state for the list "app" private Set<String> unSelectedNames = new Set<String>(); private Set<String> selectedNames = new Set<String>(); private Set<String> inaccessibleNames = new Set<String>(); public DynamicCustomizableListHandler(ApexPages.StandardSetController controller) { this.controller = controller; loadFieldsWithVisibility(); } // Initial load of the fields lists private void loadFieldsWithVisibility() { Map<String, Schema.SobjectField> fields = Schema.SobjectType.Account.fields.getMap(); for (String s : fields.keySet()) { if (s != 'Name') { // name is always displayed unSelectedNames.add(s); } if (!fields.get(s).getDescribe().isAccessible()) { inaccessibleNames.add(s); } } } // The fields to show in the list // This is what we generate the dynamic references from public List<String> getDisplayFields() { List<String> displayFields = new List<String>(selectedNames); displayFields.sort(); return displayFields; } // Nav: go to customize screen public PageReference customize() { savePage = ApexPages.currentPage(); return Page.CustomizeDynamicList; } // Nav: return to list view public PageReference show() { // This forces a re-query with the new fields list controller.reset(); controller.addFields(getDisplayFields()); return savePage; } // Create the select options for the two select lists on the page public List<SelectOption> getSelectedOptions() { return selectOptionsFromSet(selectedNames); } public List<SelectOption> getUnSelectedOptions() { return selectOptionsFromSet(unSelectedNames); } private List<SelectOption> selectOptionsFromSet(Set<String> opts) { List<String> optionsList = new List<String>(opts); optionsList.sort(); List<SelectOption> options = new List<SelectOption>(); for (String s : optionsList) { options.add(new SelectOption(s, decorateName(s), inaccessibleNames.contains(s))); } return options; } private String decorateName(String s) { return inaccessibleNames.contains(s) ? '*' + s : s; } // These properties receive the customization form postback data // Each time the [<<] or [>>] button is clicked, these get the contents // of the respective selection lists from the form public transient List<String> selected { get; set; } public transient List<String> unselected { get; set; } // Handle the actual button clicks. Page gets updated via a // rerender on the form public void doAdd() { moveFields(selected, selectedNames, unSelectedNames); } public void doRemove() { moveFields(unselected, unSelectedNames, selectedNames); } private void moveFields(List<String> items, Set<String> moveTo, Set<String> removeFrom) { for (String s: items) { if( ! inaccessibleNames.contains(s)) { moveTo.add(s); removeFrom.remove(s); } } } }
<apex:page standardController="Account" recordSetVar="accountList" extensions="DynamicCustomizableListHandler"> <br/> <apex:form > <!-- View selection widget, uses StandardController methods --> <apex:pageBlock> <apex:outputLabel value="Select Accounts View: " for="viewsList"/> <apex:selectList id="viewsList" size="1" value="{!filterId}"> <apex:actionSupport event="onchange" rerender="theTable"/> <apex:selectOptions value="{!listViewOptions}"/> </apex:selectList> </apex:pageblock> <!-- This list of accounts has customizable columns --> <apex:pageBlock title="Accounts" mode="edit"> <apex:pageMessages /> <apex:panelGroup id="theTable"> <apex:pageBlockTable value="{!accountList}" var="acct"> <apex:column value="{!acct.Name}"/> <!-- This is the dynamic reference part --> <apex:repeat value="{!displayFields}" var="f"> <apex:column value="{!acct[f]}"/> </apex:repeat> </apex:pageBlockTable> </apex:panelGroup> </apex:pageBlock> <br/> <apex:commandButton value="Customize List" action="{!customize}"/> </apex:form> </apex:page>
The second <apex:pageBlock> holds a <apex:pageBlockTable> that has columns added in a <apex:repeat>. All columns in the repeat component use a dynamic reference to account fields, {!acct[f]}, to display the user’s custom-selected fields.
<apex:page standardController="Account" recordSetVar="ignored" extensions="DynamicCustomizableListHandler"> <br/> <apex:form > <apex:pageBlock title="Select Fields to Display" id="selectionBlock"> <apex:pageMessages /> <apex:panelGrid columns="3"> <apex:selectList id="unselected_list" required="false" value="{!selected}" multiselect="true" size="20" style="width:250px"> <apex:selectOptions value="{!unSelectedOptions}"/> </apex:selectList> <apex:panelGroup > <apex:commandButton value=">>" action="{!doAdd}" rerender="selectionBlock"/> <br/> <apex:commandButton value="<<" action="{!doRemove}" rerender="selectionBlock"/> </apex:panelGroup> <apex:selectList id="selected_list" required="false" value="{!unselected}" multiselect="true" size="20" style="width:250px"> <apex:selectOptions value="{!selectedOptions}"/> </apex:selectList> </apex:panelGrid> <em>Note: Fields marked <strong>*</strong> are inaccessible to your account</em> </apex:pageBlock> <br/> <apex:commandButton value="Show These Fields" action="{!show}"/> </apex:form> </apex:page>