Creating Multi-Page Data Entry Controllers

Post on 24-Jun-2015

459 views 1 download

Tags:

description

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.

transcript

Building multi-page data entry ControllersBeyond Oz - What to do when a simple Wizard is not enoughStuart Greenberg, OppenheimerFunds, Inc., Technical Lead@Stu_GuiGumdrops

All about OppenheimerFunds, Inc.

Click to add brief company overview here. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus tortor, pulvinar sit amet blandit ac, bibendum vitae sapien.

▪ Click to add implementation highlights; no more than four

▪ Click to add implementation highlights; no more than four. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

▪ Click to add implementation highlights; no more than four

▪ Click to add implementation highlights; no more than four

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 the country. Today, a subsidiary of Massachusetts Mutual Life Insurance Company (MassMutual), OFI and its subsidiaries offer a broad array of products and services to individuals, institutional investors and corporations worldwide. OFI provides advisory services to the Oppenheimer mutual funds and OFI Global Asset Management provides services to institutional 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.

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 asset manager, OFI has a history of providing innovative investment strategies to its investors. Four core beliefs lie at the heart of the OFI investment culture: active management can deliver better outcomes, independent investment boutiques lead to better ideas, a global perspective is critical, and knowing the difference between risk & risky.

OFI’s investment professionals are a collection of distinct, yet collaborative, teams built to enable 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.

Who’s who and what’s whatStuart 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?

Single vs. wizard vs. multi-page data entrySingle page

▪ Enter data / click save

Single vs. wizard vs. multi-page data entryWizard

▪ Enter data for step 1 / click next▪ Enter data for step 2 / click next▪ Etc.

123

Single vs. wizard vs. multi-page data entryMulti-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

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

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

Object structure

Overall structure

Controllers must be the same across pages.

Classes instantiated as needed.

Interfaces allow polymorphic functionality

DEMO

GottasWachagottadew…

▪ Selections must be queued▪ All queued data must be saved at the same time▪ Page code must be setup correctly

Selectable Objectpublic 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;}

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

Selecting itemspublic 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); // Add to existing Ids to enable checkmark } } } return null;}

Savingtry { 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);}

▪ An Action must be added if any initialization is necessarywhen the page opens.

▪ All controllers must be the same to keep their constructors from being called when a new page is opened.

Event Edit Page

Task Edit Page

Selection Page

<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2" title="Calendar Event Edit">

Page code

<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2" title="Calendar Task Edit">

<apex:page controller="ActivityEditPage2Controller" extensions="ActivityObjectSelController,VFFileUpload2" action="{!initObjectSelector}" title="Activity Contact Selection">

Force instantiation on new sessionYou need to use the following when you wish to start a new multi-page session:

PageReference page = new PageReference('/apex/TaskEditPage'); page.setRedirect(true);

GotchasGotchasgonnagetcha…

You can’t leave the pageThe 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

Avoid the message on “Good” clicksFor 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>

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

var elem = document.activeElement; var e;

if (elem.outerHTML) { e = elem.outerHTML; } else { if (XMLSerializer) { e = new XMLSerializer().serializeToString(elem); } } if (e.indexOf('javascript') > -1) return true; return false; }

window.onbeforeunload = function(event) { if(isExitMessageTurnedOn == true) { if(showExitMessage == true && isJavascriptElement() == false) { return 'Any edits will be lost.'; } else { showExitMessage = true; } } }

</script>

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.

Example

Destroy Selectors after each accessOf a selection page.

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

FugetaboutitsPatient: Doc, it hurts when I do this.Doctor: Don’t do that.

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.

Time outs▪ Sessions sitting too long can be timed out and data lost.▪ Use multi-page data entry only for quick tasks.

AlternativesUse multi-page data entry when you really have toHere 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

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

Stuart Greenberg

OppenheimerFunds, Inc., Technical Lead

@Stu_GuiGumdrops