You've built a sample wizard with Visualforce, but what if your business requirements are more complex: saving across multiple objects, including Tasks and Events? Join us to learn how one customer solved this with a custom framework, using as few Apex controllers as possible, and dividing code classes into reusable modules.
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Creating Multi-Page Data Entry Controllers
1. Building multi-page data entry
Controllers
Beyond Oz What to do when a simple Wizard is not enough
Stuart Greenberg, OppenheimerFunds, Inc., Technical Lead
@Stu_GuiGumdrops
2. All about OppenheimerFunds, Inc.
Since the original Oppenheimer fund was first offered to the public in 1959, OppenheimerFunds,
Inc. (OFI) has grown into one of the largest and most reputable investment management firms in
Click to Today, a subsidiary of Massachusetts Mutual Life Insurance Company
the country. add brief company overview here. Lorem ipsum
(MassMutual), OFI and its subsidiaries adipiscing elit. Sed lectusservices to individuals,
dolor sit amet, consectetur offer a broad array of products and tortor,
institutional investors and corporations worldwide. OFI provides advisory services to the
Oppenheimer mutual funds and OFI Global Asset Management provides services to institutional
pulvinar sit amet blandit ac, bibendum vitae sapien.
clients. OFI, including subsidiaries, managed more than $222 billion in assets for more than 12
million shareholder accounts, including sub-accounts, as of September 30, 2013.
▪ Click to add implementation highlights; no more than four
For more than 50 years, OFI has embraced an investment culture that has produced results, is
sustainable, and reflects its commitment to being effective stewards of capital. A high conviction
▪ Click to add implementation highlights; no more than four. Lorem
asset manager, OFI has a history of providing innovative investment strategies to its investors.
Fouripsum dolor sit amet, consecteturinvestment culture: active management can deliver
core beliefs lie at the heart of the OFI adipiscing elit.
better outcomes, independent investment boutiques lead to better ideas, a global perspective is
critical, and knowing the difference between risk & no more than four
▪ Click to add implementation highlights; risky.
OFI’s investment professionals are a collection of distinct, yet collaborative, teams built to enable
▪ Click to add implementation highlights; no more than four
a free exchange of ideas and to uncover opportunity wherever it lies; even where others see a
disparate set of facts, events or trends. OFI is redefining what the world can expect from an asset
manager, and stands united on its mission to turn its unconventional wisdom into value for
investors.
3. Who’s who and what’s what
Stuart Greenberg
▪
▪
▪
▪
Professional programmer since 1975
Past Senior Programmer, PC Magazine Labs
Currently Tech Lead, OppenheimerFunds, Inc.
Pennsylvania Railroad engineer
And you?
▪ Visualforce developer?
▪ Written wizards or multi-page data entry?
▪ It’s late, all the other sessions were full
and I just wanted to sit down?
4. Single vs. wizard vs. multi-page data entry
Single page
▪ Enter data / click save
5. Single vs. wizard vs. multi-page data entry
Wizard
▪ Enter data for step 1 / click next
▪ Enter data for step 2 / click next
▪ Etc.
6. Single vs. wizard vs. multi-page data entry
Multi-page
▪ Enter data on main page / click option
▪ Select option / Enter data on new page / click return
▪ Select option / Enter data on another page / click return
▪ Click save
7. The problem
▪ Break the limit of one Contact/Lead and/or one other object
attached to Events and Tasks (Multi-Who / Multi-What)
▪ Allow users to select objects in any order and skip selection of
objects they don’t want
▪ Do not require the saving of Events and Tasks prior to
selecting objects
8. The solution
▪ Custom object (ActivitySidecar) connected to Events and
Tasks using the WhatId
▪ Selection pages for all associated objects
▪ Multi-page data entry to keep all data in memory and save at
the end
13. Selectable Object
public class OFI_SelectableObject {
public Boolean isSelected {set;get;}
public Boolean isEnabled {set;get;}
public Boolean isInactive {set;get;}
public Boolean isPrimary {set;get;}
public String tag
{set;get;}
public Object obj
{set;get;}
14. Queueing selections
// Lists Selectable Objects of the related objects
// The tag property is used to indicate:
// an existing object (tag = '')
// an object to be added (tag = 'A')
// an object to be deleted (tag = 'D')
public List<OFI_SelectableObject> selProducts {get;set;}
public List<OFI_SelectableObject> selContacts {get;set;}
public List<OFI_SelectableObject> selFirms {get;set;}
public List<OFI_SelectableObject> selUsers {get;set;}
public List<OFI_SelectableObject> selFinancialAccounts {get;set;}
public List<OFI_SelectableObject> selOpportunities {get;set;}
public List<OFI_SelectableObject> selCampaigns {get;set;}
public List<OFI_SelectableObject> selCases {get;set;}
15. Selecting items
public PageReference selectItems() {
Integer i;
for (OFI_SelectableObject record: recordsToDisplay) {
if (record.isSelected) {
if (!existingContactIds.contains(record.getContact().Id)) {
// Not an existing selection
for (i = 0; i < currentSelections.size(); i++) {
// Check for undelete
if (currentSelections[i].getActivityContact().Contact__c == record.getContact().Id && currentSelections[i].tag == 'D') {
currentSelections[i].tag = '';
break;
}
}
if (i == currentSelections.size()) {
// Add new selection
Activity_Contact__c actContact = new Activity_Contact__c();
// Set the Contact Id and Object. The Sidecar will be set when the Activity is saved.
actContact.Contact__c = record.getContact().Id;
actContact.Contact__r = record.getContact();
OFI_SelectableObject selObj = new OFI_SelectableObject(actContact);
selObj.Tag = 'A';
currentSelections.add(selObj);
}
existingContactIds.add(record.getContact().Id);
}
}
}
return null;
}
// Add to existing Ids to enable checkmark
16. Saving
try {
upsert evt;
} catch (DMLException e) {
ApexPages.addMessage(new
ApexPages.Message(ApexPages.SEVERITY.ERROR,
e.getLineNumber() + e.getMessage()));
return false;
}
If (sidecarId == null) {
Activity_Sidecar__c sidecar = [SELECT Id FROM Activity_sidecar__c
WHERE Activity_Id__c = :evt.Id LIMIT 1];
evt.WhatId = sidecar.Id;
}
// Add / Delete Products
List<Activity_Product__c> delProds = new List<Activity_Product__c>();
List<Activity_Product__c> addProds = new List<Activity_Product__c>();
for (OFI_SelectableObject o : sidecarEditor.selProducts) {
if (o.tag == 'D') {
delProds.add(o.getActivityProduct
} else {
if (o.tag == 'A') {
o.getActivityProduct().Activity_Sidecar__c = evt.WhatId;
addProds.add(o.getActivityProduct());
}
}
}
if (delProds.size() > 0) {
delete(delProds);
}
if (addProds.size() > 0) {
insert(addProds);
}
17. Page code
Event Edit Page
<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2"
title="Calendar Event Edit">
Task Edit Page
<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2"
title="Calendar Task Edit">
Selection Page
<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2"
action="{!initObjectSelector}" title="Activity Contact Selection">
▪ All controllers must be the same to keep their constructors
from being called when a new page is opened.
▪ An Action must be added if any initialization is necessary
when the page opens.
18. Force instantiation on new session
You need to use the following when you wish to start a new
multi-page session:
PageReference page = new PageReference('/apex/TaskEditPage');
page.setRedirect(true);
20. You can’t leave the page
The view state is maintained as long as the user doesn’t
navigate to a page that has a different set of controllers.
The solution: Javascript
21. Avoid the message on “Good” clicks
For example, the following was added to the Page Block Buttons:
<apex:pageBlockButtons location="top" >
<apex:commandButton value="Select and Return"
action="{!SelectAndReturn}"
onclick="setShowExitMessage(false);"/>
<apex:commandButton value="Select and Add More"
action="{!SelectItems}"
onclick="setShowExitMessage(false);" rerender="thePage"/>
<apex:commandButton value="Return" action="{!cancelSelection}"
onclick="setShowExitMessage(false);"/>
</apex:pageBlockButtons>
22. <script type="text/javascript">
var isExitMessageTurnedOn = true;
var showExitMessage = true;
var isChrome = false;
var isSafari = false;
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf('safari') != -1){
if(ua.indexOf('chrome') > -1){
isChrome = true;
} else {
isSafari = true;
}
}
function setShowExitMessage(val) {
showExitMessage = val;
}
function isJavascriptElement() {
if (isChrome || isSafari) return false;
23. Be careful using classes
▪ Classes called from the controllers can be instantiated
multiple times.
▪ Be sure to set the variables using the classes to null when
finished.
25. Return URLs
▪ Do not use the Return URL to go back to the initial page.
▪ Use the URL of the page itself.
▪ For example:
// Cancel selection and return to the previous
page.
public pageReference cancelSelection() {
destroySelector();
if (mainController.isTask) {
return page.TaskEditPage2;
} else {
return page.EventEditPage2;
}
}
27. The View State size
▪ The View State is the data that is held between pages.
▪ Up to 135K of data.
▪ An exception is thrown if the limit is exceeded.
▪ Cannot hold attachments, for example.
28. Time outs
▪ Sessions sitting too long can be timed out and data lost.
▪ Use multi-page data entry only for quick tasks.
29. Alternatives
Use multi-page data entry when you really have to
Here are some alternatives:
▪ One page with hidden or collapsible sections
▪ A wizard flow based on initial selections
▪ Visual Workflow
▪ Dynamically generated search and select components
30. Conclusion
▪ Multi-page data entry is possible with the proper care
▪ Use multi-page data entry once you’ve exhausted more
mainstream options
▪ Nothing is impossible… Impossible just takes a bit longer
31. Contact info and links
Contact me at:
▪ Twitter: @Stu_GuiGumdrops
▪ Blog: http://www.guigumdrops.com
Useful links:
▪ View State:
http://wiki.developerforce.com/page/An_Introduction_to_Visualforce_View_State
▪ Visualforce and Visual Workflow:
http://wiki.developerforce.com/page/User_Interface