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

SPServices Stories #9: Developing with Client-side Technologies: jQuery, REST, SPServices and jsRender

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

Introduction

Here’s another SPServices Stories post that is a bit older and talks about how you can use SPServices with other popular plugins and frameworks. While this is possible with the CSOM, I’ve always found that SPServices provides a much tighter and more controllable syntax. You probably can’t trust me on that, though, since I wrote it.

Phil Harding’s post from March 2012 called SharePoint: Developing with Client-side Technologies: jQuery, REST, SPServices and JsRender caught my eye because in it he explains how he used the popular JsRender with SPServices. Many people want to use rendering frameworks in general or specifically in conjunction with SPServices, and JsRender has been a popular choice. Knockout,js is probably becoming more popular, but JsRender still has its place. You should evaluate the options based on your own business requirements to decide what’s best for you.

Keep in mind that is doesn’t really matter what version of SharePoint you’re working with, as these techniques can work well with SharePoint 2007, 2010, and 2013.

Phil Harding (@phillipharding) is an independent SharePoint consultant from Manchester, UK, specializing in development for the 2007, 2010 and (he’s hoping really really soon) 2013.

Developing with Client-side Technologies: jQuery, REST, SPServices and JsRender

Having recently been immersed in developing client-side functionality for a SharePoint 2007 project I thought I’d share some of the techniques and tools I’ve used in doing so.

The functional requirements in this project are pretty standard CRUD operations;

  1. Collect data from the user and create new list items
  2. Display and modify existing data
  3. Display formatted lists of existing data

In this post we are going to retrieve a set of list items from a SharePoint list and display that data using a technique very similar to that used by ASP.NET data bound/templatized controls.

SPServices.

The premier tool IMHO for interacting with SharePoint 2007 from the client is the SPServices library by @sympmarc, check out the documentation as the library is pretty extensive.

Here we will be using the SPServices GetListItems method which wraps the SharePoint Lists web service.

REST.

If you’re working with SharePoint 2010, you might also consider using the REST interface for retrieving data, although this interface by default returns XML, it can also be configured to return JSON, and the jQuery getJSON() method does exactly that by configuring the $.ajax() call to the REST interface appropriately. Why JSON? Well as with SPServices, using JSON encoded results fits very nicely when using jsRender to render your output markup. Some good examples of how to use the REST interface can be found here.

Important!

Ed: Note that as of v0.7.1, SPServices contains a function called $().SPXmlToJson which makes it a no-brainer to convert GetListItems results to JSON.

Choosing a Client Side Data Retrieval Interface

If you’re using SharePoint 2007, your options are (very) limited. By limited I mean, that pretty much your only sane option is SPServices, there are other options of course, 2007 has the [SOAP] List webservice which you could grok against – you’ll have to write a lot of javascript if you want to go that route, but seriously, why bother, SPServices has you more than covered.

If you’re using SharePoint 2010 or 2013, you’ve got the JSOM interface which you can use to read list data, frankly though, given the amount of code you have to write just to read some data, I almost never use the JSOM for this.

SPServices of course is a valid choice to make – as with all things choose the right tool to accomplish your goal. Personally, I tend to use REST when my data needs are typically one-way, i.e. reading, even then I may defer to SPServices if my query is sufficiently complex – the REST interface is pretty damn flexible but it won’t allow you to model complex queries in the way that CAML does.

For updating data back to lists, again you can use REST, JSOM or SPServices. For me, I might typically use JSOM to update list items, sure I can do this using REST, but to do this you’ll have to manage the ETag – another piece of state to maintain (see the Concurrency Management section for more information). I also came across a javascript library recently called data.js, which seems to be a wrapper over OData services, I haven’t used it yet but it definitely looks interesting.

Finally, SPServices has the UpdateListItems method which serves both creating and updating data.

jsRender.

Having retrieved your data from SharePoint, you could of course, write quite a bunch of grungy JavaScript and DOM code to create the rendered display, however there is a much better way, and that is to use something latterly called jQuery Templates, which, like ASP.NET data-bound or templatized controls, allows you to write the markup template for your output and include bits which bind to the data being displayed.

jQuery Templates are officially deprecated in favour of a new but similar technology called jsRender by Boris Moore.

jsRender seems to be gaining a lot of traction and there is a growing amount of information available out there, and the documentation provided by Boris is more than enough to get you going.

Retrieving List Items.

So lets get going, first we’ll write the SPServices code to retrieve a set of list items, which we’ll then iterate over and place into an array of JSON objects (required by jsRender).

function GetListItemData() {
  var container=$('#container');
  $(container).html("");

  // get list data
  var datarows = [];
  $().SPServices({
    operation: "GetListItems",
    webURL:"http://sp2007/sites/demo",
    async: false,
    listName: "The List Title",
    CAMLViewFields:"",
    CAMLQuery:"........",
    completefunc: function(xData, Status) {
      $(xData.responseXML).SPFilterNode("z:row").each(function() {
        var x={
          title: $(this).attr('ows_Title'),
          id: $(this).attr('ows_ID'),
          reviewoutcome: $(this).attr('ows_ReviewOutcome')
        };
        datarows.push(x);
      });
    }
  });
  ..... // display list data
}

Here we use SPServices GetListItems to retrieve the data, amongst other things we supply the following parameters;

  1. operation: “GetListItems”
  2. Optional webURL parameter, if not supplied SPServices will use the equivalent of SPContext.Current [the current Web]
  3. listName: “The List Title”
  4. CAMLViewFields: the list of fields we want returned by the query
  5. CAMLQuery: the CAML query in the form; “<Query><Where>….</Where><OrderBy>….</OrderBy></Query>”

I also specify a completion function which parses and iterates over the returned rows, creates a JSON object and adds it to an array.

Note: SPServices also provides the SPXmlToJson function which will convert the returned rows to an array of JSON objects.

Next we’ll use jsRender to display the data we just retrieved using the supplied template (#reviewTemplate), in this case the template with id reviewTemplate.

function GetListItemData() {
  var container=$('#container');
  $(container).html("");

  var datarows = [];

  ..... // get list data

  // display list data
  if (datarows.length > 0) {
    $(container).html( $("#reviewTemplate").render(datarows) );
  } else {
    $(container).html("There are no items to display.");
  }
}

The return value from jsRender is then set as the html value of a container DIV or other element.

jsRender Templates.

So what’s a jsRender template? Quite simply it is a <SCRIPT /> block with an ID value and a type set to text/x-jsrender, as shown below.

<script id="reviewTemplate" type="text/x-jsrender">
<TABLE cellSpacing="0" cellPadding="0" width="100%">
  <TBODY>
    <TR>
      <TD style="width:400px">
        <A title="{{:title}}" href="{{:~reviewurl(id)}}">{{:title}}</A>
      </TD>
      <TD style="width:20px">
        {{outcome reviewoutcome /}}
        {{if reviewoutcome=='Undecided' tmpl='#reviewundecided' /}}
        {{if reviewoutcome=='Accepted' tmpl='#reviewaccepted' /}}
        {{if reviewoutcome=='Rejected' tmpl='#reviewrejected' /}}
      </TD>
    </TR>
  </TBODY>
</TABLE>
</script>
<script id="reviewundecided" type="text/x-jsrender">
  <IMG style="" title="this is undecided." border="0" src="http://sp2007/_layouts/images/erg-0.gif" />
</script>
<script id="reviewaccepted" type="text/x-jsrender">
  <IMG style="" title="this was accepted." border="0" src="http://sp2007/_layouts/images/erg-2.gif" />
</script>
<script id="reviewrejected" type="text/x-jsrender">
  <IMG style="" title="this was rejected." border="0" src="http://sp2007/_layouts/images/erg-1.gif" />
</script>

Here we’re showing 4 jsRender templates, reason being that a feature of jsRender allows you to use template composition, i.e. to use different templates to render different parts of the output. In this case we have 3 different templates to render an <IMG…/> element according to the value of the “reviewoutcome” object property (which is the listitems ows_ReviewOutcome column value). To summarise whats going on I’ll briefly describe each of the jsRender features we’re using;

Expression Evaluation / Output an Object Property Value

Use the {{:  expression  }} tag to evaluate an expression or output the property value of the current item, this form does not perform HTML encoding of the output

Use the {{> expression  }} tag to evaluate an expression or output the property value of the current item, this form performs HTML encoding of the output

Call a Custom Helper Function

Use the {{:~ customhelper(expression)  }} tag to call a custom helper function that you have written, which optionally accepts parameters, and returns the desired output – in the sample above we created a helper function called reviewurl to build a composite HREF value for an <A /> element.

See below for help writing a custom helper function.

Conditional Processing : If/Else

Use {{if  expression }}…{{/if}} {{else  expression }}…{{/else}} tags to perform conditional processing or branching of the template – in the sample above we used conditional processing to choose a different jsRender template to build <IMG /> elements based on the value of the current items reviewoutcome property value.

Call a Custom jsRender Tag

In the above example we used conditional processing to chose a jsRender template to display different <IMG /> elements, another method of doing this is to write a custom jsRender tag which will do the same thing. To use a custom jsrender tag you have written, use the following form {{mytagname expression /}}. In the sample above we created a custom tag called outcome which accepted the reviewoutcome property value of the current item and returned an <IMG /> element.

See below for help writing a custom tag function.

To create a custom helper function;

A custom helper function, as you might imagine, is simply a JavaScript function which optionally accepts parameters and returns something for display. Usefully the function can reference global variables in the page, as you can see I’m referencing global variables g_rlid and g_thispageurl.

/* jsRender helper function */
$.views.helpers( {
  reviewurl:function(fid) {
    var m="mypage.aspx?";
    m+="form=Display";
    m+="&list="+g_rlid;
    m+="&id="+fid;
    m+="&Source="+g_thispageurl;
    return m;
  }
});

To create a custom jsRender Tag function;

A custom tag function, is also a JavaScript function which optionally accepts parameters and returns something for display.

/* jsRender custom tag */
$.views.tags({
outcome: function(value) {
var ret='';
switch(value) {
case 'Undecided':
ret="<IMG style='' title='' border='0' src='http://sp2007/_layouts/images/erg-0.gif' />";
break;
case 'Accepted':
ret="<IMG style='' title='' border='0' src='http://sp2007/_layouts/images/erg-2.gif' />";
break;
case 'Rejected':
ret="<IMG style='' title='' border='0' src='http://sp2007/_layouts/images/erg-1.gif' />";
break;
}
return ret;
}
});

Putting all this together we get something that is ripe for a little CSS beautification.

http://i0.wp.com/platinumdogs.files.wordpress.com/2012/03/sps-01.png?w=440

There’s much more to go at in jsRender, including some interesting effects involving hoverstate and re-rendering of item display using dynamic templates. Hopefully the documentation (and feature-set) will continue to improve and that this post has provided a leg up should you wish to start using it.


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

$
0
0

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.

SPServices Stories #10 – jqGrid Implementation Using SPServices in SharePoint

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

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 2013.01 Is Underway – Learnings Ensue

$
0
0

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 2013.01ALPHA4 Returns a Deferred Object (Promise)

$
0
0

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.

SPServices Stories #11 – Using SPServices and jQuery to Perform a Redirect from a SharePoint List NewForm to EditForm

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

Introduction

I ran across this SPServices Story in a post on the blog at CTS. The author of the post, Matt Ingle, is a Senior Consultant at CTS in Birmingham, AL. According to Matt, he is a team lead for a group called the SharePoint Factory. The SharePoint Factory team roles correspond to the various areas of the SharePoint family of products. This role-based approach allows for technical depth and specialization within the SharePoint functional areas.

SPServices has a function called SPRedirectWithID, but it’s problematic in SharePoint 2010 due to the dialogs that SharePoint uses to show the list forms. You can ensure that the function works just fine by turning off the dialogs, but that’s not always desirable. I’d turn them all off if I had my druthers because most list forms require more screen real estate than the dialogs provide without a lot of scrolling. They also don’t obviate the need for a ca-chunk postback when you save the item.

Given this state of things, Matt came up with a way to accomplish a similar thing in a different way. Here’s Matt’s take on the task. It’s a nice way to go if your information architecture is very clear and you know that your forms won’t change much, if at all, since Matt is bypassing the simple NewForm we get for “free” from SharePoint. On the other hand, this type of approach may become more common as we move forward with SharePoint and HTML5 compatibility.

Using SPServices and jQuery to Perform a Redirect from a SharePoint List NewForm to EditForm

By Matt IngleMatt Ingle

Problem: In SP 2010, you have a parent list and multiple child lists connected through Lookup columns. You have created the custom Display and Edit forms for the parent list containing XsltListView web parts for the child lists filtered through query string view parameters. Refer to this article for more information on this solution.

Now you need a way to redirect the user from the NewForm to the EditForm after saving the parent list item while passing the newly created item ID as a query string parameter. The problem is there is no way to do this out-of-the-box (OOB).

Solution: You can use jQuery and the SPServices library from CodePlex to utilize SharePoint’s Web Services. The two operations I will use in the following example are GetUserInfo and UpdateListItems.

Note: Be sure to read the IMPORTANT NOTES on the SPServices Home page about supported versions. This example will use the (“[nodeName='z:row']“) syntax which no longer works with jQuery 1.7. I will be using jQuery 1.6.2 and SPServices 0.6.2 in my example. However, if you want to use 1.7, an alternative solution to using this syntax can be found here by Steve Workman. [ed: Use the SPFilterNode function, which will work with any version of jQuery: .SPFilterNode("z:row") and ensures cross-browser compatibility.]

By using the SPServices operation to create the new list item you will be bypassing the OOB Save process for the SharePoint list form. This means that you will lose the validation on the form and will need to create your own validation logic using jQuery. In the following example I will be implementing the required field validation for my list fields.

Example: At this point you should already have a custom NewForm for your parent list.

Below each field to be displayed on the form, add some text wrapped in a <span> tag with unique ids that will be used to display our custom validation message. I worded mine the same as the text displayed by SharePoint. Notice my id for the <span> tag below is ‘valBU’. We will use jQuery later to select this element by the id.

clip_image0024

Additionally, we will need to add a custom button to our form in place of the OOB Save button. This will allow us to attach our custom code to the Save button’s click event.

clip_image004

Next, add a Content Editor WebPart (CEWP) directly below the DataFormWebPart. This will hold our jQuery/SPServices script.

In the CEWP, add the script references to the jQuery and SPServices libraries. I uploaded mine in a new ‘Scripts’ folder stored in the ‘Style Library’ folder so that it is accessible to other pages in the site as needed.

To begin the script, add code to hide the text containing our custom validation messages. Place this code in the $(document).ready function. Next, add a line to attach the method CreateNewItem to the Save button’s click event.

clip_image006

Next, we can implement our custom validation logic. The following method will be called in our CreateNewItem method to validate our form before creating the new item. It starts by using jQuery selectors to obtain the values of the list fields on the form. We can use the ‘title’ attribute for the selectors which contains the name of the list field. In my example, I have 2 drop-down list boxes so I am looking for select elements. For a normal text field you would look for input elements.

For a PeoplePicker, the selector used is a little different. There are different ways to obtain this value, but for our purposes (looking for a single PeoplePicker on the page) you can just look for a textarea element with the title ‘People Picker’. If you have multiple PeoplePickers on the form then you would need to research a way to be more selective. [ed: You can use the SPFindPeoplePicker function in SPServices for this.]

When you have an empty PeoplePicker value you will get ’&#160;’ which is the HTML code for a non-breaking space. Simply check for this value versus an empty string as done for the other fields.

This method counts the number of empty fields and toggles display of the custom validation messages. If there are no empty required fields then it returns true, false otherwise.

clip_image008

Now we can implement the CreateNewItem method. First, use jQuery to select the form fields and obtain the values.

Again, we need to do something different for the PeoplePicker. The value of the PeoplePicker contains a lot of stuff we don’t really need so we have to parse through it to find the user login name.

With this value stored in the ‘buFinContollerPerson’ variable, we will supply it to the ‘userLoginName’ parameter for the GetUserInfo operation. This will be a value like ‘EXT\mingle’.

We are calling the GetUserInfo operation in order to get the correct format needed to save the PeoplePicker value to the list. This format is ’37;#Matt Ingle’ (<UserID>;#<UserName>).

clip_image010

To complete the CreateNewItem method we will call the UpdateListItems method. To specify the creation of a new list item you need to specify “New” for the batchCmd parameter. In addition, supply the list display name (one shown in UI) for the listName parameter. The values to be submitted for the new list item are specified as value pairs in the valuepairs parameter.

Note:We are supplying the fullUserName obtained from the GetUserInfo operation as the value for the PeoplePicker field BUFinContollerPerson.

Once the operation has completed we will parse the XML response to find the newly created item ID. This is where we use the (“[nodeName='z:row']“) syntax. This value is supplied for the ID query string parameter to redirect the user to the EditForm.

Also notice the <div> tag below the <script> tag. This is where you can specify the location of the debug output for the SPServices operations. Simply uncomment the calls to SPDebugXMLHttpResult to see the operation results displayed. Be sure to comment out the redirect though when debugging, otherwise you will not stay on the page to see the results. :-)

clip_image012

SPServices Stories #12 – SharePoint and jQuery SPServices in Education: A Case Study

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

Introduction

Ben Tedder (@bentedder) has long been a fan of SPServices and his writing about it is great, to boot. I liked this post back when Ben first put it up on his blog because it really does tell a story, and SPServices plays a significant role in it.

Ben has built quite a few real-world solutions using SPServices that transcend what SharePoint can do. By using SharePoint as the back end data repository, Ben has managed to create solutions that provide a great user experience and are far more efficient than SharePoint can be when building through the UI. He does this without deploying any server-side code, just as I always aspire to do. Great solutions with minimal footprints.

Thanks to Ben for letting me republish this post from his blog.

SharePoint and jQuery SPServices in Education: A Case Study

24 Sep 2012

bentedder-profile

This is a morphed, updated, renewed version of the SharePoint Scheduling Assistant. I’m not releasing this version quite yet, but this is a brief case study on how it’s working at a specific school.

Business Needs

The International School of Beijing needed a way for Elementary School parents to book time slots with teachers during bi-annual parent-teacher conferences.

The requirements:

  • Easy to use (training is impossible).
  • Integrate into the current intranet portal.
  • Sync with the database to omit any kind of manual setup for secretaries.
  • Deny parents the ability to book more than one slot with the same teacher.
  • Allow parents to book the same slot for two different teachers (in case mother and father come in and meet with 1 teacher each to save time)
  • If two or more parents are online at the same time, correctly queue the submissions so a double booking does not occur.
  • Allow teachers to pre-block out slots where parents cannot book times.
  • Allow teachers the ability to see their entire schedule, including which student is coming in during which time slot.

Solution (SharePoint, jQuery, and SPServices)

A SharePoint solution was built with jQuery to meet the requirements of the Elementary School. Let’s break down how the solution was created:

Part 1 – SharePoint

Within SharePoint three lists were created:

  • Bookings (Each reservation had its own row in this list that stored the details of who booked it, for which teacher, etc.)
  • Time Slots (A pre-determined list of time slots…in this case a list item was created for each 20 minute time slot from 12-6pm on Thursday and Friday Oct 18 and 19).
  • People (An external content type that pulled records from the database for each student, each of their parents, and each of their teacher names)

One of the trickier parts of this solution was grabbing the external content from the database in a usable way. Once it was in, we were able to work with the data. However, this list has 10,000+ items, so dealing with larger data like this was quite challenging. Enter jQuery.

Part 2 – jQuery and SPServices

The bulk of this solution was created with the jQuery and jQuery SPServices Libraries. Using jQuery, we stepped through the process like this:

  1. Get the details of the logged in user (parent) using SPServices
  2. Hit the SharePoint external data list once, filter it by Parent (matching to the Parent login ID), and store it in a javascript object for later use. This was the biggest strain on the system. We minimized the data call to only one time, but even still, pulling a list of 10,000 records even once isn’t super quick.
  3. Get all the children of the logged in parent, push them to a drop-down menu
  4. Get all the teachers of the selected child from #3, push them to another drop-down menu
  5. Get all time slots from the Time Slots list, push to a third drop-down menu
  6. Retroactively disable all time slot options that have already been booked (by searching through the Bookings list for records that match the time slot and the selected teacher)

Once the data was present, the parent could interact with it in two ways, book, and delete.

To book a time slot, the selected child, selected teacher, and selected time slot (along with a comments area) was submitted and saved to the list. To delete a time slot, a delete button is appended to each reservation in the on-screen schedule with the ID of the reservation as the ID of the link element.

The data from the Bookings list is checked once during the time slot selection process, and again during the form submit process to make sure no double bookings have occurred.

Benefits

This solution enables, for the first time at this school, parents to go online and manage their entire Parent-Teacher Conference schedule. Also, as an added bonus, if a mother and father both login, they can see the complete schedule for their child, even if only one of the parents made the bookings.

Once a parent books a time slot, they receive an email containing the details of their reservation.

An additional feature of this solution was that it was duplicated and tweaked for teachers to use a similar interface to pre-block out time slots where they did not want parents to come (ie, lunch, coffee break, going home). In that scenario the “one-block” restriction was removed for teachers, allowing them to customize when parents would be given the option to come in.

SPServices and Migration to SharePoint 2013

$
0
0

I get many great questions in the SPServices Discussions (that’s the best place to ask questions about SPServices, IMHO, not the MSDN forums, or StackExchange, or on the Documentation pages on the SPServices site, where I rarely see them). Some of them deserve to get wider exposure by becoming a blog post, and here is one of those.

The question came in today from jshoaf and was titled Migration to SharePoint 2013:

I’m new to SPServices and I’m using it to develop on SharePoint 2010.  I’m using SPServices 0.7.2.  My organization will be upgrading to SharePoint 2013 sometime in the future.  What will I need to do (if anything) when the new SharePoint server is installed to keep using the SPServices library?  Primarily I’m using GetListItems and Query operations.

and here is my answer:

Unfortunately, the answer will have to be the dreaded “it depends”.

The SOAP Web Services are still present in SharePoint 2013, though Microsoft has decided to deprecate them. What that will mean in reality is anyone’s guess. There are lots of deprecated pieces of functionality (think sandbox) that would very difficult to remove.

The bigger question is around what you do with the results and such. The DOM in SharePoint 2013 has changed, just as it did from 2007 to 2010. If your code is nice and modular and you are using clean selectors, I would guess that you will have to re-test, perhaps adjust the code, and most likely adjust the CSS.

Keep in mind that many of the adjustments may be outside SPServices itself and just in your own jQuery / JavaScript and CSS.

The upshot of this is that there simply can’t be a simple answer. The core of SPServices will work in 2013. My testing hasn’t been extensive enough to test every single operation, but the SOAP Web Services are there in 2013 and they work the same.

As for the value-added functions, it looks like the list forms in 2013 are essentially the same as in 2010 and even 2007. It’s mind-boggling, isn’t it? One would think – at least I do - that there were so many opportunities for improvement.  I was extremely surprised when this was the case going from 2007 to 2010, and I’m incredulous that the forms are the same going from 2010 to 2013. But so it is.

The good news about having those frumpy forms stay the same is that the value-added functions that enhance them will generally work. Again, my testing hasn’t been extensive, but the functions everyone know and love – SPCascadeDropdowns being the primary one – seem to work fine. Of course, the whole iitem creation and editing expereience has been widened so that there are more possible ways to accomplish them.

Another question I often get is “Have you rewritten SPServices for 2013?” The answer for that is “no”, as there isn’t really any need, based on the details above. However, if you decide to use SPServices with SharePoint 2013 and you run into issues, I *definitely* want to hear about them – post to the SPServices Discussions. I think SPServices has a good few years left in it, and I don’t want there to be bugs with 2013 or 2007 or 2010 if I’m able to fix them.

I’m in the midst of working on a new release of SPServices that will return jQuery .Deferred() objects (aka promises) from SPServices calls. (See SPServices 2013.01ALPHA4 Returns a Deferred Object (Promise)) One of the reasons I’m doing this is that it will bring SPServices forward to reflect better coding practices if or when you may decide to move to REST-based calls to SharePoint instead of using SPServices to make SOAP calls. In other words, even if you decide to stick with SPServices, you’ll be using an approach that will make it easier to move forward with SharePoint as it evolves.

Remember that SPServices is open source. I rely on you, the community that uses it, to let me know what works and what doesn’t. There is just not enough time in a day for me to test everything. If I hear about problems, I try to get fixes out as soon as I can, but this is free software, folks. The best situation is one where someone runs into a problem, they devise a fix, and I get the fix to incorporate into future versions. That was my understanding about open source when I got started with SPServices in 2009, but in reality, it just isn’t true most of the time. Wouldn’t it be nice if it were, though?

If you have thoughts or concerns about all this, by all means let me know, preferably in the SPServices Discussions. (Have I mentioned that is the best place to get help with SPServices) Comments here are always welcome as well.

Important!

This is what I’m talkin’ about! This afternoon, I got a request for functionality along with the proposed fix. Take a look at the Add support to allow SPUpdateMultipleListItems to use folders item in the Issue Tracker. I’m adding it into the new alpha for the 2013.01 release right away. This is how you can get what you’d like to see in SPServices, for sure.

Using _spPageContextInfo to Determine the Current SharePoint Context in Script

$
0
0

Sahil Malik had an extremely useful little post about a month back that I emailed to myself and then promptly forgot. Today, Mikael Svenson pointed back to Sahil’s in a post of his own that is also very useful. I’m going to steal a little from both posts for this one. I hope Sahil and Mikael don’t mind. I’ll ask for forgiveness later.

Mikael’s trick shows you An easy way to accomplish Home navigation links regardless of the path of your site collection just by looking at a JavaScript variable that SharePoint provides us. Knowing the current context is something I struggled with when I first was working on SPServices, and I came up with some monkey business with the WebUrlFromPageUrl operation in the Webs Web Service and some other URL hackery to figure it out. Since then, I’ve layered in checks for some of the variables that SharePoint 2010 provides to avoid Web Service calls, where possible. In the upcoming 2013.01 release, I’ll add in a few more, and even some that take advantage of some of the additional variables in SharePoint 2013. Of course, there will be fallbacks for anything that isn’t available, so the library continues to be version-resistant.

Mikael linked back to Sahil’s post _spPageContextInfo is your new best friend. In Sahil’s post, he lists the values which are available in SharePoint 2010 and 2013 in every page. Here’s the table that Sahil provides, but I’ve sorted the data alphabetically and changed the example URL to make some of the values more clear. It’s worth noting that there isn’t an SP2007 column in Sahil’s table. That’s because the _spPageContextInfo variable wasn’t introduced until SharePoint 2010. For SharePoint 2007, it’s still monkey business and URL hackery. After all, no one uses script with SharePoint 2007, right?

Assuming we are on the page https://sharepoint.com/sites/demos/SitePages/Home.aspx

Important!

2013-03-27 – Added L_Menu_BaseUrl for SharePoint 2007 based on Christophe’s helpful comment below.
Name SP2007 SP2010 SP2013 Example Value
alertsEnabled X X false
allowSilverlightPrompt X X true
clientServerTimeDelta X 17856
crossDomainPhotosEnabled X true
currentCultureName X en-US
currentLanguage X X 1033
currentUICultureName X en-US
L_Menu_BaseUrl X /Sites/Demos
layoutsUrl X _layouts/15
pageItemId X X 1
pageListId X X GUID
pagePersonalizationScope X 1
serverRequestPath X /Sites/Demos/SitePages/Home.aspx
siteAbsoluteUrl X http://sharepoint.com
siteClientTag X 21$$15.0.4454.1026
siteServerRelativeUrl X /sites/Demos
systemUserKey X 1:0).w|<sid>
tenantAppVersion X 0
userId X X 30
webAbsoluteUrl X http://sharepoint.com/sites/demos
webLanguage X X 1033
webLogoUrl X _layouts/15/images/siteicon.png
webPermMasks X Object with properties High & Low
webServerRelativeUrl X X /Sites/Demos
webTemplate X 1
webTitle X Demos
webUIVersion X X 4 (2010) or 15 (2013)

Using Git with SPServices on Codeplex

$
0
0

gitlogo@2xThis git thing gets a lot of press these days and I figure it’s time to learn it. I selected git as the code repo on the SPServices Codeplex site quite a while ago but never used it. Now I’m trying to figure out what would be useful.

SPServices has been a single file, as most of you know. However, each release has been made up of two js files (one minified, one not) and a license.txt, all in a ZIP file.

SPServices 0.7.2 ReleaseI’d like to be able to use git to:

  • Accept code from others and easily (relatively) merge it
  • Track changes better than I have been just on my hard drive and in my WSS 3.0 development environment
  • Provide offline documentation – This is a tricky one because I’ve used the Documentation wiki on Codeplex as my repository and I’m not sure how to pull the content out.
  • What else?

I welcome any thoughts you may have. I’m especially interested in how you think I can best accomplish the bullets about with a minimum of muss and fuss for everyone.

Paul Tavares (@paul_tavares) has already started a thread on the SPServices Discussions to get the ball rolling. Please jump in with your ideas or comment here.

SPServices Stories #13: Durandal SP3: Developing SharePoint SPAs Made Easy

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

Introduction

Rainer Wittman (@RainerAtSpirit) and I have been emailing back and forth for a few weeks now. He’s been trying out the new alpha of SPServices 2013.01 which includes jQuery promises aka Deferred objects capabilities (soon to be a real release, I promise!) with the Durandal JavaScript framework. Durandal is a framework for building SPAs, or Single Page Applications.

This is something that SharePoint 2013 does to some degree with the Minimal Download Strategy (MDS). If you’re interested in how MDS works, here are some posts that go into more detail about it:

Those of you who are purists will tell me that I’m comparing apples and oranges, and that’s sort of true. However, the goals are similar: send as little of the page transitions down the wire as possible to make those transitions as fluid and responsive as possible. We see this sort of approach more and more, especially in mobile interfaces.

I’ve done SPA-like things even with SharePoint 2007 using AJAX. By maintaining the overall chrome of the page (top nav, Quick Launch, footer) as a constant, we can do something as simple as AJAXing in a new copy of the same page, parsing out the main panel, and replacing it in the user’s current copy of the page in the DOM. You also see this in SharePoint 2010 with the asynchronous refresh capability available with some Web Parts (See: Asynchronous
Update in SharePoint 2010
from @wonderlaura).

If you haven’t heard of Durandal (I hadn’t), here’s some information swiped from the Durandal home page:

Comfortable

We didn’t try to re-invent the wheel. Durandal is built on libs you know and love like jQuery, Knockout and RequireJS. There’s little to learn and building apps feels comfortable and familiar. Dive in and enjoy.

Feature-rich

MVC? MVP? MVVM? Yes. Messaging, navigation, modals? Check. Durandal has the features you need to build whatever apps you can imagine; the apps of today and of tomorrow. Let your creativity soar.

Versatile

Building an Android phone app? An enterprise LOB targeted at Windows? A web gaming platform? No matter how large or small the app, it’s effortless with Durandal….and we give you the tools to develop on any platform, for any platform.

I expect we’ll hear more from Rainer about this cool way of building SharePoint interfaces. As you read through his post below, you’ll probably be very impressed to see that his approach works for SharePoint 2007, 2010, or 2013. If you’re still on 2007 and woulds like to make your installation fell a heck of a lot more “modern”, this is an approach that has legs going forward. You’ll also see some links in his post to live, working demo pages so that you can get a feel for what he is driving at.

This article was originally posted on Rainer’s blog at Durandal SP3: Developing SharePoint SPAs made easy.

Durandal SP3: Developing SharePoint SPAs Made Easy

Hi there,

In the last post I introduced you to Durandal a SPA Framework, which can be used to create SPAs on top of the SharePoint infrastructure. This time we’re going to leverage Marc Anderson’s SPServices as a data service for our SPA.

The obvious advantage is that web services are around for a long time, so one SPA (same code base) runs in SharePoint (2003), 2007, 2010 and 2013.

The obvious disadvantage is that web services are deprecated in 2013, so they might go away with the next major release. If you are already running on 2010/2013 and don’t have to support older browser versions you might want looking into using REST services with JayData or Breeze instead… but that’s worth another story.

Seeing is believing so here a couple of screen shots using IE versions that roughly matches the SP release time.

SharePoint 2013 | IE 10: 2013-04-08-SP2013Demo

SharePoint 2010 | IE 8:

2013-04-08-SP2010Demo

SharePoint 2007 | IE 7:

In SP2007 you’ll notice some issues with the CSS that is used in the SPA otherwise it’s fully functional. That is less an issue with IE7, but more with SP2007 that is running pages in Quirks mode. A quick workaround would be to apply a DOCTYPE to the pages that host your SPA.

2013-04-08-SP2007Demo

SharePoint 2003 | IE 6:

Sorry, I’m out of historic SharePoint VMs. Please give me a shot if somebody has still access to a SP2003 environment. I’d love to add the missing screen shot.

Now, after hopefully getting you excited, here’s the bad news. The demo is using the alpha release 2013.01ALPHA5 of SPServices, so I won’t make the demo code available through Github as usual. I’ll update this post once the next official version of SPServices has been released and tested with Durandal SP3.

The good news is that there are two public available pages that allows you to go hands-on. Both are best viewed with your favorite console open.

  1. development version:
  2. optimized build:

Without going into the details let see what the Promises demo app is doing when you activate the list route (/#lists).

  1. Retrieve information about the lists in the current site via SPServices GetListCollection method (with caching)
  2. Retrieve detail information about the selected list via SPServices GetList method and build a mapping that can be used in SPServices SPXmlToJson method (with caching)
  3. Retrieve item information via SPServices GetListItems (NO caching)

Update 2013/04/11: The life demo was updated based on some feedback I got. The list overview now produces a configurable row view of lists with meta information like ItemCount and Last Modified. Step 2 and 3 are performed once you select a list.

By opening up the optimized build and filtering the network tab for XHR you can see the three POST requests to lists.asmx nicely lined up. Once you start selecting other lists, you’ll noticed that the number of XHR requests decrease as more and more cached information becomes available.

2013-04-08-OptimizedBuild

The development version on the other side shows far more details via the console. That allows you to get familiar with Durandal’s application life cycle and to inspect the Json result of some spdata methods.

2013-04-08-Development

After reading so far and hopefully seen the Promises demo in action by yourself, I hope that you share my excitement of the upcoming promise support in SPServices. Once it’s part of an official release SPServices will become a perfect fit for Durandal’s life cycle methods.

Comparing SPServices 2013.01 Calls with Async vs. Promises Methods

$
0
0

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

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

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

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

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

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

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

Here’s the code:

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

$(document).ready(function() {

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

    getRecentAnnouncementsAsync();

  });

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

    getRecentAnnouncementsPromises();

  });

});

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

function getRecentAnnouncementsAsync() {

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

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

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

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

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

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

          processAnnouncement(i, $(this));

        });

      }
    });

  }

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

}
function getRecentAnnouncementsPromises() {

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

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

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

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

  }

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

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

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

        processAnnouncement(i, $(this));

      });

    }

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

  });

}

function getNow() {

  return new Date();

}

function announcementHeader() {

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

}

function processAnnouncement(i, item) {

  out += "<tr>";

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

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

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

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

  out += "</tr>";

}

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

There are two big benefits of using the promises method:

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

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

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

Here are the results in SharePoint 2013:

Async

image

Promises

image

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

Here are the results from WSS 3.0:

Asynch

image

Promises

image

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

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

But don’t take my word for it. go and grab your own copy of SPServices 2013.01 and try it yourself.

jQuery Library for SharePoint Web Services (SPServices) 2013.01 Released

$
0
0

Tonight I’ve released SPServices 2013.012. 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 nice new functionality. Thanks to everyone who downloaded the beta and provided feedback.

jQuery Promises

By far the most exciting thing in this release is jQuery promises, or deferred objects. I’ve written about this several times already herehere and here. I won’t belabor the point too much, but 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.

Other Release Notes

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. I’ve gotten a bit lazy with the release notes, so for this release the items in the Issue Tracker contain all of the details.

SPServices Stories #14: Create a Slide Show in SharePoint 2010 using an Announcements List and SPServices

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

Introduction

It’s been a while since I’ve posted a new SPServices Story. This one was sitting in my draft bucket for way too long, so a big sorry goes out to Trace Armstrong (@TraceBArmstrong).

Trace wrote this post about using SPServices to drive a slideshow using an announcements list as the source. This is the sort of common use case that SPServices often sits behind as part of the underlying plumbing.

If you’re using SharePoint 2013 or Office365 running the 15 wave, you might say that one should use REST instead, and that’s an option of course. The nice thing about using SPServices for stuff like this is that if you are on SharePoint 2007 or 2010 or 2013, the SPServices calls are exactly the same. So in a sense, we’ve got easily upgraded code.

Note: I’ve made a few small changes to Trace’s code. One change was to replace .find(“[nodeName='z:row']“) with .SPFilterNode(“z:row”). This is required to make things work with up to date versions of jQuery.

Create a Slide Show in SharePoint 2010 using an Announcements List and SPServices

twitter_trace_thumb1By Trace Armstrong

Recently, I had a client who wanted a different slide show for their SharePoint 2010 intranet home page. The original slide show ran using a custom web part that integrated the Nivo Slider, a jQuery based solution. The solution worked great but the person maintaining the news portion of the slider needed a no-code solution for easier updating.

I searched the Internet for a solution and came across this blog (https://www.nothingbutsharepoint.com/sites/eusp/Pages/Creating-your-own-Content-Slider-for-SharePoint.aspx) by Eric on http://www.nothingbutsharepoint.com. The solution was similar to what I needed but my task also required a place for slideshow images.

The first step was using the Announcements list for the announcements slider. I then created two custom list columns, “Image” and “Body”. The “Image” column is a “Hyperlink or Picture” column type and “Body” is a “multiple lines of text” column type.

Using Eric’s model, I referenced SP Services, jQuery, jShowoff, and set about editing the JavaScript code to pull an image column from the Announcements list.

The revised JavaScript looks like this:

$(document).ready(function () {
  var emptyResults = "<div class='sliderDiv'><p class='announceTitle'>Company Name</p></div>";
  var maxVal = 0;
  var toShow = false;
  var heightTemp = 0;
  $().SPServices({
    operation : "GetListItems",
    async : false,
    listName : "Announcements",
    CAMLViewFields : "<ViewFields><FieldRef Name='Title' /><FieldRef Name='Image' /><FieldRef Name='Body' /><FieldRef Name='Modified' /></ViewFields>",
    CAMLQuery : "<Query><OrderBy><FieldRef Name='Created' /></OrderBy>" + "<Where><Or><Geq><FieldRef Name='Expires' /><Value Type='DateTime'>" + "<Today /></Value></Geq><IsNull><FieldRef Name='Expires' /></IsNull></Or></Where></Query>",

    completefunc : function (xData, Status) {
      var itemCount = $(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount");
      if (itemCount > 0) {
        toShow = true;

        $(xData.responseXML).SPFilterNode("z:row").each(function () {
          var modDate = $(this).attr("ows_Modified");
          modDate = modDate.substr(5, 2) + "/" + modDate.substr(8, 2) + "/" + modDate.substr(0, 4);

          var titleHtml = "<div class='sliderDiv'><p class='announceTitle'>" + $(this).attr("ows_Title") + "</p>";
          var imgBody = "<img class='anImage' src='" + $(this).attr("ows_Image").split(',')[0] + "'></img>";
          var bodyHtml = "<p class='announceBody'>" + $(this).attr("ows_Body") + "</p>";
          var expireHtml = "<p class='announceFooter'>Modified: <span>" + modDate + "</span></p></div>";

          //div announcements is added by jshowoff js.

          $("#announcements").append(titleHtml + imgBody + bodyHtml + expireHtml);

        });

      } else {

        $("#announcements").append(emptyResults);

      }

    } //completefunc

  }); //SPServices

  if (toShow == true) {
    $('.sliderDiv').each(function () {
      heightTemp = $(this).height();
      if (heightTemp > maxVal) {
        maxVal = heightTemp
      };
    });
    $('#announcements').css('min-height', maxVal);
    $('#announcements').jshowoff({
      speed : 12000,
      changeSpeed : 3000,
      controls : true,
      animatePause : false,
      effect : 'fade',
      cssClass : true,
      links : true
    });
  } //if stm
}); //ready

I edited a few syntax issues that were unnecessary to the solution and created/referenced a new variable, “imgBody”. The variable adds the img class “anImage” and retrieves the slide show image from the list column “Image”. One of the problems in the solution was SharePoint adding a comma to the end of the file name. The split property removes the comma and the image displayed as intended on the site.

The solution appears like this on the client’s main page.

Slide-ShowThis solution enabled the company to have a no-code update to their main page news slider using an image library and the Announcements list.

SPServices Stories #15: Custom Client-side Calendar Query for Office 365 SharePoint Site Using SPServices jQuery Library

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

Introduction

My friend and fellow MVP Becky Bertram (@beckybertram) recently acquiesced to my suggestion to try using SPServices to solve a challenge she had in rolling up calendar items across lists. I know it may often seem that since SPServices is my hammer that I always say to whack the nails with it, but sometimes it’s actually a good suggestion!

Becky wrote up a nice overview of her approach on her blog. It’s always interesting to see the differences in how people use SPServices. Becky built herself some great functions, which allows for better reuse over time. It amazes me sometimes how others end up with hundreds and hundreds of lines of code rather than coming up with generalized functions, as Becky does here. Sure, some SPServices users aren’t “developers”, but that doesn’t mean that they can’t learn from the examples and build stronger code for it.

Note that the REST services also let you access calendar items (or any other list items), but CAML gives you a better syntax to use with recurring events than I’m aware of in the REST interface. Yup, sometimes, I prefer CAML over the newer stuff.

Check out Becky’s blog for lots of other great SharePoint tips and tricks, too!

Custom Client-side Calendar Query for Office 365 SharePoint Site Using SPServices jQuery Library

I’m building an Office 365/SharePoint Online intranet site for a client and they wanted to show a list of the current day’s events from various calendars on the site, in a format like this:

8:30​ a.m. ​Staff Meeting ​Room 103
​10:00 a.m. Training ​Cafeteria
​3:30 p.m. Retirement Party ​Conference Room

As you might know, you can’t use a normal CAML query to retrieve calendar entries if you want to retrieve reoccurring events that happen within a given timeframe. (If the first instance of a reoccurring event happened outside the timeframe you queried, the event would not be retrieved, even if it had reoccurring events that happened during the queried timeframe.) The Content Query Web Part will not do the trick.

On projects past, I’ve simply created a web part with a custom CAML query that utilizes the DateRangesOverlap node, and installed that web part using a solution package. This being Office 365, that’s not an option. I could have created a sandbox solution containing the web part but that’s also not a preferred approach since Microsoft seems to be deprecating sandbox solutions. At the urging of Marc Anderson, I tried using his SPServices library.

The SPServices library is a jQuery library that accesses SharePoint web services. By attaching your library to a particular Office 365 SharePoint URL, you can retrieve list items using the SharePoint web service, then use them with jQuery. There’s a discussion thread [on the SPServices Codeplex site - lots of other great stuff there, too!] about how to use SPServices to retrieve calendar entries. I decided to modify the file provided in that thread and use it. The main modification I needed was the ability to combine calendar entries from more than one calendar and show them in a common calendar. This meant utilizing a sorting function after entries from multiple calendars were retrieved so all entries would be listed in the proper order.

You can download my calendar.js file here.

Once I had added the script to my site, I added the following lines of code to my page layout in the header:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/jquery-ui.min.js" type="text/javascript"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/0.7.2/jquery.SPServices-0.7.2.min.js"></script>
<script language="javascript" src="/Style Library/Scripts/calendar.js" type="text/javascript"></script>

<script type="text/javascript">
$(document).ready(function (){
  CallSPServicesCalendar("https://[sitename].com/site1", "Calendar 1");
  CallSPServicesCalendar"https://[sitename].com/site2", "Calendar 2");
  $('div.calendarItem').sortElements(sortByDate);
  if ($('div.calendarItem').length == 0) {
   $("#calendarData").append("There are no events scheduled for today.");
  }
 });
</script>

The script block at the bottom passes in the URL of the web being queried as a first parameter, and then the name of the calendar list as a second parameter. CallSPServicesCalendar is the name of my function in calendar.js that retrieves calendar entries. At the bottom of my calendar.js I have a function that sorts the calendar entries, which you can see is being called with .sortElements in the script above. If no list items are retrieved, a message is displayed saying there are no events. If you want to query more calendars, simply add new calls to the CallSPServicesCalendar function.

In the calendar.js file you’ll notice a section with Field nodes that get passed into the CAML query. You can modify this section to add your own fields if you have custom fields you want to retrieve and display.

In the body of the page, I added a div tag like this, and this is where the event information was added in the page:

<div id="calendarData"></div>

In the calendar.js file, you can modify the HTML that gets rendered for each calendar entry.


SPServices Stories #16: Beginning SharePoint Development with KoSp – Knockout for SharePoint, REST API and SPServices

$
0
0

Introduction

There were a few tweets from Ashok Raja today that caught my eye.

There was a problem connecting to Twitter.

There was a problem connecting to Twitter.

KoSpAshok has released a new Codeplex project called KoSp which helps with the bindings between SharePoint data and Knockout. Knockout has become a very popular library to use client side to manage data and Ashok’s tool should come in handy for anyone who decides to use it.

KoSp, Knockout for SharePoint 2013 and SharePoint 2010 is a knockout binder extensions for SharePoint REST API and SPServices. KoSp provides custom knockout binding handlers that enables easy binding of json data from SharePoint lists retrieved via REST API with oData queries and  SP Services with CAML queries with client side controls.

Now, I’ll admit that I haven’t had time to try this out. After all, Ashok just released it today. I did take a quick look through the JavaScript file, though, and it looks good.

Ashok’s posts give examples of how to use KoSp with both REST calls and SOAP calls using SPServices. Which you decide to use is really up to you and which version of SharePoint you’re using. If you’re on SharePoint 2007, SPServices is definitely the ticket, on SharePoint 2010 and 2013, you have a choice of REST or SOAP. What you choose should be determined by your requirements and your available skill sets.

I wanted to reproduce the “Beginning” post from Ashok’s blog here to help give him some publicity for his efforts on KoSp. I know how it can be frustrating to do great work, put it on Codeplex, and then just hear the crickets. Given that Ashok has just released the project, I’m sure he’d love to have some downloads, but even better, some feedback on how it works for others and what they would like to see next. If you’re interested in using Knockout with SharePoint, give it a look.

As with all of the posts written by others in the SPServices Stories series, I’ve done some minor editing and added a few notes, but otherwise, the content is all Ashok’s. Enjoy.

Beginning SharePoint development with KoSp – Knockout for SharePoint , REST API and SPServices

Follow the below steps to build your first KoSp-based SharePoint application. The first method explains the REST API mode and the next method explains the SPServices mode.

Method 1: Data via Rest API

Step 1

Download ko.sp.min.Ex.js, knockout.js, jquery.js from the respective project sites.

Step 2

Upload the downloaded JavaScript libraries to the Style Library of a SharePoint site.

imageStep 3

Create a new SharePoint List named “Employees” with columns Title (Default), Skills (Choice Field With Multi-select option with values “Asp.Net”, “C#”, “SharePoint”, “VB.Net”)

Step 4

Add some records to this list so that we can display those data in our application

Step 5

Create a new SharePoint page or edit an existing page. Add a script editor web part (located under Media and Content Category) to the page. [Or use a Content Editor Web Part pointing to a Content Link]

imageStep 6

Click the “EDIT SNIPPET” link of script editor web part and copy paste the below code into it (Change the site URL in the script paths pointing to your style library or where ever you have placed the code)

<script src="http://srv2:902/ko/Style Library/kosp/jquery-1.8.3.min.js"></script>
<script src="http://srv2:902/ko/Style Library/kosp/knockout-3.0.0beta.js"></script>
<script src="http://srv2:902/ko/Style Library/kosp/ko.sp-1.0.min.Ex.js"></script>

<div>
  <table width="600px">
    <thead>
      <tr>
        <th>Employee Name</th>
        <th>Skill Set</th>
        <th>Created Date</th>
        <th>Created By</th>
      </tr>
    </thead>
    <tbody data-bind="template: { name: 'Emp', foreach: Employees }" />
  </table>
</div>

<script type="text/html" id="Emp">
  <tr>
    <td data-bind="text:Title"></td>
    <td data-bind="spChoice:Skills,multi:true"></td>
    <td data-bind="spDate:Modified,dataFormat:'DD-MMM-YYYY, hh:mm:ss a'"></td>
    <td data-bind="spUser:ModifiedBy"></td>
  </tr>
</script>

<script type="text/javascript">

function EmployeeModal() {
  var self = this;
  self.Employees = ko.observableArray([]);
  $.getJSON(_spPageContextInfo.webAbsoluteUrl + "/_vti_bin/listdata.svc/Employees?$expand=Skills,ModifiedBy",
    function (data) {
      if (data.d.results) {
        self.Employees(ko.toJS(data.d.results));
    }
  });
}

$(document).ready(function () {
  ko.applyBindings(new EmployeeModal());
});
</script>

Check in and save the page.

That’s it. Now the data will be rendered in a tabular format as shown below.

imageWell, let’s see what’s there in the above code. If you notice the code, except for spDate, spUser and spChoice in the data-bind attribute, all the remaining code is related to typical Knockout view and View Modal. The data binding handlers starting with “sp” are related to KoSp and it takes care of parsing and formatting the data.

If the default text binder of Knockout is used instead of KoSp, the below would be the expected output.
image

Template code with default text binder
<script type="text/html" id="Emp">
  <tr>
    <td data-bind="text:Title"></td>
    <td data-bind="text:Skills"></td>
    <td data-bind="text:Modified"></td>
    <td data-bind="text:ModifiedBy"></td>
  </tr>
</script>

Note: Subsequent post explains in detail about each and every Knockout binding handlers available in KoSp

Method 2 : Data via SPServices

Step 1

Download SPServices from Codeplex along with all the JavaScript libraries mentioned in Step 1 of Method 1

Step 2

Follow steps from 2 to 6 in Method 1 and replace the code with the below code.

<script src="http://srv2:902/ko/Style Library/kosp/jquery-1.8.3.min.js"></script>
<script src="http://srv2:902/ko/Style Library/kosp/jquery.SPServices-2013.01.min.js"></script>
<script src="http://srv2:902/ko/Style Library/kosp/knockout-3.0.0beta.js"></script>
<script src="http://srv2:902/ko/Style Library/kosp/ko.sp-1.0.min.Ex.js"></script>

<div>
  <table width="600px">
    <thead>
      <tr>
        <th>Employee Name</th>
        <th>Skill Set</th>
        <th>Created Date</th>
        <th>Created By</th>
      </tr>
    </thead>
    <tbody data-bind="template: { name: 'Emp', foreach: Employees }" />
  </table>
</div>

<script type="text/html" id="Emp">
  <tr>
   <td data-bind="text:Title"></td>
   <td data-bind="spChoice:Skills,src:'sps'"></td>
   <td data-bind="spDate:Modified,src:'sps',dataFormat:'DD-MMM-YYYY, hh:mm:ss a'"></td>
   <td data-bind="spUser:ModifiedBy,src:'sps'"></td>
  </tr>
</script>

<script type="text/javascript">

function Employee(data) {

  this.Title = ko.observable(data.Title);
  this.Skills = ko.observable(data.Skills);
  this.Modified = ko.observable(data.Modified);
  this.ModifiedBy = ko.observable(data.ModifiedBy);

}

function EmployeeModal() {

  var self = this;
  self.Employees = ko.observableArray([]);

  $().SPServices({
    operation: "GetListItems",
    async: false,
    listName: "Employees",
    CAMLViewFields: "<ViewFields Properties='True' />",
    CAMLQuery: "<Query></Query>",
    completefunc: function (xData, Status) {
      var spsData = $(xData.responseXML).SPFilterNode("z:row").SPXmlToJson({ includeAllAttrs: true, removeOws: true });
      if (spsData) {
        $.each(spsData, function (k, l) {
          self.Employees.push(new Employee({
            Title: l.Title,
            Skills: l.Skills,
            Modified: l.Modified,
            ModifiedBy: l.Editor
          }))
        });
      }
    }
  });
}

$(document).ready(function () {
  ko.applyBindings(new EmployeeModal());
});

</script>

[As I pointed out in a comment on Ashok's post, note that as of SPServices 2013.01, you can use jQuery promises (.Deferred() objects) This eliminates the need to do synchronous calls and can really improve performance, especially in IE. See my blog post about Comparing SPServices 2013.01 Calls with Async vs. Promises Methods.]

Save and publish the page. You can expect the same output as shown in Step 6 of Method 1

If the default text binder of Knockout is used instead of KoSp, the below would be the output you can expect.

imageHope this might have provided you a clear understanding of how to begin with KoSp for SharePoint.

Knockout for SharePoint at Codeplex

Item ‘Modified By’ Value Doesn’t Change: Fixing a Damaged Column Schema

$
0
0

I ran into an odd situation the other day at one of my clients.

Modified By ExampleWhen people edited existing list or library items in *some* lists (we couldn’t discern a pattern) the Modified By (Editor) column value did not change. The date/time stamp (Modified) was updated, but not the person who made the modification. We found several good posts out there on them Interwebz, but I though it would be useful to pull things together and outline what we tried in case it could help others.

There were two posts we found that got us started:

The first post led us to look at the Modified By column’s schema; the second led me to think that I could do the fixes using the SOAP Web Services.

I was able to pull the schema for the Modified By Site Column using the GetColumns operation in the Webs Web Service. (Of course I turned to SPServices first – it’s my hammer.)

$().SPServices({
  webURL: "/",
  operation: "GetColumns",
  completefunc: function(xData, Status) {
  }
});

I was able to look at the returned XML in Firebug, copy it out, and find the schema for the Modified By column.

Bingo. From Victor Butuza’s post, we know that the schema should look like this:

<Field ID="{d31655d1-1d5b-4511-95a1-7a09e9b75bf2}"
 ColName="tp_Editor"
 RowOrdinal="0"
 ReadOnly="TRUE"
 Type="User"
 List="UserInfo"
 Name="Editor"
 DisplayName="Modified By"
 SourceID="http://schemas.microsoft.com/sharepoint/v3"
 StaticName="Editor"
 FromBaseType="TRUE"/>

Instead, it looked like this (I’ve changed several of the attribute values to protect the guilty, but you should get the point):

<Field ID="{d31655d1-1d5b-4511-95a1-7a09e9b75bf2}"
 Name="Editor"
 SourceID="http://schemas.microsoft.com/sharepoint/v3"
 StaticName="Editor"
 Group="_Hidden"
 ColName="tp_Editor"
 RowOrdinal="0"
 Type="User"
 List="UserInfo"
 DisplayName="Modified By"
 SystemInstance="SQL_DB_Name"
 EntityNamespace="http://servername"
 EntityName="Partners"
 BdcField="OrganizationId"
 Profile=""
 HasActions="True"
 SecondaryFieldBdcNames="0"
 RelatedField="Partners_ID"
 SecondaryFieldWssNames="0"
 RelatedFieldBDCField=""
 RelatedFieldWssStaticName="Partners_ID"
 SecondaryFieldsWssStaticNames="0"
 AddFieldOption="AddFieldInternalNameHint"
 ReadOnly="TRUE"
 Version="1">
</Field>

Some way or other, the Modified By (Editor) column had been changed. We noticed quite a few odd things in the schema above, which I’ve highlighted. It looks like attributes from some BDC connection had made it into the schema, which wasn’t good. More importantly, the important FromBaseType="TRUE" attribute was missing.

I tried for a while to get the SOAP operation UpdateColumns to work in a test environment. (I was very careful to do this in a snapshot of a VM because messing with the Modified By column could be catastrophic.) Caution proved wise, as each time I tried to update the Modified By schema, the Site Column management pages (_layouts/mngfield.aspx and _layouts/fldedit.aspx) no longer worked properly. Even though I believe I had the syntax correct (based on Karthick‘s post above – note that the MSDN documentation is pretty sparse), I simply couldn’t get UpdateColumns to work without breaking things.

So I turned to Powershell. I’m no Powershell guy, but it’s a scripting language like any other and I found some good starting points out on the Web to get me going. What I ended up with was a script that repaired all of the attributes of the Modified By column in a single list.

$s = get-spsite http://servername
$w = $s.OpenWeb("/sitename/")
$l = $w.Lists["listname"]
$f = $l.Fields.GetFieldByInternalName("Editor")

write-host "BEFORE field at " $w.Url  " List "  $l.Title  "  is " $f.schemaxml

#add at the end of the schema the needed string and update the field and list
$f.SchemaXML = $f.SchemaXML -replace ' SystemInstance="SQL_DB_Name"',''
$f.SchemaXML = $f.SchemaXML -replace ' EntityNamespace="http://servername"',''
$f.SchemaXML = $f.SchemaXML -replace ' EntityName="Partners"',''
$f.SchemaXML = $f.SchemaXML -replace ' BdcField="OrganizationId"',''
$f.SchemaXML = $f.SchemaXML -replace ' Profile=""',''
$f.SchemaXML = $f.SchemaXML -replace ' HasActions="True"',''
$f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldBdcNames="0"',''
$f.SchemaXML = $f.SchemaXML -replace ' RelatedField="Partners_ID"',''
$f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldWssNames="0"',''
$f.SchemaXML = $f.SchemaXML -replace ' RelatedFieldBDCField=""',''
$f.SchemaXML = $f.SchemaXML -replace ' RelatedFieldWssStaticName="Partners_ID"',''
$f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldsWssStaticNames="0"',''
$f.SchemaXML = $f.SchemaXML -replace ' AddFieldOption="AddFieldInternalNameHint"',''
$f.SchemaXML = $f.SchemaXML -replace '/>',' FromBaseType="TRUE" />'

$f.Update()
$l.Update()

write-host "FIXED field at " $w.Url  " List "  $l.Title  "  is " $f.schemaxml

$w.Dispose();
$s.Dispose();

I also wrote a Powershell script to find all of the lists where this aberration had occurred in the Modified By column:

$siteURL = "http://servername"
$site = Get-SPSite($siteURL)

$errors = 0
$thisWebNum = 0

foreach($web in $site.AllWebs) {

  $thisWebNum = $thisWebNum + 1
  write-host $thisWebNum " Web " $web.Url  " Created on "  $web.Created

  $listCounter = $web.Lists.Count

  for($i=0;$i -lt $listCounter;$i++) {

    $list = $web.Lists[$i]
    $thisListNum = $i + 1

    write-host "(" $thisListNum "/" $listCounter ") [" $list.Title "] Created on "  $list.Created

    $f = $list.Fields.GetFieldByInternalName("Editor")

    if ($f.SchemaXML -NotMatch 'FromBaseType="TRUE"')
    {
      $errors = $errors + 1
      write-host "  Issue in schema " $f.schemaxml
    }
  }
  $web.Dispose();
}
$site.Dispose();

write-host "TOTAL ISSUES: " $errors

This script told us that we had 283 lists with the issue. Clearly this has been going on for a long time and no one had caught it, though we still can’t see any patterns in the date/time stamps or which lists have the issue.

We’ve fixed the two lists where the client folks had noticed the Modified By issue as well as the Site Column itself. We’re pausing at this point just to make sure that we don’t see any oddities based on the fix, but we’re optimistic that we know what was happening and that we have a valid fix. After we let things settle for a little while, we’ll run the fix script on the rest of the lists with the issue by combining the two scripts to loop through all of the lists with the issue.

Have you ever run into a broken schema issue like this in your environment? If so, how did you fix it?

<UPDATE date=”2013-10-17″>

We hadn’t seen any issues since we’d applied the fix I described above, so today I ran through the rest of the messed up lists and applied the fix. The Powershell script below loops through all of the sites in the Site Collection, then through all of the lists in each site, and fixes the Editor column where it’s got issues.

$siteURL = "http://servername"
$site = Get-SPSite($siteURL)

$errors = 0
$thisWebNum = 0

foreach($web in $site.AllWebs) {

  $thisWebNum = $thisWebNum + 1

  $listCounter = $web.Lists.Count

  for($i=0;$i -lt $listCounter;$i++) {

    $list = $web.Lists[$i]
    $thisListNum = $i + 1

    $f = $list.Fields.GetFieldByInternalName("Editor")

    if ($f.SchemaXML -NotMatch 'FromBaseType="TRUE"')
    {
      $errors = $errors + 1

      # fix the schema and update the field and list
      $f.SchemaXML = $f.SchemaXML -replace ' SystemInstance="GivingData"',''
      $f.SchemaXML = $f.SchemaXML -replace ' EntityNamespace="http://servername"',''
      $f.SchemaXML = $f.SchemaXML -replace ' EntityName="Partners"',''
      $f.SchemaXML = $f.SchemaXML -replace ' BdcField="OrganizationId"',''
      $f.SchemaXML = $f.SchemaXML -replace ' Profile=""',''
      $f.SchemaXML = $f.SchemaXML -replace ' HasActions="True"',''
      $f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldBdcNames="0"',''
      $f.SchemaXML = $f.SchemaXML -replace ' RelatedField="Partners_ID"',''
      $f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldWssNames="0"',''
      $f.SchemaXML = $f.SchemaXML -replace ' RelatedFieldBDCField=""',''
      $f.SchemaXML = $f.SchemaXML -replace ' RelatedFieldWssStaticName="Partners_ID"',''
      $f.SchemaXML = $f.SchemaXML -replace ' SecondaryFieldsWssStaticNames="0"',''
      $f.SchemaXML = $f.SchemaXML -replace ' AddFieldOption="AddFieldInternalNameHint"',''
      $f.SchemaXML = $f.SchemaXML -replace '/&gt;',' FromBaseType="TRUE" /&gt;'

      $f.Update()
      $list.Update()
      write-host "FIXED field at " $w.Url  " List "  $l.Title  "  is " $f.schemaxml
    }

    if ($errors -gt 0)
    {
      write-host $thisWebNum " Web " $web.Url  " Created on "  $web.Created  " had " $errors " errors"
    }
    $errors = 0;

    $web.Dispose();
  }
$site.Dispose();

</UPDATE>

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

$
0
0

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

From the Wikipedia definition of SPA:

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

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

SPA Sketch

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

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

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

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

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

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

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

Let’s get to work…

SPServices Stories #17: Multiple Form Fields Autocomplete for SharePoint 2010/2013 using JavaScript

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

Introduction

Anyone who follows this series knows that I’m always on the lookout for interesting and useful implementations of SPServices. A few weeks ago, a tweet in my feeds caught my eye:

There was a problem connecting to Twitter.

Anton Khritonenkov Anton Khritonenkov, who is a Technical Lead at Plumsail in Russia, has come up with a nice way to enable autocomplete behavior for multiple list columns based on related lists.

While I’ve got the SPAutocomplete function in SPServices, it’s pretty rudimentary and I usually suggest using the jQueryUI autocomplete function, as Anton does here. The other function in SPServices that is somewhat related to what Anton is doing in SPDisplayRelatedInfo. By using the completefunc there, you could enable some of the behavior that Anton has built, but not as cleanly.

Anton explains things well in his post below, which he was kind enough to allow me to repost here from his original on the CodeProject site.

Multiple Form Fields Autocomplete for SharePoint 2010/2013 using JavaScript

Introduction

Filling forms could be painful sometimes, especially for forms with many fields. In this article I’ll describe approach which can simplify forms filling significantly. In my case I needed to implement order form interface. In fact it contains a lot of fields I can pre-fill based on chosen customer, such as address, ZIP code and account number. All of this data is already stored in separate list called “Customers directory”.

Final requirements could looks like this:

When user starts typing in the customer field, I need to suggest list of customer names from Customers directory. When user chooses customer from suggestions list, I need to read data multiple field values from Customers directory and fill corresponding fields in the order form. User can correct filled values later if needed.

To implement such functionality I used to use JavaScript and this case is not an exception. There are many reasons for this:

  • It well fits Office 365 restrictions.
  • It easily migrates from older SharePoint versions.
  • It is easily to debug without slow SharePoint solution deployment.
  • REST SharePoint 2013 API or SPServices are almost such powerful as server code.
  • Finally I just like it.

In this article I’ll use SPServices jQuery plugin for communication with SharePoint services. It works for SharePoint 2013 as well as for SharePoint 2010. I’ll also use jQuery UI Autocomplete plugin to implement suggestions functionality.

plumFormAutocomplete plugin works well for single field as much as for multiple fields.  Plugin supports text fields only.

Plugin in action looks like this:

How to use plumFormAutocomplete jQuery plugin

You can use this plugin without looking into the code. Firstly, I’ll describe how to use it, then if you still will be interested, you can look inside plugin implementation.

Prerequisites

If you don’t have jQuery, jQuery UI or SPServices, you can download fresh version from official sites. For tutorial purposes I assume that names of downloaded files and folders are following:

  • jquery.min.js 
  • jquery.SPervices.min.js 
  • jquery-ui-1.10.3.custom.min.js
  • jquery-ui-1.10.3.custom.min.css
  • css folder for jQuery UI

I also assume that you have jquery.plumFormAutocomplete.js downloaded from source code of this article (Source code download link).

Upload jquery.min.js, jquery.SPServices.min.js, jquery-ui-1.10.3.custom.min.js and jquery.plumFormAutocomplete.js files to Style Library within your site collection. You also need to upload jQuery UI CSS styles located in the same folder with jquery-ui-1.10.3.custom.min.js. Then open New or Edit form, where autocomplete plugin will be used and add js and CSS links to placeholder PlaceHolderAdditionalPageHead.  You can use following snippet:


Configure and call plugin

Now you are ready to configure and call plugin. For my case plugin call looks like this:

//get control for autocomplete field
var fieldControl = $.getFieldControl('Title');

//call autocomplete plugin for field control
fieldControl.plumFormAutocomplete({
  sourceList: 'Customers directory',
  sourceMatchField: 'Title',
  labelFields: ['Title', 'ZIPCode'],
  labelSeparator: ', ',
  fillConcatenatedLabel: false,
  fieldsMapping: [{sourceField: 'Address', targetField: 'CustAddress'},
    {sourceField: 'AccountNumber', targetField: 'CustAccountNumber'},
    {sourceField: 'ZIPCode', targetField: 'CustZIPCode'}]
});

You can wrap plugin call inside jQuery $(document).ready() function to ensure that code will be executed after page is loaded.

Let us look at this code sample in more detail. Code is divided into two parts:

  1. Get control for autocomplete field
  2. Call autocomplete plugin for field control

For the first step you need to specify internal name of autocomplete field for getFieldControl function. It is ‘Title’ in my case.

In the second step you need to call plugin for received autocomplete field and configure plugin options. Plugin options are structured as a single object as any jQuery plugin options.

Plugin options

  • sourceList – name or GUID of source list, where suggestions will be taken. It is ‘Customers directory’ in my case.
  • sourceMatchField – internal name of the field in the source list. This field will be used to find matching list items for autocomplete keywords.
  • labelFields – an optional parameter, you can specify source list field internal names array. All field values for these  fields will be concatenated with labelSeparator and displayed in autocomplete suggestion as a single string like this: Value1, Value2, …, ValueN.
  • labelSeparator – an optional parameter, it is separator for labelFields concatenation, for example it could be comma with space (‘, ‘).
  • fillConcatenatedLabel – an optional parameter, set true if you need to fill autocomplete textbox with all concatenated labelFields values, set false if you need to fill autocomplete text box only with single field value.
  • fieldsMapping – an optional parameter, it is an array of field mapping objects. Each object declares mapping from source list field to target list field. In my case names of source and target fields are the same. For example Address in Customer directory and Address in Orders list.

Mapping object has following syntax:

{sourceField: 'Internal name of source field', targetField: 'Internal name of target field'}

Note: You can specify only non optional parameters, plugin will work correctly. This plugin works well as single field autocomplete too, just do not fill optional parameters.

Plugin configuration without optional parameters could look like this:

fieldControl.plumFormAutocomplete({
    sourceList: 'Customers directory',
    sourceMatchField: 'Title'
});

Internal implementation of the plugin

Let us look at the full plugin source code. You can download it here.

There are three major part inside the code:

  1. Getting text field input.
  2. Apply jQueryUIi autocomplete plugin and SPServices to get suggestions.
  3. Implementing helper functions.

To get field input I used jQuery selectors and simple regular expression. Unfortunately SharePoint doesn’t provide any method to get field controls from JavaScript, it only stores field internal name inside html comment in the following format:

<!-- FieldName="Title" FieldInternalName="Title" FieldType="SPFieldText" -->

So, I had to parse it to find control I needed. Final function was added to jQuery:

//function gets text field control by internal name
$.getFieldControl = function (fieldInternalName) {
  var regexStr = 'FieldInternalName="' + fieldInternalName + '"'
  var regex = new RegExp(regexStr, 'i');
  var fieldCell = $('td.ms-formbody').filter(function () {
    return (regex).test($(this).html())
  });
  return $(fieldCell.find('input')[0]);
}

In the next step I applied jQuery UI autocomplete plugin and implemented source and select plugin functions. Source function calls source list using SPServices and CAML to get suggestions. When suggestion found, I store all mapped field values inside autcomplete object:

source: function (request, response) {
  var autocompleteVals = [];
  var k = 0;

  $().SPServices({
    operation: "GetListItems",
    async: false,
    listName: options.sourceList,
    CAMLViewFields: getViewFields(options.fieldsMapping),
    CAMLQuery: getCamlQuery(options.sourceMatchField, request.term),
    completefunc: function (xData, Status) {
      $(xData.responseXML).SPFilterNode("z:row").each(function () {
        var queryResult = this;
        var fieldsValues = getFieldsValues(options.fieldsMapping, queryResult);
        var labelText = getLabelText(fieldsValues);
        autocompleteVals[k] = {
          label: labelText,
          value: options.fillConcatenatedLabel ? labelText  :
          extractFieldValue(fieldsValues, options.sourceMatchField),
          fieldsValues: fieldsValues
        };

        k++;

        function getLabelText(fieldValues){
          var result = '';
          if(options.labelFields) {
            for(i = 0; i < options.labelFields.length; i++) {	        		               var fieldName = options.labelFields[i];               var fieldVal = extractFieldValue(fieldValues, fieldName);               if(fieldVal != '') {                 if(i > 0) {
                  result += options.labelSeparator;
                }
                result += fieldVal;
              }
            }
          } else {
            result += extractFieldValue(fieldValues, options.sourceMatchField);
          }
          return result;
        }

      });
      response(autocompleteVals);
    }
  });

}

Select function fills values inside mapped fields according to matched item from source lists. It reads values stored inside ui.item and fills corresponding fields based on suggestion selection.

select: function (event, ui) {
  //Fill all depended fields
  $.each(ui.item.fieldsValues, function () {
    var fieldVal = this;
    var fieldInput = $.getFieldControl(fieldVal.key);

    var outputVal = fieldVal.value;

    if (outputVal) {
      var lookupSeparator = ';#';
      if (outputVal.indexOf(lookupSeparator) != -1) {
        var ind = outputVal.indexOf(lookupSeparator);
        var length = lookupSeparator.length;
        var startInd = ind + length;
        outputVal = outputVal.substring(startInd, outputVal.lenght)
      }
      fieldInput.val(outputVal);
    }

  });
}

Maybe you discovered that there are three helper functions inside plugin: getFieldsValues, getViewFields, and getCamlQuery.

getFieldsValues parses SPServices response and fills autocomplete object according to specified fields mapping.

//get values for all mapped fields
function getFieldsValues(fieldsMapping, queryResult) {
  var result = [];
  $.each(fieldsMapping, function () {
    var fieldMapping = this;
    var val = $(queryResult).attr("ows_" + fieldMapping.sourceField);
    result.push({ key: fieldMapping.targetField, value: val, sourceKey: fieldMapping.sourceField});
  });

  var sourceVal = $(queryResult).attr("ows_" + options.sourceMatchField);
  result.push({ value: sourceVal , sourceKey: options.sourceMatchField});

  return result;
}

getViewFields generates ViewFields xml for CAML query according to fields mapping.

//get view fields for all mapped fields
function getViewFields(fieldsMapping) {
  var result = "";
  var isSourceFieldAdded = false;

  if(fieldsMapping){
    $.each(fieldsMapping, function () {
      var mapping = this;
      var viewField = "";
      result += viewField;
    });

    isSourceFieldAdded = fieldsMapping.filter(function(){
      return this.sourceField == options.sourceMatchField;
    }).length > 0;
  }

  if(!isSourceFieldAdded){
    result += "";
  }

  result += "";
  return result;
}

getCamlQuery generates CAML query according to filter column internal name and keyword from input.

//get CAML query for keyword
function getCamlQuery(colname, keyword) {
  var where = "" + keyword + "";
  var orderBy = "";
  var query = "" + where + orderBy + "";
  return query;
}

Updates

03.10.2013

  • Additional optional parameters added: labelFields, labelSeparator, fillConcatenatedLabel.
  • Reading lookup fields from source list implemented.
  • Minor bug fixes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

SPServices Stories #18 – Retrieve Managed Metadata Using JavaScript and SPServices

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

Introduction

Stefan Bauer

Stefan Bauer

Stefan Bauer (@StfBauer) and I have known each other for a while now. We share a passion for developing solutions in SharePoint that both function well and look good. User Experience Matters.

The other day I was working on a project where I wanted to access some managed metadata (MM) values in SharePoint 2010 using the TaxonomyClientService.This SOAP Web Service was added in SharePoint 2010 along with the Managed Metadata Service itself. It’s a pretty crufty Web Service and doesn’t do nearly as much as one would want, but it’s there.

I know that I could build what I needed myself, but I did some Bingling first to see what was out there. I ran across precious little, but this great article entitled Retrieve Managed Metadata using JavaScript and SPServices from Stefan caught my eye. In the post, Stefan does a nice job of combining CSOM calls to get the SspId, GroupId, and TermSetId – we have to have those values to query the Term Store – and SPServices to access the actual data in the Term Store. He’s also built a nice TaxConfiguration helper class to store the parsed out data. The fact that we can’t get at the GUIDs for those three Ids which specify which Services Application we need easily is one of the drawbacks of the TaxonomyClientService itself.

The project I’m working on has me trying to prepopulate MM columns in a list form. Essentially, it’s an SPCascadeDropdowns-like approach for the MM column type. In this particular case, because MM columns can’t be parsed reliably in workflows, we are storing three separate MM columns in each list item that are all related. The relationships are maintained using synonyms in the Term Sets. Entering the first one implies the values of the other two, with some potential disambiguation required on behalf of the user. The MM column functionality in the form is amazingly difficult to work with because there are multiple asynchronous calls to the Term Store using methods like /_vti_bin/TaxonomyInternalService.json/GetSuggestions and /_vti_bin/TaxonomyInternalService.json/GetMatches. Those methods aren’t well documented and the MSDN documentation tells me this:

The members of this namespace or class are reserved for internal use and are not intended to be used directly from your code.

In this case, I may resort to using them, but for now I’m focusing on the TaxonomyClientService, which is allowable. But that’s probably a post for another day.

Here are a few more links about working with taxonomy (MM) fields with JavaScript/jQuery that I’m finding helpful:

Retrieve Managed Metadata using JavaScript and SPServices

In one of my last projects I ran into an interesting question regarding the taxonomy store. Will it be possible to get all taxonomy data by using pure JavaScript?

SharePoint has a great JavaScript object model for different purposes but how deep is this integration when an access to service applications like the Managed Metadata Service is required. The simple answer on this is: Not enough. Due the strict project plan we decided to use a Silverlight web part, which is capable to access the taxonomy store. This solution worked well but I wasn’t pleased by that.

Time passed by and the question left unanswered to me. At the SharePoint Conference I meet Marc D. Anderson the founder of SPServices. He told me that this Jquery library should be capable of accessing the Taxonomy Client Web Service. This was the last missing information that I needed to access the Taxonomy Store and this is my solution.

The challenges

Basically there are three gaps to take to get information from the taxonomy store.

  • Get all required data to access Taxonomy Service
  • Access the TaxonomyClientService using JavaScript
  • Handle the XML result from the web service

These goals sound simple but when it comes to JavaScript and the Taxonomy Store a lot of creativity is needed to get this done. At the end I’m really pleased with my solution. The following explanations will use a simple Metadata Field called ‘Color’.

Taxonomy in the Term Store

Taxonomy in the Term Store

Get all data to access Taxonomy Service

In SharePoint 2010 Managed Metadata information will always be used in columns and the configuration to the managed metadata service is directly stored in the field configuration. By accessing those site collections columns all required information can be retrieved with a dynamic and flexible approach. The following code access a field named “Color” in my site collection and returns the xml schema of the field.

var fieldCollection;

var colorField = null;
var texconfig = null;

// Delay Script Execution until sp.js is fully loaded
ExecuteOrDelayUntilScriptLoaded(getColor, "sp.js");

function getColor() {
  // Load the client context
  var clientContext = SP.ClientContext.get_current();
  if (clientContext != undefined && clientContext != null) {
    // Load the root web.
    var webSite = clientContext.get_site().get_rootWeb(); ;

    // Select 'Color' Field from the root web site colins
    fieldCollection = webSite.get_fields();
    this.colorField = fieldCollection.getByInternalNameOrTitle("Color");

    // Load the field or throw error
    clientContext.load(this.fieldCollection);
    clientContext.load(this.colorField);
    clientContext.executeQueryAsync(
      Function.createDelegate(this, this.OnLoadSuccess),
      Function.createDelegate(this, this.OnLoadFailed));
  }
}

function OnLoadSuccess(sender, args) {
  var fieldInfo = '';
  // Get Schema XML from the field
  var fieldschema = colorField.get_schemaXml();
  // Parse it with the Taxonomy Configuration Object
  texconfig = TaxConfiguration;
  texconfig.Configuration = fieldschema;
  texconfig.ParseConfiguration();
  // Do something with the field information

}

function OnLoadFailed(sender, args) {
  alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

For parsing this xml schema of the field I created a simple helper class that query for all required properties to access the taxonomy store. The XML parsing is pretty much straight forward and that’s how the code looks like:

var TaxConfiguration = {
  SspId : "",
  GroupId : "",
  TermSetId : "",
  Configuration : "",
  ParseConfiguration : function () {

    xmlDoc = $.parseXML(this.Configuration);
    xml = $(xmlDoc);

    var properties = xml.find("Property");

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

      propertyName = properties[i].firstChild.textContent == undefined ?
        properties[i].firstChild.text : properties[i].firstChild.textContent;
      propertyValue = properties[i].lastChild.textContent == undefined ?
        properties[i].lastChild.text : properties[i].lastChild.textContent;

      if (propertyName == propertyValue) {
        propertyValue = "";
      }

      switch (propertyName) {
      case "SspId":
        this.SspId = propertyValue;
        break;
      case "GroupId":
        this.GroupId = propertyValue;
        break;
      case "TermSetId":
        this.TermSetId = propertyValue;
        break;
      }
    }

  }
}

Somehow the xml parsing in IE and chrome is different; therefore I need to check if ‘textContent’ or simply ‘text’ exists in my jQuery object. After this gap was taken everything was ready to access the taxonomy service application.

Access the TaxonomyClientService using JavaScript

As mentioned before SPServices is powerful library to access the SharePoint Web Services using JavaScript and make use Jquery. There are ways to access asp.net web services by pure JavaScript but it’s a more comfortable way to do this.

To access all hierarchy levels of a terms stored two different web service calls needs to be made because there is no web service available that returns all the information at once. Those different methods that need to be called are:

The documentation of the result returned by the web service can be found in the MS-EMMWS: Microsoft Enterprise Managed Metadata Web Service Protocol Specification. It takes a while to figure out which property is used for what. The following xml attributes will be consumed by my script:

  • a9
    Guid of the term
  • a32
    Name of the term
  • a25
    Parent termID
  • a69
    Term has child terms

After I figured out those xml attributes I was able to build my script and here is it:

function GetTermsRecursive(sspId, termSetId) {

  var terms = new Array();
  var returnValue;

  $().SPServices({
    operation : "GetChildTermsInTermSet",
    sspId : sspId,
    termSetId : termSetId,
    lcid : 1033,
    completefunc : GetData
  });
}

function GetChildTermsRecursive(sspId, termSetId, roottermId) {

  var terms = new Array();

  $().SPServices({
    operation : "GetChildTermsInTerm",
    sspId : sspId,
    termId : roottermId,
    termSetId : termSetId,
    lcid : 1033,
    completefunc : GetData
  });

  return terms;

}

function GetData(xData, Status) {
  if (Status == "success") {

    terms = new Array();

    xmlData = xData;

    // Fix for different XML parsing in IE and Chrome
    termsContent = $.parseXML(xmlData.responseText).firstChild.textContent == undefined ?
      $.parseXML(xmlData.responseText).text :
      $.parseXML(xmlData.responseText).firstChild.textContent;

    termsXML = $.parseXML(termsContent);
    $termsXML = $(termsXML);
    console.log($termsXML);

    childTerms = $termsXML.find("T");
    parentTermId = null;

    filterOutput = "<ul>";

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

      termName = $(childTerms[i]).find("TL");
      hasChildTermsXml = $(childTerms[i]).find("TM");

      // request if child terms are available
      hasChildTerms = $(hasChildTermsXml).attr("a69");

      var tsTerm = new Object();

      // Requesting actual term id
      tsTerm.termId = $(childTerms[i]).attr("a9");

      // Requesting term name
      tsTerm.termName = termName.attr("a32");

      // Setting Parent Term ID
      parentTermId = $(hasChildTermsXml).attr("a25");

      filterOutput += "<li id='" + tsTerm.termId + "'>" + tsTerm.termName;

      // If child terms are avaliable query child terms
      if (hasChildTerms != undefined && hasChildTerms == "true") {

        // Request child Terms
        tsTerm.child = GetChildTermsRecursive(taxconfig.SspId, taxconfig.TermSetId, tsTerm.termId);
        tsTerm.hasChildTerms = true;

      } else {

        tsTerm.child = null;
        tsTerm.hasChildTerms = false;

      }

      filterOutput += "</li>";
      terms[i] = tsTerm;

    }

    filterOutput += "</ul>";

    // If parent element is specified query for parent element
    if (parentTermId != undefined || parentTermId != null) {

      $("#" + parentTermId).append(filterOutput);

    } else {

      currentFilter = $("#filter").html();
      $("#filter").html(currentFilter + filterOutput);

    }
    return terms;

  }
}

So that’s all needed to return and output the result in SharePoint. For displaying those terms I added a HTML Form web part to the page that contains a simple div element with the id called filter.

Output of JavaScript from the Managed Meta Data Service

Output of JavaScript from the Managed Meta Data Service

I decided to write a simple list to the defined div but it can be transformed to any other structure.

Conclusion

What looks like a complex problem at the beginning was easy to solve by using SPServices. In future projects now I’m able to use pure JavaScript to access Managed Metadata Service. The code for that is highly reusable too. I’m now able to access any site collection field based on managed metadata by using my TaxConfiguration object. It’s flexible and dynamic and hard coding of the required information to access the managed metadata service is not needed anymore. SPServices is also capable to boost productivity. In future if in need to decide to use Silverlight or JavaScript to access the Managed Metadata Service I will prefer to use JavaScript for a simple reason. Silverlight wasn’t made to write html to a web page. I think this is a misuse of Silverlight in this case.

If you like to download and play around yourself you can download the scripts packed up in a web part. All that needs to be done in the web part is to change the references to the SPServices and the name of the field in the getColor function.

Download JavaScript Taxonomy Web Part

Have fun with the taxonomy store !!!

Viewing all 109 articles
Browse latest View live