I'm using Apex and VisualForce to make an "open house event check-in page" for a department. For various reasons pertaining to AppExchange plugin compatibility, we are storing a separate "Event" record for each person coming to the same event, and figure out who all is coming to the same event based on Subject & StartDate matching, rather than using Campaigns & CampaignMembers or another junction-object setup. It's kludgy, but so far it works.
I've gotten the VisualForce page just about the way I want it, except that I can't seem to make an inner "Wrapper Class" around "Event" implement Comparable
in a way that relies on data stored in the outer "Controller" class.
(I want to sort my list of WEvent
s on WhoId.Name, but SOQL doesn't allow querying fields of WhoId.)
I think I can find a way around this problem (by caching my WEvents, grabbing the Names for WhoIDs as I do here, and then looping through my WEvents cache and adding a "Name" value to each WEvent), but it felt more sensible to look up the WEvent's "Name" from the outer Controller class as needed at compare-time when my VisualForce is already looping through the cached WEvents.
compareTo()
seems to refuse to let WEvent access anything outside of WEvent. Is that normal for Java/Apex? (It seems a bit intuitive that that would be the case, since I'm implementing an interface that doesn't know about my Controller class, but I want to check with experts and haven't had any luck with Google.) If that is the way it works, are there any ways around it?
public with sharing class ActivityListAttemptController {
// Attributes
public Event dateCarrierObject { get; set; } // For holding the date the page user selects
private Map<String,List<WEvent>> eventsForDate = new Map<String,List<WEvent>>(); // For holding the event records for a given date
private List<Event> eventsToUpdate = new List<Event>(); // For holding events in need of DML
private List<WEvent> wEventsToProcessInMethod = new List<WEvent>();
private Set<Id> whoIDsForDate = new Set<Id>();
private Map<Id, String> whoIDsAndTheirNames = new Map<Id, String>();
// Constructor
public ActivityListAttemptController() {
dateCarrierObject = new Event(ActivityDate=null);
}
// Set Contents of "eventsForDate" Map (and quick-ref set of subjects involved) & refresh page
public PageReference setEventsForDateAndRefresh() {
eventsForDate.clear();
String currSubject = null;
for (Event e : [SELECT Id, StartDateTime, Subject, Program__c, WhoId, Activity_Event_Attendance__c FROM Event
WHERE DAY_ONLY(StartDateTime) = :dateCarrierObject.ActivityDate
AND Subject LIKE 'Open House Event - %']) {
currSubject = e.Subject;
if (eventsForDate.containsKey(currSubject)) { eventsForDate.get(currSubject).add(new WEvent(e)); }
else { eventsForDate.put(currSubject, new List<WEvent>{new WEvent(e)}); }
whoIDsForDate.add(e.Id);
}
// Note that this cache of names & IDs persists across data saves/discards.
for (Contact c : [SELECT Id, LastName, FirstName, Middle_Name__c FROM Contact WHERE Id IN :whoIDsForDate]) {
whoIDsAndTheirNames.put(c.Id, c.LastName+c.FirstName+c.Middle_Name__c);
}
for (Lead l : [SELECT Id, LastName, FirstName, Middle_Name__c FROM Lead WHERE Id IN :whoIDsForDate]) {
whoIDsAndTheirNames.put(l.Id, l.LastName+l.FirstName+l.Middle_Name__c);
}
return null;
}
// Fetch record count of event registrants stored in this class at the moment
public String getWEventCount() { return String.valueOf(eventsForDate.size()); }
// Fetch map of event registrants stored in this class at the moment
public Map<String, List<WEvent>> getAllEventsForDate() { return eventsForDate; }
// Save changes made in page to database and refresh page.
// (Prioritzes "Attended" checkboxes over dropdown values.
// To change that priority, just add " && wE.oldAttendance == wE.e.Activity_Event_Attendance__c"
// after "if (wE.markedAttended == TRUE" in inner if.)
public PageReference save() {
resetWEventsToProcess(); // Reset list for loop.
for (List<WEvent> wL : eventsForDate.values()) { wEventsToProcessInMethod.addAll(wL); } // Rebuild list for loop.
// Add changed values to DML list
for (WEvent wE : wEventsToProcessInMethod) {
if (wE.oldAttendance <> wE.e.Activity_Event_Attendance__c || wE.markedAttended == TRUE) {
if (wE.markedAttended == TRUE) {
wE.e.Activity_Event_Attendance__c = 'Attended';
wE.markedAttended = FALSE;
}
eventsToUpdate.add(wE.e);
}
}
if (!eventsToUpdate.isEmpty()) { update eventsToUpdate; } // Do DML
return null;
}
// Discard all changes made in the page and refresh it
public PageReference cancel() {
resetWEventsToProcess(); // Reset list for loop.
for (WEvent we : wEventsToProcessInMethod) { we.markedAttended = FALSE; }
return null;
}
// Helper method
private void resetWEventsToProcess() {
wEventsToProcessInMethod.clear(); // Clear out list for loop.
for (List<WEvent> wL : eventsForDate.values()) { wEventsToProcessInMethod.addAll(wL); } // Rebuild list for loop.
}
// Helper method: Get a comparison integer for two WhoIDs
public Integer compareWhoIDToAnother(Id selfId, Id otherId) {
Integer returnValue = 0; // The return value of 0 indicates that both elements are equal.
if (whoIDsAndTheirNames.get(selfId) > whoIDsAndTheirNames.get(otherId)) {
returnValue = 1; // Set return value to a positive value.
} else if (whoIDsAndTheirNames.get(selfId) < whoIDsAndTheirNames.get(otherId)) {
returnValue = -1; // Set return value to a negative value.
}
return returnValue;
}
// Interior wrapper class "WEvent" definition
public with sharing class WEvent implements Comparable {
// Attributes
public Event e {get; set;}
public Boolean markedAttended { get; set; }
private String oldAttendance = null;
// Constructor
public WEvent(Event eParam) {
markedAttended = False;
oldAttendance = eParam.Activity_Event_Attendance__c;
e = eParam;
}
// Method: doOuterComparison
private Integer doOuterComparison(Id selfIdParam, Id otherIdParam) {
return compareWhoIDToAnother(selfIdParam, otherIdParam); // Error: "Method does not exist or incorrect signature: compareWhoIDToAnother(Id, Id)"
//return 0; // DEBUG LINE
}
// Method: compareTo
public Integer compareTo(Object compareTo) {
WEvent compareToWEvent = (WEvent)compareTo; // Cast parameter to WEvent
return doOuterComparison(e.WhoId, compareToWEvent.e.WhoId);
}
}
}
And
<apex:page docType="html-5.0" controller="ActivityListAttemptController" title="This page displays events.">
<Script>
function setFocusOnLoad() {}
</Script>
<style>
input[type='checkbox'] {
width:60px;
height:60px;
}
.pbSubheader{
background-color: #62496e !important;
border-color: none !important;
}
div.datePicker{
font-size:175%;
padding:5px;
}
span.dateInput{
font-size:175%;
}
</style>
<apex:form >
<apex:pageBlock>
<apex:outputText>
<big>Open House Event Check-In App - {!WEventCount} Record(s) Displayed</big>
<BR/>
Pick a date below and click "Pull Contact List For Events" to refresh the list.
<hr/>
<b>Instructions:</b>
<ul>
<li>To quickly mark people as "Attended," tap the checkbox at left.<br/><br/></li>
<li>
If you need to change someone to a status besides "Attended" (including back to blank, which is "--None--"), use the picklist at right.
<ul>
<li><b>Note:</b> When saving, the "Attended" checkbox trumps the drop-down list if you accidentally used both on a single row.<br/><br/></li>
</ul>
</li>
<li>
Save your work periodically (or discard all your changes) with the buttons at the bottom of the list.
<br/>
To avoid straining the database, your work will <b><font color="#ff0000">NOT</font></b> save as soon as you tap the checkbox or choose a picklist value.
<br/>
You must manually save all of your changes you have made so far every so often.
</li>
</ul>
<hr/>
</apex:outputText>
<apex:repeat value="{!AllEventsForDate}" var="key">
<apex:pageBlockSection title="{!key}"/>
<apex:pageBlockTable value="{!AllEventsForDate[key]}" var="wrapper">
<apex:column headerValue="Attended?">
<apex:inputCheckbox value="{!wrapper.markedAttended}" />
</apex:column>
<apex:column value="{!wrapper.e.WhoId}" headerValue="Contact" style="font-size:200%;"/>
<apex:column value="{!wrapper.e.StartDateTime}" headerValue="Date/Time"/>
<apex:column value="{!wrapper.e.Program__c}" headerValue="Programs"/>
<apex:column>
<apex:facet name="header">
Attended?<br/>(Current Database Value)
</apex:facet>
<apex:outputField value="{!wrapper.e.Activity_Event_Attendance__c}"/>
</apex:column>
<apex:column>
<apex:facet name="header">
Make a mistake?<br/>Other attendance options here.<br/>(Un-check 'Attended' box first.)
</apex:facet>
<apex:inputField value="{!wrapper.e.Activity_Event_Attendance__c}"/>
</apex:column>
</apex:pageBlockTable>
</apex:repeat>
<apex:commandButton value="Save Changes" action="{!save}" style="padding:16px 12px;"/>
<apex:commandButton value="Discard Changes" action="{!cancel}" style="padding:16px 12px;"/>
</apex:pageBlock>
<BR/>
<apex:pageBlock>
<apex:pageBlockSection title="Pick Event Date"/>
<apex:inputField value="{!dateCarrierObject.ActivityDate}" style="padding:16px;"/>
<BR/>
<apex:commandButton value="Pull Contact List For Events" action="{!setEventsForDateAndRefresh}" style="padding:16px 12px;"/>
</apex:pageBlock>
</apex:form>
</apex:page>