
The scenario behind this example is to capture specific information about the circumstances around losing a sales deal (an Opportunity object). During normal progression of a deal there is no reason to show a field called "Reason Lost". Only at the time the user moves the stage to "Closed Lost" should they see this field and the section in which it is contained. In addition that section should contain a lookup (reference field or foreign key) to the account record which represents the competitor to which the deal was lost - and this field should be required in this circumstance.
The framework that will be used to describe this example mirrors the design pattern after which Visualforce has been modeled, MVC or Model-View-Controller.
This example highlights a number of Visualforce capabilities including:
The model is the schema or data interface to the application. In this example the Model refers to the standard Opportunity SObject only. This example refers to two custom fields on Opportunity - Primary_Competitor__c, a lookup to account and Reason_Lost__c, a textarea.
For your convenience, an unmanaged appexchange package has been created that contains the definitions of these custom fields. To install this package in your Developer Edition or Sandbox account click on this link
The view is the presentation layer and defines how your application will appear to the user. In this example the view is comprised of a single Visualforce page DynamicOpportunityEdit. The markup for this page follows:
<apex:page standardController="Opportunity" sidebar="false">
<apex:sectionHeader title="Edit Opportunity" subtitle="{!opportunity.name}"/>
<apex:form >
<apex:pageBlock title="Edit Opportunity" id="thePageBlock" mode="edit">
<apex:pageMessages />
<apex:pageBlockButtons >
<apex:commandButton value="Save" action="{!save}"/>
<apex:commandButton value="Cancel" action="{!cancel}"/>
</apex:pageBlockButtons>
<apex:actionRegion >
<apex:pageBlockSection title="Basic Information" columns="1">
<apex:inputField value="{!opportunity.name}"/>
<apex:pageBlockSectionItem >
<apex:outputLabel value="Stage"/>
<apex:outputPanel >
<apex:inputField value="{!opportunity.stageName}">
<apex:actionSupport event="onchange" rerender="thePageBlock"
status="status"/>
</apex:inputField>
<apex:actionStatus startText="applying value..." id="status"/>
</apex:outputPanel>
</apex:pageBlockSectionItem>
<apex:inputField value="{!opportunity.amount}"/>
<apex:inputField value="{!opportunity.closedate}"/>
</apex:pageBlockSection>
</apex:actionRegion>
<apex:pageBlockSection title="Closed Lost Information" columns="1"
rendered="{!opportunity.stageName == 'Closed Lost'}">
<apex:inputField value="{!opportunity.competitor__c}" required="true"/>
<apex:inputField value="{!opportunity.Reason_Lost__c}"/>
</apex:pageBlockSection>
</apex:pageBlock>
</apex:form>
</apex:page>
The controller is the layer that provides logic, data access and navigation work to your application. In this example the controller layer is comprised of the standard controller for the Opportunity object. Standard Controllers are provided by salesforce.com for each of the entities (SObjects) described in the API as queryable. This includes all the top level objects like Account, Contact, Opportunity, Custom Objects, etc. as well as other common types such as Task, Event and more.
All standard controllers support the following actions which perform the same logic as would otherwise be seen in the respective standard pages in salesforce.com:
Standard Controllers also allow for traversal of the relationship hierarchy to the same degree as the API by using the same dot-notation that is found in the Salesforce Object Query Language (SOQL) for display of data. Check out the quotePDF page in this sample for a great example of how this works.
In order to consume this sample within your developer edition or sandbox account you should follow these steps (in order):
Creation of Visualforce Pages can be performed under setup from the menu under App Setup > Develop > Pages.
Alternatively you can also use development mode to create pages and edit them in place in the application. To enable development mode edit your user record under Setup > Personal Setup > My Personal Information > Personal Information, click edit and check the box for "Development Mode" on the right side of the page and save. Now when you navigate to a visualforce page per the instructions below you will see the footer at the bottom of the page where you can make changes to the page and instantly see them in the display area of the browser window.
Also, if you ask for a page that does not yet exist, when in development mode, you will be prompted to create it and if you accept you will be taken to the newly created page with default content supplied for you.
In order to invoke the DynamicOpportunityEdit page you can request it with the appropriate opportunity record ID from within your salesforce.com development environment.
The URL should look something like: https://<instance>.salesforce.com/<opportunityID>
Where <instance> is na1, na2, tapp0, etc. and caseID is the record ID for the opportunity you are viewing (should start with "006...")
It should look like this: https://<instance>.salesforce.com/apex/opportunityEdit?id=<opportunityID>
Hit enter and you should see the Visualforce page you just created with the values from the Opportunity you specified.
You should see the "applying values..." text appear under your stage picklist field as the changed value is being sent back to the standard controller. Note: this does not save your opportunity, it merely updates the "in progress" state of your opportunity with the values you have supplied. When this completes you should see the new section appear under the main section within the edit opportunity page block.
If you don't, make sure you have a "Closed Lost" value in your opportunity stage picklist. If you do not you can either change the expression on line 26 of the markup or update the definition of the opportunity stage field under setup to include the expected stage value.
Since this example utilizes the standard controller for Opportunity you can also now make this Visualforce page your standard edit screen for all opportunities when accessed from anywhere in the application - either by clicking on the standard "edit" button on the opportunity detail (view) page or when clicking on the edit link next to any opportunity in a list view, related list, etc. To make this change navigate to the setup area for opportunities, App Setup > Customize > Opportunity and click on the menu item labeled "Buttons and Links". Now click on the Override link for the "Edit" action and choose your new Visualforce page. Now navigate back to an opportunity and click on the edit button and you will be taken to your new edit page for opportunities with the dynamic nature.
This example includes input elements for the following opportunity fields:
With the last two being conditional as the basis for this example.
Let's say the fields above start out with the following values:
| Name | Burlington - Generator |
| StageName | Negotiation/Review |
| Amount | 1,105,000.00 |
| CloseDate | 6/29/2008 |
| Primary_Competitor__c | |
| Reason_Lost__c |
In other words the user requests this Visualforce page for an opportunity with the above values for the respective fields.
Now the user changes the value of Opportunity.StageName to "Closed Lost".
Refer to this snippet of page markup for the following description:
Because the component bound to this field, apex:inputField has been decorated by the subordinate apex:actionSupport component upon the specified javascript event on the inputField component, in this case "onchange" a request is made back to the page's controller.
At the same time the specified apex:actionStatus component is immediately updated to reflect there is an operation taking place. The binding between components in the page is highlighted here:
No action was specified in the apex:actionSupport component so there will be no commitment of data to salesforce.com in this request. The values contained in all input elements included in the action region will be applied to the model, in this case the "in-memory" opportunity record.
By default the entire page is the action region here a specific portion of the page has been declared as the action region, using the apex:actionRegion component which you'll see in your page along with the nested input elements which are included in the controller request to apply the values:
In this example we need not update the model with information outside of the actionRegion which could unnecessarily block the user from changing the stage from "Closed Lost" to some other value since we've declared Primary_Competitor___c as required.
Simply put, you can control what information is sent back to the controller using apex:actionRegion as you see in this example.
Once the values have been applied the opportunity record now has these values (assuming the user only changed the stage):
| Name | Burlington - Generator |
| StageName | Closed Lost |
| Amount | 1,105,000.00 |
| CloseDate | 6/29/2008 |
| Primary_Competitor__c | |
| Reason_Lost__c |
When the response is received by the browser two things will happen:
The declaration of what to rerender, similar to the binding from apex:actionSupport to the apex:actionStatus component, is shown here:
Within this rerender target area, "thePageBlock", exists the apex:pageBlockSection component which is conditionally rendered based on the current value of Opportunity.StageName. Since this opportunity started out with a value of "Negotiation/Review" in the expression you see bound to the rendered attribute on the apex:pageBlockSection component below resolved to false it was not initially included in the view:
However, due to the action take by the user the model has been updated with a value that makes the same expression now resolve to true so the section is now included in the view and presented according to the components contained within it.
No update has occurred in the salesforce.com database. The user can continue to manipulate the values in the form and submit when finished by clicking save which calls the standard controller save action to finally commit the opportunity.