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
Katie DeLuna 18Katie DeLuna 18 

Visualforce page error - Cross objects

Hello!
I am not a developer, but I'm trying to do developing (stupid me!)
I am trying to create a visualforce page that shows Contact information as a related list on Opportunities. Below is the code that I've been working on. 
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0">
  <apex:input value="Contact"/> 
   <apex:pageBlock title="Main Contact Info">
         <apex:pageBlockSection >
           Contact First Name : {!Contact.FirstName}<br/>
           Contact Last Name : {!Contact.LastName} <br/>
           Preferred Contact Time : {!Contact.Preferred_Contact_Time__c} <br/>
           Inquiring For : {!Contact.Inquiring_For__c} <br/>
           Interested In : {!Contact.Interested_In__c} <br/>
           Number of Listings : {!Contact.Number_of_Listings__c} <br/>
           Number of Agents : {!Contact.Number_of_Agents__c} <br/>
           Number of Offices : {!Contact.Number_of_Offices__c} <br/>
          </apex:pageBlockSection>
    </apex:pageBlock>   
</apex:page>


This is the error I receive: Attribute value in <apex:input> must contain only a formula expression that resolves to a single controller variable or method in ContactRelatedList at line 2 column 32

I have no idea what this means. I've been googling, but can't seem to figure it out. Help!
Best Answer chosen by Katie DeLuna 18
Alain CabonAlain Cabon

<apex:pageBlockTable> instead of <apex:dataTable>
 
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0" extensions="OppContacts">
 <div style="overflow: scroll; height: 300px;">
  <apex:pageBlock title="Pardot Contact Info">
     <apex:pageBlockSection columns="1">
        <apex:pageBlockTable value="{!myOppContacts}" var="cnt">
            <apex:column headerValue="Preferred Contact Time" value="{!cnt.Preferred_Contact_Time__c}"/>
            <apex:column headerValue="Inquiring For" value="{!cnt.Inquiring_For__c}"/>
            <apex:column headerValue="Interested In" value="{!cnt.Interested_In__c}"/>
            <apex:column headerValue="Number of Listings" value="{!cnt.Number_of_Listings__c}"/>
            <apex:column headerValue="Number of Agents" value="{!cnt.Number_of_Agents__c}"/>
            <apex:column headerValue="Number of Offices" value="{!cnt.Number_of_Offices__c}"/>
       </apex:pageBlockTable>
    </apex:pageBlockSection>
  </apex:pageBlock>
</div>
</apex:page>

 

All Answers

Alain CabonAlain Cabon
Hi,

You cannot write it like that directly. You need an extension for your standard controller.
 
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0" extensions="OppContacts">
    <apex:pageBlock title="Main Contact Info">
        <apex:pageBlockSection >
            <apex:dataTable value="{!myOppContacts}" var="cnt">
                <apex:column headerValue="Contact First Name" value="{!cnt.FirstName}"/>
                <apex:column headerValue="Contact Last Name" value="{!cnt.LastName}"/>
                <apex:column headerValue="Preferred Contact Time" value="{!cnt.Preferred_Contact_Time__c}"/>
                <apex:column headerValue="Inquiring For" value="{!cnt.Inquiring_For__c}"/>
                <!-- add your other columns here -->
            </apex:dataTable>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:page>

Apex class = extension
 
public class OppContacts {
  private final Id oppId;
    public List<Contact> myOppContacts {get;set;}
    public OppContacts(ApexPages.StandardController stdController) {
        this.oppId = ((Opportunity)stdController.getRecord()).Id;
        Opportunity opp = [select id,accountid from opportunity where id = :oppId];
        if (opp.AccountId != null) {
            myOppContacts = [select firstname,lastname,AccountId,Preferred_Contact_Time__c,Inquiring_For__c from contact where accountid = :opp.AccountId];
        }
    }
}

 
Katie DeLuna 18Katie DeLuna 18
That worked! However, it's showing up odd. Is there a way it can take up the entire width of the page or have it scrollable?
User-added image
 
Alain CabonAlain Cabon

1)  the entire width of the page (just add: columns="1"):    <apex:pageBlockSection columns="1">.

2)  Scrollbar :  https://www.thephani.com/pageblock-scrollbars/

3)  Prettier? You need to play with the CSS tags.

https://developer.salesforce.com/forums/?id=906F0000000kFwCIAU

But these solutions are sometimes exclusive one of the other and for the CSS, it is unlimited.
Katie DeLuna 18Katie DeLuna 18
Adding the columndidnt help. I think I need to separate it differently. The fields just run into eachother :(
Alain CabonAlain Cabon
CSS:      <apex:column styleClass="inBorder">

<style type="text/css">
    .outBorder
    {
        border:3px outset black;
    }
    
    .inBorder
    {
        border-top:3px dotted black;
        border-left:3px dotted black;
    }    
</style>

You can change the colors, the borders, and so on (everything).
Katie DeLuna 18Katie DeLuna 18
You're so helpful!

Here is my code (where would I update yours??):
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0" extensions="OppContacts">
  <apex:pageBlock title="Pardot Contact Info">
     <apex:pageBlockSection columns="1">
        <apex:dataTable value="{!myOppContacts}" var="cnt">
            <apex:column headerValue="Preferred Contact Time" value="{!cnt.Preferred_Contact_Time__c}"/>
            <apex:column headerValue="Inquiring For" value="{!cnt.Inquiring_For__c}"/>
            <apex:column headerValue="Interested In" value="{!cnt.Interested_In__c}"/>
            <apex:column headerValue="Number of Listings" value="{!cnt.Number_of_Listings__c}"/>
            <apex:column headerValue="Number of Agents" value="{!cnt.Number_of_Agents__c}"/>
            <apex:column headerValue="Number of Offices" value="{!cnt.Number_of_Offices__c}"/>
        </apex:dataTable>
    </apex:pageBlockSection>
  </apex:pageBlock>
</apex:page>
Alain CabonAlain Cabon
For the scrollbar, just add a <div> 
 
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0" extensions="OppContacts">
 <div style="overflow: scroll; height: 300px;">
  <apex:pageBlock title="Pardot Contact Info">
     <apex:pageBlockSection columns="1">
        <apex:dataTable value="{!myOppContacts}" var="cnt">
            <apex:column headerValue="Preferred Contact Time" value="{!cnt.Preferred_Contact_Time__c}"/>
            <apex:column headerValue="Inquiring For" value="{!cnt.Inquiring_For__c}"/>
            <apex:column headerValue="Interested In" value="{!cnt.Interested_In__c}"/>
            <apex:column headerValue="Number of Listings" value="{!cnt.Number_of_Listings__c}"/>
            <apex:column headerValue="Number of Agents" value="{!cnt.Number_of_Agents__c}"/>
            <apex:column headerValue="Number of Offices" value="{!cnt.Number_of_Offices__c}"/>
        </apex:dataTable>
    </apex:pageBlockSection>
  </apex:pageBlock>
</div>
</apex:page>
Alain CabonAlain Cabon
The last step is the CSS (optional).

You are in Lightning or Classics ?
Katie DeLuna 18Katie DeLuna 18
I'm in Classic
Katie DeLuna 18Katie DeLuna 18
Let me try that code! :)
Katie DeLuna 18Katie DeLuna 18
So it looks like now it's just a scroll. The fields just look like they run together. It would be nice if they were separated either going down line by line with a scroll option OR have a line between each field so that the labels don't run in together. I'm at a loss :(

User-added image
Alain CabonAlain Cabon

<apex:pageBlockTable> instead of <apex:dataTable>
 
<apex:page standardController="Opportunity" sidebar="false" doctype="html-5.0" extensions="OppContacts">
 <div style="overflow: scroll; height: 300px;">
  <apex:pageBlock title="Pardot Contact Info">
     <apex:pageBlockSection columns="1">
        <apex:pageBlockTable value="{!myOppContacts}" var="cnt">
            <apex:column headerValue="Preferred Contact Time" value="{!cnt.Preferred_Contact_Time__c}"/>
            <apex:column headerValue="Inquiring For" value="{!cnt.Inquiring_For__c}"/>
            <apex:column headerValue="Interested In" value="{!cnt.Interested_In__c}"/>
            <apex:column headerValue="Number of Listings" value="{!cnt.Number_of_Listings__c}"/>
            <apex:column headerValue="Number of Agents" value="{!cnt.Number_of_Agents__c}"/>
            <apex:column headerValue="Number of Offices" value="{!cnt.Number_of_Offices__c}"/>
       </apex:pageBlockTable>
    </apex:pageBlockSection>
  </apex:pageBlock>
</div>
</apex:page>

 
This was selected as the best answer
Katie DeLuna 18Katie DeLuna 18
That worked!!!! Any way I can get the scrollbar removed or have it so it looks like a related list (full  yellow bar across the entire width)? Maybe even have the length shorter since it's only one row? You've been so  helpful!

User-added image
Alain CabonAlain Cabon
<apex:dataTable> needs a lot of CSS code for a prettier result while <apex:pageBlockTable> is simpler like above.

Datatable with CSS: https://developer.salesforce.com/forums/?id=906F0000000kFwCIAU

You are closer to your expected result.
Alain CabonAlain Cabon
Bravo Katie, and now you can say to your colleagues: "I had done that myself!"
Katie DeLuna 18Katie DeLuna 18
HAHA you did it! Thank you! Also, I figured out how to make it the length of the page - it was default to the 2 column view instead of the 1! I also adjusted the visualforce page properties in the Height section. Thank you so muchhhhh!!!!!!!
Katie DeLuna 18Katie DeLuna 18
Oh no! Now i need code coverage, which i've never done. EEK! I didn't even think about that!
Alain CabonAlain Cabon
Hi Katie,

For the test, you need to reverse the tested class creating the objects, then after the standard controller using the opportunity and finally the class using the controller. You add at least one assertion at the end in order to verify that the creation is done (the list must not be empty).

Here, there is no user action so it is a short test class like below.
@isTest
public class OppContactsTest {
    @isTest static void test1() {
        Account acc = new Account(Name='my account');
        insert acc;
        Contact cnt = new Contact(firstname='toto',lastname='titi',accountid=acc.id);
        insert cnt;
        Opportunity opp = new Opportunity(Name='my oppt',StageName='Closed',CloseDate=Date.newInstance(2018,6,30),accountid=acc.id);
        insert opp;
        Test.startTest();
        ApexPages.StandardController stdController = new ApexPages.StandardController(opp);
        OppContacts oppcontacts = new OppContacts(stdController);
        Test.stopTest();
        System.assertEquals(1,  oppcontacts.myOppContacts.size());
    }
}

If you have other mandatory fields for the account or the contact, you will need to add these fields for the creations (new).
 
public class OppContacts {
  private final Id oppId;
    public List<Contact> myOppContacts {get;set;}
    public OppContacts(ApexPages.StandardController stdController) {
        this.oppId = stdController.getId();
        Opportunity opp = [select id,accountid from opportunity where id = :oppId];
        if (opp.AccountId != null) {
            myOppContacts = [select firstname,lastname,AccountId from contact where accountid = :opp.AccountId];
        }
    }
}

this.oppId = stdController.getId();  // simpler