SPServices 2013.01ALPHA4 Returns a Deferred Object (Promise)

I’ve just posted a new alpha (ALPHA4) for SPServices 2013.01. This alpha implements the return of a deferred object, aka a promise, as well as executing a completefunc (if provided) for backward compatibility. Note that the deferred object capability was introduced in jQuery 1.5, so this version of SPServices requires that version or greater. I’ve done my initial testing with jQuery 1.8.2.

So, what does this mean to you, the SPServices user? Well, in the short term for most of you it may not mean anything unless you understand the first paragraph above. If you do, I could really use some help in testing the deferred object capability.

Here’s how I’ve implemented it. There are two big differences in the core SPServices function. First, I’ve set it up so that it runs a completefunc if one is provided, just has it always has, but also returns a promise. This means the code has gone from this:

var cachedXML;
var status = null;

if(opt.cacheXML) {
  cachedXML = $("body").data(msg);
}

if(cachedXML === undefined) {
  // Make the Ajax call
  $.ajax({
    url: ajaxURL,                      // The relative URL for the AJAX call
    async: opt.async,                    // By default, the AJAX calls are asynchronous.  You can specify false to require a synchronous call.
    beforeSend: function (xhr) {              // Before sending the msg, need to send the request header
      // If we need to pass the SOAPAction, do so
      if(WSops[opt.operation][1]) {
        xhr.setRequestHeader("SOAPAction", SOAPAction);
      }
    },
    type: "POST",                      // This is a POST
    data: msg,                        // Here is the SOAP request we've built above
    dataType: "xml",                    // We're getting XML; tell jQuery so that it doesn't need to do a best guess
    contentType: "text/xml;charset='utf-8'",        // and this is its content type
    complete: function(xData, Status) {
      if(opt.cacheXML) {
        $("body").data(msg, xData);        // Cache the results
      }
      cachedXML = xData;
      status = Status;
      opt.completefunc(cachedXML, status);        // When the call is complete, do this
    }
  });

} else {
  opt.completefunc(cachedXML, status);            // Call the completefunc
}

to this:

// Check to see if we've already cached the results
var cachedXML;
if(opt.cacheXML) {
  cachedXML = promisesCache[msg];
}

if(typeof cachedXML === "undefined") {

  // Finally, make the Ajax call
  var spservicesPromise = $.ajax({
    // The relative URL for the AJAX call
    url: ajaxURL,
    // By default, the AJAX calls are asynchronous.  You can specify false to require a synchronous call.
    async: opt.async,
    // Before sending the msg, need to send the request header
    beforeSend: function (xhr) {
      // If we need to pass the SOAPAction, do so
      if(WSops[opt.operation][1]) {
        xhr.setRequestHeader("SOAPAction", SOAPAction);
      }
    },
    // Always a POST
    type: "POST",
    // Here is the SOAP request we've built above
    data: msg,
    // We're getting XML; tell jQuery so that it doesn't need to do a best guess
    dataType: "xml",
    // and this is its content type
    contentType: "text/xml;charset='utf-8'",
    complete: function(xData, Status) {
      // When the call is complete, call the completefunc if there is one
      if($.isFunction(opt.completefunc)) {
        opt.completefunc(xData, Status);

      }
    }
  });

  spservicesPromise.then(
    function() {
      // Cache the promise if requested
      if(opt.cacheXML) {
        promisesCache[msg] = spservicesPromise;
      }
    },
    function() {
                                // TODO: Allow for fail function
    }
  );

  // Return the promise
  return spservicesPromise;

} else {
  // Call the completefunc if there is one
  if($.isFunction(opt.completefunc)) {
    opt.completefunc(cachedXML, null);
  }
  // Return the cached promise
  return cachedXML;
}

I don’t usually post the differences in the code in such detail, but I want to get some eyeballs on it so that I can gather yeas or nays on the implementation. I wanted to keep it as clean as possible yet maintain 100% backward capability. I seem to have accomplished both in all of my testing, but I’d love feedback. I’m especially curious what folks think I should do in the failure case of the .then() function. In the past I’ve not done anything, leaving it to the SPServices user to decide what they would like to do. The only two options I can think of are:

  1. Add a retry loop to give it a few more shots before giving up
  2. Pop up an error message of some sort

Neither of these two options feel right to me. If the .ajax() call fails, in my experience it’s because the server isn’t responding 99.9999% of the time. Trying again won’t usually solve that unless it’s simply a lag after an IISRESET. Popping up an error doesn’t really help, either, as it would have to be some sort of generic “Contact your administrator” nonsense, which I avoid assiduously. Thoughts?

The second change is that I’ve moved from using the .data() jQuery function for caching to caching the promises in an array, as you can see in the code above. This difference should be invisible to all end users and to most developers who use SPServices. However, since it means a difference in how the caching works since I introduced it in v0.7.2, I want to be sure that I get people testing that as well.

Finally, as a further test of the deferred object capability, in this alpha the SPArrangeChoices function also uses the returned promise for GetList as a test to improve the performance and user experience. This function was a good candidate for the first internal test of the new architecture, as it is relatively straightforward and only makes one Web Services call to GetList. I’ll be implementing more uses of promises inside the library as it makes sense before finalizing this release.

So if you’re a hard-core SPServices user, please give this one a test for me. Obviously I’m interested in any regression, but I’d also like to know how the returned promises work for you if you can write new calls against this version. As an illustration of how you can use this new capability, here’s what I’ve done in SPArrangeChoices:

// Rearrange radio buttons or checkboxes in a form from vertical to horizontal display to save page real estate
$.fn.SPServices.SPArrangeChoices = function (options) {

  var opt = $.extend({}, {
    listName: $().SPServices.SPListNameFromUrl(),          // The list name for the current form
    columnName: "",          // The display name of the column in the form
    perRow: 99,            // Maximum number of choices desired per row.
    randomize: false        // If true, randomize the order of the options
  }, options);

  var columnFillInChoice = false;
  var columnOptions = [];
  var out;

  // Get information about columnName from the list to determine if we're allowing fill-in choices
  var thisGetList = $().SPServices({
    operation: "GetList",
    async: false,
    cacheXML: true,
    listName: opt.listName
  });

  // when the promise is available...
  thisGetList.done(function() {
    $(thisGetList.responseXML).find("Field[DisplayName='" + opt.columnName + "']").each(function() {
      // Determine whether columnName allows a fill-in choice
      columnFillInChoice = ($(this).attr("FillInChoice") === "TRUE") ? true : false;
      // Stop looking;we're done
      return false;
    });
[...]
  });
}; // End $.fn.SPServices.SPArrangeChoices

Here’s another chunk of test code that I’ve been using to see that the caching is working as I’d like. The Census Data list on my Demos site has over 3000 items in it, so it’s a good list to test timings with, as it takes a comparatively long time to send the data down the wire. I’ve got the async option and the completefunc commented out below; you can play around with the combinations here as I have if you’d like.

function getIt() {
  var outUl = $("#WSOutput ul");

  logTime(outUl, "Start: ");
  var getListItemsPromise = $().SPServices({
    cacheXML: true,
//    async: false,
    operation: "GetListItems",
    webURL: "/Demos/",
    listName: "Census Data"
//    completefunc: function (xData, Status) {
//      logTime(outUl, "completefunc: " + $(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount"));
//    }
  });
  logTime(outUl, "End: ");

    getListItemsPromise.done(function() {
    logTime(outUl, "promiseComplete: " + $(getListItemsPromise.responseXML).SPFilterNode("rs:data").attr("ItemCount"));
    });
}

function logTime(o, t) {
  o.append("<li>" + new Date() + " :: " + t + "</li>");
}

with this simple markup:

<input type="button" onclick="getIt();" value="Get It"/>
<div class="ms-vb" style="width:100%;" id="WSOutput"><ul></ul></div>

Let me know what you think, and stay tuned for more changes in this release, which you can keep track of in the Issue Tracker.

Thanks go to Scot Hillier (@scothillier) for opening my eyes to the value of deferred objects by showing examples in several of his recent sessions.

SPTechCon San Francisco 2013 Wrap Up

Well, I’m back in the snowy Northeast after a great four days at SPTechCon San Francisco 2013. As always, the BZ Media folks put on a great show. SPTechCon is consistently well-run and packed with excellent content, vendors, and attendees.

I wanted to post links to the slide decks from my three sessions. They are available on SlideShare.

If you weren’t at SPTechCon or you were there and missed this session, I’ll be doing it again for the MetaVis SharePoint MVP Webinars Series on Wednesday, March 13, 2013 from 2:00 PM – 3:00 PM EDT. You can register here. The webinar will also be recorded for later viewing.

In addition, as I promised in my third session, here’s a link to a WSP which contains the demos I showed. The WSP is simply the Office365 site saved as a template. In the past, some people have had difficulty instantiating my demo templates in their environments due to activated features not matching. (My rants on the lack of  true portability of SharePoint-based content will probably continue, like Trials and Tribulations: Migrating My Demos Site to Office365, Office365 SharePoint Online Portability Issues Strike Again, and Moving Lists from Hosted WSS 3.0 to Office365 – The ShareGate Way.) If you run into this sort of issue, let me know in the comments and we’ll see what we can work out.

SPServices 2013.01 Is Underway – Learnings Ensue

I’ve started really cranking on the new version of SPServices and I’m having a blast, as usual. I’ve learned a few things already, and I thought I’d capture them in a post.

“use strict”;

Just gotta do it. I balked at implementing this previously, as it kept hating my code. Well, it hated my code for a reason. "use strict"; makes you fix some pretty dumb mistakes, and I had quite a few of them. SPServices ran just fine with those mistakes, but by implementing "use strict";, they aren’t even there anymore.

If you want to understand why "use strict"; is a good idea with jQuery, read John Resig’s post ECMAScript 5 Strict Mode, JSON, and More.

JSLint vs. JSHint

JSHintAs part of my refactoring and housecleaning. I went to run SPServices through JSLint as I usually do. It seems that Douglas Crockford, godfather of JavaScript and author of JSLint, has introduced what amounts to either an annoying new feature or a bug into JSLint. I saw the message “Use spaces, not tabs.” on so many lines that JSLint only was making it through about 1% of my code before giving up.

There’s a post over on StackOverflow called New JSLint errors “use spaces, not tabs” and “unsafe character” wherein someone has tracked down what’s going on. I like using tabs in my code, so I took the hint and the advice in the post and headed over to JSHint instead.

It turns out that JSHint does some nice things that JSLint doesn’t do, anyway. Each message shows the line number as a link which takes you into the code at the proper spot to fix it right there. With JSLint, I was always copy/pasting back and forth to SharePoint Designer to make my fixes. JSHint also color codes your code in the edit window so that you can really do editing. Very nice. The only drawback is that JSHint throws an error when I try to run it in IE9, but I’m fine with working in Firefox.

Refactoring

Every single time I go through SPServices, I do a lot of refactoring. I’m a learning human, and each time I know more (at least I like to think I do) about good coding practices than the last time. Sometimes I really want to slap myself up side the head seeing what I did in some of my earliest efforts. I’m taking this opportunity to refactor things yet again, creating many more complex objects to store things than I had originally. This is a good thing, not a complicating thing, as it will make the code far more manageable and readable over the long haul. (Yes, there is a long haul. SPServices still has a good number of years in it, IMO.)

I’m also taking advantage of some of the SharePoint 2010 context information which is available in JavaScript variables (if we’re in 2010, of course, with nice fall back for 2007). Thanks to John Liu’s post SharePoint – JavaScript current page context info which gave me the impetus for this.

Version Numbers

You may have noticed that I’ve switched my version numbering scheme. I have been confusing myself with the version numbers over the last year or so. On one level I wanted to sort of align with the jQuery version numbers, and on another I was being coy and avoiding ever getting to version 1.0. (I’m asymptotic in my tendencies.)

With this version, I’ve switching the versing scheme altogether. This next version will be 2013.01, meaning the first version in 2013. It moves things away from the jQuery alignment and still is an increasing function, so it should make everyone happy. Except the people who will complain about it.

OK, enough chit chat. Back to work. Mark Miller and I are working on some magic and hijinks for his keynote next week at SPTechCon in San Francisco. Hope to see you there!

SPServices Stories #10 – jqGrid Implementation Using SPServices in SharePoint

Introduction

Today’s SPServices Story comes from Prateek Kulkarni in Bengaluru, India. Prateek posted this originally on the C# Corner site as jqGrid Implementation Using SpServices in SharePoint.

As with several of the earlier SPServices Stories posts, I found this one interesting because it shows how to use SPServices with another framework to render the results obtained from the Web Services calls.

I’ve had several people tell me that there isn’t enough “story” to these SPServices Stories. Have a better one? Let me know!

jqGrid with SPServices

This article is regarding implementation of jqGrid(demo) using SPService(CodePlex)

SPService is a jQuery library which abstracts SharePoint’s Web Services and makes them easier to use. It also includes functions which use the various Web Service operations to provide more useful (and cool) capabilities. It works entirely client side and requires no server installation.

Use of SPService and jQuery is best explained at Marc’s blog.

Following Js files are needed for JqGrid Implementation with SPService

  • jquery-1.8.2.js
  • grid.locale-en.js
  • jquery.jqGrid.min.js  // Structuring jqGrid
  • json2-min.js  // Parsing data to json format
  • jquery.SPServices-0.7.2.js  // For getting the list items

Css file required

  • ui.jqgrid.css  //Style sheet of Grid

HTML controls for jqGrid are as mentioned below.

<div id='tblMain' style="float:left">
  <table  id="list" ></table>
  <div id="pager" style="text-align:center;"></div>
</div>

Loading JqGrid on the page load:

jQuery("#list").jqGrid({
  datatype: GetMyData,
  colNames:["Project ID","Project Name","Delivery Manager","ApprovalStatus"],
  colModel:[{name:'ProjectId',index:'ProjectId',align:'left',sortable: true},
            {name:'ProjectName',index:'ProjectName',align:'left',sortable: true },
          {name:'DeliveryManager',index:'DeliveryManager',align:'left',sortable:true},
          {name:'ApprovalStatus',index:'ApprovalStatus',align: 'left',sortable: true }
                       ],
  pager: true,
  pager: '#pager',
  pageinput: true,
  rowNum: 5,
  rowList: [5, 10, 20, 50, 100],
  sortname: 'ApprovalStatus',
  sortorder: "asc",
  viewrecords: true,
  autowidth: true,
  emptyrecords: "No records to view",
  loadtext: "Loading..."
});

In the above jqGrid load function I have mentioned the datatype for the grid as GetMyData() which is a function that gets triggerred first.

The GetMyData method has function GetDataOnLoad which uses the SpServices which has the basic operation of getting the list items i.e. GetListItems, which need optional CAML Query property which will fetch the data from list with some WHERE clause.

In the code I have a list called ProjectDetailsList which will contain details of some projects which are inserted by some Project Manager or delivery manager. So the requirement was when a user log in to the system I should get the current login user name and pass the same user name to the “where” clause of query so the grid will contain data of projects to which the current logged in user is assigned as PM or DM.

To get the current login user am using SpServices Operation SpGetCurrentUser.

The method GetTheOrderByType function will make the query part for SpServices.

The functions code is as follows:

function ForGettingUserName() {
  var userName = $().SPServices.SPGetCurrentUser({
      fieldName : "Title",
      debug : false
    });
  return userName;
}

function GetMyData() {
  sortIdexName = jQuery("#list").getGridParam("sortname"); //Maintaining Consitant SortName after the Sortcol event
  sortOrderName = jQuery("#list").getGridParam("sortorder"); //Maintaining Consistant Sort Order
  Query = GetTheOrderByType(sortIdexName, sortOrderName);
  var CAMLViewFields = "<ViewFields>" +
     + "<FieldRef Name='projectName' /><FieldRef Name='projectID' />"
     + "<FieldRef Name='Title' /><FieldRef Name='deliveryManager' />"
     + "<FieldRef Name='projectSQA' /><FieldRef Name='approvalStatus' />"
     + "<FieldRef Name='projectStartDate' /><FieldRef Name='projectEndDate' />"
     + "<FieldRef Name='sqasiteurl' /><FieldRef Name='ID' />"
     + "</ViewFields>";
  GetDataOnLoad(Query, CAMLViewFields);
}
Function for Getting the Query Type and the particular WHERE CLAUSE TO send the Query to GetListItem function
function GetTheOrderByType(index, sortOrder, userName) {
  var OrderByType;
  if (index == "ProjectName") {
    if (sortOrder == "desc") {
      OrderByType = "<Query>" +
         + "<Where><Or>" + "<Eq>" + "<FieldRef Name='deliveryManager'/><Value Type='Text'>"
         + userName + "</Value>" + "</Eq>"
         + "<Eq><FieldRef Name='projectManager'/><Value Type='Text'>"
         + userName + "</Value></Eq>" + "</Or></Where>" +
         + "<OrderBy><FieldRef  Name='projectName' Ascending='FALSE' /></OrderBy>" +
         + "</Query>";
    } else {
      OrderByType = "<Query>" +
         + "<Where><Or>" + "<Eq>" + "<FieldRef Name='deliveryManager'/><Value Type='Text'>"
         + userName + "</Value>" + "</Eq>"
         + "<Eq><FieldRef Name='projectManager'/><Value Type='Text'>"
         + userName + "</Value></Eq>" + "</Or></Where>" +
         + "<OrderBy><FieldRef  Name='projectName' Ascending='FALSE' /></OrderBy>" +
         + "</Query>";
    }
  } else if (index == "ApprovalStatus") {
    if (sortOrder == "desc") {
      OrderByType = "<Query>" +
         + "<Where><Or>" + "<Eq>" + "<FieldRef Name='deliveryManager'/><Value Type='Text'>"
         + userName + "</Value>" + "</Eq>"
         + "<Eq><FieldRef Name='projectManager'/><Value Type='Text'>"
         + userName + "</Value></Eq>" + "</Or></Where>" +
         + "<OrderBy><FieldRef  Name='approvalStatus' Ascending='FALSE' /></OrderBy>" +
         + "</Query>";
    } else {
      OrderByType = "<Query>" +
         + "<Where><Or>" + "<Eq>" + "<FieldRef Name='deliveryManager'/><Value Type='Text'>"
         + userName + "</Value>" + "</Eq>"
         + "<Eq><FieldRef Name='projectManager'/><Value Type='Text'>"
         + userName + "</Value></Eq>" + "</Or></Where>" +
         + "<OrderBy><FieldRef  Name='approvalStatus' Ascending='FALSE' /></OrderBy>" +
         + "</Query>";
    }
    return OrderByType;
  }

  //This function gets the data from List using SpServices
  Function GetDataOnLoad(Query, CAMLViewFields) {
    $().SPServices({
      operation : "GetListItems",
      async : false,
      listName : "ProjectDetailsList",
      CAMLQuery : Query,
      CAMLViewFields : CAMLViewFields,
      completefunc : processResult
    });
  }

The processResult is the function which formats the data which can be converted to Json and adds to the JqGrid.
The reason of formatting of data in the following particular format is to make it readable by the Json parser which is json2.js file. I had implemented the same JqGrid in asp.net application with AJAX calls where it was returning the data in this format and some other bloggers also used the same data format in the MVC or asp.net application with the help for JsonHelper class which mainly formats the data returned from the DB

//Processing the XML result to formatted Json so that We can bind data to grid in Json format
function processResult(xData, status) {
  var counter = 0; // Gets the total number of records retrieved from the list (We can also use xData.ItemCount method for counting the number of rows in the data )
  var newJqData = "";

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

    var JqData;
    if (counter == 0) {
      JqData = "{id:'" + $(this).attr("ows_projectID") + "',"
         + "cell:[" + "'" + $(this).attr("ows_projectID") + "','" +
        $(this).attr("ows_projectName") + "','" +
        $(this).attr("ows_deliveryManager") + "','," +
        "]}";
      newJqData = newJqData + JqData;
      counter = counter + 1;
    } else {
      var JqData = "{id:'" + $(this).attr("ows_projectID") + "',"
         + "cell:[" + "'" + $(this).attr("ows_projectID") + "','" +
        $(this).attr("ows_projectName") + "','" +
        $(this).attr("ows_deliveryManager") + "','," +
        "]}";
      newJqData = newJqData + JqData;
      counter = counter + 1;
    }

  });
  FinalDataForGrid(newJqData, counter);
}

That’s it. Add the data to the grid with the div control and the other page number calculation is for showing the pager.

function FinalDataForGrid(jqData, resultCount) {
  dataFromList = jqData.substring(0, jqData.length - 1);
  var currentValue = jQuery("#list").getGridParam('rowNum');
  var totalPages = Math.ceil(resultCount / currentValue);
  var PageNumber = jQuery("#list").getGridParam("page"); // Current page number selected in the selection box of the JqGrid
  //formatting rows
  newStr = "{total:" + '"' + totalPages + '"' + "," + "page:" + '"' + PageNumber + '"' + ","
     + "records:" + '"'
     + resultCount + '"' + ","
     + "rows:"
     + "[" + dataFromList + "]}";
  var thegrid = jQuery("#list")[0];
  thegrid.addJSONData(JSON.parse(newStr)); //Binding data to the grid which is of JSON Format
}

And the grid works fine and fast on paging, sorting and also even search, we can make the particular column as hyperlink which I will blog in the next part. Sample grid is as follows and this grid has some extra columns then the mentioned in above code

SPServices Futures: Moving to jQuery’s Deferred Objects and More

As I gear up to work on the next release of SPServices, I want to make some pretty fundamental changes/improvements to the internal plumbing. What I’m hoping to do with this post is to gather any ideas and feedback that the user community has about the implementation before I go too far with things.

SPServices

Deferred Object and Promises

In all prior versions, we’ve relied on the completefunc (which I made up) to process the results we get back from a Web Services operation call. In the meantime, jQuery has moved forward significantly.

In jQuery 1.5, we got the first deferred object capabilities. I was loathe to change the internal workings of SPServices at the time because we had a substantial user base, even then.

Now the user base is significantly larger (as of this writing, there have been almost 75,000 [!] total downloads), but I think it’s time to add the deferred capabilities into the core SPServices function. More and more people have been exposed to deferred processing capabilities in script at this point, so it will be less of a shock to existing SPServices users (I hope!).

Oddly, I’ve only had one request (at least that I can remember) to add deferred processing into SPServices. It came from a Codeplex user named MgSam in a thread s/he titled JQuery Deferred compatibility, and I’ll admit that I blew it off a bit when he raised it as a possibility.

My strongest impetus for considering this fundamental architecture shift is having seen Scott Hillier talk several times about Utilizing Promises in SharePoint 2013 Apps. In actual fact, his talk applies to JavaScript (and thus jQuery) development more than it applies to SharePoint 2013 development. We can use the deferred methodology even if we’re running our scripts on SharePoint 2007. It’s a way to manage requests in the script and has little to do with the version of SharePoint.

In doing this, my goal will be to keep full backward compatibility so that all existing code continues to function with no changes, but I may deprecate the current completefunc focused methodology, either in this next release or shortly thereafter.

in the simplest form, it will look something like this (borrowing from MgSam’s example in the thread above):

function myCode(someOtherListId) {
    /* Ideally, I should be able to do this call SPListNameFromUrl asynchronously
     * as well, perhaps add an optional "async" parameter
     * which, if used, returns the jqXHR object and invokes
     * a callback upon completion rather than blocking until
     * the data returns. This is not the main point of this code though.
     */
    var currentListId = $().SPServices.SPListNameFromUrl();

    //Call 1
    var a = $().SPServices({
        operation: "GetListItems",
        async: true,
        listName: currentListId,
        completefunc: function(xData) {
            //Do something useful
        }
    });

    //Call 2
    var b = $().SPServices({
        operation: "GetList",
        async: true,
        listName: currentListId,
        completefunc: function(xData) {
            //Do something useful
        }
     });

     //Call 3
     var c = $().SPServices({
        operation: "GetListItems",
        async: true,
        listName: someOtherListId,
        completefunc: function(xData) {
            //Do something useful
        }
     });

     /* Now I want to do something once all 3
      * asynchronous calls have completed. Under the current
      * model, there's no easy way to do this.
      * I either have to hand-roll some ugly code,
      * or contort my completefuncs so that they work with
      * jQuery's Deferred.
     */
}

Moving toward the deferred object approach will also mean that I’ll replace the simplistic caching mechanism I put into place using the .data() function in v0.7.2. There will be no visible difference in the way it works, but the underlying mechanism will be deferred promises instead.

SharePoint 2007

I’m also considering finally ending development specifically for SharePoint 2007. I’ve continued to use WSS 3.0 as my development environment since the first release of SPServices so that I can “test up” rather than down *and* up.

I still have a lot of people contacting me with questions who are using SharePoint 2007. In fact, some of my best clients are still on SharePoint 2007. It’s still a great platform that works well for many people who are still using it.

That said, there are some capabilities in SharePoint 2010 and now SharePoint 2013 that I haven’t tried to take advantage of because they aren’t there in SharePoint 2007. It isn’t that I want to abandon the SharePoint 2007 folks, but I’m considering making the main focus SharePoint 2010.

I’m very interested in hearing people’s thoughts on that. There’s no easy way for me to know what versions everyone out there has, so chime in.

Version Numbering

These changes will definitely merit a more significant version bump than just going from 0.7.2 to 0.7.3, of course. Is it finally time to ship a 1.0.0? Or maybe I should just decide to go to 5.3.8 or something. The numbers mean nothing, of course, as long as they go in a consistent direction. I’m even toying with 2013.01. Any thoughts on this?

Thanks to everyone out there who uses SPServices, especially those of you who have contacted me to let me know how it has helped you accomplish your business goals. If you have a story to tell, please consider writing it up for my SPServices Stories series. I’m planning to keep that series going as long as there are good stories to tell.