SPServices Stories #19 – Folders in SharePoint are as necessary as evil. Make the best of it using jQuery and SPServices.

This entry is part 18 of 21 in the series SPServices Stories


Ever on the hunt for good SPServices Stories, I spotted this cool one a few weeks back when Patrick Penn (@nfpenn) posted a screenshot of something he had done on Twitter. I encouraged him to do a post about it and this is the result: Folders in SharePoint are as necessary as evil. Make the best of it using jQuery and SPServices.

I liked this post for several reasons. First, it’s a great example of how we can improve the user experience (UX) with SPServices and client side scripting. Second, Patrick tells us in some depth what he was trying to accomplish from a non-technical perspective and how he made it work. Some have accused me of posting purely technical pieces in this series without enough story to them; this post has both.
A few notes from my end:

  • SPServices has a function to parse the query string called SPGetQueryString, so Patrick’s function getQueryStrings isn’t really needed.
  • Using the .find(“z\\:row, row”) syntax will cause issues in some browsers due to the z namespace. I’ve got a function in SPServices called SPFilterNode which you should use instead: .SPFilterNode(“z:row”). The function will work with any selectors in the XML (at least all that I’ve tried), but you should definitely use it for z:row and rs:data. In other words, use SPFilterNode anywhere you see namespacing in an XML node.

Patrick PennPatrick Penn (@nfpenn) is a SharePoint Architect at netflower (http://netflower.de) in Germany. He loves SharePoint but also knows its weak points. Based on this knowledge he gives advice to clients and builds custom-made solutions to improve efficiency and usability within SharePoint.

Folders in SharePoint are as necessary as evil. Make the best of it using jQuery and SPServices.

Let me say right upfront, this post is not about Folders vs Metadata. If you’re searching for that, you will find a rather good one here.

Hopefully you know about the benefits of SharePoint and its features like, enterprise keywords, taxonomy and metadata navigation. But sometimes you or your client need a good old folder hierarchy. If you’re a valuable consultant you will neither roll your eyes nor surrender but assure him with a smile that you will build a solution that will absolutely meet his needs.

Some Reasons Why Folders are Necessary

Some logical arguments for using folders in SharePoint are:

  • If you have many document types which need different permissions within a single document library. Sure, you can configure dedicated permissions for each single document, but this may be hard to maintain.
  • If your customer needs a quick solution to share documents without manually setting managed metadata for each single document, because they often upload documents in a bulk.
  • If you need to logically group different document types and provide a dynamically generated status based on documents or its metadata, which needs to be displayed on a higher hierarchy level to provide an consolidated overview about the content. Sounds complicated? Practically speaking it may be needed to show a completeness status about documents to deliver, which brought me to the solution dealt with in this post.
  • Another reason is to simply not to overstrain the users, if they’re new to SharePoint. They quite likely know the folder structure and you as a consultant have the possibility to provide a solution which include the best of both worlds. To work future-oriented, be creative, for example you’re able to automatically define metadata predefined by a documents name or its parent folder or even better by its content. There’s an outdated solution on Codeplex, that may give you an idea. Maybe I dedicate a post to this topic in the near future.

While you’re reading this, you may think: “Why the hell didn’t he use Document Sets?”.
We involved this feature in our planning, but there is no metadata navigation within Document Sets, which could have been an advantage. Furthermore the customer needed a multi-level hierarchical structure. As we had no significant arguments for it, the latter was a show stopper for Document Sets.

How to Pep Up Folders

As you can imagine, you have many possibilities to get more out of old and boring folders. For example JSLink may be your favorite approach, but I didn’t use it, because this is a migrated solution.

Something like the following is a simple approach to provide much more value to folders.

peppedup_folders_1-1The presumably simplest way may be consulting SharePoint Designer and add conditional formatting to change the presentation of a document library view.
But if you think further and consider a more professional deployment you may realize, that there must be a better way. Also using XSLT will not be fun to handle the content of multiple subfolders and I’m sure the result will not be a smooth experience either.

To keep things simple and manageable I decided to use jQuery and a powerful javascript library from SharePoint MVP Marc D. Anderson, author of SPServices.

I’m not allowed to publish the whole source code regarding this solution, due to the NDA with our client, but I will hopefully give you enough information to build a solution like this by yourself. Sorry for that!

Let’s Begin

I used the following javascript libraries for SharePoint 2013:
jQuery 1.10.2 and SPServices 2013.02a

Because I use jQuery and SPServices in multiple places I decided to place the script references within the custom masterpage. You can do it manually or use a more professional approach like this, by using a custom delegate control.

For testing purpose you can simply add a reference within the content editor webpart. But be sure to implement this before you call the functions.

<script src="/Style%20Library/scripts/jquery/jquery-1.10.2.min.js" type=text/javascript></script>
<script src="/Style%20Library/scripts/jquery/jquery.SPServices-201302a.js" type=text/javascript></script>

Congrats! Now you’re able to use the full power of jQuery and SPServices!

I want to keep things as simple as possible. So to implement the pepped up folder (PUF) functionality within a document library, I just added a content editor webpart (CEWP) below the list view, which is invisible for users.

webpart_placholders_1-1Then I added a reference (Content Link) for the PUF-script.

webpart_contentlink_1-1To load a .js file as Content Link you should put the whole code between these tags. The alternative is to use a simple .txt file.

<script type="text/javascript">
  //your code here

I like the way using .js files, because of reusability outside a CEWP Content Link.


  • folder-2default folder: Containing files within the folder or in any of its subfolders. The user knows that there will be content and the click will not be for nothing
  • folder_empty-2empty folder: No files are contained within the folder and each of its subfolders. So the user doesn’t have to look for any content and knows that there is still something to deliver.
  • folder_not_reviewed-2not reviewed folder: No files are contained within the folder and each of its subfolders and the status is “not reviewed”.
  • folder_reviewed-2reviewed folder: The folder status is set to “reviewed” or all child folders are set to “reviewed”. Sometimes there are no files to deliver for a specific folder, so it can directly be marked as “reviewed”.

Optionally you can notify the user that pepping up begins and load the pepUpFolders function with a short delay.

  var loadingNotifyId = SP.UI.Notify.addNotification('Pepping up folders ...', false);

It may happen, that folders are already updated before the user is able to read the notification, so the delay helps.
You can decide which approach is the best for your users. Our customer wanted to notify users about what’s going to happen.

Use this function to retrieve the document libraries root folder from the url parameter if you’re currently in any subfolder.

function getQueryStrings() {
  var assoc  = {};
  var decode = function (s) { return decodeURIComponent(s.replace(/\+/g, " ")); };
  var queryString = location.search.substring(1);
  var keyValues = queryString.split('&');

  for(var i in keyValues) {
    var key = keyValues[i].split('=');
    if (key.length > 1) {
      assoc[decode(key[0])] = decode(key[1]);

  return assoc;

Last but not least the more exciting part. This is where the magic happens.

As I mentioned before this function is a bit truncated, but for the result you’ll see a difference regarding folders with and without content. The nice part is, that the most functionality loads asynchronously so the user feels nearly no delay, when navigating through the folder structure.

function pepUpFolders() {
    $(document).ready(function() {
        var sitecollectionUrl = _spPageContextInfo.siteServerRelativeUrl;
        if (sitecollectionUrl == "/") {
            sitecollectionUrl = "";
        var emptyFolderIconPath = sitecollectionUrl + "/Style Library/scripts/images/folder_empty.gif";
        var folderIconPath = sitecollectionUrl + "/_layouts/15/images/folder.gif?rev=23";
        var loaderIconPath = sitecollectionUrl + "/Style Library/scripts/images/loading.gif";
        var folderPrefix = "";

        /* You need this prefix for SharePoint 2010 to replace folder icons

          case 1031:
          folderPrefix = "";//"Ordner: ";

          case 1033:
            folderPrefix = "";//"Folder: ";

        var folderStatusReviewedString = "reviewed";

        var listName = $().SPServices.SPListNameFromUrl();
        var siteUrl = $().SPServices.SPGetCurrentSite();

        //Parsing the server relative url of the current web
        if (siteUrl.startsWith("http")) {
            var siteServerRelativeUrl = siteUrl.match(/\/[^\/]+(.+)?/)[1] + "/";
        } else {
            var siteServerRelativeUrl = siteUrl;

        var currentFolderPath;
        var qs = getQueryStrings();
        var rootFolder = decodeURI(qs["RootFolder"]);

        //If the rootFolder is not set, then we are currently in the root folder
        if (rootFolder != null) {
            rootFolder = rootFolder.replace(siteServerRelativeUrl, "");
            currentFolderPath = rootFolder;
        } else {
            //The root folder is not set, then we need to get the rootFolder from list
                operation: "GetList",
                async: false,
                listName: listName,
                completefunc: function(xData, Status) {
                    $(xData.responseXML).find("List").each(function() {
                        currentFolderPath = $(this).attr("RootFolder");

        currentFolderPath = currentFolderPath.replace(siteServerRelativeUrl, "");
        parentFolderPath = currentFolderPath.substring(0, currentFolderPath.lastIndexOf('/'));
        parentFolderName = currentFolderPath.substring(currentFolderPath.lastIndexOf('/') + 1);

        //We request only a list of folders as we don't need to inspect files from current folder
        var query = "<Query><Where><Eq><FieldRef Name='FSObjType'></FieldRef><Value Type='Lookup'>1</Value></Eq></Where></Query>";
        var queryOptions = '<QueryOptions><Folder><![CDATA[' + currentFolderPath.substring(1) + ']]></Folder></QueryOptions>';
        var viewFields = "<ViewFields Properties='true'><FieldRef Name='Level' /></ViewFields>";

        var promFolders = [];
        promFolders[0] = $().SPServices({
            operation: "GetListItems",
            listName: listName,
            CAMLQuery: query,
            CAMLQueryOptions: queryOptions,
            CAMLViewFields: viewFields

        var promSubFolders = [];
        $.when.apply($, promFolders).done(function() {
            $(promFolders[0].responseXML).SPFilterNode("z:row").each(function() {
                var subFolderPath = $(this).attr("ows_FileRef").split(";#")[1];
                subFolderPath = subFolderPath.replace(siteServerRelativeUrl.substring(1), "").substring(1);
                var subFolderName = $(this).attr("ows_FileLeafRef").split(";#")[1];
                $("[title='" + folderPrefix + subFolderName + "']").attr("src", loaderIconPath);

                //Search for Files in any subfolder and return 1 item per Folder max.
                var query = "<Query><Where><Eq><FieldRef Name='FSObjType'></FieldRef><Value Type='Lookup'>0</Value></Eq></Where></Query>";
                var queryOptions = "<QueryOptions><Folder><![CDATA[" + subFolderPath + "]]></Folder><ViewAttributes Scope='Recursive' /></QueryOptions>";

                    operation: "GetListItems",
                    async: true,
                    listName: listName,
                    CAMLRowLimit: 1,
                    CAMLQuery: query,
                    CAMLQueryOptions: queryOptions,
                    completefunc: function(xData, Status) {
                        //Replace Folder Icon with empty Folder icon
                        if ($(xData.responseXML).find("z\\:row, row").length == 0) {
                            $("[title='" + folderPrefix + subFolderName + "']").attr("src", emptyFolderIconPath);
                        } else {
                            $("[title='" + folderPrefix + subFolderName + "']").attr("src", folderIconPath);

function getQueryStrings() {
    var assoc = {};
    var decode = function(s) {
        return decodeURIComponent(s.replace(/\+/g, " "));
    var queryString = location.search.substring(1);
    var keyValues = queryString.split('&');

    for (var i in keyValues) {
        var key = keyValues[i].split('=');
        if (key.length > 1) {
            assoc[decode(key[0])] = decode(key[1]);
    return assoc;

I’m sure there is something to optimize. But the intention of this post was to show a solution to pep up boring folders and make them more valuable.

I was struggling with async calls somehow, so Marc D. Anderson recommended me to use promises instead of regular async calls, because of a better controllable program flow.

So, I updated my code and combined both approaches, because of depending calls. Now everything seems to work as expected and it’s clear what javascript promises are and how they can help to improve program flow.

I hope this post gives you some fresh ideas how to gain more value out of folders, because they are still necessary, albeit evil.

If you need some advice feel free to contact me.


Office 365 Update Changes ‘Display Name’ on Required Fields

Observant SPServices user GregRT noticed something on Office365 today that I figured couldn’t be true. He posted the following in the discussions on the SPServices Codeplex site:

FYI – I had left my debug on.
Users were getting errors on there forms that a field could not be found by SPServices.
MSFT has changed the display name of required fields when it lands in the DOM. I had a cascade going from parentColumn ‘Division’ to childColumn ‘SalesCenter’ (both required fields) – working last week with the display names as ‘Divison’ and ‘SalesCenter’ … now when the form renders in the DOM the display names are ‘Division Required Field’ and ‘SalesCenter Required Field’ – no indication in the UI that this is modified.
Hope this helps someone!

My first reaction was to question whether this could be right.

For years, we’ve been able to reliably select a regular dropdown (select) easily by using the column’s Title. The markup has looked something like this (depending on SharePoint version):

&lt;select id=&quot;Region_59566f6f-1c3b-4efb-9b7b-6dbc35fe3b0a_$LookupField&quot; title=&quot;Region&quot;&gt;

2014-01-23_14-39-44so we could use a jQuery selector like this:

var regionSelect = $(&quot;select[Title='Region']&quot;);

However, GregT is right. In one of my demo sites on Office365, when I set a the Region column to required I’m seeing this markup instead:

&lt;select id=&quot;Region_59566f6f-1c3b-4efb-9b7b-6dbc35fe3b0a_$LookupField&quot; title=&quot;Region Required Field&quot;&gt;

2014-01-23_14-40-50I expect this will be a problem not just for everyone using SPServices, but for many custom scripts as well.

Not only does this become a breaking change, it’s just plain bad practice.

If you run into this issue, a quick fix solution would be:

var regionSelect = $(&quot;select[Title='Region'], select[Title='Region Required Field']&quot;);

It’s not enough for your selector to look for a Title which *starts* with ‘Region’; you might have a column called ‘Region2’ or ‘Regions’ as well, so you’d get false positives.

I’ll see if I can find out any more details on why this may have happened and whether it’s permanent. Ugh.

[notice]2014-01-24 8pm EST – Based on info from Christophe below and several others, the issue seems to arise somewhere between vti_buildversion:SR|16.0.2308.1208 and vti_buildversion:SR|16.0.2510.1204. You can check your version by going to  /_vti_pvt/buildversion.cnf in your Offic365 tenant. See: http://corypeters.net/2013/07/getting-the-product-version-for-sharepoint-online/[/notice]

[notice]2014-01-24 11pm EST – The PG reached out to me and they are investigating the issue. I’ll post more info as I get it.[/notice]

[notice]2014-01-31 9am EST – Based on the information I’ve gotten from the PG so far (not a lot), I’m going to assume this is a permanent change. The ostensible reason, as suggested by several people in the comments below, is to improve accessibility using screen readers. It’s hard to argue with that, but it’s still an unfortunate change.

I’ve also checked with a few people running non-English tenants, and the titles are localized, like so:

  • Swedish: “Namn Obligatoriskt fält” or “Name Required Field”.
  • Russion: “RegSelect Обязательное поле” or “RegSelect Required Field”
  • etc.

I’ve also received reports (several below in the comments) that this issue is popping up with SharePoint 2010 after applying the December CU. More on that as I get further details.

Based on this, I’m getting a fix ready in the 2014.01 release and will try to get it out there shortly. Methinks it’s going to become a game of catch up from now on.

[notice]2014-02-03 12:40m EST – SPServices 2014.01ALPHA2 posted with a fix for the issue on Office365. As soon as I can get an example of how the issues arises in SharePoint 2010, I hope to have that fix as well. [/notice]

[notice]2014-02-19 SPServices 2014.01 released with fixes for every implication of these changes of which I am aware. If you’ve run into this issue, please upgrade!

jQuery Library for SharePoint Web Services (SPServices) 2013.02 Re-Released

Back on December 28, 2013, I released SPServices 2013.02, just squeaking in under the wire to be able to name it thusly.

Soon after the release, Paul Tavares (@paul_tavares) and I noticed a few significant issues. These issues are fixed in 2013.02a. If you downloaded 2013.02, please replace it with 2013.02a.

For the record, the issues were:

  • In SharePoint 2010, the current site context was not calculated correctly
  • GetListItemChanges and GetListItemChangesWithToken failed as they had in previous versions
  • In SPDisplayRelatedInfo, if the relatedListColumn was not specified in relatedColumns, the function would fail with an error. This issue was probably present in previous versions, but as far as I know had not been reported.

Sorry for the inconvenience, and thanks to Paul (yet again!) for his help and support.

The link to SPServices 2013.02a on cdnjs is coming. Thanks to Josh McCarty (@joshmcrty) for his continuing help with git, which for some reason remains unfathomable to me.

Single-Page Applications (SPAs) in SharePoint Using SPServices – Part 4 – GetListItemChangesSinceToken

As I mentioned in the last part of the series, when we build a Single Page Application (SPA), we’ll usually want to keep the data we’re displaying up to date. GetListItems and GetListItemChanges are two of the operations that can help with this, but the more robust option is GetListItemChangesSinceToken (MSDN documentation). In fact, it’s becoming my favorite list-data-getting operation, even over the rock solid GetListItems.

GetListItems is great for simply grabbing items from a list, but if you want to monitor the list for changes efficiently, you’ll need GetListItemChanges and/or GetListItemChangesSinceToken. Of the two, GetListItemChangesSinceToken is the far more powerful. In fact, once you start using it, you may well stop using GetListItems altogether.

[important]GetListItemChanges and GetListItemChangesSinceToken do not work in versions of SPServices before 2013.02.[/important]

(I wish I had gotten these two operations working long ago, now that I realize how powerful they are.)

In this post, let’s take a look at the GetListItemChangesSinceToken operation.


GetListItemChangesSinceToken is clearly related to its siblings, GetListItems and GetListItemChanges, with a cross between the two of their characteristics, plus some extra goodness. We can be very specific about what we request, as with GetListItems, but we also can decide to only receive changes since a specific database token (more on this below).


See the GetListItems post in this series.


See the GetListItems post in this series.


See the GetListItems post in this series.


See the GetListItems post in this series.


See the GetListItems post in this series.


See the GetListItems post in this series.


See the GetListItems post in this series. Note that the MSDN documentation for GetListItemChangesSinceToken provides far more parameters than the MSDN documentation for GetListItems. Most (if not all – I haven’t tested everything) of these parameters work with GetListItems as well.


A string that contains the change token for the request. For a description of the format that is used in this string, see Overview: Change Tokens, Object Types, and Change Types. If null is passed, all items in the list are returned.


A Contains element that defines custom filtering for the query and that can be assigned to a System.Xml.XmlNode object, as in the following example.

  <FieldRef Name="Status"/>
  <Value Type="Text">Complete</Value>

This parameter can contain null.

In simpler terms, this can be a snippet of CAML to add an additional filter to the request.


Contrary to the statements above about passing nulls for changeToken and contains, you cannot do so from the client (it may be allowable in server side code, but I’m not sure). If you pass an empty node in your SOAP request, the request will fail. SPServices – as of version 2013.02 – will handle empty values for you. This is the bug I fixed.

GetListItemChangesSinceToken Returns

In your first call to GetListItemChangesSinceToken, you won’t have a token yet. Or, you may choose not to pass a token that you do have. In that case, you get a robust amount of information returned to you.

The simplest call looks like this:

  operation: "GetListItemChangesSinceToken",
  listName: "Sales Opportunities"

Here, I’m just asking for the information for the Sales Opportunities list; I’m not providing any parameter values at all. On that first call, the results will look something like this (inside the SOAP envelope and GetListItemChangesSinceTokenResult):

<listitems xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema" MinTimeBetweenSyncs="0" RecommendedTimeBetweenSyncs="180" MaxBulkDocumentSyncSize="500" AlternateUrls=",http://www.sympraxisconsulting.com/,,http://sympraxisconsulting.com/" EffectivePermMask="FullMask">
  <Changes LastChangeToken="1;3;37920121-19b2-4c77-92ff-8b3e07853114;635243604504600000;33126">
    <List DocTemplateUrl="" DefaultViewUrl="/Intranet/JQueryLib/Lists/Sales Opportunities/AllItems.aspx" MobileDefaultViewUrl="" ID="{37920121-19B2-4C77-92FF-8B3E07853114}" Title="Sales Opportunities" Description="" ImageUrl="/_layouts/images/itgen.gif" Name="{37920121-19B2-4C77-92FF-8B3E07853114}" BaseType="0" FeatureId="00bfea71-de22-43b2-a848-c05709900100" ServerTemplate="100" Created="20090825 06:24:48" Modified="20131216 03:52:25" LastDeleted="20120627 07:54:25" Version="362" Direction="none" ThumbnailSize="" WebImageWidth="" WebImageHeight="" Flags="612372480" ItemCount="7" AnonymousPermMask="0" RootFolder="/Intranet/JQueryLib/Lists/Sales Opportunities" ReadSecurity="1" WriteSecurity="1" Author="3" EventSinkAssembly="" EventSinkClass="" EventSinkData="" EmailInsertsFolder="" EmailAlias="" WebFullUrl="/Intranet/JQueryLib" WebId="42f65d3f-343d-4627-a9a3-abf3d4d6491f" SendToLocation="" ScopeId="c8b78fea-7952-433a-be20-cda628ea6cbb" MajorVersionLimit="0" MajorWithMinorVersionsLimit="0" WorkFlowId="" HasUniqueScopes="False" AllowDeletion="True" AllowMultiResponses="False" EnableAttachments="True" EnableModeration="False" EnableVersioning="False" Hidden="False" MultipleDataList="False" Ordered="False" ShowUser="True" EnableMinorVersion="False" RequireCheckout="False">
        <Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Type="Text" Name="Title" DisplayName="Title" Required="TRUE" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="Title" FromBaseType="TRUE" ColName="nvarchar1"/>
        <Field Name="Lead_x0020_Source" FromBaseType="FALSE" Type="MultiChoice" DisplayName="Lead Source" Required="TRUE" FillInChoice="TRUE" ID="{0ab3481c-a7b4-432f-8292-c1617744a167}" Version="6" StaticName="Lead_x0020_Source" SourceID="{f4ebb20a-91e0-4306-997d-208e1d1920b7}" ColName="ntext3" RowOrdinal="0">
            <CHOICE>Newspaper Advertising</CHOICE>
            <CHOICE>Web Site</CHOICE>
            <CHOICE>Personal Referral</CHOICE>
        <Field Type="Currency" DisplayName="Potential Value" Required="FALSE" Decimals="2" LCID="1033" ID="{e9058e0c-b2e8-4af6-882c-9551a5f21451}" SourceID="{f4ebb20a-91e0-4306-997d-208e1d1920b7}" StaticName="Potential_x0020_Value" Name="Potential_x0020_Value" ColName="float1" RowOrdinal="0" Version="1">
  <rs:data ItemCount="7">
    <z:row ows_Attachments="1" ows_LinkTitle="Tue Feb 14 15:58:39 EST 2012" ows_Modified="2013-12-11 10:04:38" ows_Author="3;#Marc D Anderson" ows_Lead_x0020_Source=";#Newspaper Advertising;#" ows_Potential_x0020_Value="12.6100000000000" ows_Lead_x0020_Date="2013-04-20 00:00:00" ows_Country="1;#United States" ows_Region="1;#Northeast" ows_State="2;#Rhode Island" ows_City="706;#Providence" ows_Course_x0020_Title="1;#ss" ows_System="3;#Alienware" ows_StateID="North Dakota" ows_Domain="7;#baloney.com" ows_Notes="&lt;div&gt;&lt;/div&gt;" ows_Web_x0020_Service_x0020_Operatio="13;#GetList" ows_Radio_x0020_Buttons="C" ows_Sales_x0020_Rep="104;#Marc Anderson;#89;#Eric Mullerbeck;#91;#Renzo Grande" ows_a_x0020_b_x0020_bcb_x0020_n="" ows_CalcTest="float;#151.320000000000" ows_CalcTestDate="datetime;#2009-08-25 14:24:48" ows_CalcTestYesNo="boolean;#1" ows_YesNo="1" ows_UpdateList_Field_Sun_x0020_Oct_x="lk;k;lk;lk" ows__Level="1" ows_UniqueId="5;#{3345A2C2-0B2D-4D79-9CB8-4FEFA993F5FA}" ows_FSObjType="5;#0" ows_Created_x0020_Date="5;#2009-08-25 14:24:48" ows_Title="Tue Feb 14 15:58:39 EST 2012" ows_Created="2009-08-25 14:24:48" ows_ID="5" ows_owshiddenversion="256" ows_FileLeafRef="5;#5_.000" ows_FileRef="5;#Intranet/JQueryLib/Lists/Sales Opportunities/5_.000" ows__ModerationStatus="0"/>

Yes, that is a heck of a lot of XML, but it’s not even all of it (note the ellipses). This may seem like overkill in many cases, and it may well be. In those cases, GetListItems is still the best way to go. But in the cases where you want to build an SPA, GetListItemsWithToken may be the ticket.

Like I said, you get a lot of robust information. Here are some more details on what’s there:

  • A bunch of info about what frequency and size requests are allowed. Generally you won’t need to care about these values, but they are there when you get to that point.
  • AlternateUrls – These are the URLs for each access path you might take to your list. Sweet! (The security folks will probably blanch at this.)
  • In any call to GetListItemChangesSinceToken we get a new value for LastChangeToken whether we have passed a changeToken or not. This token isn’t something we’re supposed to pick apart or understand; it represents a certain point in the database life. If you really want to understand what the pieces of the token mean, you can check out the article Overview: Change Tokens, Object Types, and Change Types. You’ll want to grab that value, as you will use it in subsequent calls. You can decipher what each part of it means from the documentation, but you really shouldn’t care. Basically, it’s *like* a timestamp, but in database terms.
  • The full list schema. You may not ever need this, but it’s great in cases where you find yourself doing a GetList/GetListItems pair of calls to set up further work in your script.
  • Server information (lines 17-31 above). This is really cool. I’ve never been able to figure out a consistent way to determine the language and locale settings, the time zone offset, etc. from the client, but here they are. Keep in mind that these are the *server* settings, not the client settings.
  • All of the list items. With large lists (not so large that they hit the 5000 item throttling limit – that’s another story entirely, and another post), you probably don’t want to get all of the list items. That’s where the contains parameter comes in handy. You might make the first call specifying that you only want the item with ID=1 or only items where the Title is null (generally this will result in no items) or something.

In the next call, you’re probably going to pass in the LastChangeToken value from the first call:

  operation: "GetListItemChangesSinceToken",
  listName: "Sales Opportunities",
  changeToken: "1;3;37920121-19b2-4c77-92ff-8b3e07853114;635243604504600000;33126"

If there have been no changes, the results will look something like this. Nice and simple.

<listitems MinTimeBetweenSyncs="0" RecommendedTimeBetweenSyncs="180" MaxBulkDocumentSyncSize="500" AlternateUrls=",http://www.sympraxisconsulting.com/,,http://sympraxisconsulting.com/" EffectivePermMask="FullMask" xmlns:rs="urn:schemas-microsoft-com:rowset">
<Changes LastChangeToken="1;3;37920121-19b2-4c77-92ff-8b3e07853114;635175524963570000;31260"> </Changes>
<rs:data ItemCount="0"></rs:data>

If there have been changes to items in the list, the results will look something like this:

<listitems MinTimeBetweenSyncs="0" RecommendedTimeBetweenSyncs="180" MaxBulkDocumentSyncSize="500" AlternateUrls=",http://www.sympraxisconsulting.com/,,http://sympraxisconsulting.com/" EffectivePermMask="FullMask" xmlns:rs="urn:schemas-microsoft-com:rowset">
<Changes LastChangeToken="1;3;37920121-19b2-4c77-92ff-8b3e07853114;635175524963570000;31260"> </Changes>
<rs:data ItemCount="2">
	<z:row ows_Attachments="0" ows_LinkTitle="MyItem1" ows_MetaInfo="3;#" ows__ModerationStatus="0"
		ows__Level="1" ows_Title="MyItem1" ows_ID="3" ows_owshiddenversion="2"
		ows_UniqueId="3;#{9153FDD3-7C00-47E9-9194-956BB20AAA8D}" ows_FSObjType="3;#0"
		ows_Created_x0020_Date="3;#2007-08-31T21:34:59Z" ows_Created="2007-08-31T21:34:59Z"
		ows_FileLeafRef="3;#3_.000" ows_FileRef="3;#sites/MyWebSite/Lists/MyList/3_.000"
		ows_ServerRedirected="0" />
	<z:row ows_Attachments="0" ows_LinkTitle="MyItem2" ows_MetaInfo="5;#" ows__ModerationStatus="0"
		ows__Level="1" ows_Title="MyItem2" ows_ID="5" ows_owshiddenversion="3"
		ows_UniqueId="5;#{5BDBB1C0-194D-4878-B716-E397B0C1318C}" ows_FSObjType="5;#0"
		ows_Created_x0020_Date="5;#2007-08-31T21:43:23Z" ows_Created="2007-08-31T21:43:23Z"
		ows_FileLeafRef="5;#5_.000" ows_FileRef="5;#sites/MyWebSite/Lists/MyList/5_.000"
		ows_ServerRedirected="0" />

If there have been any item deletions, the Changes node will look a little different (you may see deletes, update, and adds depending on what’s been going on and how long it’s been since you asked):

<Changes LastChangeToken="1;3;641d61d7-b03e-4078-9e6c-379fa0208d6f;635243635233730000;33127">
  <Id ChangeType="Delete">1</Id>

This is just about the only place I know of where you can get information about deletes from the client side. Generally the only way to “catch” deletes is to write an event receiver to run on the server. Of course, you’ll only see the deletes since the last time you passed in a token value, but it allows you to reflect those deletes on the client.

If there have been any changes to the list settings (schema), we’ll get the schema again before the changed items. The results in this case look like what we get back from GetList and from the first call above. We receive everything we need to know to work with the list. For this reason, we would generally make a call to GetListItemChangesSinceToken first to get the list schema and all of the list items – at least those that match our filters – and then use either GetListItemChangesSinceToken, GetListItems, or GetListItemChanges for subsequent calls for our SPA.


As you can see, GetListItemsWithToken is incredibly powerful and returns a wealth of information. In fact, I’m not sure that you could get all of this from a REST call, but probably from CSOM. These old, crufty SOAP Web Services still have their place in the world.

Even better, they can provide you with what you need to get going on SPAs of you own. Hopefully you’re starting to see the possibilities.

jQuery Library for SharePoint Web Services (SPServices) 2013.02 Released

Yesterday I released SPServices 2013.02.

Here are the release headlines:

  • Improved compatibility with SharePoint 2013, whether on premises or on Office365, especially in the value-added functions (SPCascadeDropdowns, SPDisplayRelatedInfo, etc.)
  • Better API stability, including fixes for GetListItemChanges, GetListItemChangeSinceToken, and others
  • Bug fixes and performance improvements

While this is primarily a maintenance release, if you are using an earlier version of SPServices, I strongly suggest that you upgrade to this version, as you will see some performance improvements and there is some new functionality. Thanks to everyone who downloaded the beta and provided feedback.

Both releases in 2013 (2013.01 and 2013.02) work equally well – at least as best as I have been able to ensure – with SharePoint 2007, 2010, and 2013. My next planned release version will be 2014.01, which should make it obvious that the primary version number in my recent scheme is just the year of release.

One thing I wanted to mention that’s old news, but some people may have missed it. By far the most exciting thing in the 2013.01 release was jQuery promises, or deferred objects. All calls to the core operations return jQuery promises as of 2013.01. If you’re interested in increasing the efficiency of your code and getting ready for the way you are likely to work with the REST Web Services in SharePoint 2013, you should get familiar with using promises.

There are other goodies in this release, and you can see the full list of enhancements and improvements on the download page. Note the link to the Issue Tracker items for this release. For posterity, here are links to the release notes showing all the fixes and improvements from the Issue Tracker on Codeplex.

New Functionality

Alpha Issue Tracker Item Function Description
ALPHA5 10195 $().SPServices.SPGetQueryString Case insensitive SPGetQueryString?

Bug Fixes and Efficiency

Alpha Issue Tracker Item Function Description
ALPHA1 10152 $().SPServices.SPCascadeDropdown Multi-Select Lookup Cascading Dropdown’s Not Working
ALPHA1 10153 $().SPServices.SPComplexToSimpleDropdown Make SPComplexToSimpleDropdown Work in 2013 Publishing Pages
ALPHA1 10144 $().SPServices.SPXmlToJson userToJsonObject userId broken in 2013.01
ALPHA1 10146 $().SPServices ResolvePrincipals with addToUserInfoList=true requires SOAPAction
ALPHA2 10184 $().SPServices Lists.GetListItemChangeSinceToken operation input parameters not defined
ALPHA2 10183 $().SPServices Webs.GetColumns – Passing Unneeded webUrl Parameter
ALPHA2 10148 $().SPServices async on version 2013.01
ALPHA2 10177 $().SPServices First 2 arguments for the of addToPayload method for the GetTermSets are incorrect
ALPHA2 10154 $().SPServices SPCascadeDropdown: matchOnId not working with multi-select parents
ALPHA3 10165 $().SPServices SPServices() uses wrong server relative URL on SP2010
ALPHA3 10162 $().SPServices.SPRedirectWithID Bug in SPRedirectWithID
ALPHA3 10067 $().SPServices.SPAutocomplete SPAutocomplete dropdown issue
ALPHA3 10167 $().SPServices Caching bug if making same call to different webURLs
ALPHA4 10200 $().SPServices SPConvertDateToISO invalid TZD
ALPHA5 10147 $().SPServices.SPAutocomplete SPServices / jQuery ID Selectors in SP 2013, SPAutoComplete
ALPHA5 10189 $().SPServices.SPGetCurrentUser SPGetCurrentUser not being async
ALPHA5 10189 $().SPServices.SPGetCurrentUser SPGetCurrentUser data on iPhone
BETA2 10206 $().SPServices.SPCascadeDropdowns Multi-Select Dropdown Controls Don’t Have Name Attributes
BETA3 NA $().SPServices.SPDisplayRelatedInfo Fixed issues with lookup column handling