Wednesday, November 26, 2008

jQuery in a SharePoint Custom Field

One way of gaining new SharePoint functionality without "disturbing" the structure or content of lists and document libraries is to create custom fields that use jQuery to perform client-side processing.

AJAX calls to the built-in web services are simple once the SOAP message structure has been determined. The data returned by the call can then be made to interact with and modify controls on the page using the powerful selector mechanism in jQuery.

In this post I'll cover a few tips I learnt whilst creating one such custom field.

The Ajax Call

In a previous post I covered the structure of a jQuery AJAX call to a SharePoint web service. But how to derive the actual SOAP body for that call? One way I found to do this which removed lots of the guess work was as follows:
  1. Create a simple console application in Visual Studio and add a reference to the web service you want to call

  2. Write code in that console application to call the required method on that web service. Begin by using the simplest set of parameters in the call and making sure they return data as expected before adding additional parameters. For example, in my calls to the Lists web service (lists.asmx) my first tests used empty elements for the query, viewfields and queryoptions arguments in the GetListItems() call

  3. Start up Fiddler (or your favourite HTTP traffic logging tool). Ensure that the "Capture Traffic" option is ticked in the file menu.

  4. Run your console application, which will make the web service call.

  5. In Fiddler, view the HTTP call that was made by the console app by clicking on that listed call - it will be to the URL you set in the console app, and will have a result of '200'.

If the right-hand pane of Fiddler is showing the Statistics tab, click on the Inspectors tab - this displays the outgoing message from your console app in the top half, and the return message in the bottom half. Open the Raw tab within each half of the right-hand pane, and you will see the soap envelope that was sent in the web service call in the top pane. That is the XML to be used in the data parameter of the jQuery AJAX call.

One other item to note from the Raw content of the outgoing HTTP call is the SOAPAction, as that value needs to be used to set the soapaction in the beforesend anonymous function in the jQuery AJAX call.


Communicating from SharePoint to jQuery
One important point I have not yet covered was the fact that it is possible for a custom field to render in a SharePoint form, and yet to have no displayed elements. This is one way to use jQuery to modify controls and data in the form. My custom field outputs only script tags plus an empty span. It is still necessary to hide the table row in the form that contains this custom field, as the standard SharePoint rendering of a field in the new/edit list item form shows the field title in the left column and the field content in the right.

Hiding of the row is achieved by giving the empty span a class (for example 'hiddenfield'), and then rendering in the custom field some jQuery script that hides table rows that contain spans with the 'hiddenfield' class.

For the custom field to interact with data in other fields using jQuery, data needs to be passed from the server side into the client-side script in the custom field. AJAX web service calls can be used for this once the form has rendered, but what if you want your client-side script to have knowledge about the particular controls it is required to modify?

The first step is to ensure that the page on which the custom field will be rendered references the jQuery library. My approach is to add script elements to the RenderingTemplate in the custom field ASCX user control (which is deployed to the CONTROLTEMPLATES 12 hive folder), and to deploy the jQuery library to the LAYOUTS 12 hive folder.

Then, to enable the code in the custom field to set parameters in the client-side script (for example, to set the name of another field in the new/edit form that the script is to interact with), I added in the RenderingTemplate another script tag that contains an asp:Literal server control. Custom field code in the BaseFieldControl subclass can then output script into this literal control.

I created a custom field editor which derives the information to be rendered into that Literal control, and save this information in a custom property of my custom field. The technique for storing custom properties is explained well by Anton Myslevich in this forum thread (thanks, Anton!), particularly the difficulty in saving properties for a new instance of a custom field.

To summarise, the SharePoint list administrator adds a new field of type "MyCustomjQueryField" to a list and applies some custom configuration settings in the "Create New Field" form. These settings are processed and stored as custom properties of the custom field instance. When a list item is create or edited by a list user, the new or edit form is displayed, and the custom field uses its saved custom properties to render JavaScript in a hidden table row in that form. The JavaScript then uses the power of jQuery to interact with other fields in that form.

Displaying a Message During the AJAX Call

One small use of jQuery was to show a message in the new/edit form whilst a web service call initiated by the custom field was occuring. This is important given the asynchronous nature of the call and the fact that it is possible for the call to take a noticeable time to complete.

This was achieved using the 'after()' jQuery manipulation method to add a span directly after the HTML element in the form that was being modified by the web service call. Before the jQuery AJAX call is made, I add this span, and then remove the span in the event handlers for the error and success events triggered on completion of the AJAX call.

One small but pleasing touch was to fade out the message using the 'fadeOut()' jQuery method. Unnecessary perhaps, but less jarring to the eye than the text just disappearing instantly.

Wednesday, November 19, 2008

Silverlight in a Content Editor Web Part

First trial with Silverlight - getting Scott Gu's Silverlight DIGG client running in a SharePoint web part page with the simplest possible implementation.

The steps:
  1. Created a web part page document library
  2. Uploaded DiggSample.xap into the library
  3. Create a new web part page in the library
  4. Added a content editor web part to the page.
  5. Added an object tag pointing to the XAP file in the source code of the content editor web part

And, voila.... nothing displayed! Played with adding the Silverlight.js file to the library, and various other techniques. Finally found the key was the interaction of the object tag width and height with the rendered dimensions of the web part.


Solved by either:

  • Setting a fixed height and width in the web part appearance properties, and giving the object tag a height and width of 100%, OR
  • Setting an absolute height and width for the object tag and leaving the web part with non-fixed height and width

The former approaches works better as the latter approaches results in the Silverlight element's UI not matching the web part's width.


The content of the object tag is as shown below:

Using jQuery for AJAX User Profile Queries in SharePoint

Continuing on my vein of experimenting with customisation of SharePoint using jQuery, I wanted to see if I could use the AJAX methods in jQuery to call SharePoint web services. And it just so happens a suitable requirement arose - the need to use details from the profile for the current logged-in user of a web part page.

A data view web part was required to display staff located in the same office as the current logged-in user of the web part page. The office name for each staff member was being stored in the user profiles.

Of course this functionality could have been developed in a custom web part, but I am growing to like the rapidity of creating business functionality with data view web parts. Combined with jQuery, they offer a suprising amount of flexibility.

In SharePoint designer I created a data source pointing at the user profiles. Then I created a data form web part using this data source to display a table of information on each user profile. Also included in the web part was a hidden text field that contained the login name of the current user. The purpose of this hidden value was to provide the search text for the AJAX call to the profile web service, so that I could retrieve the details for the current user.

I needed the help of Fiddler and the SharePoint Search Service Tool to help craft the necessary QueryPacket XML. Once I had the working XML, I placed it in a JavaScript file referenced within the data form web part.

The jQuery went something like this:

$(document).ready(function() {
    startAjaxOfficeSearch();
});

function startAjaxOfficeSearch() {
    var userlogin = $("#CurrentUserLogin").text();
    if (userlogin != '') {
      var queryXml = 'urn:Microsoft.Search.Response.Document:Documentselect accountname, preferredname, firstname, lastname, workemail, workphone, Title, Path, pictureurl, description, write, rank, size, OfficeNumber from scope() where "scope"= \'People\' and accountname = \'' + userlogin + '\' order by preferredname asc';
      var queryXmlOptions = '110truetruetruetruetruetruetrue';
      var completeQueryXml = queryXml + queryXmlOptions;

      var regex = new RegExp(String.fromCharCode(60), 'g');
      queryXml = completeQueryXml.replace(/</G, '&lt;').replace(/>/g, '&gt;');

      var soapMessage = String.fromCharCode(60) + '?xml version="1.0" encoding="utf-8"?>' + queryXml + '';
      $.ajax({ type: "POST",
            url: "/_vti_bin/search.asmx",
            contentType: "text/xml",
            dataType: "xml",
            data: soapMessage,
            processData: false,
            beforeSend: function(req) {
              req.setRequestHeader("X-Vermeer-Content-Type", "text/xml; charset=utf-8");
              req.setRequestHeader("soapaction", http://microsoft.com/webservices/OfficeServer/QueryService/QueryEx);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) { ajaxError(XMLHttpRequest,textStatus, errorThrown); },
            success: function(xml) { ajaxFinish(xml); }
      });
    }
}

function ajaxFinish(xml) {
  $("RelevantResults", xml).each(function() {
    var selectedOfficeValue = $("OFFICENUMBER", this).text();
    //Do other stuff with this office value
  });
}   

function ajaxError(xmlObj,textStatus,errorThrown)   {
    alert("(Ajax error: "+textStatus+")");
    alert(xmlObj.statusText);
}

The login of the current user is contained in the hidden text box with ID of 'CurrentUserLogin', as created by the data form web part. The derived query seeks exact matches for that user login against the accountname property in the user profile records - you may find this needs to query against the preferredname property instead of accountname.

The ajaxfinish method gets all the XML returned from the web service call, and uses jQuery parsing to extract the office name from the returned profile.

Note that the url parameter in the ajax call really needs to be replaced by a variable that adjusts to the actual site collection root, and that the ajaxerror function is not production ready!

I've missed out a lot of detail in this post (for instance, how to create a data source in SharePoint designer that points at the user profile store), but hopefully have given a taster of another use for jQuery, and another way of approaching SharePoint customisations.

One other tip - to find out the property names to use in the query, go to the Shared Services Administration page for your farm, open the search settings page, and then open the Metadata property mappings page. That is where you will discover those strange names (such as OFFICENUMBER)!

Wednesday, November 12, 2008

jQuery in SharePoint Example - Rounded Corners

A recent project involved the branding of MOSS to incorporate custom design elements as supplied in a PhotoShop page mockup. One of these design requirements was rounding the external corners of the quick launch menu.

Hmmm, what to do. There are plenty of techniques discussed on the web for achieving this outcome, but often the basis for these techniques is to start with clean, web-standards compliant HTML. That's certainly not what SharePoint provides - I needed to work with the HTML that is created by the mixture of master pages and user controls, and did not want to override standard elements for this styling exercise. One aim was to minimize the changes, if any, to the master page.

Many of the rounded corner approaches use JavaScript. And I was, at the time of this project, starting to see the potential of jQuery combined with SharePoint. So a little more research located a neat way forward - the jQuery.Round plug-in.

After a few minutes (hours?) experimentation I derived the jQuery statements to apply rounded corners to the menu. This of course also required adding references to the jQuery and jQuery.round libraries in the master page - so it was necessary after all to change that page!

The statements were:

function AddQuickLaunchCorners()
{
    $(".ms-quicklaunchouter").corner("10px");
    $("div.ms-quickLaunch h3.ms-standardheader span div.ms-quicklaunchheader").css("background-color", "#c1ecff").corner("top 7px");
    $("table.ms-recyclebin:last td").corner("bottom 7px");
}

Note the assumption in the last JavaScript statement that the recycle bin will be the bottom row in the quick launch menu.

These statements, together with modifications to some of the other CSS styles, resulted in the following look for the quick launch menu:




But then I came across a very annoying behaviour. Imagine the following scenario:

  • I view a page containing this menu in one tab in IE7
  • Then I open another tab in IE7 and navigate around any page in that second tab

Not an unusual behaviour really. But on returning to the first tab displaying the SharePoint page with menu, this is what the menu then looked like:


Yeuch! More "minutes" of investigation lead to the inclusion of the following code in the JavaScript file applying the dynamic styling to the page:

window.onfocus = ReapplyQuickLaunchCorners;

function ReapplyQuickLaunchCorners()
{
    RemoveQuickLaunchCorners();
    AddQuickLaunchCorners();
}

function RemoveQuickLaunchCorners()
{
    $("div.ms-quicklaunchouter>div:not(.ms-quickLaunch)").remove();
    $("div.ms-quickLaunch h3.ms-standardheader span div.ms-quicklaunchheader>div").remove();
    $("table.ms-recyclebin:last td>div").remove();
}

Not a nice solution (OK, it's a hack!) but it works and I ran out of time.

So where am I leading with this post - well, just to illustrate the great things you can do with jQuery to mould SharePoint. Sometime I'll blog about using jQuery for AJAX calls to the MOSS profile search web services, but that's for another time.....

Sunday, November 2, 2008

jQuery Introduction Talk at Christchurch Code Camp

Gave a 5 minute lightning talk on "A Brief Introduction to jQuery" at the Christchurch Code Camp on Saturday - didn't quite get to my last slide in the 5 minutes and Dave Mateer took great delight in "riffing" me off the stage with his electric guitar turned up loud!

Really like the lightning talk concept - you don't need to be an expert on the subject, and they are very easy to prepare for, given that you can run through the slides lots of times beforehand (though having said that, I obviously didn't practice enough to get my talk short enough!).

I have published the slide deck from my talk - it includes an overview of a couple of ways in which I recently solved SharePoint UI requirements using jQuery, including a way of adding rounded corners to the quicklaunch menu using the jQuery.Corner library.

Amongst the talks on the day were:

Just wanted to say well done to the small team of organisers who did a great job in a short time - those half million listeners of Dot Net Rocks who heard the announcement about the camp but who didn't attend missed out on a treat!