1

Background: I am working on a VisualForce page to allow our customers to 'Add to Calendar' from an email alert that they receive when we book an Event. The Apex page takes 2 parameters right now, Id (Event) and online (Boolean).

The kicker here is that, in some cases I need the page to spit out an .ics file (Outlook/iCalendar), and other times I need it to redirect to a new pageReference URL where I pass in the Event Start/End parameters (Google Calendar, Outlook.com)

The reason I am doing it like this is because Salesforce UI doesn't allow us to access StartDateTime and EndDateTime. We have access to ActivityDateTime (translates to StartDateTime) but that isn't enough for my purposes. On top of that, I can't get the right formatting in a formula field without a lot of extra work).


To solve this, I figured I would build my VF page with a contentType of text/Calendar by default (to spit out the .ics):

<apex:page contentType="text/Calendar" controller="CalendarInviteController">
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
TZ:+00 
BEGIN:VEVENT
UID:uid1@example.com
DTSTAMP:{!startDate}
ORGANIZER;CN=MSZ:MAILTO:education@site.com
DTSTART:{!startDate}
DTEND:{!endDate}
SUMMARY:{!summary}
STATUS:CONFIRMED
LOCATION: {!location}
END:VEVENT
END:VCALENDAR
</apex:page>

BUT if &online=true it would interpret that as an online version (Google Calendar/Outlook.com) and would change the Content-Type (using currentPage().getHeaders().put('','')) and then ultimately redirect to the correct google or outlook URL via pageReference:

public CalendarInviteController(){
        // Get Event from Id Parameter passed in URL
        event = [SELECT Id,StartDateTime,EndDateTime,Subject FROM Event WHERE Id = :ApexPages.currentPage().getParameters().get('id')];

        // Set some Field Values based on Event Info/Parameters
        startDate = event.StartDateTime;
        endDate = event.EndDateTime;
        summary = event.Subject;
        location = 'Phone Training';
        // Get Online Boolean Parameter to determine if Web Based (Online) Email (Google Calendar/Outlook.com) or Offline Calendar (Outlook/iCalendar)    
        online = Boolean.ValueOf(ApexPages.currentPage().getParameters().get('online'));

        // if online = false
        if(!online){
            ApexPages.currentPage().getHeaders().put('content-disposition','inline; filename=calendarEvent.ics');
        }
        // else online = true, change content type to html to attempt to run redirect to pageRef
        else{
            ApexPages.currentPage().getHeaders().put('Content-Type','text/html');
            ApexPages.currentPage().getHeaders().put('content-disposition','inline; filename=calendarEvent.ics');
        }  
    }

This works as is right now. If I do mysite.com/CalendarInvite?id=SFID_HERE&online=true it directs me to a plain html page where it outputs the contents from the page onto the screen, and if i do online=false it correctly spits out an .ics file - so far so good.

Problem: At this point I needed a way to actually trigger the pageReference (for online scenarios) without user interaction, so at first I opted for a simple <apex:page action="{!run}" /> and built a run method that, using some criteria, should return the right pageReference and redirect depending on Google or Outlook.com. However, as soon as I save the page and reload, it starts spitting out an .ics regardless of whether or not the param for online is true or false. If I remove the action parameter from <apex:page /> it works fine again (with the exception of no redirects of course).

From there I moved on to trying to add an <apex:form rendered="{!renderOnline}" /> with an <apex:actionFunction action="{!run} /> inside of it to see if having it inside a rendered section calling an actionFunction would make a difference, but as soon as I added those pieces to the page I ended up with the same result as before (always kicking back an .ics file regardless of online true or online false).

Below you will see my full code, and a WORKING example of the VF page as well as the NOT working version. Note that the ONLY difference between the two is that I added the action="{!run}" attribute to the <apex:page> tag. The {!run} method right now just returns null, and even that doesn't work - As soon as I add an action, the page no longer seems to do the Header swap from my constructor to dynamically return an .ics or an html page. I've tried building the header logic into {!run}, but it doesn't appear to make any difference. No matter what code I put in the {!run} method, the page breaks.


Ultimately my goal is one VF page that can handle all 4 types of "Add to Calendar" links. I would always pass the Event ID and a true/false value for online, and then the page would determine if it should return an .ics file for me or if it should redirect me to outlook or google. Is this possible? If so, can someone point me in the direction of what I am doing wrong or how I might improve upon this?


Full Code

Controller:

public class CalendarInviteController {

    private DateTime startDate;
    private DateTime endDate;
    private String summary;
    private String location;
    private Event event;
    private Boolean online;
    private String endDateGMT;
    private String startDateGMT;
    private String type;

    public CalendarInviteController(){
        // Get Event from Id Parameter passed in URL
        event = [SELECT Id,StartDateTime,EndDateTime,Subject FROM Event WHERE Id = :ApexPages.currentPage().getParameters().get('id')];

        // Set some Field Values based on Event Info/Parameters
        startDate = event.StartDateTime;
        endDate = event.EndDateTime;
        summary = event.Subject;
        location = 'Phone Training';
        // Get Online Boolean Parameter to determine if Web Based (Online) Email (Google Calendar/Outlook.com) or Offline Calendar (Outlook/iCalendar)    
        online = Boolean.ValueOf(ApexPages.currentPage().getParameters().get('online'));

        // if online = false
        if(!online){
            ApexPages.currentPage().getHeaders().put('content-disposition','inline; filename=calendarEvent.ics');
        }
        // else online = true, change content type to html to attempt to run redirect to pageRef
        else{
            ApexPages.currentPage().getHeaders().put('Content-Type','text/html');
            ApexPages.currentPage().getHeaders().put('content-disposition','inline; filename=calendarEvent.ics');
        }  
    }

    public PageReference run() {
        return null;
    }

    public String getLocation() {
        return location;
    }

    public String getSummary() {
        return summary;
    }

    public String getEndDate() {
        DateTime dt = endDate;
        DateTime GMTDate = DateTime.newInstanceGMT(dt.dateGMT(),dt.timeGMT());
        String formatDt = GMTDate.format('yyyyMMdd\'T\'HHmmss\'Z\'','GMT');
        endDateGMT = formatDt;
        return formatDt;
    }

    public String getStartDate() {
        DateTime dt = startDate;
        DateTime GMTDate = DateTime.newInstanceGMT(dt.dateGMT(),dt.timeGMT());
        String formatDt = GMTDate.format('yyyyMMdd\'T\'HHmmss\'Z\'','GMT');
        startDateGMT = formatDt;
        return formatDt;
    }
}

VF Page (THE WORKING ONE):

<apex:page contentType="text/Calendar" controller="CalendarInviteController">
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
TZ:+00 
BEGIN:VEVENT
UID:uid1@example.com
DTSTAMP:{!startDate}
ORGANIZER;CN=MSZ:MAILTO:education@site.com
DTSTART:{!startDate}
DTEND:{!endDate}
SUMMARY:{!summary}
STATUS:CONFIRMED
LOCATION: {!location}
END:VEVENT
END:VCALENDAR
</apex:page>

VF Page (AFTER ADDING ACTION, DOESN'T WORK ANYMORE):

<apex:page contentType="text/Calendar" action="{!run}" controller="CalendarInviteController">
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
TZ:+00 
BEGIN:VEVENT
UID:uid1@example.com
DTSTAMP:{!startDate}
ORGANIZER;CN=MSZ:MAILTO:education@site.com
DTSTART:{!startDate}
DTEND:{!endDate}
SUMMARY:{!summary}
STATUS:CONFIRMED
LOCATION: {!location}
END:VEVENT
END:VCALENDAR
</apex:page>
  • The action simply performs a server side action. If it is not doing what you want then it is an issue with your code and not due to adding the action per say. If it is not Online does the parameter exist in the url like online=false because if so that would be the case. you are checking for null not boolean value – Eric May 2 '17 at 21:35
  • I pass online=false if it is the Outlook/iCalendar link, and online=true if its the Google Calendar/Outlook.com link. The reason I check for null first is to avoid a null pointer reference in the event that I forget a parameter. The line directly below that is me getting the boolean value of the parameter and assigning it to the field online. Even if I remove the null check the result is the same. My problem is that, as soon as I add an action, all the sudden the constructor seems to not care about rendering the right content type based on the online parameter. – Morgan Marchese May 3 '17 at 12:23
  • I'll post my full code when I get to work this morning in about an hour – Morgan Marchese May 3 '17 at 12:24

Your Answer

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

Browse other questions tagged or ask your own question.