Visualforce Sample - Dynamic Edit Page

Getting the highest user adoption typically depends on the simplicity and minimality of the interface presented to your users. One way to increase the simplicity is to make various elements of an interface dependent on data conditions both for their visibility and state. In this example we explore the use of a Visualforce component that augments or decorates another component giving it some AJAX behavior for the purpose of controlling the visibility of a section of the given Visualforce page dynamically as the user modifies values in a form.

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.

Visualforce dynamicEditPage screenshot.png

About this Example

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:

  • Using a standard controller, without having to write Apex, for:
    • leveraging the standard save functionality for a custom edit page
    • automatic styling of the page to the specific type
    • using the customized page as the standard edit page for opportunity
  • AJAX without writing JavaScript for partial page updates which:
    • controls what data is displayed for selection
    • controls which areas of the page are refreshed/updated
    • provides a status indication to the user
  • Metadata aware inputField for generating date pickers, picklists with appropriate values, and text with requiredness
  • Expressions for controlling visibility (rendered) attributes for conditional display
  • Page-level requiredness for various input elements

Model

The model is the schema or data interface for 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. You must install this package in your Developer Edition or Sandbox account before adding the Visualforce page below. To do so, click on this link

View

The view is the presentation layer that defines how your application will appear to the user. In this example the view is composed of a single Visualforce page, DynamicOpportunityEdit. The markup for this page follows:

DynamicOpportunityEdit

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

Controller

The controller is the layer that provides logic, data access, and navigation functionality to your application. In this example, the controller layer is composed 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 that would be seen in the respective standard pages in salesforce.com:

  • Save
  • Edit
  • Cancel
  • Delete

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.

Installing this sample

In order to install this sample within your developer edition or sandbox account, you should follow these steps (in order):

  1. Install this package containing the schema (as referenced in the model section above)
  2. Create the DynamicOpportunityEdit page using the markup above (name it "opportunityEdit").

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.

Using this sample

In order to invoke the DynamicOpportunityEdit page, you can request it with the appropriate opportunity record ID from within your salesforce.com development environment.

Navigate to the appropriate opportunity record.

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...")

Update the URL

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.

Change the stage to "Closed Lost"

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.

Additional usage options

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.

Understanding this sample

This example includes input elements for the following opportunity fields:

  • Name
  • StageName
  • Amount
  • CloseDate
  • Primary_Competitor__c
  • Reason_Lost__c

With the last two being conditional as the basis for this example.

Initial State

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.

State Change - User Action

Now the user changes the value of Opportunity.StageName to "Closed Lost".

Refer to this snippet of page markup for the following description:

Visualforce dynamicEditPage snippet actionsupport.gif

Request Initiated

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:

Visualforce dynamicEditPage snippet actionsupport actionstatus.gif

Values Updated

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:

Visualforce dynamicEditPage snippet actionregion.gif

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

Response Returned

When the response is received by the browser two things will happen:

  1. The action status will switch to it's "stop" or finished state (nothing displayed in this case)
  2. Any components (and their children) that were specified for re-render will be refreshed using the updated model (Opportunity) state.

The declaration of what to rerender, similar to the binding from <apex:actionSupport> to the <apex:actionStatus> component, is shown here:

Visualforce dynamicEditPage snippet rerender binding.png

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:

Visualforce dynamicEditPage snippet pageblocksection expression.png

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.