Forum Discussion

Natkinson's avatar
Natkinson
Genius
3 years ago

Events Add to Calendar Button: How We Built It

Before we get into this, a couple disclaimers: I'm self-taught and a one-man-band on our team for Khoros dev. I'm still learning best practices and Khoros dev in general, so if something is done poorly or wrong, please let me know and I'll fix it and update this post. Also, I'm sharing this with no guarantees that it'll make all your dreams come true or work the way you're hoping. I'm not responsible for broken communities (or broken dreams). And finally, this depends on the amazing open source https://github.com/add2cal/add-to-calendar-button project, which I have no affiliation with and can make no guarantees about. It's working for us but might not meet the requirements of your project/community.

Now, on to the good stuff!

We're always looking for ways to promote our live events/webinars and improve the community event experience. One thing we've heard a lot is that ICS files are not a great experience and some of our users have no idea what to do with them. After scouring the internets for solutions, I stumbled across the afore-mentioned Add to Calendar Button open source project. Perfect! I stuck a bit of FreeMarker code into it and here we go, a huge upgrade to the Add to Calendar functionality in Events boards.

Check out the Github repository for full details on customization, but there's a lot you can do with this tool. I'll walk you through my basic code (again, I'm a n00b, so please forgive any inefficiencies or bad practices)

The first bit, we need to load the custom CSS and JS needed to make the button work and look nice. I've downloaded these and put them in our assets folder as I was having issues getting the CDN to work properly. Load them however works best for you. Putting them in assets also let me customize the CSS and swap out a few icons.

Then I have a query to pull in some event data from the message being viewed (using env.context.message.uniqueId to get the current message ID)

Then a quick if statement to check if the user is logged in. If they are, I grab their timezone from their profile. If not, I set a default timezone.

 

<!--Load the Add to Calendar Button stylesheet and JS-->
<link rel="stylesheet" href="${asset.get("/html/assets/atcb.min.css")}">
<script src="${asset.get("/html/assets/atcb.min.js")}" async defer></script>

<!--Query to get event data for the current event page-->
<#assign eventsQry=rest("2.0","/search?q=" + " SELECT subject, body, occasion_data FROM messages WHERE id='${env.context.message.uniqueId}'"?url).data.items />

<!--Check if the user is logged in, if they are get their timezone set in their profile. If not, set a default timezone-->
<#if user.anonymous==false>
    <#assign userTimeZone = rest("/users/login/${user.login}/settings/name/config.timezone").value />
<#else>
    <#assign userTimeZone='US/Mountain' />
</#if>

 

Next, I list out the event data from the query. A bit of timezone conversion and date/time formatting, and we have variables with separate dates and times in the user's timezone for our event. These must be formatted this way to work with the ATCB code.

 

<#list eventsQry as eventPosts>
<!--Do some date/time conversion for the user's timezone and give us separate date variables and time variables formatted properly for the ATCB requirements-->
    <#assign eventDateStartOld=eventPosts.occasion_data.start_time?datetime />
    <#assign eventDateStart=eventDateStartOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("yyyy-MM-dd") />
    <#assign eventTimeStart=eventDateStartOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("HH:mm") />
    <#assign eventDateEndOld=eventPosts.occasion_data.end_time?datetime />
    <#assign eventDateEnd=eventDateEndOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("yyyy-MM-dd") />
    <#assign eventTimeEnd=eventDateEndOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("HH:mm") />

 

After that, we have everything we need to build the button. Just need to stick everything in the right place. You can customize any of the data, but the formatting must remain the same or things will break. I recommend customizing the iCalFileName since you probably don't want downloaded events called "Instructure Live". Also, the ?no_esc on the event body text is important so any HTML content in the event doesn't break stuff.

 

<!--The ATCB div that contains the data and creates the button. See https://github.com/add2cal/add-to-calendar-button to customize options. 
    Using FreeMarker variables to fill in the event data.-->
    <div class="atcb" style="display:none;">
        {
        "name":"${eventPosts.subject}",
        "description":"${eventPosts.body?no_esc}",
        "startDate":"${eventDateStart}",
        "endDate":"${eventDateEnd}",
        "startTime":"${eventTimeStart}",
        "endTime":"${eventTimeEnd}",
        "location":"${eventPosts.occasion_data.location}",
        "label":"Add to Calendar",
        "options":[
        "Apple",
        "Google",
        "iCal",
        "Microsoft365",
        "MicrosoftTeams",
        "Outlook.com",
        "Yahoo"
        ],
        "timeZone":"${userTimeZone}",
        "trigger":"click",
        "iCalFileName":"Instructure Live: ${eventPosts.subject}"
        }
    </div>
</#list>

 

 And that's it! Now you should have an awesome Add to Calendar button that gives users way more options and a much better experience. Here's the full code for an easy copy/paste. Again, if anything is wrong or bad, please let me know and I'll fix it (and learn something new in the process).

 

<!--Load the Add to Calendar Button stylesheet and JS-->
<link rel="stylesheet" href="${asset.get("/html/assets/atcb.min.css")}">
<script src="${asset.get("/html/assets/atcb.min.js")}" async defer></script>

<!--Query to get event data for the current event page-->
<#assign eventsQry=rest("2.0","/search?q=" + " SELECT subject, body, occasion_data FROM messages WHERE id='${env.context.message.uniqueId}'"?url).data.items />

<!--Check if the user is logged in, if they are get their timezone set in their profile. If not, set a default timezone-->
<#if user.anonymous==false>
    <#assign userTimeZone = rest("/users/login/${user.login}/settings/name/config.timezone").value />
<#else>
    <#assign userTimeZone='US/Mountain' />
</#if>
<!--List the event data-->
<#list eventsQry as eventPosts>
<!--Do some date/time conversion for the user's timezone and give us separate date variables and time variables formatted properly for the ATCB requirements-->
    <#assign eventDateStartOld=eventPosts.occasion_data.start_time?datetime />
    <#assign eventDateStart=eventDateStartOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("yyyy-MM-dd") />
    <#assign eventTimeStart=eventDateStartOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("HH:mm") />
    <#assign eventDateEndOld=eventPosts.occasion_data.end_time?datetime />
    <#assign eventDateEnd=eventDateEndOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("yyyy-MM-dd") />
    <#assign eventTimeEnd=eventDateEndOld?iso("${userTimeZone}")?datetime("yyyy-MM-dd'T'HH:mm:ss")?string("HH:mm") />

<!--The ATCB div that contains the data and creates the button. See https://github.com/add2cal/add-to-calendar-button to customize options. 
    Using FreeMarker variables to fill in the event data.-->
    <div class="atcb" style="display:none;">
        {
        "name":"${eventPosts.subject}",
        "description":"${eventPosts.body?no_esc}",
        "startDate":"${eventDateStart}",
        "endDate":"${eventDateEnd}",
        "startTime":"${eventTimeStart}",
        "endTime":"${eventTimeEnd}",
        "location":"${eventPosts.occasion_data.location}",
        "label":"Add to Calendar",
        "options":[
        "Apple",
        "Google",
        "iCal",
        "Microsoft365",
        "MicrosoftTeams",
        "Outlook.com",
        "Yahoo"
        ],
        "timeZone":"${userTimeZone}",
        "trigger":"click",
        "iCalFileName":"Instructure Live: ${eventPosts.subject}"
        }
    </div>
</#list>

 

Add that code to a custom component and put it wherever you'd like in the Occasion Message View quilt.

  • This is amazing. I won't pretend to understand how it all works, so I just sent a link of this to our engineer saying to implement it 😂😎  Thanks so much for sharing, this is such a hugely important thing to have for Events.