Expand SharePoint 2013 Calendar Events with Under 20 Lines of Code 

Tags: IT Pro, How To, Admin, SharePoint 2013, SharePoint 2010, Pre SharePoint2010, SharePoint Designer, Dev, Best Practices, Migrate, jQuery

A recent upgrade to SharePoint 2013 disabled some of the calendars. In SharePoint 2010, the out-of-the-box Calendar view renders a maximum of three events by default with an option to click a "more items" link which renders all events for the entire week containing that day. For those wanting to have all the events expanded, the solution in SharePoint 2010 does not work for SharePoint 2013 and in fact prevents the calendars from rendering events.

Do You Have Broken Calendar Pages?

If you have a site recently migrated to SharePoint 2013 with calendar pages that no longer show events on the calendar, and which pages no longer allow the buttons in the ribbon to become active, skip to the last section of this post (Fixing WSS and SharePoint 2010 Calendars with Formerly Expanded Events).

SharePoint 2013 Uses a Lot of AJAX

The technique that works in SharePoint 2010 relies on the "more items" link posting back to the server for each click. As with a lot of SharePoint 2013, there is no post back to the server to draw the entire page; clicking the expand event links as well as calendar navigation (next and previous month, etc.) rely on client-side JavaScript to change the calendar content. We needed a new way to do it.

The jQuery Solution

Add this JavaScript to a Script Editor web part to a page with a single SharePoint calendar.

Edit Page > Add Web Part > Categories – Media and Content > Script Editor > Add > Edit Snippet and insert this code:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>    

<script type="text/javascript">

function expandCal() {

  setInterval(expandCalCheck, 900);

}

 

function expandCalCheck() {

  var a=$("a.ms-cal-nav", $("div.ms-acal-rootdiv:last>div")).get(0);

  if (a) {

    for (var r=0; r<6; r++) {

      var a=$("a.ms-cal-nav", $("div.ms-acal-rootdiv:last>div").get(r)).get(0);

      if (a)

        if (a.innerText.indexOf("more") >= 0)

          a.click();

    }

  }

}

ExecuteOrDelayUntilScriptLoaded(expandCal, "sp.ui.applicationpages.calendar.js");

</script>

 

In production, I put the jQuery file into a document library called js, and put the rest of the code into a separate file called expand.js, ending up with these two lines in the Script Editor web part:

<script type="text/javascript" src="/js/jquery-1.9.1.min.js"></script>

<script type="text/javascript" src="/js/expand.js"></script>


Using this organization allows one to use an editor outside of the Web UI to edit the JavaScript file, gets the code away from users who might edit the page and separates code and content. For these reasons, it is a "best practice", but takes a little more effort which may not really be worth it for some people.

I got burned once when an external jQuery file went away. My policy now is to put jQuery source where we have control over it.

Caveats

This works, but it is not ideal.

The Timer

I tried associating the expansion with clicking on the different calendar navigation buttons, but sometimes it got lost. The above solution checks every 900 milliseconds, (just under one second), for links that need to be expanded. Monitoring the browser's CPU usage with the Windows Task Manager reveals that this check does chew up a measurable amount of the CPU. Checking every quarter of a second really put a significant load on. The longer between checks, the less of a load, but it also means that average time it takes for the events to expand the links is longer, forcing the user to wait. Nine-tenths of a second seems to be a good compromise.

See more below about avoiding the timer.

Only One Calendar is Affected Per Page

I have not tested it, but using this code with more than one calendar on the page will probably result in only the last calendar being expanded. You can reuse this script for as many calendars as desired as long as they are on different pages.

Localization

This depends on the word "more" be in the link to expand the events. For other languages, you probably need to use a translated word, like "mas" or "plus", or perhaps refer to the appropriate resource file from the JavaScript.

How Does it Work?

First, we wait until the calendar template has been initialized:

ExecuteOrDelayUntilScriptLoaded(expandCal, "sp.ui.applicationpages.calendar.js");

The expandCal() function sets up a timer to see if we need to expand the calendar every 900 ms.

SharePoint renders a calendar template server side and then populates it with JavaScript callbacks. Before it populates the events, there is one div with a class of "ms-acal-rootdiv". After the callbacks, there are two, and we want the last one which is where the jQuery selector

div.ms-acal-rootdiv:last

comes from. This div contains five or six classless div's, one for each week's row shown in the calendar's current month. Adding the jQuery selector ">div" selects these six rows:

div.ms-acal-rootdiv:last>div

The "more items" link exists in an HTML <A> tag with a class of "ms-cal-nav". Because clicking any one of these links expands or collapses the entire week, we only want to do one per week. Since we don't want to collapse already expanded events, we execute the click() only for links with the "more items" text in it.

The code above first determines whether there exists such an A element anywhere in the current calendar, and then loops through all six weeks, clicking on the first "more items" link it finds for each week.

Avoiding the Constant Timer?

I wrote code to avoid the timing loop, but it got complicated fast and does not work 100% of the time. Specifically, if you click on a month without any weeks that need to be expanded twice in a row, adding new events to the click() triggers failed. I tried and tried to get it to work. I believe it is a timing issue.

For some clicks, the opposite happened. Adding the click()'s was sometimes geometrically cumulative. Each click doubled the number of events added, resulting in way too many calls to the expand function.

Ideally, the SharePoint process that renders the actual events would throw a catchable callback notification, but this appears to happen only on initialization of the calendar template.

Fixing WSS and SharePoint 2010 Calendars with Formerly Expanded Events

This blog post explains how to expand all calendar events via HTML and JavaScript: http://moblog.bradleyit.com/2009/08/sharepoint-expand-calendar-month-view.html

Unfortunately, this code in SharePoint 2013 does not allow the callbacks that render the actual events to fire, probably because the window.onload function is overridden. Only the template is rendered. Even worse, the code suppresses the JavaScript that allows the ribbon's buttons to work. This means that while you can put the page into edit mode, you will find it hard to actually edit anything.

The Solution is to Edit with SharePoint Designer

You can remove this custom code with SharePoint Designer 2013, a free download at http://www.microsoft.com/en-us/download/details.aspx?id=35491. Choose the 32- or 64-bit version that matches the Office version of the target machine. Best practice is to use the 32-bit version unless you are using humungous Excel spreadsheets or something.

  1. Follow these steps for each calendar that is broken. A search on calendar.aspx can be a help to find them all.
  2. Open the appropriate site or subsite in SPD 2013.
    1. Take everything up to the /Lists… in the URL. E.g., for http://site/Lists/SiteCalendar/calendar.aspx, use http://site
  3. Navigate to the page's folder, make a backup of the page and open it
    1. E.g., for http://site/Lists/Client%20Calendar/calendar.aspx, click Navigation - Site Objects > All Files > Lists > Client Calendar
    2. Copy and paste the page as a backup
      1. Right click > Copy > right click > Paste
      2. This typically creates calendar_copy(1).aspx
    3. Open the page by clicking on its name
  4. Remove all code within the square brackets of <!CDATA[]>
    1. For example,
      <![CDATA[<div id="cover" style="background: white; left: 0px; top: 0px; width: 1920px; height: 940px; display: none; position: absolute;"></div><script type="text/javascript">var yScroll = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;var y = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;var xScroll = window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft;var x = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;var cover = document.getElementById("cover");cover.style.height = (y + yScroll) + "px";cover.style.width = (x + xScroll) + "px";function expand() {if (document.referrer != location.href && !location.href.match(/CalendarPeriod=((week)|(day))/)) { GetMonthView('11111111'); }else { cover.style.display = "none"; }}window.onload = function() { expand(); };</script>]]>
      

      becomes just this:

      <![CDATA[]]>

    2. Save the page and test that it renders events again
  5. Optional: remove the content editor web part from the page altogether by opening the page in a browser and then editing the page. Note that the ribbon buttons and therefore editing does not work until after the code has been removed from within CDATA in the previous step.
 
Posted by AndyGett on 4-Apr-13
10 Comments  |  Trackback Url | Bookmark this post with:        
 

Comments


Will Cooper commented on Wednesday, 22-Jan-2014
Hi Andy, One additional thing that is needed: You need to fire off the expand event after the user changes months also. You need a delay allow time for the events to load: $(document).ready(function() { $('a[title="Previous Month"]').click(function() { setTimeout(expandCalCheck, 1000); }); $('a[title="Next Month"]').click(function() { setTimeout(expandCalCheck, 1000); }); });


chris commented on Monday, 4-Aug-2014
Sorry but your code doesent work. I have following every step but nothing work :((. What im doing wrong ?


AndyGett commented on Sunday, 31-Aug-2014
Chris, hard to say what is wrong without more information. Does anything happen?


marc commented on Tuesday, 2-Sep-2014
I had the same problem until I changed this line if (a.innerText.indexOf("more") >= 0) to if (a.innerHTML.indexOf("more") >= 0) then it worked fine!!


SBlangkon commented on Wednesday, 11-Feb-2015
Thanks, it work for me. I have publish the result of this script on My Blog


SBlangkon commented on Wednesday, 11-Feb-2015
Thanks, it work for me. I have publish the result of this script on My Blog


Alan commented on Tuesday, 9-Aug-2016
If you wrap the ExecuteOrDelayUntilScriptLoaded function as follows you can still edit the page without using SharePoint designer. if(($("#MSOLayout_InDesignMode").val()!= "1") && ($("#_wikiPageMode").val()!="Edit")) { // Execute Script ExecuteOrDelayUntilScriptLoaded(expandCal, "sp.ui.applicationpages.calendar.js"); }


Christian commented on Thursday, 3-Nov-2016
Hi Andy, I followed your instruction but no success. Debugger says that "$ is undefined". I am not very familiar with scripting, so do you (or anyone else) have an idea why this is?


Andy Gettings commented on Thursday, 29-Dec-2016
Christian, the "$ is undefined" sounds like jQuery is not loaded properly. I just checked that the Google CDN entry still works. Check the spelling of your URL.


Matt commented on Thursday, 16-Feb-2017
Worked perfectly for me with some 2013 calendar overlays after a lot of other solutions failed. Thanks!

Name:
URL:
Email:
Comments: