This week's post concerns bulk creation of an unknown number of sobject records
- Accounts, for example. In this situation I want to display a table containing
a set number of new records (5 in this case), enter the various pieces of
information and save to the database en-masse. However, I also want to be
able to add or remove rows prior to saving anything, so that if I need to I can
enter 1, 3, 10 etc at once. I promise this is the last post for a while
around list handling!
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: