[SalesForce] Override Standard New Button with Visualforce Page

I have a custom object that has a Master-Detail relationship to the Opportunity object. I created a Visualforce page that I want to use to override the standard "New" list button on the related list of the custom object on the Opportunity. However, I want to be able to pass through the Opportunity ID and the Owner ID to the newly created page, as there are some variables that are formula lookups to fields on the Opportunity and Account. Does anyone know how I can do this? In the past I've used a URL hack to create the button, but I understand those will be going away soon, so I would like a more scalable way to do this. Thanks!

VF Page:

<apex:page standardController="Opportunity" tabStyle="Opportunity" extensions="VF_SFController" action="{!SF}">

<apex:form >

    <apex:repeat value="{!ESF}" var="SF">

    Contact Information
        <Table columns="5" width="100%">
            <tr>
                <td width="10%">Name:</td>
                <td width="38%"><apex:outputField value="{!SF.Rep__c}"/></td>
                <td width="4%">&nbsp;</td>
                <td width="10%">Amount:</td>
                <td width="38%"><apex:outputField value="{!SF.Amount__c}"/></td><p class="double"></p>
            </tr>
            <tr>
                <td width="10%">Phone:</td>
                <td width="38%"><apex:outputField value="{!SF.Sales_Rep_Phone__c}"/></td>
                <td width="4%">&nbsp;</td>
                <td width="10%">Opportunity:</td>
                <td width="38%"><apex:outputField value="{!SF.Opportunity__c}"/></td><p class="double"></p>
            </tr>
            <tr>
                <td width="10%">Email:</td>
                <td width="38%"><apex:outputField value="{!SF.Sales_Rep_Email__c}"/></td>
                <td width="4%">&nbsp;</td>
                <td width="10%">Account:</td>
                <td width="38%"><apex:outputField value="{!SF.Legal_Name__c}"/></td><p class="double"></p>
            </tr>
        </Table>

    </apex:repeat>
</apex:form>
</apex:page>

Custom Controller:

public class VF_SFController{

public List<SF__c> ESF {get; set;}

    private final Opportunity opp;
    public VF_SFController(ApexPages.StandardController myController){
        SF = new List<SF__c>();
        opp=(Opportunity)myController.getrecord();
    }

    public SF__c SF2 = new SF__c();
        public void SF(){

            SF2.Opportunity__c = opp.Id;
            SF2.Rep__c = opp.OwnerId;
            SF2.Amount__c = opp.Amount;
            SF.add(SF2);
        }

    public PageReference save() {

        PageReference RetPage = new PageReference('/apex/SFViewPage?id=' + SF[0].id);
        RetPage.setRedirect(true);
        return RetPage;
    }
}

Best Answer

One common misconception is that getRecord() works like trigger contexts and the entire Opportunity record is available to the controller. Not true. getRecord() only makes available to the controller the fields defined on the page of the form e.g. {!Opportunity.xxx} - these can be hidden fields -- or, you can use method addFields().

here is the standard controller doc but I've excerpted the relevant bits:

getRecord()

Usage Note that only the fields that are referenced in the associated Visualforce markup are available for querying on this SObject. All other fields, including fields from any related objects, must be queried using a SOQL expression.

Tip

You can work around this restriction by including a hidden component that references any additional fields that you want to query. Hide the component from display by setting the component's rendered attribute to false.

addFields(...)

When a Visualforce page is loaded, the fields accessible to the page are based on the fields referenced in the Visualforce markup. This method adds a reference to each field specified in fieldNames so that the controller can explicitly access those fields as well.

Usage

This method should be called before a record has been loaded—typically, it's called by the controller's constructor. If this method is called outside of the constructor, you must use the reset() method before calling addFields().

The strings in fieldNames can either be the API name of a field, such as AccountId, or they can be explicit relationships to fields, such as foo__r.myField__c.

So it is up to you which way to go, for example with addFields you could do in your constructor...

myController.addFields(new List<String> {'ownerId','amount'});
opp=(Opportunity)myController.getrecord();