As the records haven't been added to the database yet, they don't have a unique identifier that I can use to locate them, so the first task is to create a wrapper class that encapsulates an Account and an identifier - I decided on an Integer.
public class AccountWrapper { public Account acc {get; private set;} public Integer ident {get; private set;} public AccountWrapper(Integer inIdent) { ident=inIdent; acc=new Account(Name='Bulk Acc ' + ident); } }Note that the wrapper class provides a useful constructor, that creates the wrapped record and automatically populates the Name of the Account based on the unique identifier. I added this as the Name is a required field when used as the value for an apex:inputfield in Visualforce, so this saved me some time when testing. I could just as easily have changed the component to an apex:inputText to work around this.
As I'm only concerned with the uniqueness of the AccountWrapper within the context of my page, generating a unique identifier is as simple as defining a property in my controller:
private Integer nextIdent=0;
and then post-incrementing this each time I create a new wrapper instance. In my constructor I create the initial five records:
wrappers=new List<AccountWrapper>(); for (Integer idx=0; idx<5; idx++) { wrappers.add(new AccountWrapper(nextIdent++)); }
In my page, I have an apex:pageBlockTable backed by the wrappers list. Each row of the table has a Delete button that if clicked, removes the entry from the list:
<apex:column headerValue="Action"> <apex:commandButton value="Delete" action="{!delWrapper}" rerender="wtable"> <apex:param name="toDelIdent" value="{!wrapper.ident}" assignTo="{!toDelIdent}"/> </apex:commandButton> </apex:column>
The unique identifier is passed to the controller via the apex:param component. If this is the first time that you've encountered this component, you may be interested in another blog post of mine explaining its use in detail
The action method associated with the delete simply iterates the list of wrappers and deletes the entry associated with the supplied identifier:
public void delWrapper() { Integer toDelPos=-1; for (Integer idx=0; idx<wrappers.size(); idx++) { if (wrappers[idx].ident==toDelIdent) { toDelPos=idx; } } if (-1!=toDelPos) { wrappers.remove(toDelPos); } }
The page also contains a couple more buttons, for adding one row, adding 5 rows and saving to the database.
<apex:page controller="ManageListController" tabstyle="Account"> <apex:form > <apex:pageBlock title="Bulk Account Create"> <apex:pageBlockTable value="{!wrappers}" var="wrapper" id="wtable"> <apex:column headerValue="Ident"> <apex:outputText value="{!wrapper.ident}"/> </apex:column> <apex:column headerValue="Name"> <apex:inputField value="{!wrapper.acc.Name}"/> </apex:column> <apex:column headerValue="Parent"> <apex:inputField value="{!wrapper.acc.ParentId}"/> </apex:column> <apex:column headerValue="Industry"> <apex:inputField value="{!wrapper.acc.Industry}"/> </apex:column> <apex:column headerValue="Type"> <apex:inputField value="{!wrapper.acc.Type}"/> </apex:column> <apex:column headerValue="Action"> <apex:commandButton value="Delete" action="{!delWrapper}" rerender="wtable"> <apex:param name="toDelIdent" value="{!wrapper.ident}" assignTo="{!toDelIdent}"/> </apex:commandButton> </apex:column> </apex:pageBlockTable> <apex:commandButton value="Add Row" action="{!addRows}" rerender="wtable"> <apex:param name="addCount" value="1" assignTo="{!addCount}"/> </apex:commandButton> <apex:commandButton value="Add 5 Rows" action="{!addRows}" rerender="wtable"> <apex:param name="addCount" value="5" assignTo="{!addCount}"/> </apex:commandButton> <apex:commandButton value="Save" action="{!save}"/> </apex:pageBlock> </apex:form> </apex:page>
Note that I'm using the apex:param technique for both the Add Row and Add 5 Rows buttons - this means I can have additional buttons for any number of rows without having to change the Apex code.
The full controller is shown below:
public class ManageListController { public List<AccountWrapper> wrappers {get; set;} public static Integer toDelIdent {get; set;} public static Integer addCount {get; set;} private Integer nextIdent=0; public ManageListController() { wrappers=new List<AccountWrapper>(); for (Integer idx=0; idx<5; idx++) { wrappers.add(new AccountWrapper(nextIdent++)); } } public void delWrapper() { Integer toDelPos=-1; for (Integer idx=0; idx<wrappers.size(); idx++) { if (wrappers[idx].ident==toDelIdent) { toDelPos=idx; } } if (-1!=toDelPos) { wrappers.remove(toDelPos); } } public void addRows() { for (Integer idx=0; idx<addCount; idx++) { wrappers.add(new AccountWrapper(nextIdent++)); } } public PageReference save() { List<Account> accs=new List<Account>(); for (AccountWrapper wrap : wrappers) { accs.add(wrap.acc); } insert accs; return new PageReference('/' + Schema.getGlobalDescribe().get('Account').getDescribe().getKeyPrefix() + '/o'); } public class AccountWrapper { public Account acc {get; private set;} public Integer ident {get; private set;} public AccountWrapper(Integer inIdent) { ident=inIdent; acc=new Account(Name='Bulk Acc ' + ident); } } }
The only additional item of interest in the controller is the returned PageReference from the save method:
return new PageReference('/' + Schema.getGlobalDescribe().get('Account').getDescribe().getKeyPrefix() + '/o');
This sends the user to the Accounts tab. I originally had the three character key prefix hardcoded, but that always feels wrong. Thus, while its highly unlikely that Salesforce will change the prefix, if they do I'm covered.
Here's some screenshots of the page in action. First the initial page:
After I've clicked the Add 5 Rows Button
After I've deleted some entries at random:
And finally, after saving the records they are displayed in the recent list on the Accounts tab: