Comparing SPServices 2013.01 Calls with Async vs. Promises Methods

SPServices 2013.01 is almost ready for release and I’m hoping that when you see the coolness here that you’ll want to get your hands on the beta to do a little testing with it.

I’ve put together a pretty simple demo page to show how great it can be to use jQuery .Deferred objects (aka promises) to retrieve data with SPServices versus the asynchronous way we’ve tended to use until now. The demo page is running on my old WSS 3.0 site hosted at FPWeb. Unfortunately, I don’t have a public-facing SharePoint 2013 instance anywhere yet.

The idea here is pretty simple. I wanted to keep the complexity of the code pretty low and also to use as much common code as possible between the two methods so that the real apples to oranges comparison is the async method versus the promises method.

I’ve set up three Announcements lists in a single site: Corporate Announcements, Finance Announcements, and HR Announcements. Each list has three announcements in it. I even got to use Bacon Ipsum to fill in some dummy content.

The demo page has two buttons on it: one for async and one for promises. When you push one of the buttons, the script loops through the array containing information about the three lists, retrieves the three items from each of the lists using the chosen method, and displays them on the page. There’s a slightly different version of the code in each of the functions (getRecentAnnouncementsAsync and getRecentAnnouncementsPromises) that run in each case.

The getRecentAnnouncementsAsync function runs the way we’re all used to using SPServices. The async option is set to false and processing of the returned data happens inside the completefunc.

In the promises version, the async option isn’t set, so it has the default of true (we don’t wait for the returned data, but continue processing). The promise returned from each call to GetListItems is put into an array, and the line $.when.apply($, announcementsPromises).done(...) is where the check for all three promises to be complete happens. When that occurs, the data for the three calls is processed.

Here’s the code:

// Show the most recent Annoucements from three lists in the current site
var announcementsPerList = 3;
var lists = [{
    name : "Corporate Announcements"
  }, {
    name : "HR Announcements"
  }, {
    name : "Finance Announcements"
  }
];
var camlQuery = "<Query><OrderBy><FieldRef Name='Created' Ascending='FALSE'/></OrderBy></Query>";
var camlViewFields = "<ViewFields>" +
  "<FieldRef Name='ID' />" +
  "<FieldRef Name='Title' />" +
  "<FieldRef Name='Body' />" +
  "<FieldRef Name='Author' />" +
  "<FieldRef Name='Created' />" +
  "<FieldRef Name='FileDirRef' />" +
  "</ViewFields>";
var out;

$(document).ready(function () {

  //$("#ctl00_site_share_button").hide();
  $("input[value='async']").click(function () {

    getRecentAnnouncementsAsync();

  });

  $("input[value='promises']").click(function () {

    getRecentAnnouncementsPromises();

  });

});

//++++++++++++++++++++++++++++++++++++

function getRecentAnnouncementsAsync() {

  var recentAnnouncementsContainer = $("#demo-recent-announcements");
  recentAnnouncementsContainer.empty();
  recentAnnouncementsContainer.addClass("demo-loading");

  out = "<table>";
  announcementHeader();

  for (var i = 0; i < lists.length; i++) {

    $().SPServices({
      operation : "GetListItems",
      async : false,
      listName : lists[i].name,
      CAMLQuery : camlQuery,
      CAMLViewFields : camlViewFields,
      CAMLRowLimit : announcementsPerList,
      completefunc : function (xData) {

        // List header
        out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";

        $(xData.responseXML).SPFilterNode("z:row").each(function (itemNum) {

          processAnnouncement(i, $(this));

        });

      }
    });

  }

  out += "</table>";
  $("#demo-recent-announcements").removeClass("demo-loading");
  recentAnnouncementsContainer.html(out);

}
function getRecentAnnouncementsPromises() {

  var recentAnnouncementsContainer = $("#demo-recent-announcements");
  recentAnnouncementsContainer.empty();
  var announcementsPromises = [];
  recentAnnouncementsContainer.addClass("demo-loading");

  out = "<table>";
  announcementHeader();

  for (var i = 0; i < lists.length; i++) {

    announcementsPromises[i] = $().SPServices({
        operation : "GetListItems",
        listName : lists[i].name,
        CAMLQuery : camlQuery,
        CAMLViewFields : camlViewFields,
        CAMLRowLimit : announcementsPerList
      });

  }

  // When all of the promises are fulfilled...
  $.when.apply($, announcementsPromises).done(function () {
    for (var i = 0; i < lists.length; i++) {

      // List header
      out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";

      $(announcementsPromises[i].responseXML).SPFilterNode("z:row").each(function (itemNum) {

        processAnnouncement(i, $(this));

      });

    }

    out += "</table>";
    $("#demo-recent-announcements").removeClass("demo-loading");
    recentAnnouncementsContainer.html(out);

  });

}

function getNow() {

  return new Date();

}

function announcementHeader() {

  out += "<tr class='ms-WPHeader'><td colspan='99'><h3 class='ms-standardheader ms-WPTitle'>Recent Announcements as of " + getNow() + "</h3></td></tr>";
  out += "<tr>" +
  "<th class='ms-vh2'>Title</th>" +
  "<th class='ms-vh2'>Body</th>" +
  "<th class='ms-vh2'>Created By</th>" +
  "<th class='ms-vh2'>Created</th>" +
  "</tr>";

}

function processAnnouncement(i, item) {

  out += "<tr>";

  // Title as a link to the announcement
  var thisLink = item.attr("ows_FileDirRef").split(";#");
  out += "<td class='ms-vb demo-note-details' style='width:40%;'>" +
  "<a href='/" + thisLink[1] + "/DispForm.aspx?ID=" + thisLink[0] + "&Source=" + location.href + "' data-announcement-id='" + item.attr("ows_ID") +
  "' data-list='" + lists[i].name + "' >" +
  item.attr("ows_Title") +
  "</a>" +
  "</td>";

  // Body
  var thisBody = item.attr("ows_Body");
  out += "<td class='ms-vb'>" + ((typeof thisBody !== "undefined") ? thisBody : "NA") + "</td>";

  // Author as a link to the User Profile
  var thisAuthor = item.attr("ows_Author").split(";#");
  out += "<td class='ms-vb' style='width:15%;'>" +
  "<a href='/_layouts/userdisp.aspx?ID=" + thisAuthor[0] + "' onclick='GoToLink(this);return false;'>" + thisAuthor[1] + "</a>" +
  "</td>";

  // Created date/time
  out += "<td class='ms-vb' style='width:15%;'>" + item.attr("ows_Created") + "</td>";

  out += "</tr>";

}

Note that I’m using the same version of SPServices here. SPServices 2013.01 can be used as you’re used to using it and everything will work as you are used to. If you are making multiple calls and the promises method would make more sense (mainly in terms of efficiency), then you can take advantage of it.

There are two big benefits of using the promises method:

  • If there are multiple calls to the Web Services that can logically happen concurrently (there’s less benefit if there is only one call), the promises method allows that to happen.
  • In the promises method, the browser doesn’t get locked up like it does with the async method. Setting async to true means that the browser is going to wait for the results to come back from the call before it will allow anything else to happen. With promises, the processing continues even when the results aren’t available. (This can take some getting used to, and I’ll undoubtedly do more posts about it.)

I’ve tested the code in SharePoint 2013 on Office365 and WSS 3.0 at FPWeb, which are about the most opposite ends of the spectrum that we can try these days. The code that I tested is *exactly* the same in the two environments. I simply copied and pasted the file from SharePoint 2013 where I started into the WSS 3.0 environment. How’s that for consistency, even on a downgrade? Oh, if Microsoft’s code only did that!

Using Firebug, I’ve captured information from the two methods on the Net tab for the XHR traffic.

Here are the results in SharePoint 2013:

Async

image

Promises

image

As you can see the total elapsed time decreased from about 503ms to 107ms. These results are fairly consistent. I haven’t tried to build any fancy instrumentation around it, but if I click the buttons repeatedly, I get consistent results in both cases.

Here are the results from WSS 3.0:

Asynch

image

Promises

image

Here, the total elapsed time decreased from about 409ms to 130ms.

In both versions of SharePoint, you can quite obviously see the difference. With the async method, the three calls happen in order, whereas with the promises method, they happen pretty much concurrently. (Even with promises, there’s a small lag due to the processing to set up the calls.)

But don’t take my word for it. go and grab your own copy of the

Moving Lists from Hosted WSS 3.0 to Office365 – The ShareGate Way

The other day I did a post in which I was grousing about what a pain it is to move individual lists from a 2007 environment to a 2010 environment. Even worse, I wanted to move the lists from a hosted WSS 3.0 environment at FPWeb to Office365. Because of this, there was no access to the back end, and it was a one-time move, anyway. I did figure out a really messy way to do it, and my prior post outlines that if you’re interested.

[notice]I’m going to stick with my premise that portability is one of SharePoint’s Achilles’ heels. We absolutely have to get to a point where there are better answers to these questions than “hire a developer” or “buy a third party tool”.[/notice]

GSoft GroupBen Niaulin (@bniaulin) at GSoft Group saw my complaining on Twitter and reminded me that as an MVP I could get a copy of their Sharegate product for free. BTW, GSoft fielded an awesome Iron SharePoint team called the SHAREPOINT G-NIUSES at the recent SharePoint Summit in Toronto. I was there to see what they came up with, and I was impressed. Check them out if you need SharePoint Services in Canada (or anywhere, I suppose).

SharegateI was certainly willing to try Sharegate out, of course. Setting it up and getting it running was a snap. The user interface is really nice – something that I think many end users would be totally comfortable with. Ben wanted to give me a tutorial up front, but I figured that wouldn’t be a fair test. I wanted to see if I could figure it out myself, just with the existing help.

My first challenge was that I couldn’t figure out how to copy the structure of the lists from WSS to Office365. It was clear how to move the content, but not the structure. I caved and pinged Ben. Apparently, this was by design.

In Ben’s words:

Sharegate is currently a Content Migration tool, you need to have the destination library there.

The idea is that when migrating from SharePoint 2007 to 2010 many new features and structures exist. What  may have been a lookup column could become a Managed Metadata column in 2010 instead. That’s what we focus on, migrating the content, for now.

This wasn’t a big deal, though, because I was interested in moving only about a dozen lists, and the structures weren’t that complex. I had hoped to be able to move the structures *and* content, but I just bit the bullet and built the structures (that I hadn’t built already based on my prior, manual method) by hand.

Here are some screenshots that show how straightforward it was to move the content. The first step, which I don’t capture here, was simply to connect to the source and destination locations.

Next you select the source “container”, in my case, an individual list.

image

Then you specify the destination “container”.

image

Once you’ve made those choices, you can see all of the items in the source which you can migrate.

image

I wanted to move everything, so I just clicked the “Select All” link.

image

The next prompt let me choose between an easy and a more powerful route. I just wanted to go the Full copy route, as I was moving the content wholesale. The option on the right, which I poked around in but didn’t use this time, gave me a *lot* of control over how the content moved. I could remap fields, change values based on conditions, all sorts of stuff. The other migration tools I’ve looked at also do this, but I really liked the interface in Sharegate.

image

Next came a warning about mapping the permissions. Since I didn’t have access to the back ends, I couldn’t install the Sharegate Extension. This meant that the permissions wouldn’t go across. This was something else I didn’t care about, so I simply clicked Continue.

image

image

And voila, the content moved across, with a nice progress indicator telling me how things were going. It wasn’t a lot of content, so it was pretty quick.

Lather, rinse, and repeat for each of my other lists and I was all set.

All in all, it was a pretty painless experience. I think that Sharegate’s UI is one of the big selling points for it. Also, since it doesn’t try to be too many things (like some of the products out there), the process was streamlined for basically the exact path I wanted to take. If you’ve got some content to migrate from one place in SharePoint to another, give it a look.

And thanks, Ben!

"One of the most impressive web pages I have ever seen in SharePoint"

Several people have asked me what I showed Dan Antion last week which caused him to say “Marc began by showing us one of the most impressive web pages I have ever seen in SharePoint.” I’m not sure that it was actually all *that* impressive (I have better!) but it does do some pretty cool things.

I was showing Dan and his team some of the things you can do by developing in SharePoint’s Middle Tier. The reason I pulled this demo out of the vault was that it’s a pretty good example of the type of dashboard that you can build using garden variety SharePoint tools and a little ingenuity. No fancy BI add ons or KPIs here; we built what we needed with plain old Data View Web Parts (DVWPs).

The page I showed Dan and his team was one which I worked on with Marcel Meth (@marcelmeth) and Christina Wheeler (@cwheeler76) well over a year ago. Marcel was working with an organization here in Newton, MA, that wanted to build a Web site which would help households manage their carbon footprints. We put together a prototype using a hosted instance of WSS. Yes, WSS 3.0. People often dismiss WSS as being too underpowered to solve any sophisticated business problems, but that’s just not the case.

We built the prototype on spec and unfortunately it never saw the light of day, but we did have a lot of fun working on it.

Here’s the page I showed Dan:

image

What this amounts to is a dashboard for the household. Christina came up with a really nice, clean design for the site but keep in mind, while it looks pretty good, we dropped it midstream because we knew it wasn’t going to be used. It was going to be even better!

There are four Data View Web Parts (DVWPs) on the page. We decided to use DVWPs for all four Web Parts because we wanted to have very flexible control over the data displayed as well as the formatting of that data.

Household Demographics

Each household could be part of a team so that teams could compete to reduce their carbon footprint more than other teams which had similar characteristics. This DVWP showed the household’s info and a little bit about their team.

image

Household Size Details

We planned to use this Web Part to show what changes, if any, there had been to the overall household size, whether in square feet or number of people, which might impact the carbon consumption. We were going to allow the household to enter data either on a monthly or annual basis.

image

Household’s Annual Data

Here we displayed the most important pieces of data per household (we were going to collect more data points than we would show here).

image

Comparative Household Information

This is the really cool DVWP. Here we show the household’s performance against other households on several different measures. Since there can be a lot of variation in household size and number of residents, we wanted to allow each household to see the comparisons along each dimension. Over time our plan was to begin showing the comparisons for each household which made sense based on that household’s characteristics as well as the characteristics of its near peers.

imageThe dropdown for year in the upper left of the DVWP changed the data to the year requested. We highlighted that year’s data based upon how the household compared to other households, both from the median and against the top 25% of households.

We did the calculations and formatted each cell based on calls to generalized XSL templates. For example, we built a template called GetPercentileValue which we could use to generate both the Best 25% and Median simply by passing in a different value for the Percentile:


xsl:call-template>

This template has been available in my SPXSLT Codeplex Project since the first release. By building generalized templates like this which we could call repeatedly, the XSL for this fairly complex DVWP ended up much simpler than it would have been otherwise. All told, the XSL for this one DVWP is about 600 lines, but it’s highly malleable because of the modular approach we took.

Of course, coming up with a good underlying information architecture was key to making all of this work well. That’s another place where we still had some work to do, but by creating five key interrelated lists, we believed that we could allow for easy data addition as well as a pretty sophisticated set of display mechanisms.

So you be the judge: impressive page or not?

Finding the Contents of a SharePoint Rich Text Column with jQuery

I’ve had two questions this week about how to find the contents of a Rich Text column reliably with jQuery. Rich Text columns are rendered in a more complex way than just a nice textarea. Like some of the other column types they are a sort of hybrid, with some HTML elements and some script to hold it all together.

Taking a look at a Rich Text column with the Developer Tools in IE8, all of the gunky HTML below makes up the column. The value in the column is stored in a div inside one of the iframes. So it’s available, but you need to do some digging!

image

There are two spans wrapping everything, and within them there are the following elements:

  • textarea – This textarea contains the previously set value, not the currently edited value.
  • table – This table contains the buttons above the box which allow you to set the formatting.
  • iframe – I’m not exactly sure what this iframe is for; in my test WSS environment, it simply contains the image /_layouts/images/blank.gif that SharePoint uses as a placeholder.
  • div – this div contains the iframe where the real value is.
  • script – Finally, this script block sort of holds everything together.

So, as you can see in the screenshot above, we want to grab the contents of the body inside the iframe inside the div. Make sense?

To do this, we can use the following jQuery:

$("textarea[Title='System Description']").closest("span").find("iframe[Title='Rich Text Editor']").blur(function(){
  alert($(this).contents().find("body").html());
});

The name of my column in this example is System Description. First I find the texarea with System Description as the title. Then I find the closest ancestor which is a span. (I usually prefer to use .closest() rather than .parent() when I can in case anything else ends up inserted between the two elements.) Next I am finding the iframe which has its title attribute set to ‘Rich Text Editor’. That gets me into the right neighborhood. To test this, I decided to alert the contents of the iframe’s body when a blur event is triggered. That will happen when you’re finished editing the System Description (in this case) and my cursor exits the column.

If you’d just like to grab the value, you could use this jQuery. Yes, it’s a bit messy, but it gets the job done!

var systemInformation = $("textarea[Title='System Description']").closest("span").find("iframe[Title='Rich Text Editor']").contents().find("body").html();

p.s. This all works exactly the same in SharePoint 2010 as it does in SharePoint 2007.

The Magic of Hosted WSS

Whoa, I’m really falling down on my blogging lately.  It’s not that I don’t have as much to say, it’s that Twitter and other things end up taking more of my time these days. ("Microblogging" is the word we use to make tweets legitimate.)

So here’s something. I think I may have outlined this a bit before, but I thought it might interest someone out there to know how much I manage to wring out of my $29/month of hosted WSS.  My friends over at FPWeb.net keep me up and running and manage to make me feel like a Very Important Customer, even though Sympraxis Consulting is just a one man shop at the moment (that would be me).

WSS is generally pooh-poohed by the SharePoint aficionados out there as too low end to be useful.  (Of course, Microsoft would also love it if everyone purchased licenses for MOSS.) However, I’m using my WSS site for a number of things, and I think that this is pretty impressive given that it’s free software:

  • As my Internet-facing site (http://sympraxisconsulting.com) — This is fairly vanilla, though I do have a custom master page with custom CSS and a little jQuery just to give it some sizzle.
  • As my Intranet (permission-protected)– While I have a custom master page and CSS applied here, the branding serves little purpose other than to give me a visual cue where I am.
  • As my Extranet (permission-protected) – When I’m working with clients, I set up a site for each where we can share documents, etc.  These are plain vanilla Team Sites or even Blank Sites to start. To date, these sites have gotten very little use.  (It’s always surprising how little people who hire you to build SharePoint stuff for them actually want to use SharePoint to collaborate on the process!)
  • As a demo site for some of the stuff I build (http://sympraxisconsulting.com/Demos) – This is a vanilla Blank Site with some customized pages with JavaScript, jQuery, etc., which I use as a place to demo some of the jQuery Library for SharePoint Web Services functionality and some other stuff.
  • As my development platform for my jQuery Library for SharePoint Web Services (permission-protected) — This is where I’m doing my "work".

All this without ever touching the servers!