MetaVis SharePoint MVP Webinars Series – Single-Page Applications (SPAs) in SharePoint Using SPServices

Thanks to all who attended the webinar that Dave Coleman (@davecoleman146) and I did today about Single-Page Applications (SPAs) in SharePoint Using SPServices. The webinar was based on my series here of the same name, so if you’re interested in more detail on how I go about building these things, give that a gander. Thanks to Dave for playing the master of ceremonies role as well as typing in English football player names during the demo.

MetaVisMetaVis is great to sponsor the SharePoint MVP Webinars Series, so a big shout out to them as well.

I’ve ZIPped up the most relevant files from the demo and you can download them here. Several alert viewers pointed out some inconsistencies in the code I showed in the demo, so I’ve done some cleanup. Also note that I’ve included a working version of SPServices named jquery.SPServices-2013.02b. In building up the demo, I found a few more lingering bugs in 2013.02a (for which I am sorely ashamed and will do penance), so this working copy contains fixes for those issues. I will release it shortly, but please do not consider it a released version; it is for demo purposes only.

I’ve also saved the subsite from Office365 into a WSP, which you can also download. I used a vanilla Team Site, so hopefully it can be instantiated relatively easily, though I’ve had some issues with feature mismatch between Office365 and on premises installations in the past.

If you’d like to see the slides, they are posted on SlideShare, embedded below, as is a link to the recording.

Again, thanks to all of you who attended and feel free to post any outstanding questions here and I’ll try to get back to you in a timely manner.

 


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

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).

[webURL]

See the GetListItems post in this series.

listName

See the GetListItems post in this series.

viewName

See the GetListItems post in this series.

CAMLQuery

See the GetListItems post in this series.

CAMLViewFields

See the GetListItems post in this series.

CAMLRowLimit

See the GetListItems post in this series.

CAMLQueryOptions

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.

changeToken

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.

contains

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.

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

This parameter can contain null.

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

Note

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:

$().SPServices({
  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://204.144.120.200/,http://www.sympraxisconsulting.com/,http://172.29.4.39/,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">
      <Fields>
        <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">
          <CHOICES>
            <CHOICE>Newspaper Advertising</CHOICE>
            <CHOICE>Web Site</CHOICE>
            <CHOICE>Personal Referral</CHOICE>
          </CHOICES>
        </Field>
        <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">
          <Default>0</Default>
        </Field>
...
      <RegionalSettings>
        <Language>1033</Language>
        <Locale>1033</Locale>
        <AdvanceHijri>0</AdvanceHijri>
        <CalendarType>1</CalendarType>
        <Time24>False</Time24>
        <TimeZone>300</TimeZone>
        <SortOrder>2070</SortOrder>
        <Presence>True</Presence>
      </RegionalSettings>
      <ServerSettings>
        <ServerVersion>12.0.0.6421</ServerVersion>
        <RecycleBinEnabled>True</RecycleBinEnabled>
        <ServerRelativeUrl>/Intranet/JQueryLib</ServerRelativeUrl>
      </ServerSettings>
    </List>
  </Changes>
  <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"/>
...
  </rs:data>
</listitems>

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:

$().SPServices({
  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://204.144.120.200/,http://www.sympraxisconsulting.com/,http://172.29.4.39/,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>
</listitems>

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://204.144.120.200/,http://www.sympraxisconsulting.com/,http://172.29.4.39/,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" />
</rs:data>
</listitems>

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>
</Changes>

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.

Conclusion

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.

Single-Page Applications (SPAs) in SharePoint Using SPServices – Part 3 – GetListItemChanges

In building a Single Page Application (SPA), we’ll usually want to keep the data we’re displaying up to date. You can probably think of many examples where you see this on the Web, but newsfeeds are a prime example. While we’re sitting on the page, we see newly posted content pop up, usually on the top of the feed. To do this, we can simply set up a call to run at fixed intervals using JavaScript’s timing functions setTimeout() or setInterval() to pull back the data.

If the content we want is in a list, it’s “expensive” to request all of the items at each interval. Instead, it’s much better to either cache the items if they rarely change – as is available in SPServices 0.7.2+, though the approach in 2013.01 is much improved – or to request only those items which have changed since the last request.

There are two operations which can help with this: GetListItemChanges and GetListItemChangesWithToken.

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

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

GetListItemChanges

GetListItemChanges retrieves changes to the list since a specified date/time. The parameters are similar to those for GetListItems, with a few additions:

[webURL]

See the GetListItems post in this series.

listName

See the GetListItems post in this series.

viewFields

See the GetListItems post in this series.

since

A date/time specified in Coordinated Universal Time (UTC) ISO8601 format. What that means is that the date/time has to look like “2013-06-30T15:29:43Z”. That’s “YYYY-MM-DDThh:mm:ssZ”. The “Z” means “Zulu” time, or GMT. You can also provide time offsets, like “2013-06-30T15:29:43-05:00” for Eastern (US) Time.

Because I’m a nice guy, I have a function in SPServices called SPConvertDateToISO to convert JavaScript date/time objects to the ISO8601 format.

contains

The contains parameter is much like the CAMLQuery parameter in GetListItems, but a little simpler. For instance, you may only be interested in list changes where the current user is the Author, in which case you’d pass:

contains: "<Eq><FieldRef Name='Author'/><Value Type='Integer'><UserID/></Value></Eq>",

If you don’t pass anything for contains, you’ll get back all of the changes since since.

Because the GetListItemChanges operation retrieves only changes in the list, the requests will return very slim results in all but the most active lists. However, the operation will prove valuable in our SPA development because it will allow us to keep our display current for the user with a minimum of fuss unless there have been changes to the underlying data.

GetListItemChanges Returns

When Paul Tavares (@paul_tavares) pointed out the GetListItemChangesWithToken operation a while back, I recalled that I never could get it running properly in my test environment back when I first wrapped it in SPServices. I wrote it off to either my own ineptitude or simply a bug on the Microsoft side (that wouldn’t be a first). At the time, it didn’t seem as though the operation offered enough benefit to chase it down any further.

I was wrong on several counts, of course. The bit about my ineptitude was the one part I was right about. When I wrapped the operation, I accepted two things about it that the documentation told me:

changeToken

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.

contains

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.

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

This parameter can contain null.

The italicized, maroon parts were the issue. While it’s probably possible to pass null on the server side, there’s no such analogous value on the client that we can pass. Since everything we pass is text, generally an empty string will work in those operations where null is allowable. But not in this case.

This means that in all versions of SPServices until the alpha I’ve currently got posted, the operations GetListItemsChanges and GetListItemsChangesWithToken won’t work, no matter how hard you try. That’s because I passed empty strings for the elements above, which just throws an error.

All that said, GetListItemsChanges returns XML that looks the same as what GetListItems gives us.

<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"
   xmlns="http://schemas.microsoft.com/sharepoint/soap/"
   TimeStamp="2013-11-14T16:30:36Z">
   <rs:data ItemCount="4">
      <z:row ows_Number_Field="6555.00000000000"
         ows_Created="2003-06-18T03:41:09Z"
         ows_ID="3" ows_owshiddenversion="3" />
      <z:row ows_Number_Field="78905456.0000000"
         ows_Created="2003-06-18T17:15:58Z"
         ows_ID="4" ows_owshiddenversion="2" />
         ...
   </rs:data>
</listitems>

There’s one subtle difference. Can you spot it? GetListItemChanges gives us one additional piece of data with the results, and that’s the TimeStamp value. This date/time value tells us when we requested the data, so we can use that value to inform the user when the data was last updated, should we want to.

Processing GetListItems Results

Processing the results from GetListItemChanges is just about the same as with GetListItems.

var out = "<ul>";
$().SPServices({
  operation: "GetListItemChanges",
  since: "2013-11-14T11:00:00-5:00",
  async: false,
  listName: "Announcements",
  CAMLRowLimit: 0,
  CAMLViewFields: "<ViewFields><FieldRef Name='Title' /></ViewFields>",
  completefunc: function (xData, Status) {
    var timeStamp = $(xData.responseXML).SPFilterNode("listitems").attr("TimeStamp");
    var itemCount = $(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount");
    $(xData.responseXML).SPFilterNode("z:row").each(function() {
      out += "<li>" + $(this).attr("ows_Title") + "</li>";
    });
    out += "</ul>";
    $("#announcementsContainer").html(out);
  }
});

Here I’m making the call to GetListItemChanges to get all of the items in the Announcements list which have changed since 11am this morning in Boston.

Conclusion

GetListItemChanges is a great addition to our toolkit because it allows us to make requests for items in a list that have changed since a time we specify. While we could accomplish something similar with GetListItems by passing in a filter for Modified in the CAMLQuery, GetListItemChanges is built for exactly what we need, and I would hope that it is therefore more efficient on the server side.
is a workhorse, all right, but we need more for our SPA work. In the next three parts of the series, I’ll introduce its siblings: GetListItemChanges, GetListItemChangesWithToken, and UpdateListItems. These four operations together will help us to build a highly interactive SPA and provide valuable functionality for our users.

Single-Page Applications (SPAs) in SharePoint Using SPServices – Part 2 – GetListItems

As I mentioned in the first part of the series, we have several workhorse operations at our disposal in the SOAP Web Services with which we can build our Single-page Applications (SPAs). (Of course, which API you use is somewhat unimportant. All of the techniques here should work using REST or CSOM, too. I’m just choosing to focus on SPServices and the SOAP Web Services.) There are many more SOAP operations that can prove useful, of course, but at first we’ll focus on the key set below which help us get data out of or push data into SharePoint lists and libraries.

While each of these operations is documented on the SPServices Codeplex site, I’m going to repeat some of that documentation here for anyone who is learning about them for the first time.

[important]GetListItemChanges and GetListItemChangesWithToken do not work in versions of SPServices before 2013.02. I’ll talk more about this in the next part in the series.[/important]

First, let’s review the primary workhorse operation, GetListItems.

GetListItems

GetListItems is generally the first operation that people try out when they start working with SPServices. It does exactly what its name implies: it enables you to get items from a list. In this sense, lists and libraries are the same thing. All useful SharePoint content that is available through the UI is stored in lists, even much of the configuration data that we set up. Document Libraries are lists. Publishing pages are are in lists, too. They just happen to be in a special flavor of Document Library, which is a list, called Pages.

GetListItems takes a set of parameters that makes it extremely powerful. If you can imagine a way to pull data from a list, you can probably do it, or get very close and then process the data client side into the form you need.

What this operation doesn’t do for you is any sort of aggregation. Once you have the data client side, you can work on aggregation from there in your code. This means that it’s important to carefully think about the data volume you might be pulling from the list and whether munging through it on the client side is a good idea or not based on what you need to accomplish.

The available parameters are listed below.

[webURL]

If you’d like to get content from a list in a different site than the current one (a different context), this parameter is what you need. You can specify any valid path. I recommend relative paths wherever possible so that you don’t run into cross site scripting issues moving from one environment to another.

listName

You must specify a listName at the bare minimum, as this tells the Web service where to get the content for you. You can provide either the list’s GUID (“{3bf9d2f3-78ec-4e92-a4c0-7c5f7ed51755}”) or the list’s name (“Announcements”). Note also that if you use the GUID, you do not need to specify the webURL if the list is in another site.

viewName

By default, GetListItems retrieves the items which are displayed in the default view for the list or library. If you specify a view’s GUID for the viewName parameter, you can retrieve the items shown in any existing view. In most cases, I’d recommend specifying what you’d like to retrieve using the CAML parameters below, as views can be easily changed through the UI and you may not retrieve what you’d expect.

CAMLViewFields

In the CAMLViewFields, you specify which list columns you’d like to retrieve. By default, you’ll get the columns defined in the default view. In many cases, you’ll want to request different columns, and you should also request as few columns as possible in order to reduce the number of bytes coming across the wire.

As an example, this is what you would specify in the CAMLViewFields to retrieve the Title and Created By (Author) columns.

CAMLViewFields: "<ViewFields><FieldRef Name='Title' /><FieldRef Name='Author' /></ViewFields>",

The column names should be the InternalNames for the columns, which might look like “My_x0020_Column”, as special characters like spaces are encoded. In this case, the space becomes “_x0020_” because a space is ASCII character 20 (hexadecimal). See http://www.asciitable.com/ for more info.

The easiest way to identify the InternalName for a column is to go to the List [or Library] Settings, click on the column name, and look on the end of the URL, which will end with something like this in SharePoint 2007:

/_layouts/FldEditEx.aspx?List={37920121-19B2-4C77-92FF-8B3E07853114}&Field=Sales_x0020_Rep

or this in SharePoint 2013:

/_layouts/15/FldEdit.aspx?List={F3641DF3-80A2-4BBA-A753-E6BFB3FD98E4}&Field=ImageCreateDate

Your column’s InternalName is at the end of the URL after “Field=”.

Note that regardless what you specify in the CAMLViewFields, you will get some additional columns even though you don’t want them, including the item’s ID.

CAMLQuery

In CAMLQuery, you specify any filters or sorting you’d like to apply to the results. Yes, it’s CAML, and no, most people don’t like CAML much. I find it great because it’s an easy language to build up programmatically in SPServices, but that’s me.

The syntax for the CAMLQuerycan be complicated, but CAML is well-documented. Here’s a simple example:

CAMLQuery: "<Query><Where><Contains><FieldRef Name='Title'/><Value Type='Text'>and</Value></Contains></Where><OrderBy><FieldRef Name='Title'/></OrderBy></Query>",

This CAMLQuery will retrieve items which contain “and” in their Title and sort those items by Title.

CAMLRowLimit

CAMLRowLimit allows you to specify the number of items you would like to retrieve. You can specify any positive integer.

CAMLRowLimit: 10,

CAMLQueryOptions

There are a number of CAMLQueryOptions, some of which work more as advertised than others. I’ll leave it as an exercise for the reader to read up on the available options in the MSDN documentation. Note that the options listed on the GetListItems page are not complete; see the GetListItemChangesSinceToken documentation for a more complete list.

Notes for GetListItems

When I first started building SPServices, I named the parameters [CAMLViewFields, CAMLQuery, CAMLRowLimit, CAMLQueryOptions] differently than the actual parameter names, which are [viewFields, query, rowLimit, queryOptions]. I’ve since set it up inside SPServices so that you can use either name, but most people continue to use the naming I came up with originally with the CAML prefix.

If you specify any of the parameters [CAMLViewFields, CAMLQuery, CAMLRowLimit, CAMLQueryOptions] explicitly, you will override the default view. One trick I use frequently to force the override is to just pass 0 for the CAMLRowLimit, which tells the GetListItems to return all items. I’ve seen quite a few other people pass in a CAMLQuery like:

<Query><Where><Gt><FieldRef Name='ID'/><Value Type='Counter'>0</Value></Gt></Where></Query>

This says to retrieve all items where the ID is greater than zero, which is true for all items since IDs start at one and increase from there. That seems like more code than “0” to me, though.

GetListItems supports paging, though I rarely take advantage of it. I find that it often makes more sense to request all of the relevant items and do the paging on the client side. This makes sense because the more “expensive” action is the request for data from the server. However, if you are requesting items from large lists and may not need the majority of them, be sure to use tight filters or paging for efficiency.

GetListItems Returns

The GetListItems operation returns XML, as do all of the SOAP Web Services operations. The results take the following form:

<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"
   xmlns="http://schemas.microsoft.com/sharepoint/soap/">
   <rs:data ItemCount="4">
      <z:row ows_Number_Field="6555.00000000000"
         ows_Created="2003-06-18T03:41:09Z"
         ows_ID="3" ows_owshiddenversion="3" />
      <z:row ows_Number_Field="78905456.0000000"
         ows_Created="2003-06-18T17:15:58Z"
         ows_ID="4" ows_owshiddenversion="2" />
         ...
   </rs:data>
</listitems>

There are some important things to note about the results:

  • There is an rs:data element which contains an ItemCount element, telling us how many items have been returned
  • Each of the returned items is contained in a z:row element
  • Each item’s columns are provided as attributes on the z:row elements
  • All column names are preceded with the prefix “ows_”
  • All values are text (everything in both directions is text)

Processing GetListItems Results

Processing the results from GetListItems is fairly straightforward, but there are a few nuances. Assuming you have a container in the page with its id set to announcementsContainer, the following code will create an unordered list (bullets) showing the titles of each announcement in the Announcements list.

var out = "<ul>";
$().SPServices({
  operation: "GetListItems",
  async: false,
  listName: "Announcements",
  CAMLRowLimit: 0,
  CAMLViewFields: "<ViewFields><FieldRef Name='Title' /></ViewFields>",
  completefunc: function (xData, Status) {
    var itemCount = $(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount");
    $(xData.responseXML).SPFilterNode("z:row").each(function() {
      out += "<li>" + $(this).attr("ows_Title") + "</li>";
    });
    out += "</ul>";
    $("#announcementsContainer").html(out);
  }
});

Here I’m making the call to GetListItems to get all of the items in the Announcements list. (By passing a CAMLRowLimit of zero, I’m requesting all items.) When the results come back, I’m iterating through all of the items (the z:row elements), building up my out string to contain the list bullets. Finally, I set the innerHTML of the announcementsContainer to what I’ve built up in the out variable.

Conclusion

GetListItems is a workhorse, all right, but we need more for our SPA work. In the next three parts of the series, I’ll introduce its siblings: GetListItemChanges, GetListItemChangesWithToken, and UpdateListItems. These four operations together will help us to build a highly interactive SPA and provide valuable functionality for our users.

Single-Page Applications (SPAs) in SharePoint Using SPServices – Part 1 – Introduction

Single-page applications (SPAs) are nothing new on the Web. However, like Responsive Web Design (RWD), SPAs are gaining favor as a way to enable real work with an improved user experience (UX).

From the Wikipedia definition of SPA:

A single-page application (SPA), also known as single-page interface (SPI), is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience akin to a desktop application.

SPAs first started popping up regularly – at least based on my experience – in creative contexts on public Web sites. A post called 40 Inspiring Single Page Websites from Gisele Muller shows some great examples of the SPA approach. Most of the sites Gisele highlights are marketing sites: they are primarily publishing content in a one-to-many context. Also note that the date of her post is early 2010. These sites have been out there for a while and are becoming more prevalent.

SPA Sketch

SPA concept sketch image from viaboxx systems at http://viaboxxsystems.de/event-driven-gui

We see many more examples of SPAs all the time, whether we think about them this way or not. The most obvious and prevalent examples are the Facebook and Yammer News Feeds. In the News Feed examples, we tend to stay on that single page until we see something scroll by or some content change that makes us want to drill into the details, taking us away from the SPA intentionally based on an action – usually a mouse click – we take. Other full-scale applications that enable us to do real work, like Trello, are good examples of the SPA concept. We also see the SPA idea in apps on our phones, phablets, tablets, and even showing up in Windows 8 world. In other words, this is the wave of the future for apps.

Since the idea with SPAs is that one need not leave the single page to accomplish some high percentage of the tasks at hand, it’s a great concept to apply in a SharePoint context. With SharePoint, we are trying to fundamentally change the way people work, moving from a hierarchical to a more collaborative work style. While this work evolution has been progressing well over the last dozen or so years of SharePoint’s existence, SharePoint itself has changed a lot, as should the way we consider building our applications within it.

The days where a clunky postback-ridden application was acceptable is fading into the rear view mirror. The SharePoint Product Group has taken steps toward giving SharePoint itself an SPA “feel” with the introduction of the Minimal Download Strategy (MDS) in SharePoint 2013. See Wictor Wilén‘s (@wictor) excellent post SharePoint 2013 – Introduction to the Minimal Download Strategy (MDS) for details on how MDS works. MDS doesn’t give us a true SPA per se, but the goals for MDS get us a much more fluid, SPA feel for SharePoint.

But what if you are using older versions of SharePoint? Maybe 2007 or 2010? Or even 2003? (Yes, I still run into a few people using 2003 from time to time.) Well, there’s no need to despair, because the tooling to create SPAs is there for you to use anytime you want it. With AJAX available for use as the data transport mechanism, we can fetch content from the SharePoint server anytime we choose without a page refresh. In the past, I’ve taken various different approaches to this, like Refreshing a Page Section with a User-selected Interval Set with jQueryUI’s Slider or Refreshing a Web Part Without a Postback Using jQuery’s AJAX Function. You’ll also see a few SPA examples in my SPServices Stories series, like SPServices Stories #13: Durandal SP3: Developing SharePoint SPAs Made Easy from Rainer Wittman (@RainerAtSpirit).

My friend and SPServices co-conspirator, Paul Tavares (@paul_tavares), and I have been discussing several Lists Web Services operations of late that I’d never paid much attention to: GetListItemChanges and GetListItemChangesSinceToken. Together with the simpler GetListItems – the true workhorse of the SPServices toolkit – we have three variations on a theme: fetching content from a list. Using those three “get” operations, along with the UpdateListItems operation to write changes back to the list, we can build our own SPAs in SharePoint.

We could choose to use some of the fancy frameworks out there, but in this series, I’m going to keep it simple. I’m going to limit myself to jQuery and SPServices only to see what I can come up with. (I may sprinkle in a little fairy dust using jQueryUI for fun.) While frameworks like Knockout or AngularJS can be extremely helpful tools, I find that in many cases simplicity works better for me – it keeps me closer to the data and lets me control what goes on. I also think that I can keep the examples more straightforward by avoiding other plugins.

Let’s get to work…