3

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 WEvents 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;"/>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <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>
4

You can pass in a reference to the controller class in e.g. the constructor:

public class WEvent implements Comparable {
    private ActivityListAttemptController controller;
    public Event e {get; set;}
    public Boolean markedAttended { get; set; }
    private String oldAttendance = null;
    public WEvent(ActivityListAttemptController controller, Event eParam) {
        this.controller = controller;
        markedAttended = False;
        oldAttendance = eParam.Activity_Event_Attendance__c;
        e = eParam;
    }
    ...

and then use that reference in your compareTo method or anywhere else you need it in the inner class.

When you construct the WEvent, the this reference supplies a reference to the outer class (the controller in this case):

new WEvent(this, e)

(In Java by default the inner class can access the fields of the containing class. But in Java inner classes marked as static and always in inner classes in Apex, that is not the case and you have to pass in the reference.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.