Tweet |
To this end I've created a page and associated controller that allows a subset of details for all contacts (and the parent account) to be edited and saved in one go. This also allows new contacts to be created and existing contacts to be deleted. Below is a screen shot of the page:
The page itself is pretty clunky - clicking the Delete contact button fires the action method to carry out the delete without any user confirmation, while the New Contact button creates a new contact called "Change Me" and refreshes the page:
It does have one smarter feature though - if the page is accessed without specifying an ID, it goes into new account mode, and doesn't render any content or buttons related to contacts.
The Visualforce markup is shown below. If you are a regular reader of this blog you'll recognise it as heavily based on the sample from the Persisting List Edits in Visualforce post. Note that as usual you don't need a lot of markup to produce some pretty useful functionality.
<apex:page standardController="Account" extensions="AccountAndContactsEditExtensionV1" tabStyle="Account" title="Prototype Account Edit"> <apex:pageMessages /> <apex:form > <apex:pageBlock mode="mainDetail"> <apex:pageBlockButtons > <apex:commandButton action="{!cancel}" value="Exit" /> <apex:commandButton action="{!save}" value="Save" /> <apex:commandButton action="{!newContact}" value="New Contact" rendered="{!NOT(ISBLANK(Account.id))}"/> </apex:pageBlockButtons> <apex:repeat value="{!$ObjectType.Account.fieldSets}"> </apex:repeat> <apex:pageBlockSection title="Account Details" collapsible="true" id="mainRecord" columns="2" > <apex:inputField value="{!Account.Name}"/> <apex:inputField value="{!Account.Type}"/> <apex:inputField value="{!Account.BillingStreet}"/> <apex:inputField value="{!Account.ShippingStreet}"/> <apex:inputField value="{!Account.Industry}"/> <apex:inputField value="{!Account.Phone}"/> </apex:pageBlockSection> <apex:outputPanel id="contactList"> <apex:repeat value="{!contacts}" var="contact" > <apex:pageBlockSection columns="1" title="Contact {!contact.Name}" collapsible="true"> <apex:pageBlockSectionItem > <apex:pageBlockSection columns="2"> <apex:inputField value="{!contact.title}"/> <apex:inputField value="{!contact.phone}"/> <apex:inputField value="{!contact.FirstName}"/> <apex:inputField value="{!contact.LastName}"/> <apex:inputField value="{!contact.email}"/> </apex:pageBlockSection> </apex:pageBlockSectionItem> <apex:commandButton value="Delete Contact" action="{!deleteContact}" rerender="contactList"> <apex:param name="contactIdent" value="{!contact.id}" assignTo="{!chosenContactId}"/> </apex:commandButton> </apex:pageBlockSection> </apex:repeat> </apex:outputPanel> </apex:pageBlock> </apex:form> </apex:page>
Deleting a contact requires that the controller is notified of the ID to delete. This is accomplished via the nested param component for the "Delete Contact" button:
<apex:commandButton value="Delete Contact" action="{!deleteContact}" rerender="contactList"> <apex:param name="contactIdent" value="{!contact.id}" assignTo="{!chosenContactId}"/> </apex:commandButton>
The ID of the contact chosen to delete is propagated back to the controller via the the assignTo attribute. This means that by the time the deleteContact action method is invoked, the chosenContactId controller property will have been populated with the ID from the page. One important point to note about this - I've found that since Winter 11, you must have a rerender attribute on the commandButton for the parameter to be passed to the controller.
The controller is a little more complex, as it has action methods to support each of the buttons - Save and Exit, Save (and remain on page), Delete Contact and New Contact.
public class AccountAndContactsEditExtensionV1 { private ApexPages.StandardController std; // the associated contacts public List<Contact> contacts; // the chosen contact id - used when deleting a contact public Id chosenContactId {get; set;} public AccountAndContactsEditExtensionV1(ApexPages.StandardController stdCtrl) { std=stdCtrl; } public Account getAccount() { return (Account) std.getRecord(); } private boolean updateContacts() { boolean result=true; if (null!=contacts) { List<Contact> updConts=new List<Contact>(); try { update contacts; } catch (Exception e) { String msg=e.getMessage(); integer pos; // if its field validation, this will be added to the messages by default if (-1==(pos=msg.indexOf('FIELD_CUSTOM_VALIDATION_EXCEPTION, '))) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, msg)); } result=false; } } return result; } public PageReference saveAndExit() { boolean result=true; result=updateContacts(); if (result) { // call standard controller save return std.save(); } else { return null; } } public PageReference save() { Boolean result=true; PageReference pr=Page.AccountAndContactsEditV1; if (null!=getAccount().id) { result=updateContacts(); } else { pr.setRedirect(true); } if (result) { // call standard controller save, but don't capture the return value which will redirect to view page std.save(); ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Changes saved')); } pr.getParameters().put('id', getAccount().id); return pr; } public void newContact() { if (updateContacts()) { Contact cont=new Contact(FirstName='Change', LastName='Me', AccountId=getAccount().id); insert cont; // null the contacts list so that it is rebuilt contacts=null; } } public void deleteContact() { if (updateContacts()) { if (null!=chosenContactId) { Contact cont=new Contact(Id=chosenContactId); delete cont; // null the contacts list so that it is rebuilt contacts=null; chosenContactId=null; } } } public List<Contact> getContacts() { if ( (null!=getAccount().id) && (contacts == null) ) { contacts=[SELECT Id, Name, Email, Phone, AccountId, Title, Salutation, OtherStreet, OtherState, OtherPostalCode, OtherPhone, OtherCountry, OtherCity, MobilePhone, MailingStreet, MailingState, MailingPostalCode, MailingCountry, MailingCity, LeadSource, LastName, HomePhone, FirstName, Fax, Description, Department FROM Contact WHERE AccountId = : getAccount().ID ORDER BY CreatedDate]; } return contacts; } }
In part 2, I'll look at improving the user experience by adding confirmation of delete and allowing the user to specify fields when creating a new contact. This page also looks like it would benefit from fieldsets, so its likely those will be introduced as well.