SharePoint Calendar Event Callout

This post will go over a simple customization for SharePoint 2013/Online calendars, to display a callout for each event displayed in a calendar. Refer to the github project to see the code.

Library Overview

This section will go over the code for the library. To see the library in action, do the following: 1. Download the library 2. Upload the library to a SharePoint Library (Example: “/SiteAssets/SPEventCallout/sp-event-callout.js”) 3. Create a calendar or go to an existing calendar. (Example: “My Calendar”) 4. Edit the page 5. Add a Script Editor webpart 6. Add the following code to the Script Editor

<script type="text/javascript" src="/SiteAssets/SPEventCallout/sp-event-callout.js"></script>
<script type="text/javascript">
    new SPEventCallout("My Calendar");
</script>

Attaching to Calendar Render Event

The SP.UI.ApplicationPages.Calendar.js file can be referenced for additional events you can attach to. This post will use the onItemsSucceed event to attach a SharePoint Callout control to them. This method will get called each time the calendar is re-rendered. Switching between Month/Week/Day views is an example of the calendar being re-rendered. The code shown below displays how we overload the event and call the “attachCalloutsToEvents” method after the items are rendered.

            // Wait for the calendar script to be loaded
            ExecuteOrDelayUntilScriptLoaded(() => {
                let _this_ = this;

                // Overload the onItemsSucceed event
                this._onItemsSucceed = SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed;
                SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed = function($p0, $p1) {
                    // Call the base
                    _this_._onItemsSucceed.call(this, $p0, $p1);

                    // Attach the callouts to the calendar events
                    _this_.attachCalloutsToEvents();
                };

                // Attach the callouts to the calendar events
                this.attachCalloutsToEvents();
            }, "SP.UI.ApplicationPages.Calendar.js");

Attaching Callouts to Events

The attachCalloutsToEvents method displays how to create a callout component. First we query for all the events using the “ms-acal-item” class, and create a callout for each one using the item id as the unique identifier. We will store them in an array, in order to control when to show/hide them. The callout will be displayed while hovering over the event, so we will add “mouseover” and “mouseout” event listeners to show/hide the callout.

    // Method to attach callouts to the events
    private attachCalloutsToEvents() {
        // Clear the callouts
        this._callouts = [];

        // Parse the calendar events
        let calEvents = <any>document.querySelectorAll(".ms-acal-item");
        for(let calEvent of calEvents) {
            // Add hover events
            calEvent.addEventListener("mouseover", this.hoverOverEvent);
            calEvent.addEventListener("mouseout", this.hoverOutEvent);

            // Get the item id for this event
            let link = calEvent.querySelector("a");
            let itemId = link ? link.href.substr(link.href.indexOf("ID=") + 3) : 0;

            // Create the callout options
            let calloutOptions = new CalloutOptions();
            calloutOptions.content = "<div>Loading the Event Information...</div>";
            calloutOptions.ID = itemId;
            calloutOptions.launchPoint = calEvent;
            calloutOptions.title = calEvent.title;

            // Remove the default hover text
            calEvent.removeAttribute("title");

            // Create the callout
            this._callouts[itemId] = CalloutManager.createNew(calloutOptions);
        }
    }

Refer to MSDN for additional information on SharePoint Callout Control.

Displaying the Callout

The code shown below goes over getting the item information when hovering over an event. The key part to take away is how we can can customize the callout element after it’s created. We get the list item asynchronously, so this will come in handly after we get the item.

    // The hover over event
    private hoverOverEvent = (ev) => {
        // Get the item id for this event
        let link = ev.currentTarget.querySelector("a");
        let itemId = link ? link.href.substr(link.href.indexOf("ID=") + 3) : 0;
        if(itemId > 0 && itemId != this._currentItemId) {
            // Set the current item id
            this._currentItemId = itemId;

            // Get the callout
            let callout = this._callouts[this._currentItemId];

            // Get the item
            this.getItemInfo(this._currentItemId).then((item) => {
                let content = "";

                // Get the content element
                let elContent = callout.getContentElement().querySelector(".js-callout-body");

                // Parse the fields to display
                for(let field of this._fields) {
                    let title = field;
                    let value = item[field];

                    // See if this is a date/time field
                    if(field == "EndDate" || field == "EventDate") {
                        // Convert the date field
                        value = (new Date(value)).toString();

                        // Set the title
                        title = field == "EndDate" ? "End Date" : "Start Date";
                    }

                    // Update the content
                    content += "<div><strong>" + title + ": </strong>" + value + "</div>";
                }

                // Update the content element
                elContent.innerHTML = content;
            });

            // Open the callout
            callout.open();
        }
    }

Closing the Callout

The code shown below demonstrates how to close the callout.

    // The hover out event
    private hoverOutEvent = () => {
        // Get the callout
        let callout = this._callouts[this._currentItemId];
        if(callout) {
            // Close the callout w/ animation
            callout.close(true);
        }

        // Clear the current item id
        this._currentItemId = 0;
    }

Getting the Event Information

The code to get the list item is using the gd-sprest library to get the item. To ensure we don’t make redundant calls, we will store the item in an array, so we minimize the requests to the server.

    // Method to get the item Information
    private getItemInfo(itemId) {
        // Return a promise
        return new Promise((resolve, reject) => {
            // See if we already queried for this item
            if(this._items[itemId]) {
                // Resolve the request
                resolve(this._items[itemId]);
            } else {
                // Get the list
                List(this._listName)
                    // Get the item
                    .Items(itemId)
                    // Execute the request
                    .execute((item) => {
                        // Save a reference to the item
                        this._items[itemId] = item;

                        // Resolve the promise
                        resolve(item);
                    });
            }
        });
    }

Demo

Calendar Callout