function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Laura SpellmanLaura Spellman 

Custom list button To open VF page with Custom Controller

Hey there, 

I am new to salesforce dev and am having issues getting a visual force page to open from a custom list button on a custom object. When I click the custom list button from the object (sales activity) , my page doesnt open and a standard add contact form does. I've researched a bit in this forum, and learned that this can be due to a missing standard controller. I followed that advice and dont think that is my issue, although I could very easily be wrong. The full page source is below, meant to allow users to add a contact from various sources to be an 'Impact Contact'. What I think might be the issue is in the custom controller, which I also have the code listed below. 
<apex:page standardController="IMPACT_Contact__c" recordSetVar="contacts" extensions="IMPACTContactExt" title="Add IMPACT Contacts to Sales Activity">


    <apex:tabPanel switchType="client" selectedTab="available" id="contactPanel">
        <apex:tab label="Available Contacts" name="available" id="tabAvailable">
            <apex:form id="formAddContact">
            <apex:pageBlock title="Available Contacts">
                <apex:pageBlockButtons >
                    <apex:commandButton action="{!addOthers}" value="Add Selected Contacts" reRender="otherContacts, existingContactsTable"/>
                    <apex:commandButton action="{!done}" value="Done"/>
                </apex:pageBlockButtons>
                 <apex:selectCheckboxes id="otherContacts" layout="pageDirection" value="{!otherSelectedContacts}">
                    <apex:selectOptions value="{!otherContacts}"/>
                </apex:selectCheckboxes>
            </apex:pageBlock>
            </apex:form>        
        </apex:tab>

        <apex:tab label="Find Contacts" name="find" id="tabFind">
            <apex:form id="formFindContact">
                <apex:pageBlock title="Find Contacts">
                    <apex:pageBlockButtons >
                        <apex:commandButton action="{!findContacts}" value="Find Contacts" reRender="findResults"/>
                        <apex:commandButton action="{!addFound}" value="Add Selected Contacts" reRender="findResults, existingContactsTable"/>
                        <apex:commandButton action="{!done}" value="Done" immediate="true" />
                    </apex:pageBlockButtons>
                    <apex:outputPanel id="findResults">
                        <apex:pageBlockSection title="Contact to Find" columns="1">
                            <apex:inputField value="{!findContact.FirstName}" />
                            <apex:inputField value="{!findContact.LastName}" required="true" />
                        </apex:pageBlockSection>
                        <apex:selectCheckboxes id="foundContacts" layout="pageDirection" value="{!foundSelectedContacts}">
                            <apex:selectOptions value="{!foundContacts}"/>
                        </apex:selectCheckboxes>
                        <apex:outputPanel id="noContactsFound"  rendered="{! IF(AND(foundContacts.size = 0, findContact.LastName <> null), 'TRUE', 'FALSE') }">
                            There were no contacts found matching your search criteria.
                        </apex:outputPanel>
                    </apex:outputPanel>
    
                </apex:pageBlock>
            </apex:form>        
        </apex:tab>

        <apex:tab label="Create New Contact" name="new" id="tabNew">
            <div>
                Use this form to add a new contact to Salesforce and associate it 
                with this sales activity with the selected role.  A search of existing contacts will
                be conducted automatically before the new contact record is created. If
                an existing contact is found, they will be associated with the sales activity.
            </div>
            <apex:form id="formNewContact">
                <apex:pageBlock >
                    <apex:pageBlockButtons >
                        <apex:commandButton action="{!addNew}" value="Save and Add" reRender="newContactSection, existingContactsTable"/>
                        <apex:commandButton action="{!done}" value="Done" immediate="true" />
                    </apex:pageBlockButtons>
                    <apex:pageBlockSection columns="2" id="newContactSection">
                        <apex:inputField value="{!newContact.FirstName}" required="true"/>
                        <apex:inputField value="{!newContact.LastName}" required="true"/>
                        <apex:inputField value="{!newContact.Title}"/>
                        <apex:inputField value="{!newContact.Phone}"/>
                        <apex:inputField value="{!newContact.Email}"/>
                        <apex:inputField value="{!newContact.AccountID}" required="true"/>
                        <apex:inputField value="{!newIMPACTContact.Contact_Role__c}" required="true"/>
                        {!addContactResult}
                    </apex:pageBlockSection>
                </apex:pageBlock>
            </apex:form>
        </apex:tab>        

        <apex:tab label="Existing Key Contacts" name="existing" id="tabExisting">
            <apex:form id="formExistingContact">
                <apex:pageBlock title="Associated Contacts">
                    <apex:pageBlockButtons >
                        <apex:commandButton action="{!done}" value="Done"/>
                    </apex:pageBlockButtons>            
                    <apex:pageBlockTable value="{!impactContacts}" var="ic" id="existingContactsTable">
                        <apex:column value="{!ic.Contact__r.Name}"/>
                        <apex:column value="{!ic.Contact__r.Title}"/>
                        <apex:column value="{!ic.Contact__r.Account.Name}"/>
                        <apex:column value="{!ic.Contact_Role__c}"/>
                    </apex:pageBlockTable>
                </apex:pageBlock>
            </apex:form>
        </apex:tab>
        
        
    </apex:tabPanel>
When attempting to preview my page, I get an error <List has no rows for assignment to SObject > which makes me think I have an issue with my query, specifically
this.salesActivity
So My questions are this: Will a query mistake in the custom controller prevent the entire page from loading, including all tabs, formatting, etc? and secondly, is there anything glaringly obvious with the way I am approaching this that is wrong? I have been researching the issue and am pretty stuck now. 
public with sharing class IMPACTContactExt {
    private final IMPACT_Contact__c  impactContact;
    private final Sales_Activity__c  salesActivity;
    
    public IMPACTContactExt(ApexPages.StandardSetController controller) {
        
        this.impactContact = (IMPACT_Contact__c)controller.getRecord();
        this.salesActivity = [SELECT Id, Activity_Type__c, Activity_Status__c FROM Sales_Activity__c WHERE Id = :ApexPages.currentPage().getParameters().get('id')];
    
    }
    
    public IMPACTContactExt(Sales_Activity__c p) {
        this.salesActivity = [SELECT Id, Activity_Type__c, Activity_Status__c FROM Sales_Activity__c WHERE Id = :p.Id];
    }
    
    public IMPACT_Contact__c newIMPACTContact {
        get{ 
            if (newIMPACTContact == null) newIMPACTContact = new IMPACT_Contact__c(Sales_Activity__c = salesActivity.Id);
            return newIMPACTContact; 
        } 
        set;
    }
    
    public Contact newContact {
        get{ 
            if (newContact == null ) newContact = new Contact();
            return newContact; 
        } 
        set;
    }
    
    public String addContactResult {get; set;}
    
    public Contact findContact {
        get{ 
            if (findContact == null ) findContact = new Contact();
            return findContact; 
        } 
        set;
    }

    public List<IMPACT_Contact__c> getIMPACTContacts() {
        List<IMPACT_Contact__c> impactContacts;
    
        if(impactContacts == null){
            impactContacts = [
                SELECT 
                    Name
                    ,Id
                    ,Contact__r.Title
                    ,Contact__r.Email
                    ,Contact__r.Phone
                    ,Contact__r.Name
                    ,Contact__r.AccountId
                    ,Contact__r.Account.Name
                    ,Contact__r.Id
                    ,Contact__c
                    ,Contact_Role__c 
                FROM IMPACT_Contact__c
                WHERE Sales_Activity__c = :salesActivity.Id
            ];
        }
        
        return impactContacts;
    }

    private Set<ID> GetExistingContactIds() {
        Set<ID> existingContacts = new Set<ID>();
            
        for (IMPACT_Contact__c c : [SELECT Contact__r.Id FROM IMPACT_Contact__c WHERE Sales_Activity__c = :salesActivity.Id ])
            existingContacts.add(c.Contact__r.Id);
        
        return existingContacts;
    }
    
    
    public String[] otherSelectedContacts {
        get{
            if (otherSelectedContacts == null) otherSelectedContacts = new String[]{};
            return otherSelectedContacts;
        }
        set;
    }
    
    public String[] foundSelectedContacts {
        get{
            if (foundSelectedContacts == null) foundSelectedContacts = new String[]{};
            return foundSelectedContacts;
        }
        set;
    }
    
    public List<Selectoption> otherContacts {
        get {
            List<Contact> otherContacts;
            List<Selectoption> otherContactSelect = new List<Selectoption>();
            List<ID> accounts = new List<ID>();
            Set<ID> existingContacts = GetExistingContactIds();
            
            if (salesActivity.Activity_Type__c != null) accounts.add(salesActivity.Activity_Type__c);
            if (salesActivity.Activity_Status__c != null) accounts.add(salesActivity.Activity_Status__c);
    
            otherContacts = [
                SELECT Id, Title, Name, Account.Name
                FROM Contact
                WHERE AccountId IN :accounts AND Id NOT IN :existingContacts
            ];
            
            for (Contact c : otherContacts) {
                String contactDescription = c.Name;
                if (c.Title != null && c.Account.Name != null) {
                    contactDescription += ', ' + c.Title + ' - ' + c.Account.Name;
                } else if (c.Title != null) {
                    contactDescription += ', ' + c.Title;
                    
                } else if (c.Account.Name != null) {
                    contactDescription += ' - ' + c.Account.Name;
                }

                otherContactSelect.add(new Selectoption(c.Id, contactDescription));
            }
            
            return otherContactSelect;
        }
        set;
    }
    
    public List<Selectoption> foundContacts {
        get {
            if(foundContacts == null) foundContacts = new List<Selectoption>();
            return foundContacts;
        }
        set;
    }
    
    public PageReference findContacts() {

        if (findContact.LastName != null) {
            String newContactName;
            if(findContact.FirstName != null) {
                newContactName = findContact.FirstName + ' ' + findContact.LastName;
            } else {
                newContactName = findContact.LastName;
            }
            
            List<List<Sobject>> contactSearch = [FIND :newContactName IN NAME FIELDS RETURNING Contact(Id, Name, Title, Account.Name)];
            Contact [] contactsFound = ((List<Contact>)contactSearch[0]);
            foundContacts = new List<Selectoption>();
    
            if (contactsFound.size() != 0) {
                Set<ID> existingContacts = GetExistingContactIds();
                
                for(Contact c : contactsFound) {
                    // don't add a contact if it is already on our list
                    if (existingContacts.contains(c.Id)) continue;
                        
                    String contactDescription = c.Name;
                    if (c.Title != null && c.Account.Name != null) {
                        contactDescription += ', ' + c.Title + ' - ' + c.Account.Name;
                    } else if (c.Title != null) {
                        contactDescription += ', ' + c.Title;
                        
                    } else if (c.Account.Name != null) {
                        contactDescription += ' - ' + c.Account.Name;
                    }
                    
                    foundContacts.add(new Selectoption(c.Id, contactDescription));
                }
            }
        } else {
            foundContacts = new List<Selectoption>();
        }
        
        return null;
    }   
    
    public PageReference addNew() {
        // search for the contact before adding
        String findContactName;
        
        if (newContact.FirstName == null) {
            findContactName =  newContact.LastName;
        } else {
            findContactName =  newContact.FirstName + ' ' + newContact.LastName;
        }
        
        Boolean matchFound = false; 
        List<List<Sobject>> contactSearch = [FIND :findContactName IN NAME FIELDS RETURNING Contact(Id, FirstName, LastName, Phone, Email, AccountId)];
        Contact [] contactsFound = ((List<Contact>)contactSearch[0]);
        
        if (contactsFound.size() != 0) {
            for(Contact c : contactsFound) {
                if (newContact.LastName == c.LastName && (newContact.Phone == c.Phone || newContact.Email == c.Email)) {
                    newContact = c;
                    addContactResult = 'An existing contact was found that matched your input.  This contact was added to the Sales Activity and can be seen by going to the Existing IMPACT Contacts tab.';
                    matchFound = true; 
                    break;
                }
            }
        }
        
        if (matchFound == false) {
            insert newContact;
            addContactResult = 'New contact created and added to the sales activity.  You can view this contact by going to the Existing IMPACT Contacts tab.';
        }
        
        newIMPACTContact.Contact__c = newContact.Id;
        insert newIMPACTContact;
        
        newContact = null;
        newIMPACTContact = null;
        
        return null;
    }
    
    public PageReference addOthers() {
        List<IMPACT_Contact__c> impactContacts = new List<IMPACT_Contact__c>();
        
        for (String opt : otherSelectedContacts) {
            impactContacts.add(new IMPACT_Contact__c(Contact__c = opt, Sales_Activity__c = salesActivity.Id));
        }
        
        insert impactContacts;
        
        return null;
    }
    
    public PageReference addFound() {
        List<IMPACT_Contact__c> impactContacts = new List<IMPACT_Contact__c>();
        List<Integer> optionsToRemove = new List<Integer>();
        
        for (String opt : foundSelectedContacts) {
            impactContacts.add(new IMPACT_Contact__c(Contact__c = opt, Sales_Activity__c = salesActivity.Id));
        }

        insert impactContacts;
        
        // go through the found contacts list and remove any contacts we just added
        // to the sales Activity
        for (Integer i = 0; i <= foundContacts.size() - 1; i++) {
            for (String opt : foundSelectedContacts) {
                if (opt == foundContacts[i].getValue()) {
                    optionsToRemove.add(i);
                    break;
                }
            }
        }

        for (Integer i : optionsToRemove) {
            foundContacts.remove(i);
        }

        return null;
    }
    
    public PageReference done() {
        PageReference salesActivityPage = new ApexPages.StandardController(salesActivity).view();
        salesActivityPage.setRedirect(true);
        
        return salesActivityPage;
    }



}

 
Best Answer chosen by Laura Spellman
Alain CabonAlain Cabon
Hi,

private final IMPACT_Contact__c impactContact;
private final Sales_Activity__c salesActivity;

These variables will be not in the view state. They should be public (or global).

1) What do you see in the debug log?  Just open developer console while you use your VFP.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_debugging_debug_log.htm

2) What do you see in the view state?

https://developer.salesforce.com/page/An_Introduction_to_Visualforce_View_State

Salesforce allows you to examine the View State (encrypted data otherwise for firebug).

The data in the view state should be sufficient to recreate the state of the page when the postback is received. To do this, it stores the following data:
  • All non-transient data members in the associated controller (either standard or custom) and the controller extensions.
  • Objects that are reachable from a non-transient data member in a controller or controller extension.
  • The component tree for that page, which represents the page's component structure and the associated state, which are the values applied to those components.
  • A small amount of data for Visualforce to do housekeeping.
  • View state data is encrypted and cannot be viewed with tools like Firebug. The view state inspector described below lets you look at the contents of view state.
https://developer.salesforce.com/page/An_Introduction_to_Visualforce_View_State

Best regards
Alain

All Answers

Alain CabonAlain Cabon
Hi,

private final IMPACT_Contact__c impactContact;
private final Sales_Activity__c salesActivity;

These variables will be not in the view state. They should be public (or global).

1) What do you see in the debug log?  Just open developer console while you use your VFP.

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_debugging_debug_log.htm

2) What do you see in the view state?

https://developer.salesforce.com/page/An_Introduction_to_Visualforce_View_State

Salesforce allows you to examine the View State (encrypted data otherwise for firebug).

The data in the view state should be sufficient to recreate the state of the page when the postback is received. To do this, it stores the following data:
  • All non-transient data members in the associated controller (either standard or custom) and the controller extensions.
  • Objects that are reachable from a non-transient data member in a controller or controller extension.
  • The component tree for that page, which represents the page's component structure and the associated state, which are the values applied to those components.
  • A small amount of data for Visualforce to do housekeeping.
  • View state data is encrypted and cannot be viewed with tools like Firebug. The view state inspector described below lets you look at the contents of view state.
https://developer.salesforce.com/page/An_Introduction_to_Visualforce_View_State

Best regards
Alain
This was selected as the best answer
Alain CabonAlain Cabon
Hi,

Thanks for the best answer but private variables can be in the view state as long as they are not static

Static and Instance Methods, Variables, and Initialization Code: They aren’t transmitted as part of the view state for a Visualforce page.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_static.htm 

Example of a private object in the view state: private final Account acct;
https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_controller_extension.htm

or sample 3 (private):   https://developer.salesforce.com/page/An_Introduction_to_Visualforce_View_State

Checking the debug log and the view state is still an interesting answer anyway. 

Best regards
Alain