3

I am referring to a code from salesforce documentation https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_dynamic_vf_sample_standard.htm where it is shown how to build a list dynamically by getting the user input of what field should be shown in the list. I am experiece an error when I am reusing the code in my developer org.

When I click customize link and select the fields I want to display I get an error saying - SObject row was retrieved via SOQL without querying Account.Field__c. I verified that the selected list i.e. DisplayFields List has the field name that was selected by the user selected by the user and I use this DisplayFields with controller.addFields(getDisplayFields()); so that the controller queries the field to be displayed. Any assistance is appreciated. Thanks!

Controller class:

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();
        List<String> test = getDisplayFields();
        for(String s : test){
            System.debug('***********************************************' + s );
        }
        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);
            }
        }
    }
}

VF Page 1 - DynamicCustomizableList

<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>

VF Page 2 - CustomizeDynamicList

<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>

In controller, this where it is querying the field:

// Nav: return to list view
public PageReference show() {
    // This forces a re-query with the new fields list
    controller.reset();
    List<String> test = getDisplayFields();
    for(String s : test){
        System.debug('***********************************************' + s );
    }
    controller.addFields(getDisplayFields());
    return savePage; 
}

All code is form this link

Edit 1- As Adrian said I believe the problem is with using recordsetvar to display the field. Look at a simple VF page below which uses recordsetvar variable to display phone field from account. I get the same error saying - System.SObjectException: SObject row was retrieved via SOQL without querying the requested field: Account.Phone

<apex:page StandardController="Account" recordSetVar="acclist">

<apex:pageblock >
            <apex:pageBlockTable value="{!acclist}" var="acct">
                <apex:column value="{!acct.Name}"/>
                <apex:column value="{!acct['Phone']}"/>
            </apex:pageBlockTable>
</apex:pageblock>


</apex:page>
  • you have the field names, how are you retrieving field values? – Santanu Boral Nov 22 '16 at 16:11
  • @SantanuBoral I am using controller.addFields(getDisplayFields()); to retrieve the field values. Is that what you asked? – SfdcBat Nov 22 '16 at 16:22
  • 1
    That would only modify the records returned by controller.getRecords(), I believe. You would have to loop over those instead of the default recordSetVar collection. Too much code to take the time to repro right now but I suspect that's what you need to change. – Adrian Larson Nov 22 '16 at 16:28
  • Am I not querying the values using a SOQL here? by the way the description of the code is at the link I provided If you wanna get a quick glance at what all this code is doing. It is copied from salesforce developer documentation. – SfdcBat Nov 22 '16 at 16:32
  • @AdrianLarson what you said is correct. Please see my edit. Do you suggest not to use recordsetvar for this scnerio? – SfdcBat Nov 22 '16 at 16:43
4

Repro

The MVR here is pretty simple:

<apex:page standardController="Contact" recordSetVar="records">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!records}" var="record">
            <apex:column value="{!record['Email']}" />
        </apex:pageBlockTable> 
    </apex:pageBlock>
</apex:page>

Fix

In the simple context of the MVR (just one page in play), you can simply call addFields in your constructor or as a page action.

Extension

public with sharing class MyExtension
{
    final ApexPages.StandardSetController controller;
    public with sharing class MyExtension(ApexPages.StandardSetController controller)
    {
        this.controller = controller;
    }
    public void addFields()
    {
        controller.addFields(new List<String>{'Email'});
    }
}

Page

<apex:page standardController="Contact" recordSetVar="records"
    extensions="MyExtension" action="{!addFields}">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!records}" var="record">
            <apex:column value="{!record['Email']}" />
        </apex:pageBlockTable> 
    </apex:pageBlock>
</apex:page>

Two Page Alternative

Since you're involving multiple pages, you may need to use a controller side property to retrieve the record set. Below is a demo that works with a two page setup.

Extension

public with sharing class MyExtension
{
    public List<String> fields { get; private set; }
    public List<Contact> records { get; private set; }

    final ApexPages.StandardSetController controller;
    public MyExtension(ApexPages.StandardSetController controller)
    {
        this.controller = controller;
        this.fields = new List<String>();
    }

    public void addFields()
    {
        controller.addFields(fields);
        records = controller.getRecords();
    }
    public PageReference selectFields()
    {
        fields.add('Email');
        fields.add('FirstName');
        fields.add('LastName');
        return Page.DemoPage2.setRedirect(false);
    }
}

Page 1

<apex:page standardController="Contact" recordSetVar="unused"
    extensions="MyExtension" action="{!selectFields}" />

Page 2

<apex:page standardController="Contact" recordSetVar="unused"
    extensions="MyExtension" action="{!addFields}">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!records}" var="record">
            <apex:repeat value="{!fields}" var="field">
                <apex:column value="{!record[field]}" />
            </apex:repeat>
        </apex:pageBlockTable> 
    </apex:pageBlock>
</apex:page>

Back and Forth Alternative

If you want to alternate back and forth you'll need to query every time. Below is a demo that works with a two page setup. You may want to change controller.getRecords() to controller.getSelected()` depending on your needs.

Extension

public with sharing class MyExtension
{
    public List<String> fields { get; private set; }
    public List<Contact> records
    {
        get
        {
            if (fields.isEmpty()) fields.add('Name');
            String soql = 'SELECT ' + String.join(fields, ',') +
                ' FROM Contact WHERE Id IN :ids';
            return Database.query(soql);
        }
    }

    final Set<Id> ids;
    public MyExtension(ApexPages.StandardSetController controller)
    {
        this.ids = new Map<Id, SObject>(controller.getRecords()).keySet();
        this.fields = new List<String>();
    }

    public PageReference customize()
    {
        return Page.Page2.setRedirect(false);
    }
    public PageReference selectFields()
    {
        fields.add('Email');
        fields.add('FirstName');
        fields.add('LastName');
        return Page.Page1.setRedirect(false);
    }
}

Page 1

<apex:page standardController="Contact" recordSetVar="unused" extensions="MyExtension">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!records}" var="record">
            <apex:repeat value="{!fields}" var="field">
                <apex:column value="{!record[field]}" />
            </apex:repeat>
        </apex:pageBlockTable> 
    </apex:pageBlock>
</apex:page>

Page 2

<apex:page standardController="Contact" recordSetVar="unused" extensions="MyExtension">
    <apex:form>
        <apex:commandButton value="Select" action="{!selectFields}" />
    </apex:form>
</apex:page>
  • I believe you are right. I added the addFields method to constructor as well as checked whether selectedNames list has the names of the field selected but it still throws the same error. Any idea whats wrong. I am wondering how does the code in salesforce documentation could be broken. – SfdcBat Nov 22 '16 at 17:30
  • When I used: this.controller.addFields(new List<String>{'AccountNumber'}); instead of this.controller.addFields(getDisplayFields()); in the constructor , I was able to see the AccountNumber in the list column. – SfdcBat Nov 22 '16 at 17:41
  • You might try moving the addFields method to a separate action you add to the second <apex:page>. It may just run the query once, though, when moving from one page to another. If so, you'd need to work out a lazy load pattern that allows you to refresh the list. – Adrian Larson Nov 22 '16 at 17:44
  • 1
    @DerekF Not if your test builds the StandardSetController without a query, or includes the relevant fields in its own query. Also, unless your Apex code references that field, you would never see that error anyway. – Adrian Larson Nov 22 '16 at 17:46
  • 1
    @SfdcBat I added an example that works with two distinct pages. – Adrian Larson Nov 22 '16 at 18:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.