Retrieving Expanded Calendar Events with REST vs. SOAP

There is very little now in 2013 for which you need SPservices [sic]

from /r/sharepoint

It wouldn’t surprise you to hear that my answer to this is “it depends”. SPServices is nice in that it works the same way across SharePoint versions from 2007-2013, which might be enough reason to use it. But there are still cases where the SOAP services do something that REST doesn’t [yet] provide.

The other day I ran across an example of this. I wanted to pull events from a SharePoint calendar from today forward and I wanted to expand all of the recurring events. Since I was working on SharePoint 2013, I immediately tried to make this work using a REST call.

The first hurdle I faced was that it wasn’t possible to filter on a data like StartDate in a REST query. I know that sounds crazy, but if it’s possible I couldn’t find any way to do it.

The other challenge was the expansion of recurring events. It turned out that this was only possible using SOAP. REST doesn’t know anything about SharePoint calendar events, especially recurrence or all-day events.

I ended going back to an old thread in the SPServices discussions started by Jim Bob Howard (@jbhoward) which gives the CAML to make this work. The code I ended up with is below. In this case, I was using KnockoutJS to populate a customized view of multiple SharePoint calendars – the “rollup” idea. We’ve the various calendar locations stored in a list, and when we get to this code we have an array of calendars to work with.

// Calendar rollup
var calendars = [];
var events = [];
var calendarPromises = [];

// For each calendar, go and get the events
for (var i = 0; i < calendars.length; i++) {

  var thisCalendar = calendars[i];
  var today = moment().format();

  calendarPromises[i] = $().SPServices.SPGetListItemsJson({
      listName : thisCalendar.title,
      webURL : thisCalendar.listUrl,
      CAMLViewFields : "<ViewFields>" +
			  "<FieldRef Name='ID' />" +
			  "<FieldRef Name='Title' />" +
			  "<FieldRef Name='EventDate' />" +
			  "<FieldRef Name='EndDate' />" +
			  "<FieldRef Name='Location' />" +
			  "<FieldRef Name='Description' />" +
			  "<FieldRef Name='Category' />" +
			  "<FieldRef Name='fRecurrence' />" +
			  "<FieldRef Name='RecurrenceData' />" +
			  "<FieldRef Name='fAllDayEvent' />" +
		"</ViewFields>",
      CAMLQuery : "<Query>" +
			  "<Where>" +
				  "<And>" +
					  "<DateRangesOverlap>" +
						  "<FieldRef Name='EventDate' />" +
						  "<FieldRef Name='EndDate' />" +
						  "<FieldRef Name='RecurrenceID' />" +
							  "<Value Type='DateTime'>" +
							  "<Year />" +
						  "</Value>" +
					  "</DateRangesOverlap>" +
					  "<Or>" +
						  "<Eq>" +
							  "<FieldRef Name='HomePageHide' />" +
							  "<Value Type='Bool'>False</Value>" +
						  "</Eq>" +
						  "<IsNull>" +
							  "<FieldRef Name='HomePageHide' />" +
						  "</IsNull>" +
					  "</Or>" +
				  "</And>" +
			  "</Where>" +
			  "<OrderBy>" +
				  "<FieldRef Name='EventDate' />" +
			  "</OrderBy>" +
		"</Query>",
      CAMLQueryOptions : "<QueryOptions>" +
			  "<CalendarDate>" + today + "</CalendarDate>" +
			  "<ExpandRecurrence>TRUE</ExpandRecurrence>" +
			  "<RecurrenceOrderBy>TRUE</RecurrenceOrderBy>" +
			  "<ViewAttributes Scope='RecursiveAll'/>" +
		"</QueryOptions>",
      mappingOverrides : {
        "ows_fAllDayEvent" : {
          "mappedName" : "fAllDayEvent",
          "objectType" : "Boolean"
        },
        "ows_fRecurrence" : {
          "mappedName" : "fRecurrence",
          "objectType" : "Boolean"
        }
      }
    });

}

$.when.apply($, calendarPromises).then(function () {

  var calendarEvents = this;

  // For each calendar, go and get the items and push them into an array
  for (var i = 0; i < calendars.length; i++) {

    $(calendarEvents[i].data).each(function () {
      events.push({
        calendar : calendars[i].title,
        id : this.ID,
        title : this.Title,
        location : this.Location != null ? this.Location : "",
        category : this.Category,
        eventDate : moment(this.EventDate),
        endDate : this.EndDate,
        fAllDayEvent : this.fAllDayEvent,
        fRecurrence : this.fRecurrence,
        RecurrenceData : this.RecurrenceData
      });
    });

  }

  events.sort(function (a, b) {
    return a.eventDate.isAfter(b.eventDate) ? 1 : (a.eventDate.isBefore(b.eventDate) ? -1 : 0);
  });

  ko.applyBindings(new calendarViewModel(), document.getElementById("calendar-rollup"));

});

The code is written to handle a variable number of calendars (we started with four). Because I’m putting the promise for each call to GetListItems in the calendarPromises array, I can use

to ensure that all of the promises have been fulfilled before processing the results.

I’m also using the fantastic MomentJS library to work with dates and times. Don’t ever try to do that manually again.

I’d love to hear that I don’t need to use SOAP for this anymore, but this seems to be a case where SOAP still wins out. Maybe there’s a place for SPServices in 2013, after all.

Advertisements

SPTechCon Developer Days Follow Up

SPTechCon Dev DaysI got lucky on the scheduling for SPTechCon Developer Days and did both of my sessions on the first day. So today I get to attend sessions and just learn, learn, learn!

As is true of every conference the BZMedia folks put on, this one is well run and packed with excellent sessions. If you’d like to stay on top of what’s happening in the SharePoint world, sign up for their weekly SPTechReport newsletter.

Since this is a conference focused on developers, both my sessions were developer-focused. If you attended either session and have questions or feedback, feel free to get in touch.

Simple Expand/Collapse for the Quick Launch in SharePoint 2013 or SharePoint Online

Yesterday I helped my pal Marcel Meth (@marcelmeth) with a pretty simple little task, but everything’s simple if you know how to do it! We needed to add simple accordion-like expand/collapse functionality to the Quick Launch on the pages in a Site Collection. Because there were going to be a lot of links in the Quick Launch (mostly links to different views of lists), it was getting pretty cumbersome to wade through. By adding this expand/collapse capability, we could improve the user experience (UX). It’s little stuff like this that makes SharePoint usable. Never underestimate the power of a small tweak like this.

This code works on a Team Site on SharePoint Online in Office365 with its Quick Launch exposed in the normal way. Any other customization you have in place may interfere with this working. (Yadda, yadda, disclaimer, YMMV, etc.) I’m sure it will work for you – possibly with some tweaks – in any SharePoint 2013 or SharePoint Online site. You can add this script to a single page or to the master page if you’d like it to work on all pages in the Site Collection.

// Find all the top level links in the Quick Launch that have children
var topLevelLinks = $("div[id$='QuickLaunchMenu'] > ul > li:has('ul') > a");

// Prepend the little "twiddle" icon to each top level link
topLevelLinks.prepend("<span class='ms-commentexpand-iconouter ql-icon'><img alt='expand' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentexpand-icon'></span>");

// We're starting with all of the sections collapsed. If you want them expanded, comment this out.
topLevelLinks.closest("li").find("> ul").hide();

// Set up for the click even of on the top level links
topLevelLinks.click(function(e) {

  // We're going to stop the default behavior
  e.preventDefault();

  // Find the elements we need to work with
  var childUl = $(this).closest("li").find("> ul");
  var isVisible = childUl.is(":visible")

  // If the section is visible, hide it, and vice versa
  if(isVisible) {  

    // Replace the icon with its antitheses
    $(this).find(".ql-icon").replaceWith("<span class='ms-commentexpand-iconouter ql-icon'><img alt='Expand' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentexpand-icon'></span>");
    // Hide the child links by sliding up. Note: You could change the effect here.
    childUl.slideUp();

  } else {

    // Replace the icon with its antitheses
    $(this).find(".ql-icon").replaceWith("<span class='ms-commentcollapse-iconouter ql-icon'><img alt='Collapse' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentcollapse-icon'></span>");
    // Show the child links by sliding down. Note: You could change the effect here.
    childUl.slideDown();

  }

});

The Medium Has the Message and My Blog Is Trying to Catch Up

I’ve really been digging the content I’ve been finding through Medium for the last few months or so. The Web is ever-evolving and to me Medium represents a new paradigm for Web publishing and content consumption. It’s sort of blogging combined with content crowd-sourcing combined with highlighting like I have on my Kindle combined with… Well, it’s unique, so it’s not really just the sum of those parts.

To me, one of the best parts of Medium is the Staff Picks and Top Stories. These two categories give me solid content to read that is either vetted by the platform owners or popular with the platform readers. One of the cool things about this is that it cuts across all topic areas. I’m as likely to want to read one of Eric Elliott’s (@_ericelliott) excellent posts about JavaScript and its communities like this or this or something like When the heck did learning to code become cool? And why it sucks to be a beginner today… which are squarely in my own wheelhouse to interesting takes on Web design like Optical Adjustment – Logic vs. Designers.

But what makes it even more fun is the stuff I run across that to many people would seem sort of random. None of us is so one-dimensional that we cannot enjoy reading something completely new to us. Medium ensures that when we go off on one of those mental tangents, that it’s a higher quality one.

When I spot one of these seemingly random posts, I can see about how long it might take me to read it, so it’s clear going in if how much time I’ll need to invest. (I’m a pretty fast reader, so for me the number is usually an upper bound – YMMV.) Those times must be built up magically based on how long people really spend and the posts and seem pretty accurate to me.

How many minutes?

The biggest thing I notice as I read posts on Medium is that I feel a need to up my own game, to write more pithy and valuable posts. Ever since I started blogging, I’ve had a little voice in my head that writes posts that come out of ordinary, every day experiences. These aren’t the “here’s a piece of code to fix a particular problem” posts, but often the softer stuff that makes the fact that the code exists have more purpose. It might have nothing to do with my professional life at all. (Maybe it’s hard to believe, but I do have a few other interests besides SharePoint.) I wish I actually spent the time to get more of those posts written.

I found an article that shows how to make your own blog a bit more “Medium-like” and I’ve added some new plugins to my blog here based on those suggestions. (See WordPressium: Creating a Medium-like Experience)

As of today, you can do a few new things on my blog…

Selection Sharer

Courtesy of Hans van Gent’s Selection Sharer plugin, you can now highlight any piece of text in one of my posts and send it as a Tweet, post it to LinkedIn, or send it in an email. (This assumes I ever say anything quotable, which I know is a huge assumption.) I would expect that he’ll add some more sharing options over time.

Reading Time

The Reading Time plugin adds some info on how long a post might take to read. You’ll see the estimate based on an average reading speed at the top of each post as you visit it. As you stay on the page, a little progress bar shows how much of that time has elapsed. Of course, reading a post full of code is different than just reading prose, so time will tell how useful this is. It can at least be a little game; can you beat the progress bar?

I hope that you find these two new capabilities useful. Let me know what you think!

SharePoint Evolution Conference 2015 Wrap Up

It’s been a busy few weeks in my life. First I was in London at the SharePoint Evolution Conference, then home for a week, then off to Microsoft Ignite in Chicago, then back home, and boom! I was heading out the back to put some burgers on the grill, I missed the second to the last step (of a grand total of 3!), fell, and was in excruciating pain with a broken ankle. After surgery, I’m the proud owner of some new hardware in my ankle and a 6-8 week “no weight bearing” edict.

I usually try to do a bigger write up after a great conference like SharePoint Evolution, but I’m going to flake out and just post my presentations. I do want to thank Steve Smith and Zoe Watson, along with their entire team, for an incredible experience.