Recently we had a user request for the ability to add a contact to multiple campaigns at once from the Contact screen. This seemed like a reasonable request, so I started to look into it, but it’s one of those things that’s been languishing on the IdeaExchange with not enough votes, so I decided to take a crack at it myself. We’re Classic users still, so we’re talking Visualforce here.
So as we want to embed a custom piece of UI functionality on a page layout, we of course want a Visualforce page. The controller for that Visualforce page must have Contact as it’s StandardController in order to be embedded on the Contact page layout. We also need to add a controller extension to contain our additional logic.
Within this extension controller, I make use of a tried and tested approach for multi-select checkboxes — the Wrapper class. This is a small lightweight class in the code (in my case, an internal class of the controller itself) that has a field for the checkbox state and a field to represent the Campaign. Ultimately, it is these that we’ll be rendering out on our page shortly.
The next challenge we need to address is scalability — when we want to render out a list of campaigns that our contacts could be added to, we are subject to limits (of course), so what I did was to replicate the filter design used natively on the platform when selecting a single campaign, like so
which will help cut down the number of results returned. To actually process the selection and search results, I make use of Dynamic SOQL to build up a query string based on the parameters provided. Of course, I’m extra careful to sanitise the user input to prevent SOQL Injection attacks.
Finally, we need to track which campaigns our contacts are already a member of and pre-check those. We do this by iterating through the list of CampaignMembers looking for that ContactID. I actually add them to a Map structure so that I can get a list of campaignmembers by specifying the Campaign ID. I can then use this to see if my contact is in that campaign already by building the map from a query
Map<Id, List<CampaignMember>> memberMap = new Map<Id, List<CampaignMember>>();List<CampaignMember> CampaignMembership=[Select Id, CampaignId, ContactId From CampaignMember WHERE ContactId = :myContact.Id ];for(CampaignMember member: CampaignMembership)
if(memberMap.containsKey(member.CampaignId) == false)
Meanwhile, on the Visualforce side of things, I can build an apex:pageBlockTable that iterates over a list of CampaignMemberWrappers — this way I get the checkboxes and the records at the same time
All that is left to do now is to parse the selections when the Update button is clicked. We iterate through the list of CampaignWrappers and add items to one of two lists — if checked, ItemsToAdd and if unchecked, ItemsToDelete. As we process each of these lists, we further check to see if a campaign member record already exists for this combination, as we cannot add duplicates. This is a means of differentiating what has been checked manually versus what was already selected. Once we have our finalised lists of items to add or remove from CampaignMembers, we simply DML them in.