Quantcast
Channel: Marc D Anderson's Blog » jQuery library for SharePoint Web Services
Viewing all 109 articles
Browse latest View live

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

$
0
0

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 the current alpha: 2013.02ALPHA3. I’ll talk more about this in the next part in the series.

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.


SPServices Passes 100,000 Total Downloads

$
0
0

Here are some numbers for the meaningless-but-fun-statistics bin. Codeplex collects a bunch of numbers for me, so I can preserve them here.

Yesterday, Sunday, October 27th, 2013, SPServices crossed the 100,000 total downloads threshold. I posted the first version of SPServices (SPServices 0.2.3) on Codeplex on Aug 19th, 2009 as an alpha. That means it’s taken 4 years, 2 months, and 9 days – that’s 1531 days – to reach 100,000 downloads. That’s over 65 downloads per day!

SPServices 100,000 DownloadsDuring that same period, SPServices has also had almost 3,000,000 page views – 2,945,339 to be exact. I like to think that this number is a testament to the quality of the documentation.

SPServices 3,000,000 Page ViewsThere have been over 3/4 million visits to the Codeplex site.

SPServices 3/4 Million VisitsIt’s been a fun 4 years, 2 months, and 9 days working on and supporting SPServices. I doubt it’s got another 4 years in it, but I look forward to many more months and downloads to come.

Yes, I do make it up in volume.

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

$
0
0

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 the current alpha: 2013.02ALPHA3+.

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.

From SharePoint Magazine: Variations in Multiselect Controls in Different SharePoint Language Versions

$
0
0
This post originally appeared in SharePoint Magazine on December 23, 2010. SharePoint Magazine

Consistency in one’s development practices is a critical part of generating reliable, maintainable code. Most large software contains some inconsistencies, but in your development efforts with SharePoint, you should try your best not to introduce any inconsistencies of your own.

In this article, I’ll show you the effect of one of SharePoint’s little inconsistencies and why it was an issue in my development of SPServices.

If you’ve ever tried to implement jQuery in multi-language SharePoint solutions, you really need to pay attention.

Recently, I had to fix something in SPServices for someone who was using the German version of SharePoint 2010. The issue was with the way some Document Object Model (DOM) elements are identified in the German version. In fact, I’ve run into these inconsistencies before. These inconsistencies can add up to larger bugs not just for people like me who are trying to understand the DOM to make script work, but they probably contribute to issues that arise from installing service packs, hotfixes, and so on.

Whomever did the translations internally to some of the different languages wasn’t consistent in how they made some of the changes to things such as DOM element IDs. The specific case here is with multiselect columns. This inconsistency is the same whether you’re working with SharePoint 2007 or SharePoint 2010.

Anatomy of a Multiselect Column’s Markup

When SharePoint renders the rather complex controls for a multi-select column, it looks something like the image shown here. This is an example from a SharePoint 2010 installation, but it looks basically the same as it does in SharePoint 2007. The control is also the same whether you’re working with a NewForm or an EditForm.

image_thumb4The City column is a multiselect Lookup column. This seemingly simple type of column actually consists of many different DOM elements as well as some fairly complicated underlying JavaScript. (Yes, Virginia, SharePoint uses a lot of JavaScript right out of the box.)

Here is a snapshot of the DOM for the control at a high level:

SNAGHTML4bbbec4_thumbLet’s break that markup down into its components to see how it works:

SNAGHTML4bb3451_thumbBefore we even get to anything visual, there is a set of hidden INPUT elements that the scripts use to manage things. Each of the three INPUT elements have long IDs preceded by [long GUID-based stuff] and ending with the names in the following table:

MultiLookupPicker Used to store the currently selected set of values, using the odd separator ‘|t’, as in ’1|tAbington|t2|tActon|t3|tAcushnet’.
MultiLookupPicker_data Stores the full set of allowable values, like this ’1|tAbington|t |t |t2|tActon|t |t |t3|tAcushnet|t |t ‘ (and so on). It’s not clear why there are extra separators in this one.
MultiLookupPicker_initial Stores the initial value of the column as of page load.

Next comes a table that contains the visual elements of the control:

image_thumb5The left side of the control is a SELECT that has some script attached to its dblclick and change events. The important thing we need to know about this SELECT element is its title. In an English installation, the title=”City possible values”. It’s the column name followed by ‘ possible values’.

<TD class=ms-input>
  <SELECT style="WIDTH: 143px; HEIGHT: 125px; OVERFLOW: scroll" id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_SelectCandidate title="City possible values" ondblclick="GipAddSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" multiple onchange=GipSelectCandidateItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); name=ctl00$m$g_856b69b7_94a3_499a_af52_e1789266b73a$ctl00$ctl05$ctl02$ctl00$ctl00$ctl04$ctl00$ctl00$SelectCandidate>
    <OPTION title=Abington selected value=1>Abington</OPTION>
    <OPTION title=Acton value=2>Acton</OPTION>
    <OPTION title=Acushnet value=3>Acushnet</OPTION>
    ...
    <OPTION title=Wrentham value=350>Wrentham</OPTION>
    <OPTION title=Yarmouth value=351>Yarmouth</OPTION>
  </SELECT>
</TD>

Next comes a 10-pixel-wide spacer TD. It’s not very interesting, but I mention it for completeness. Usually you’d probably do spacing like this in the CSS rather than having a dedicated TD for it.

<td style="padding-left: 10px;"/>

image_thumb6

Next come the two buttons. Both buttons have some script attached to their click event that manages the movement of the values between the left and right boxes.

<TD class=ms-input vAlign=center align=middle>
  <BUTTON id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_AddButton class=ms-ButtonHeightWidth onclick="GipAddSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" type=submit>Add &gt;</BUTTON>
  <BR>
  <BR>
  <BUTTON id=ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_RemoveButton class=ms-ButtonHeightWidth disabled onclick="GipRemoveSelectedItems(ctl00_m_g_856b69b7_94a3_499a_af52_e1789266b73a_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" type=submit>&lt; Remove</BUTTON>
</TD>

image_thumb7

Now another 10-pixel-wide spacer TD:

image_thumb8

And finally, we have the right side box. Like the left side, this is also a SELECT with some script attached to its dblclick and change events. The important thing we need to know about this SELECT element to work with it is its title. In an English installation, title=”City selected values”. It’s the column name followed by ‘ selected values’.

<TD class=ms-input>
  <SELECT style="WIDTH: 143px; HEIGHT: 125px; OVERFLOW: scroll" id=ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_SelectResult title="City selected values" ondblclick="GipRemoveSelectedItems(ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); return false" multiple onchange=GipSelectResultItems(ctl00_m_g_517395ec_085b_47db_b888_b55773e76d86_ctl00_ctl05_ctl02_ctl00_ctl00_ctl04_ctl00_ctl00_MultiLookupPicker_m); name=ctl00$m$g_517395ec_085b_47db_b888_b55773e76d86$ctl00$ctl05$ctl02$ctl00$ctl00$ctl04$ctl00$ctl00$SelectResult>
    <OPTION title=Abington value=1>Abington</OPTION>
    <OPTION title=Acton value=2>Acton</OPTION>
    <OPTION title=Acushnet value=3>Acushnet</OPTION>
  </SELECT>
</TD>

image_thumb9

There’s a lot more to this simple little thing that you might have expected, eh? And I haven’t even gone into how the underlying script works. In fact, that’s so complicated that I’m going to leave it for another time.

The Inconsistency

But back to the whole point of this article, which is the inconsistency across language versions of SharePoint. The key is to be able to find the important piece parts of the control in the DOM with SPServices (in my case) and be sure that we are working with the right one if there are multiple multiselect columns in the form. To do this, I have a function in SPServices called dropdownCtl. Its little purpose in life is to find a “dropdown” and return an object reference to it in the DOM as well as its “Type”. The functions SPCascadeDropdowns, SPDisplayRelatedInfo, and SPLookupAddNew in SPServices all use the dropdownCtl function to find the right elements in the DOM to work with.

One other tricky part is that the are three types of “dropdowns” we can have in the form. (I’m using “dropdowns” in quotes because it’s a little bit of a stretch to call the multiselect control a dropdown.) Each type has different representations in the DOM elements, so dropdownCtl needs to find the right element and also decide which type of element it is.

The first two types are what you usually would think about as dropdowns, and they are easier to find in the DOM. They are either a “simple SELECT,” which is rendered if there are fewer than 20 options for the column, or a “complex SELECT,” which SharePoint uses if there are 20 or more options. The “complex SELECT” is actually a hybrid INPUT/SELECT with some script to drive it.

In the case of a “multiselect dropdown,” it’s a little trickier. The best way to find the various elements in the page for a “multiselect dropdown” is to look for the SELECTs’ titles. This is where the translation differences come into play.

It’s understandable (perhaps) that the English titles should be translated into the other languages. What doesn’t make sense is that the format of those translations is quite different. Let’s look at what the City column’s right box would have as its title in three different languages:

English City selected values
Russian Выбранных значений: City
(this is for the possible values, but the point is the same)
German Ausgewählte Werte für “City”

As you can see, the pattern of the text varies, not just the words themselves. I don’t speak Russian or German, but it’s hard to imagine that it’s necessary to change the construct from ‘City selected values’ to ‘Selected values: City’ or ‘Selected values for “City”‘ in order to reflect some sort of nationalistic thinking.

In my code, I don’t want to care about what language is being used; I just want to be able to find the right title. By using different patterns, SharePoint makes this more difficult than it should be.

The Solution

Because of the differences between the titles in the different language versions of SharePoint, in my dropdownCtl function I have to have these three conditions:

// Multi-select: This will find the multi-select column control in English and most other languages sites where the Title looks like 'Column Name possible values'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).html() != null) {
  this.Type = "M";
  // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).html() != null) {
  this.Type = "M";
  // Multi-select: This will find the multi-select column control on a German site (and perhaps others) where the Title looks like 'Mögliche Werte für &amp;quot;Tops&amp;quot;.'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$='\"" + colName + "\".']")).html() != null) {
  this.Type = "M";

Obviously, I can handle this in my code, but every time I hear from someone who uses a different language version of SharePoint and sees a bug, I need to revisit what the pattern of the text is in that language.

The Moral of the Story

The moral of the story is that seemingly tiny decisions can actually make more significant differences. Consider very carefully why you want to change the style of something like an ID or markup that you’re rendering in a control because there can be ramifications later. We all want to leave a little bit of our personal style in our code, but try not to do so at the expense of reusability, interoperability, or consistency.


Postscript

Since I wrote this article back in 2010, I’ve made adjustments to handle additional languages. I’ve added Italian and I’ve recently been asked for a fix for Portuguese. Should you find another inconsistency, please let me know by posting in the SPServices Discussions on Codeplex.

Here’s what my DropdownCtl function looks like in version 2013.01:

// Find a dropdown (or multi-select) in the DOM. Returns the dropdown onject and its type:
// S = Simple (select);C = Compound (input + select hybrid);M = Multi-select (select hybrid)
function DropdownCtl(colName) {
  // Simple
  if ((this.Obj = $("select[Title='" + colName + "']")).length === 1) {
    this.Type = "S";
    // Compound
  } else if ((this.Obj = $("input[Title='" + colName + "']")).length === 1) {
    this.Type = "C";
    // Multi-select: This will find the multi-select column control in English and most other languages sites where the Title looks like 'Column Name possible values'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a German site (and perhaps others) where the Title looks like 'Mögliche Werte für &quot;Column name&quot;.'
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$='\"" + colName + "\".']")).length === 1) {
    this.Type = "M";
    // Multi-select: This will find the multi-select column control on a Italian site (and perhaps others) where the Title looks like "Valori possibili Column name"
  } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=' " + colName + "']")).length === 1) {
    this.Type = "M";
  } else {
    this.Type = null;
  }
} // End of function DropdownCtl

jQuery Library for SharePoint Web Services (SPServices) 2013.02 Released

$
0
0

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

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

$
0
0

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.

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

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

$
0
0

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.

Office 365 Update Changes ‘Display Name’ on Required Fields

$
0
0

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

<select id="Region_59566f6f-1c3b-4efb-9b7b-6dbc35fe3b0a_$LookupField" title="Region">

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

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

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:

<select id="Region_59566f6f-1c3b-4efb-9b7b-6dbc35fe3b0a_$LookupField" title="Region Required Field">

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 = $("select[Title='Region'], select[Title='Region Required Field']");

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]

[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]


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

$
0
0
This entry is part 18 of 19 in the series SPServices Stories

Introduction

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

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

Logic

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

$(document).ready(function(){
  var loadingNotifyId = SP.UI.Notify.addNotification('Pepping up folders ...', false);
  setTimeout('pepUpFolders();',1100);
});

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

        switch(_spPageContextInfo.currentLanguage)
        {
          case 1031:
          folderPrefix = "";//"Ordner: ";
          break;

          case 1033:
            folderPrefix = "";//"Folder: ";
            break;
        }*/

        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
            $().SPServices({
                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>";

                $().SPServices({
                    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.

Finding a Task’s Status Using GetListItems and SPServices in SharePoint 2013

$
0
0

I get interesting questions all this time. This one came from a client who was trying to use GetListItems with SPServices in SharePoint .

Hey Marc, I’m trying to do a GetListItems operation on tasks list and I’m having trouble with the “Task Status” column. This is SP2013.

var thisTaskStatus = $.trim($(this).attr("Status"));
var thisTaskStatus = $.trim($(this).attr("ows_Status"));
var thisTaskStatus = $.trim($(this).attr("ows_Task_x0020_Status"));

None of the above work and I went into the edit column settings of the task list and clicked on the status column and it says the field name is “Status”. When I look at the XML returned using Firebug, a lot of other fields show up like Title, Due Date, Created By, etc. but I don’t see the status column showing up at all in the responseXML.

Many of the out of the box columns have rather obscure StaticNames. In a Tasks list, the column which has the “Status” DisplayName has a StaticName of “Status” as well, so that’s pretty simple. Here’s the field definition, which I grabbed by calling GetList on my own Tasks list:

<Field Type="Choice" ID="{c15b34c3-ce7d-490a-b133-3f4de8801b76}" Name="Status" DisplayName="Task Status" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="Status" ColName="nvarchar4">
  <CHOICES>
   <CHOICE>Not Started</CHOICE>
   <CHOICE>In Progress</CHOICE>
   <CHOICE>Completed</CHOICE>
   <CHOICE>Deferred</CHOICE>
   <CHOICE>Waiting on someone else</CHOICE>
  </CHOICES>
  <MAPPINGS>
    <MAPPING Value="1">Not Started</MAPPING>
    <MAPPING Value="2">In Progress</MAPPING>
    <MAPPING Value="3">Completed</MAPPING>
    <MAPPING Value="4">Deferred</MAPPING>
    <MAPPING Value="5">Waiting on someone else</MAPPING>
  </MAPPINGS>
  <Default>Not Started</Default>
</Field>

Calling GetList to take a look at the list schema is a good way to keep yourself from going crazy.

Now that said, you don’t always get all of the columns for a list when you call GetListItems. By default, you get the columns which are shown in the default view.

In an out of the box Tasks list that I just created, when I make this call:

$().SPServices({
  operation: "GetListItems",
    listName: "Tasks"
});

Status isn’t returned because it’s not in the default view.

<z:row
  ows_Checkmark="boolean;#0"
  ows_LinkTitle="boo"
  ows_AssignedTo=""
  ows_PercentComplete="0.500000000000000"
  ows__ModerationStatus="0"
  ows__Level="1"
  ows_Title="boo"
  ows_ID="1"
  ows_UniqueId="1;#{CC125F6A-F329-46D7-8140-F9353F1AC7EC}"
  ows_owshiddenversion="1"
  ows_FSObjType="1;#0"
  ows_Created_x0020_Date="1;#2014-01-29 13:04:43"
  ows_Created="2014-01-29 13:04:43"
  ows_FileLeafRef="1;#1_.000"
  ows_PermMask="0x7fffffffffffffff"
  ows_Modified="2014-01-29 13:04:43"
  ows_FileRef="1;#sites/Demos2013/jquerylib/Lists/Tasks/1_.000">
</z:row>

Instead, you can add the CAMLViewFields option to request it:

$().SPServices({
  operation: "GetListItems",
    listName: "Tasks",
    CAMLViewFields: "<ViewFields><FieldRef Name='Status'/></ViewFields>"
});

Then I get:

<z:row
  ows_Status="Deferred" 
  ows_MetaInfo="1;#" 
  ows__ModerationStatus="0" 
  ows__Level="1"
  ows_Title="boo" 
  ows_ID="1" 
  ows_UniqueId="1;#{CC125F6A-F329-46D7-8140-F9353F1AC7EC}" 
  ows_owshiddenversion="2" 
  ows_FSObjType="1;#0" 
  ows_Created="2014-01-29 13:04:43" 
  ows_PermMask="0x7fffffffffffffff" 
  ows_Modified="2014-01-29 13:11:21" 
  ows_FileRef="1;#sites/Demos2013/jquerylib/Lists/Tasks/1_.000">
</z:row>

Being explicit about the columns you want to retrieve is always a good practice. People can change the default view easily.

SPServices Stories #20 – Modify User Profile Properties on SharePoint Online 2013 using SPServices

$
0
0
This entry is part 19 of 19 in the series SPServices Stories

Introduction

Sometimes people ask me why I’m still bothering with the crufty old SOAP Web Services in SPServices. After all, there are REST and CSOM to play with and Microsoft has decided to deprecate the SOAP Web Services.

Well in some cases, the Shiny New Toys don’t let you get the job done. In cases where you’re implementing on Office365 and simply can’t deploy server side code, SPServices can sometimes be just the right tool. It’s easy to use and it gets stuff done that you need. What more can I say?

I found this nice story from Gary Arora about updating User Profile data a few weeks back. In it, Gary shows us how to easily make those updates on Office365 using SPServices. Gary calls himself “your friendly neighborhood SharePointMan” and he is happy to be have his story be one of my stories.

The Use Case

SharePoint 2013 users need to modify (specific) user-profile-properties client-side without having to navigate away to their ‘MySite’ site and swift through rows of user properties.
(Following is an mock-up showing a simple interface to update a user profile property via CEWP)

Modify_User_Profile_Properties_SharePoint

Simple interface to update Fax number from a CEWP. (Basic demo)

The Usual Solution

In the SharePoint 2013 universe there are 2 ways to read/write data client-side. CSOM and REST. Unfortunately CSOM and REST are not fully there yet when it comes to matching the server side functionality.

In this specific case, one could use CSOM or REST to retrieve (read) User Profile Properties but there is no way to modify (update) these properties from client-side. Here’s Microsoft’s official position.

Not all functionality that you find in the Microsoft.Office.Server.UserProfiles assembly is available from client APIs. For example, you have to use the server object model to create or change user profiles because they’re read-only from client APIs (except the user profile picture)

Hence The Dirty Workaround

So our workaround is SOAP, the forgotten granddaddy of Web services. The User Profile Service web service, from the SharePoint 2007 days, has a method called ModifyUserPropertyByAccountName which functions exactly as it sounds. But since SOAP can be a bit intimidating & ugly to write, we’ll use SPServices, ”a jQuery library which abstracts SharePoint’s Web Services and makes them easier to use”

So here’s how we’ll use SPServices to modify User Profile Properties. The method is applicable to SharePoint 2013 & 2010 (online & on-prem) versions.

1. Reference SPServices library

You have two options here. You can either download the SPService library and reference it locally or reference it from its CDN:

<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2013.01/jquery.SPServices-2013.01.min.js"></script>

2. Create updateUserProfile() Function

The following function simulates the ModifyUserPropertyByAccountName method on the User Profile Service web service.

function updateUserProfile(userId, propertyName, propertyValue) {

  var propertyData = "<PropertyData>" +
  "<IsPrivacyChanged>false</IsPrivacyChanged>" +
  "<IsValueChanged>true</IsValueChanged>" +
  "<Name>" + propertyName + "</Name>" +
  "<Privacy>NotSet</Privacy>" +
  "<Values><ValueData><Value xsi:type=\"xsd:string\">" + propertyValue + "</Value></ValueData></Values>" +
  "</PropertyData>";
  
  $().SPServices({
    operation: "ModifyUserPropertyByAccountName",
    async: false,
    webURL: "/",
    accountName: userId,
    newData: propertyData,
    completefunc: function (xData, Status) {
      var result = $(xData.responseXML);
    }
  });

}

3. Invoke updateUserProfile() Function

This function takes 3 parameters.

  • userId: Your userID. The format is “domain\userId” for on-prem and “i:0#.f|membership|<federated ID>” for SharePoint Online.
  • propertyName: The user profile property that needs to be changed
  • propertyValue: The new user profile property value

Example:

updateUserProfile(
  "i:0#.f|membership|garya@aroragary.onmicrosoft.com",
  "Fax", "555 555 5555");

Note: The above code works but notice that you are passing a hardcoded userId.

To pass the current userId dynamically, we can use CSOM’s get_currentUser(). But since that’s based on the successful execution of ClientContext query, we need to “defer” invoking “updateUserProfile()” until we have received current userId. Therefore we’ll create a Deferred object as follows:

function getUserLogin() {
  var userLogin = $.Deferred(function () {
    var clientContext = new SP.ClientContext.get_current();
    var user = clientContext.get_web().get_currentUser();
    clientContext.load(user);
    clientContext.executeQueryAsync(
      function () {
        userLogin.resolve(user.get_loginName());
      }
      ,
      function () {
        userLogin.reject(args.get_message());
      }
      );
  });
  return userLogin.promise();
}

Now when we invoke updateUserProfile(), it will execute getUserLogin() first, implying that the ClientContext query was successful :

getUserLogin().done(function (userId) {
  updateUserProfile(userId, "Fax", "555 555 5555");
});

4. Full working code

Steps to replicate the demo

  1. Copy the following code snippet and save it as a text file (.txt)
  2. Upload this text file anywhere on your SharePoint site (e.g. Site Assets). Remember its path.
  3. On the same SharePoint site, add/edit a page and insert a CEWP (Content Editor Web Part)
  4. On the web part properties of CEWP, add the path to the text file under “Content Link” section
  5. Click Ok, and save the page.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2013.01/jquery.SPServices-2013.01.min.js"></script>
<script type="text/javascript">

//updateUserProfilePreFlight defers until getUserId() is "done"
//then it invokes updateUserProfile
function updateUserProfilePreFlight(){
  getUserId().done(function (userId) {
    var propertyName = "Fax"
    var propertyValue =  $("#Fax").val();
    updateUserProfile(userId, propertyName, propertyValue);
  });
}

//getUserLogin() uses CSOM to retrive current userId.
function getUserId() {
  var userLogin = $.Deferred(function () {
    var clientContext = new SP.ClientContext.get_current();
    var user = clientContext.get_web().get_currentUser();
    clientContext.load(user);
    clientContext.executeQueryAsync(
      function () {
        userLogin.resolve(user.get_loginName());
      }
      ,
      function () {
        userLogin.reject(args.get_message());
      }
      );
  });
  return userLogin.promise();
}

//updateUserProfile updates the userprofile property 
function updateUserProfile(userId, propertyName, propertyValue) {

  var propertyData = "<PropertyData>" +
  "<IsPrivacyChanged>false</IsPrivacyChanged>" +
  "<IsValueChanged>true</IsValueChanged>" +
  "<Name>" + propertyName + "</Name>" +
  "<Privacy>NotSet</Privacy>" +
  "<Values><ValueData><Value xsi:type=\"xsd:string\">" + propertyValue + "</Value></ValueData></Values>" +
  "</PropertyData>";

  $().SPServices({
    operation: "ModifyUserPropertyByAccountName",
    async: false,
    webURL: "/",
    accountName: userId,
    newData: propertyData,
    completefunc: function (xData, Status) {
      var result = $(xData.responseXML);
    }
  });

}


</script>

<input id="Fax" type="text" placeholder="Update Fax" />
<input onclick="updateUserProfilePreFlight()" type="button" value="Update" />

Note

  • You can only edit the user profile properties that are editable (unlocked) on your MySite. Certain fields like department are usually locked for editing as per company policy

Regex Selector for jQuery by James Padolsey FTW

$
0
0

In researching how to fix the issue with Office 365 Update Changes ‘Display Name’ on Required Fields in SPServices, I came across some true awesomeness from James Padolsky (@).

jQuery is eminently extendable, and people do it all the time by creating plugins, additional libraries, etc. After all, it’s all just JavaScript, right? However, I’ve never seen such a nicely packaged selector extension as what James has done with his :regex extension.

In his Regex Selector for jQuery, James has given us exactly what we need to get around this nasty ” Required Field” thing.

jQuery.expr[':'].regex = function(elem, index, match) {
  var matchParams = match[3].split(','),
    validLabels = /^(data|css):/,
    attr = {
      method: matchParams[0].match(validLabels) ?
        matchParams[0].split(':')[0] : 'attr',
        property: matchParams.shift().replace(validLabels,'')
    },
    regexFlags = 'ig',
    regex = new RegExp(matchParams.join('').replace(/^\s+|\s+$/g,''), regexFlags);
  return regex.test(jQuery(elem)[attr.method](attr.property));
}

By adding this nice little chunk of JavaScript into SPServices, I get a nice, clean way to implement a fix. In the snippet of code below from the DropDownCtl function in SPServices, I can maintain backward compatibility (all the way back to SharePoint 2007 – WSS 3.0, even)

// Simple, where the select's title attribute is colName (DisplayName)
//  Examples:
//      SP2013 <select title="Country" id="Country_d578ed64-2fa7-4c1e-8b41-9cc1d524fc28_$LookupField">
//      SP2010: <SELECT name=ctl00$m$g_d10479d7_6965_4da0_b162_510bbbc58a7f$ctl00$ctl05$ctl01$ctl00$ctl00$ctl04$ctl00$Lookup title=Country id=ctl00_m_g_d10479d7_6965_4da0_b162_510bbbc58a7f_ctl00_ctl05_ctl01_ctl00_ctl00_ctl04_ctl00_Lookup>
//      SP2007: <select name="ctl00$m$g_e845e690_00da_428f_afbd_fbe804787763$ctl00$ctl04$ctl04$ctl00$ctl00$ctl04$ctl00$Lookup" Title="Country" id="ctl00_m_g_e845e690_00da_428f_afbd_fbe804787763_ctl00_ctl04_ctl04_ctl00_ctl00_ctl04_ctl00_Lookup">
if ((this.Obj = $("select[Title='" + colName + "']")).length === 1) {
  this.Type = "S";
// Simple, where the select's id begins with colStaticName (StaticName) - needed for required columns where title="colName Required Field"
//   Examples:
//      SP2013 <select title="Region Required Field" id="Region_59566f6f-1c3b-4efb-9b7b-6dbc35fe3b0a_$LookupField" showrelatedselected="3">
} else if ((this.Obj = $("select:regex(id, (" + colStaticName + ")(_)[0-9a-fA-F]{8}(-))")).length === 1) {
  this.Type = "S";

The regex I need to use isn’t too complicated, and the best part is that I can use it right in a jQuery selector with the :regex extension. Here I’m looking for an id which starts with the column’s StaticName followed by an underscore (_) and then 8 hexadecimal characters [0-9a-fA-F] and then a dash (-). That ensures that I’m finding the right id without the greedy selection issues on the title I may run into using some of the other methods people have suggested.

This fix is in the latest alpha of 2014.01, which is named 2014.01ALPHA2. I think that this will be the winning fix, and I’ve already gotten some positive feedback from folks who have tested it. If you have the issue and could test as well, I’d appreciate it. Because the DropDownCtl is the one function I use to find dropdowns for all of the other SPServices function, this should fix all of them.

Now, if we could just get the SharePoint Product Group to stop messing with our markup!

jQuery Library for SharePoint Web Services (SPServices) 2014.01 Released

$
0
0

SPServicesHot on the heels of the short-lived SPServices 2013.02 and it’s younger, wiser sibling 2013.02a, comes SPServices 2014.01. I wanted to do a new release with some cool new functionality, but Microsoft really forced my hand with some changes to title attributes on some column types. (See: Office 365 Update Changes ‘Display Name’ on Required Fields)

I recommend jQuery 1.10.x with this release. Even if you are a happy user of an older release of SPServices, I’d recommend upgrading to this release.

The headlines for this release:

  • Fix for the ” Required Field” change to column titles noted above on Office365 buildversion 16.0.2510.1204 (or thereabouts) and above and SharePoint 2010 with the December 2013 CU
  • New function SPGetListItemsJson
  • Exposed existing private function DropdownCtl as public function SPDropdownCtl
  • New objectType for SPXmlToJson: JSON
  • Case insensitive query string values with $().SPServices.SPGetQueryString
  • Added ClaimRelease operation to the Workflow Web Service

The SPGetListItemsJson function is pretty cool, if I do say so myself. When I fixed the operation GetListItemChangesSinceToken in 2013.02, I saw the possibility for an auto-conversion of the returned data to JSON with only one SOAP call. Because GetListItemChangesSinceToken returns both the list schema and the data, I’m able to parse the schema and return the data as JSON using SPXmlToJson. You can also pass in your own mappingOverrides if you need to.

GetListItemChangesSinceToken is also cool because it allows you to retrieve content deltas by passing in the old changeToken, and SPGetListItemsJson returns the changeToken and also accepts it for future calls. This is great support for the types of Single Page Applications I discuss in my ongoing blog series Single-Page Applications (SPAs) in SharePoint Using SPServices.

While I was building SPGetListItemsJson, I was working on a client project where we stored JSON as text in a Multiple lines of text column. To facilitate conversion of that data to internal JSON, I added an objectType to SPXmlToJson of “JSON”. This allows us to request automatic conversion of text-based JSON data to internally represented data simply by specify the JSON objectType in a mappingOverride. (The function uses jQuery’s $.parseJSON.)

With the kerfuffle about the Office 365 Update Changes ‘Display Name’ on Required Fields and the attendant fixes I needed to do in SPServices, I decided to make the SPDropdownCtl function I’ve been using for years public.  It’s not for the faint of heart, as it passes back a variable object depending on the type of “dropdown”, but it should help some people in building their own applications.

As usual, there are also a number of additional changes to fix existing bugs or improve efficiency (yes, I’m still able to improve on my old code, and I expect that will continue).

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
ALPHA1 10216 $().SPServices.SPGetListItemsJson New Function: SPGetListItemsJson
ALPHA1 10221 $().SPXmlToJson Add new objectType to SPXmlToJson for text columns containing JSON
ALPHA1 10218 $().SPXmlToJson Add sparse parameter to SPXmlToJson
ALPHA1 10195 $().SPServices.SPGetQueryString Case insensitive SPGetQueryString?
ALPHA3 10229 $().SPServices.SPDropdownCtl Make DropdownCtl Function Public
ALPHA4 10230 $().SPServices.SPDropdownCtl Return optHid input element in SPServices.SPDropdownCtl

New Operations

Alpha Web Service Operation Options MSDN Documentation Issue Tracker Item
ALPHA2 Workflow ClaimRelease item, taskId, listId, fClaim http://msdn.microsoft.com/en-us/library/workflow.workflow.claimreleasetask(v=office.12).aspx 10222

Bug Fixes and Efficiency

Alpha Issue Tracker Item Function Description
ALPHA1 10211 $().SPServices.SPGetQueryString LMenuBaseUrl is undefined
ALPHA1 10192 $().SPServices.SPGetQueryString DeleteWeb needs_SOAPAction Value Incorrect
ALPHA1-2 10219 $().SPServices.SPXmlToJson Various Fixes to SPXmlToJSON
ALPHA2 10224 $().SPServices.SPCascadeDropdowns ParentObject Null in $.fn.SPServices.SPCascadeDropdowns
ALPHA2 10225 $().SPServices.SPXmlToJson SPXmlToJson – Fix Date/Time Conversion
ALPHA2 10226 $().SPServices.SPCascadeDropdowns Cascade Dropdowns Do not work if Fields Are Required
ALPHA4 10231 $().SPServices.SPCascadeDropdowns Title Changed for Required Fields after CU for Sharepoint 2010
BETA2 10238 $().SPServices.SPDisplayRelatedInfo SPDisplayRelatedInfo not working in SPServices 2014.01
BETA2 10236 NA Replace .attr(“value”) usage with .val()
BETA2 10235 $().SPServices.SPRequireUnique SPRequireUnique bug getting entered value

Microsoft Cloud Show Episode 016 – Interview with Marc Anderson on Recent Changes Impacting Customers on Office 365

$
0
0
MSCloudShow

Microsoft Cloud Show

A few weeks back, I sat down (virtually, of course) with Andrew Connell (@AndrewConnell) and Chris Johnson (@LoungeFlyZ) to record an episode of the Microsoft Cloud Show. Andrew was in Florida, I was in Boston, and Chris was way around the world in New Zealand. Ah, the wonders of modern technology.

The only place to stay up to date on everything going on in the Microsoft cloud world including Azure and Office 365.

Whether you are new to the cloud, old hat or just starting to consider what the cloud can do for you this podshow is the place to find all the latest and greatest news and information on what’s going on in the cloud universe.  Join long time Microsoft aficionados and SharePoint experts Andrew Connell and Chris Johnson as they dissect the noise and distill it down, read between the lines and offer expert opinion on what is really going on.  Just the information … no marketing … no BS, just two dudes telling you how they see it.

I was honored to be the very first guest on the show, which already had 15 excellent episodes in the can.

In Episode 016 – Interview with Marc Anderson on Recent Changes Impacting Customers on Office 365, we talked about a number of extremely important things that have been going on with Office365 lately.

I had done a post about one issue that has caused me and users of SPServices the most consternation, Office 365 Update Changes ‘Display Name’ on Required Fields and Andrew had posted about a few others one his blog in Office 365 Needs to Treat the UX as an API if Our Customizations are to Stay Off the Server.

Last week, I released SPServices 2014.01, which addresses the title changes (adding ” Required Field” to the title attribute of some required dropdowns), but there’s a bigger set of issues at play here, as Andrew alludes to in his post.

In the podcast, we talked about the impact of these changes as well as the mindset behind them from the Microsoft side.

If you do any client side development with SharePoint – and that’s where everyone is headed – you owe it to yourself to listen to the podcast. You’ll understand more about what changes to the DOM might mean for you as a developer, or even what might happen to you as a user of customizations that rely on the DOM being stable and predictable.

One things seems certain: we’ll see more changes like the ones we discussed in the podcast and they will have an impact on everyone, not just people replying on Office365. (The same issues started to crop up for people who have applied the December 2013 Cumulative Update (CU) for SharePoint 2010 on premises.)

I want to thank Chris and Andrew for inviting me in for a chat. Assuming I didn’t annoy them too much with my scatological terminology, maybe I’ll be able to visit with them again the next time a round of changes like this pop up and cause ripples in the SharePoint time-space continuum.

SPServices Stories #21 – Redirect If User Clicked a Button Previously

$
0
0
This entry is part 20 of 20 in the series SPServices Stories

Introduction

teylynIngeborg Hawighorst (@IngeborgNZ) is a long-time SPServices user who has come up with any number of intriguing uses for the library. I’d recommend her blog anytime if you’d like to learn about interesting things you can do with SharePoint, but even more so if Excel is your bag. Ingeborg has been an Excel MVP for years running (see her profile on the MVP site). Some of the best solutions using SPServices come out of discussions in various SharePoint-oriented forums. In this case, Ingeborg spotted some suggestions from Eric Alexander (@ejaya2 aka PirateEric) and decided to build it out. Without further ado, here is Ingoborg’s article, reposted from her blog cheers, teylyn.

Redirect If User Clicked a Button Previously

I just came across this question in sharepoint.stackexchange.com. When a user visits a SharePoint site, they are presented with a splash screen and need to accept the policy before they can proceed. Upon subsequent visits, the splash screen does not show, because SharePoint will remember the  user. PirateEric outlined a possible solution: Use a SharePoint list to save the user name when the button is clicked. When the page loads, look up the user in the list. If they already exist, redirect the page, if not, show the splash page with the button to accept the policy. If the policy changes and users need to be made aware of that, simply remove all items in the list that tracks the users. All this can be done with jQuery and web services. That intrigued me and I had a go at actually building this, using Marc Anderson’s SPServices.

How to set it up

Create a SharePoint custom list with two fields, Title and UserName. The former is the out of the box field, the latter is a simple text field. Create two pages, the Splash page with the button and the page that is the desired destination page for all visitors. In my sample these are called Splash.aspx and MainContent.aspx On the Splash page the code that you can see below will be loaded before any other web part. If you use a Content Editor Web Part to load the code with a content link, make sure that it’s the first web part on the page. In many cases, JavaScript and jQuery will be placed in the last web part of the page and run after the DOM has loaded. But in this case this would mean that the Splash page appears briefly, even if it is followed by a redirect to a different page. The Splash page provides the policy (or terms and conditions) that the user must accept, and a link or a button that the user can click to accept. This link or button must then trigger a JavaScript function. That is very easy to do. I used a button and put the html straight into a CEWP like this:

<button onclick="PolicyButtonClick()" type="submit">
   I accept the policy
</button>

So the user clicks the button and the JavaScript function PolicyButtonClick() will run. This function can be found in the code below. First, the jQuery library and the SPServices library are loaded. Then the user name of the current user is retrieved with the SPServices call using SPGetCurrentUser. It returns a text string in the format DOMAIN\Account.  Next, the SPServices call uses the GetListItem. In the call, a CAML query is constructed that will return only list items where the column UserName equals the current user.  The items returned by that query are then counted. Since the user account is a unique value, we can safely assume that the query will either return one result or no result at all. If the query returned an item, this means that the user has previously accepted the policy and the script will redirect to the MainContent.aspx page.  If the query did not return anything, the current page will continue to be displayed. When the user clicks the button to accept the policy,  the user name is written into a variable. Then the SPServices operation to UpdateListItem is called and will create a new item in the list “PolicyAccepted”, storing the previously established account name in the column UserName. Then the MainContent.aspx page is loaded. The next time the user opens the Splash page, their account name will be found in the PolicyAccepted list and they will not see the Splash page again, unless the entry in the list is deleted. Here is the complete script:

<script type="text/javascript" src="/path/jquery-1.10.2.min.js" language="javascript"></script><script type="text/javascript" src="/path/jquery.SPServices-2013.02a.min.js" language="javascript"></script><script type="text/javascript">// <![CDATA[
// start the code even before the DOM is loaded, so not waiting for document ready
//$(document).ready( function() {
// get the user name
 var userName= getUserName();
// find the user name in the list
 var userAccepted = matchUserName(userName);
 if (userAccepted == 1 )
 {
 // redirecting page
 window.location.replace("http://path/Documents/MainContent.aspx");
 }
//});

function getUserName() {
 var thisUserAccount= $().SPServices.SPGetCurrentUser({
 fieldName: "Name",
 debug: false
 });
 return(thisUserAccount);
}

function createNewItem(theTitle, theUser) {
 $().SPServices({
 operation: "UpdateListItems",
 async: false,
 batchCmd: "New",
 listName: "PolicyAccepted",
 valuepairs: [["Title", theTitle], ["UserName", theUser]],
 completefunc: function(xData, Status) {
 }
 });
}

function matchUserName(userName) {
 var queryText = "<Query><Where><Eq><FieldRef Name='UserName'/><Value Type='Text'>" + userName + "</Value></Eq></Where></Query>";
 $().SPServices({
 operation: "GetListItems",
 listName: "PolicyAccepted",
 async: false,
 CAMLQuery: queryText,
 completefunc: function (xData, status) {
 itemCount = $(xData.responseXML.xml).find("rs\\:data, data").attr("ItemCount");
 }
 });
 return(itemCount);
}

function PolicyButtonClick() {
 var userName= getUserName();
 var theTitle= "Accepted";
 createNewItem(theTitle, userName);
 window.location.href = "http://path/Documents/MainContent.aspx";
}
// ]]></script>

SPServices: What About Deprecation of the SOAP Web Services?

$
0
0

SPServices user JSdream asked a question in the SPServices Discussions on Codeplex the other day that’s worthy of a more prominent response.

Should we, who use SPServices, be concerned at all with Microsoft recommending not to use either the ASMX web services in SharePoint 2013 (http://msdn.microsoft.com/en-us/library/office/jj164060.aspx#DeprecatedAPIs) or the owssvr.dll (I used this one a lot when doing InfoPath stuff) for development purposes?

The Choose the right API set in SharePoint 2013 article is an important one, for sure. In it, there’s pretty clear direction about which API to choose based on the specific requirements of the solution you are building. As with anything, though, there’s a strong dash of “it depends”. It depends on things like your available skills, the device the code will run on, etc.

Figure 1. Selected SharePoint extension types and SharePoint sets of APIs

Figure 1. Selected SharePoint extension types and SharePoint sets of APIs

I would say that we should all be “cautious” rather than “concerned”. The SOAP Web Services are used by many parts of SharePoint still, with the most prominent being SharePoint Designer. If you sniff around in the traffic between processes, you’ll spot other places as well.

That said, deprecated is deprecated. If you build your solutions with the data access layer separated out from the business logic layer, you should be able to replace the calls if and when it becomes necessary.

I am not aware of any official specific time frames for the SOAP services going away, and the Product Group would need to give us a good, long lead time because lots of code depends on it, SPServices or not. Many people have built managed code-based solutions which use the SOAP Web Services as well. GetListItems is a workhorse operation no matter how you build your solutions.

There are many things you simply cannot do with REST yet, though in many cases CSOM may provide better coverage. If you are using SPServices for the operations which aren’t available in REST or CSOM, obviously you’ll want to continue doing so until there is a viable replacement. The REST coverage is improving steadily. (See Overview of SharePoint Conference 2014 and new content for developers for some information.) Keep in mind, though, that the majority of improvements will end up in Office365 first and in the SharePoint Server product at some point later or perhaps not at all. So your time horizons and cloud plans are also critical decision factors.

[If I had to put my money on a horse, it'd be REST over CSOM over the long term.]

All that said, if you are starting a new project on SharePoint 2013 of any significant size, you should be considering using the App Model, CSOM, and REST. SPServices may still fit in there somewhere, so don’t toss it out with the bath water just yet. I’ll continue evolving it as long as it has people wanting to use it. I think I still have plenty of things to do.

Uploading Attachments to SharePoint Lists Using SPServices

$
0
0

Easy!For years people have been asking me how they could upload files using SPServices. I’ve dodged the questions every time because I simply didn’t know how.

On a current client project,  I’m building a slick Single Page Application (SPA) to manage tasks in SharePoint 2010. It’s basically a veneer over the clunky out-of-the-box task list experience. Rather than hopping from page to page, the team using the application can accomplish everything they need to do on a single page.

Every project has specific needs, and for this one I’m using KnockoutJS. But the code snippets I give below are generic enough that you should be able to use them in any context with a little thinking.

It’s been going well, but I had been putting off implementing adding attachments because…well, as I said, I didn’t know how.

One of the benefits of the shift from all server side development to a much more significant focus on client side techniques is that some of the bright minds in the SharePoint development community have turned their eyes toward solving these things, too. Usually it’s on SharePoint 2013 or SharePoint Online at Office 365. However, at least now when I search for “SharePoint JavaScript xxx”, there are likely to be some great hits I can learn from.

In this case, there were two excellent posts, one from James Glading and one from Scot Hillier. (See the Resources section below for links.)

The first step is to enable HTML5 capabilities in IE10. This requires two small changes to the master page. This seems simple, but it can have effects that you don’t expect. In other words, don’t consider this just a “no brainer”. You need to plan for the change across your Site Collection and any impacts it may have.

The first change is to switch from using “XHTML 1.0″ to “HTML” as the DOCTYPE. This is what “turns on” HTML5.

<!DOCTYPE html>

Then, in this project we set the content meta tag to IE=10 because we want to aim for IE10.

<meta http-equiv="X-UA-Compatible" content="IE=10"/>

If your browser base is different, you can, of course, set this differently. In this project, we will require all users of the application to be running IE10 or greater. Firefox and Chrome have supported the bits of HTML5 we need for so long, that there’s little concern about people who choose to use those browsers.

Once we have those two small changes in the master page (we are using a copy of v4.master with no other customizations at the moment), we “have HTML5″. It’s most obvious because some of the branding flourishes I’ve put in like rounded corners show up in IE10 now, rather than just square boxes.

Once we have HTML5 enabled, we can start to use the File API, a.k.a. the FileReader. This is such a simple little thing, that it’s hard to believe it gives us the capability it does.

<input type="file" id="attachment-file-name"/>

That’s it. That simple HTML gives us a file picker on the page. Depending on your browser, it will look something like this:

File Picker

When you make a file selection with this very familiar widget, you can query the element using jQuery.

var file = $("#attachment-file-name").files[0];

Note that we’re after a single file, so we grab the first object in the file list array. We get an object that looks something like this screen shot from Firebug:

File object from the File Picker

Once we have the file object, we can look at what the Lists SOAP Web Service needs to get in order to upload the file. Again, it’s pretty simple. Here is the list of inputs and output from the MSDN documentation page for the Lists.AddAttachment Method.

Parameters

listName
A string that contains either the title or the GUID for the list.
listItemID
A string that contains the ID of the item to which attachments are added. This value does not correspond to the index of the item within the collection of list items.
fileName
A string that contains the name of the file to add as an attachment.
attachment
A byte array that contains the file to attach by using base-64 encoding.

Return Value

A string that contains the URL for the attachment, which can subsequently be used to reference the attachment.

The first three inputs are straightforward. We pass in the name of the SharePoint list, the ID of the list item, and the name of the file we want to attach. It’s that last input parameter called “attachment” that’s a bit tricky.

When we upload a file via an HTTP POST operation – which is what all of the SOAP Web Services use – we have to pass text. But the files we want to upload are as likely as not to be binary files. That’s where the Base64 format comes in. Believe me, you don’t need to know exactly what the format actually is, but you should probably understand that it enables us to send binary files as text bytes. Those bytes are then decoded on the server end so that the file can be stored in all of its original, pristine glory.

Here’s the code I ended up with, skinnied down as much as possible to make it a little clearer to follow. I draw heavily from Scot and James’ posts for this, but nuanced for SPServices. I’ve also stripped out all of the error handling, etc.

  • First, the getFileBuffer function reads the contents of the file into the FileReader buffer. readAsArrayBuffer is an asynchronous method, so we use a jQuery Deferred (promise) to inform the calling function that the processing is done.
  • The contents of the buffer are then converted from an ArrayBuffer – which is what the FileReader gives us – into a Base64EncodedByteArray. This works by passing the buffer through a Uint8Array along the way.
  • Finally, we use the toBase64String method to convert the SP.Base64EncodedByteArray to a Base64String.

Yeah, that’s a lot to swallow, but again, you don’t really need to understand how it works. You just need to know that it does work.

Finally, we call Lists.AddAttachment using SPServices to do the actual upload.

/* From Scot Hillier's post:
http://www.shillier.com/archive/2013/03/26/uploading-files-in-sharepoint-2013-using-csom-and-rest.aspx */
var getFileBuffer = function(file) {

  var deferred = $.Deferred();
  var reader = new FileReader();

  reader.onload = function(e) {
    deferred.resolve(e.target.result);
  }

  reader.onerror = function(e) {
    deferred.reject(e.target.error);
  }

  reader.readAsArrayBuffer(file);

  return deferred.promise();
};

getFileBuffer(file).then(function(buffer) {
  var bytes = new Uint8Array(buffer);
  var content = new SP.Base64EncodedByteArray(); //base64 encoding
  for (var b = 0; b < bytes.length; b++) {
    content.append(bytes[b]);
  }

  $().SPServices({
    operation: "AddAttachment",
    listName: "Tasks",
    listItemID: taskID,
    fileName: file.name,
    attachment: content.toBase64String()
  });

});

Very cool! And as I tweeted yesterday, far easier than I ever would have expected. Yes, it took me a good chunk of a day to figure out, but it definitely works, and pretty fast, too.

If you use this example as a base, you could fairly easily build out some other file uploading functions. Combined with the other attachment-oriented methods in the Lists Web Services, you can also build the other bits of the attachment experience:

  • GetAttachmentCollection – Returns a list of the lit item’s attachments, providing the full path to each that you can use in a list of links.
  • DeleteAttachment – Once you’ve uploaded an attachment and realized it was the wrong one, this method will allow you to delete it.

Moral of the story: Fear not what you do not know. Figure it out.

Resources

What’s the Story for HTML5 with SharePoint 2010? by Joe Li

Uploading Files Using the REST API and Client Side Techniques by James Glading

Uploading Files in SharePoint 2013 using CSOM and REST by Scot Hillier

Lists.AddAttachment Method

This article was also posted at ITUnity on Jun 02, 2014. Visit the post there to read additional comments.

Update 2014-05-28 11:55 GMT-5

Hugh Wood (@HughAJWood) took one look at my code above and gave me some optimizations. Hugh could optimize just about anything; he is *very* good at it. I *think* I can even see the difference with larger files.

getFileBuffer(file).then(function(buffer) {
  var binary = "";
  var bytes = new Uint8Array(buffer);
  var i = bytes.byteLength;
  while (i--) {
    binary = String.fromCharCode(bytes[i]) + binary;
  }
  $().SPServices({
    operation: "AddAttachment",
    listName: "Tasks",
    listItemID: taskID,
    fileName: file.name,
    attachment: btoa(binary)
  });
});

SPServices Stories #2 – Charting List Data with HighCharts

$
0
0
This entry is part 2 of 20 in the series SPServices Stories

Introduction

This submission comes to us from an anonymous reader, who can’t publish the details under his (or her) name due to confidentiality issues. However, s/he has been able to generate some very useful charts with HighCharts using SharePoint list data as the underlying data sources.

Charting List Data with HighCharts

HighChartsIn the following code, the source list contains columns with Year (string), Month (1-12), and Value (number with decimals).

While HighCharts isn’t free, the licensing costs are quite reasonable. A similar approach would work with other charting engines out there which may be free.

The page has a Content Editor Web Part dropped into it with the Content Link pointing to a file containing the following:

<!-- jquery and spservices are in the masterpage here this is a CEWP noConflict is on-->

<script type="text/javascript"src="//code.highcharts.com/highcharts.js"></script>
<script type="text/javascript" src="//code.highcharts.com/modules/exporting.js"></script>
<script type="text/javascript">
function GetYearSeries(series, year)
{
  var gotOne = false;
  var seriesOptions;

  jQuery.each (series, function(index, dataItem) {
    if (dataItem.name === year)
    {
      seriesOptions = dataItem;
      gotOne=true;
    }
  });

  if (!gotOne)
  {
    seriesOptions = {
      name: year,
      data: [0,0,0,0,0,0,0,0,0,0,0,0]
    };

    series.push (seriesOptions);
  }

  return seriesOptions;

}

jQuery(function($) {

  var CamlQuery = "<Query><OrderBy><FieldRef Name='Year' /><FieldRef Name='Month' Ascending='False' /></OrderBy></Query>";

  $().SPServices({
    operation: "GetListItems",
    async: true,
    listName: "Sales",
    CAMLQuery: CamlQuery,
    CAMLViewFields: "<ViewFields><FieldRef Name='Year' /><FieldRef Name='Month' /><FieldRef Name='Value' /></ViewFields>",
    completefunc: GraphIt
  });

  function GraphIt(xmlResponse)
  {

    var options = {
      chart: {
        renderTo: 'container',
        type: 'column'
      },
      title: {
        text: 'Sales'
      },
      xAxis: {
        categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
      },
      yAxis: {
        title: {
          text: 'Value'
        }
      },
      series: []
    };

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

      var seriesOptions = GetYearSeries(options.series, $(this).attr('ows_Year'));
      var month=parseInt($(this).attr('ows_Month'))-1;

      seriesOptions.data[month]=parseFloat($(this).attr('ows_Value'));
    });
    var chart = new Highcharts.Chart(options);

  }

});//docReady
</script>

<div id="container" style="min-width: 400px; height: 400px; margin: 0 auto"></div>

This generates a chart which looks something like this:

HighCharts Example

SPServices Compatibility Issues with the Minified Version of jQuery 1.9.0

$
0
0

Download jQuery 1.9.0Codeplex users tedka and danstaley have reported issues using SPServices with jQuery 1.9.0. You can read their issues here and here, respectively.

I’ve done some quick testing, and the problem seems to be with the minified version of jQuery 1.9.0, *not* 1.9.0 itself. When I use the non-minified version, my test pages perform just fine.

I’ve added a note to the home page of SPServices to this effect:

2013-01-29 – At this time, SPServices seems to work just fine with jQuery 1.9.0, but NOT with the minified version. If you need to use jQuery 1.9.0, please stick with the non-minified version or minify your own version.

When I create my own minified version of 1.9.0 using The JavaScript CompressorRater (I chose the first YUI Compressor 2.4.2 result, as I do when I minify SPServices), my minified version works fine, too.

At this point, I’m not sure what the exact problem is, but I’ll try to contact the jQuery team to see if there have been any other reports of issues. I’m not sure how well that will go, but at least there’s a workaround.

I found some other reports of issues which are caused by this line at the end of the minified version of jQuery 1.9.0:

//@ sourceMappingURL=jquery.min.map

While the source mapping capability sounds useful, having this line in the minified version causes another library following that line to throw errors, which is explained well on this thread at StackOverflow.

[important]This is a known issue with jQuery 1.9.0, which the team has already fixed for the next version. Here’s the bug in the jQuery bug tracker: http://bugs.jquery.com/ticket/13274[/important]

jQuery Library for SharePoint Web Services (SPServices) 2014.02 Released

$
0
0

SPServicesJust in time for the holiday gift-giving season, I’m releasing SPServices 2014.02. This is the second release in 2014 (which you should be able to glean from the release name). If you’re using an earlier version of SPServices, I strongly recommend an upgrade. Read on.

The most important change in this release is due to an egregious error on my part that goes way back to 2013.01. Paul Tavares (@paul_tavares) spotted quite a while back and I was just too dumb to realize how important it was to fix. The net effect of my mistake was that SPServices was always caching requests, regardless how you set cacheXML. On pages where we simply called SPServices a few times on page load, it usually wouldn’t make much of a difference, but a little more memory might be required for the browser. However, in things like Single Page Applications (SPAs) which use SPServices for the data transport layer (See my blog post series: Single-Page Applications (SPAs) in SharePoint Using SPServices), it could make a *huge* difference. It effectively meant that for every unique request, we were caching data whether we should or not. Over a long page lifespan, that could add up to a lot of memory eaten up. Bad news. All better now.

I found out over the last few months the insidious ” Required Field” issue that I thought I had fixed completely in 2014.01 wasn’t totally fixed. For folks on SharePoint 2010 who applied last December’s CU, the markup in the DOM was a little different than the fix I had put into 2014.01. (See: Office 365 Update Changes ‘Display Name’ on Required Fields) This *should* be fixed for everyone now. Please let me know if you see any issues going forward. And Microsoft, please don’t do that again.

There is a handful of new operations in this release, added due to user requests:

  • WebPartPages.SaveWebPart2
  • RecordsRepository.GetRecordRouting
  • RecordsRepository.GetRecordRoutingCollection
  • RecordsRepository.GetServerInfo
  • RecordsRepository.SubmitFile

Finally, I recently moved my SPServices development repository over to Github at sympmarc/SPServices. I’m still getting my feet wet with Github (See: SPServices and Github – This Time I Mean It), but I plan to have my most current work available there going forward. I’ll still be posting the releases over on the Codeplex site, and I’ll monitor the issues there as well. I’m hoping that by using Github, we’ll have more people contributing to SPServices. It’s always been a community-driven project, but I’d love to get more direct contributions.

This release will be, as all of the recent releases have been, available on cdnjs as soon as possible. If you’d like to serve up SPServices from a CDN, cdnjs is the place to get it. cdnjs hosts a lot of JavaScript libraries that don’t have enough coverage to interest the big boy CDNs, so check it out in any case.

Finally, I want to thank Paul Tavares again for his help with SPServices over the years and with this release in particular. Both he and Josh McCarty (@joshmcrty) have also been trying to help get me over onto Github for a long time, but my thick skull just hasn’t gotten it before. I’m using Jetbrains Webstorm as my IDE these days and the Github integration there has finally made Github make sense to me. As we say here in Massachusetts, “light dawns on Marblehead“.

As usual, there are also a number of additional changes to fix existing bugs or improve efficiency (yes, I’m still able to improve on my old code, and I expect that will continue).

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

Issue Tracker Item Function Description

 

New Operations

Web Service Operation Options MSDN Documentation Issue Tracker Item
WebPartPages DeleteWebPart pageUrl, storageKey, storage WebPartPagesWebService.DeleteWebPart Method 10273
WebPartPages SaveWebPart2 pageUrl, storageKey, webPartXml, storage, allowTypeChange WebPartPagesWebService.SaveWebPart2 Method 10273
ALPHA6 RecordsRepository GetRecordRouting recordRouting RecordsRepository.GetRecordRouting Method 10257
RecordsRepository GetRecordRoutingCollection NA RecordsRepository.GetRecordRoutingCollection Method 10257
RecordsRepository GetServerInfo NA RecordsRepository.GetServerInfo Method 10257
RecordsRepository SubmitFile fileToSubmit, properties, recordRouting, sourceUrl, userName RecordsRepository.GetRecordRouting Method 10257

 

Bug Fixes and Efficiency

Issue Tracker Item Function Description
10267 $().SPServices.SPComplexToSimpleDropdown Possible bug in SPComplexToSimpleDropdown
10253 $().SPServices.SPFindMMSPicker SPFindMMSPicker missing from 2014.01
10279 $().SPServices.SPGetListItemsJson SPGetListItemsJson Doesn’t Handle Attachments Correctly
10284 $().SPServices.SPFilterDropdown SPFilterDropdown Not filtering dropdown column.
10272 $().SPServices.SPGetCurrentSite $().SPServices.SPGetCurrentSite Documentation
ALPHA5 10277 $().SPServices.SPGetCurrentUser fix for SPGetCurrentUser at webUrl /
10248 $().SPServices.SPGetCurrentUser Trouble with SPSservices and Sharepoint Form
10265 $().SPServices.SPGetCurrentUser $().SPServices.SPGetCurrentUser isn’t returning ID
10254 $().SPServices.SPDisplayRelatedInfo SPDisplayRelatedInfo – DIV named improperly with Required field
10256 $().SPServices.SPCascadeDropdowns SPCascadeDropdowns Required Lookup issue
10262 $().SPServices.SPComplexToSimpleDropdown SPComplexToSimpleDropdown – required fields get renamed without the ‘Required Field’ appended to the field name
10146 $().SPServices ResolvePrincipals with addToUserInfoList=true requires SOAPAction
10271 $().SPServices.SPGetListItemsJson SPGetListItemsJson not using defaults.webURL
10283 $().SPServices.SPGetListItemsJson CAMLViewName not working?
ALPHA7 10182 $().SPServices async on version 2013.01 and caching
10298 $().SPServices GetTermSets argument names are slightly wrong
10299 $().SPServices WebUrl not working
Viewing all 109 articles
Browse latest View live