Already since a long time I’ve been thinking about a web part that would search-as-you-type using SharePoint’s search engine. The idea behind this concept is that while you’re typing a query in the search box, asynchronously the query is already executed and the results are being displayed. Every time the query is changed, the results will be updated dynamically. This will allow users to see the results without going to the Search Results page, and even more: they don’t have to go back to alter their query. In such a scenario you want to avoid full page postbacks of course, so AJAX-techniques have to be used to accomplish this. A while back my first approach would be to make use of ASP.NET AJAX to build the necessary functionality in a web part for example. But during the last couple of weeks I’ve become a really big fan of using jQuery Javascript Library in SharePoint sites, and it happens to be that the search-as-you-type functionality can be created with the help of jQuery very easily. The beauty of this solution is that everything is happening on the client (in the web browser), so there is absolutely no requirement to deploy something to the server (nowadays this seems to be called ‘Assembly Free’).

Before you get too excited; it’s quite obvious that when you use this sample on a production server with lots of users, the SharePoint Search component can be hammered with lots of requests (which can be bad for the performance). So use it wisely! To minimize the impact on the performance of the server, the code will only execute a search query when (by default) three characters are entered, and while the user is still typing no queries are executed at all (there’s a configurable delay).

So how is all of this implemented? Well the idea is to display an HTML textbox in a Content Editor Web Part. Using the jQuery library, an eventhandler is added to that textbox for every keypress. When there are more than three characters (value is configurable) entered in the textbox, jQuery will make an asynchronous call to the Search web services (/_layouts/search.asmx). The resulting found items are displayed in an HTML div element which is positioned right below the textbox, on top of all other HTML elements. The user can select a result using the arrow keys, or by hovering the mouse over a result. When a result is selected by pressing the enter key or by clicking on it, the user is redirected to the corresponding page or document. You can see the code working in a web part in the following animated screenshot.

 

If you want to try this out yourself, just follow these steps. Once again, there is absolutely nothing you need to tweak and/or deploy on your server. You can do all of this through the web user interface of SharePoint.

  1. Download the code file here.
  2. Add a new Content Editor Web Part, which is available in SharePoint out-of-the-box, to a page.
  3. Modify the newly added web part, use the Source Editor button in the properties task pane to add the downloaded code.
  4. Optionally you can give the web part a meaningful Title in the Appearance group (e.g. Quick Search).

When you check out the code, notice that the first line is a reference to the jQuery library, hosted on Google’s servers. If you plan to use this in production, I’d recommend you to host the jQuery library in your own environment so you have to update the reference with your own URL.

[Update July 3th 2009] If you are using a non-English version of SharePoint, you need to change the script because by default the All Sites Search Scope is being used. On top of the script look for the quickSearchConfig variable and set the scope appropriately (you can look up the name of your Search Scopes in Central Admin/SSP). Also notice that currently using WSS is not supported (the MOSS Search Engine is used)!

var quickSearchConfig = {
    delay: 500,           
    minCharacters: 3,     
    scope: "All Sites",   
    numberOfResults: 15,  
    resultsAnimation: 200,
    resultAnimation: 0    
};

Later on this week, on Friday May 29th, I will be presenting two sessions at the Developer Days in The Hague (The Netherlands). I'm pretty excited to be there, not only because it's always lots of fun to speak at this event; but there's lots of cool SharePoint 2007 stuff (IMHO) packed in my presentations:

  • Getting Started with .NET 3.5 in SharePoint (more info)
    Abstract: Since the introduction of SharePoint 2007 lots has changed in the .NET world: version 3.0 and 3.5 of the .NET Framework have been released, which include technologies such as WCF, Silverlight and Linq. This session will focus on how you can use these new technologies in your SharePoint 2007 projects, the do's and the don'ts, and the resulting advantages.
  • Pimp Up Your SharePoint Site (more info)
    Abstract: SharePoint 2007 has a nice web user interface available for its users, but this out-of-the-box user interface can look quite old fashioned compared to new Web 2.0 web sites on the internet. This session will focus how you can give your existing SharePoint sites some extra punch by using Web 2.0 technologies such as AJAX, Silverlight and jQuery. And the good news is that it's not always required to implement very intrusive solutions; even small changes can make your SharePoint sites rise and shine again.
Btw, the DevDays are a great way to interact with the Dutch SharePoint community; which is very well represented: Mirjam van Olst, Wouter van Vugt, Ton Stegman, Waldek Mastykarz, and many, many more. So I hope to see you there and feel free to drop by to say hi! :-)

When working with the out-of-the-box SharePoint web services, it may have happened to you that the Web Service response contained the following exception embedded in the XML:

Exception of type 'Microsoft.SharePoint.SoapServer.SoapServerException' was thrown.
The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again.
Error code: 0x8102006d

Undoubtedly this exception is quite strange in the context of a web service call; there is no Back button you can click. But in my cases the exception was just plain wrong, the issue had nothing to do with security whatsoever. It turned out that when you make a web service call to the SharePoint web services, in some cases you need to set the SOAPAction header  in the HTTP Request, in other cases it’s not necessary to do this (but it won’t do any bad if you do). When you consume web services from .NET code, you probably have Visual Studio generated proxies; they pass the correct header so you don’t need to do anything special. But if you construct your own HTTP Request to make the Web Service call, for example using Javascript and jQuery, you need to think about this. Check out following Javascript code for example, which creates a new List item by using the Lists.asmx web service (discussed in more detail in my previous post):

var soapEnv =
    "<?xml version=\"1.0\" encoding=\"utf-8\"?> \
    <soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \
        xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" \
        xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"> \
      <soap:Body> \
        <UpdateListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\"> \
          <listName>Tasks</listName> \
          <updates> ... Updates XML ... </updates> \
        </UpdateListItems> \
      </soap:Body> \
    </soap:Envelope>";

$.ajax({
    url: "http://yoursite/_vti_bin/lists.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=utf-8"
});

This code will cause the Security Validation exception, because the SOAPAction Header is not correct. How do you know what value you need to set the SOAPAction Header to? Well, that’s very easy to figure out! Just navigate the the Lists.asmx web service (or UserGroup.asmx, Webs.asmx, Sites.asmx, SiteData.asmx or any Web Service you want to call of course) in your browser, and drill down to the Web Method you want to invoke.

 

As you can see, the required value of the SOAPAction value is displayed in the generated web page for the Web Method, so you can just copy and paste it in your code. When using the jQuery ajax function, you can use the beforeSend option so set additional Headers:

$.ajax({
    url: wsURL,
    beforeSend: function(xhr) {
        xhr.setRequestHeader("SOAPAction",
        "http://schemas.microsoft.com/sharepoint/soap/UpdateListItems");
    },

    type: "POST",
    dataType: "xml",
    data: soapEnv,
    complete: processResult,
    contentType: "text/xml; charset=utf-8"
});

That’s it, now the Web Service call will work without the irrelevant Security Validation exception.

If you have read some of the previous posts on this blog related to using jQuery in SharePoint 2007 sites, you probably know that it’s perfectly possible to make call the out-of-the-box SharePoint web services by making use of Javascript running in the client’s browser. This opens up a huge stream of possibilities from which I already covered some of them on my blog. A very important piece of information you need if you want to make a call to a web service is of course the URL of the web service. Figuring out this URL seems to be more trivial than it actually is. My first idea was to use the URL of the page in which the call to a web service actually happened; e.g. you’ve got a Site Page accessible in SharePoint using the URL http://mysite/mypage.aspx, so you strip the /mypage.suffix and add /_vti_bin/lists.asmx (if you’d like to call the Lists web service of course). The thing is, this will only work if your mypage.aspx file is setting in the Root Folder of your SharePoint site. For example: when you would put the Site Page in a Document Library instead, the URL of the page would be http://mysite/Shared Documents/mypage.aspx, so you’d have to strip the /Shared Documents/mypage.aspx and replace it with the Web Service suffix. This can get very complicated when you don’t know upfront in what kind of location the page will be stored (a Site Page in the Root Folder or a Document Library, an Application Page in _layouts, ...). It’s possible to write a bunch of code to figure that out, or you can make use of the following technique!

A very easy and quick way to get a reference is to make use of the alternate link SharePoint will but by default in the head section of every rendered page:

<html><head>
...
<link type="text/xml" rel="alternate" href="http://weblogs.asp.net/_vti_bin/spsdisco.aspx" />
...
</head>...</html>

The href attribute will always point to a server relative URL of the spsdisco.aspx page; which is located in the same folder as the out-of-the-box SharePoint Web Services (/_vti_bin). Even if you are in a sub site, another site collection, an Application Page, ... the link elemen will always be there, and point to the correct URL. So the following piece of Javascript code, will retrieve the prefix you can use to make calls to the correct URL’s of the SharePoint Web Services:

var spsdiscoUrl = $("head link[rel='alternate']:eq(0)").attr("href");
spWSUrlPrefix = spsdiscoUrl.substr(0, spsdiscoUrl.length - 13);


(If you haven’t already noticed: I’m using the jQuery Javascript library.) The first line will get the value of the alternate link in the head of the HTML page; the second line will strip spsdisco.aspx (13 characters). Once you’ve got this prefix, you can use it to construct the URL of the SharePoint Web Services as follows:

$.ajax({
    url: spWSUrlPrefix + "lists.asmx",
    type: "POST",
    dataType: "xml",
    data: soapEnvTasks,
    complete: processResultTasks,
    contentType: "text/xml; charset=\"utf-8\""
});

UPDATE As Jaap pointed out in the comments, SharePoint creates a Javascript variable called L_Menu_BaseUrl that contains the value needed determine the URL of the SharePoint Web Services. I haven't been able to test it myself, or to figure out who/what is responsible to declare this variable, but it looks promising! The only downside I see related to this approach would be that you rely on the OOB Javascript to be 1) loaded in the page and 2) to have executed before your code is started.

In my previous post I explained how you can make use of the Lists.asmx web service of SharePoint, to load list items by using the jQuery Javascript library. The example discussed in that post is simple and easy to understand, but very, very boring. Let’s try to do something useful with that technique: display fancy, unobtrusive notifications for open tasks, when a user visits a SharePoint site. The screenshot below shows the result, but it’s static. In real life the user would see the yellow boxes popping up, and after a couple of seconds they would disappear again (they don’t block the user interface at all).

 

To display these notifications I’ll use the excellent jGrowl extension for jQuery. So to make use of this demo, you’ll need to upload both the jquery.jgrowl_minimized.js and jquery.jgrowl.css files to SharePoint (check the download at the end of this post to get the files). The code below assumes that those two files, and the jQuery library itself, are uploaded to a document library called Shared Documents (which is created by default in a Team Site).

<script type="text/javascript" src="Shared Documents/jquery-1.3.2.min.js"></script>

<script type="text/javascript" src="Shared Documents/jquery.jgrowl_minimized.js"></script>

<link href="Shared Documents/jquery.jgrowl.css" rel="Stylesheet"></link>

<script type="text/javascript">

$(document).ready(function() {
    var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>Tasks</listName> \
                        <viewFields> \
                            <ViewFields> \
                               <FieldRef Name='Title' /> \
                               <FieldRef Name='Body' /> \
                           </ViewFields> \
                        </viewFields> \
                        <query> \
                            <Query><Where> \
                                <And> \
                                    <Eq> \
                                        <FieldRef Name='AssignedTo' /> \
                                        <Value Type='Integer'><UserID Type='Integer' /></Value> \
                                    </Eq> \
                                    <Neq> \
                                        <FieldRef Name='Status' /> \
                                        <Value Type='Choice'>Completed</Value> \
                                    </Neq> \
                                </And> \
                            </Where> \
                        </Query>\
                        </query> \
                    </GetListItems> \
                </soapenv:Body> \
            </soapenv:Envelope>";
            
        $.ajax({
            url: "_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
            complete: processResult,
            contentType: "text/xml; charset=\"utf-8\""
        });
});

function processResult(xData, status) {
    $.jGrowl.defaults.position = "bottom-right";
    $.jGrowl.defaults.life = 10000;
    
    var firstMessage = true;
    
    $(xData.responseXML).find("z\\:row").each(function() {
        if(firstMessage)
        {
            firstMessage = false;
            $.jGrowl("<div class='ms-vb'><b>You have open tasks on this site.</b><div>",
                {   
                    life: 5000
                }
            );
        }
    
        var messageHtml =
            "<div class='ms-vb'>" +
                "<a href='Lists/Tasks/DispForm.aspx?ID=" + $(this).attr("ows_ID")
                     + "&Source=" + window.location + "'>" +
                     "<img src='/_layouts/images/ITTASK.GIF' border='0' align='middle'> " +
                     $(this).attr("ows_Title") + "</a>" +
                "<br/>" + $(this).attr("ows_Body") +
            "</div>";
        $.jGrowl(messageHtml);
    });
}
</script>

Since we’d like to display those notifications when a user visits the site, we need to put a Content Editor Web Part of the home page (typically /default.aspx). In this content editor web part, copy and paste the Javascript code from above. In this code once again, first the SOAP envelope message is constructed. Notice that both the Title and Description fields are requested (the internal name of the Description field is Body). In the query element two conditions are set; the AssignedTo field should be equal to the currently logged on user, and the Status field can’t be equal to Completed. The message is POST-ed to the Lists.asmx web service by using jQuery’s ajax function.

The response of the web service call is processed in the processResult function. For every row element in the result, the jGrowl function is called to display a notification. The contents of such a notification are a small HTLM string containing a link to the task, and the body of the task. So that’s the story of how a small piece of Javascript code can have a pretty nice result in SharePoint! :-)

You can download the source code for this demo here. The zip file contains the necessary libraries and CSS files (which you have to upload to the Shared Documents document library for example) and the code you have to copy/past in a Content Editor Web Part (using the Source Editor button!). Additionally you can also find an exported web part (.dwp file) in the zip file, which you can very easily import or add to the web part gallery of a site (so you don’t have to copy/past the code yourself).

Due to popular demand I’ve created another sample of how you can make use of the jQuery Javascript library in your SharePoint sites. This example uses SharePoint’s Lists.asmx web service to retrieve all the list items of a specific list. In my previous posts I showed how you could use jQuery in SharePoint Site Pages (regular .aspx pages uploaded to a Document Library), so let’s do something different now; let’s use jQuery in a plain Content Editor Web Part.

To try this sample navigate to the home page (usually /default.aspx) of a SharePoint site that has a list with some list items in it, in my code I’ll use the Task list of a plain vanilla Team Site. Switch the page to Edit mode (Site Actions, Edit Page), and add a new instance of the Content Editor Web Part to the page. In the properties of that web part, copy and paste the following code using the Source Editor button.

<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>

<script type="text/javascript">
    $(document).ready(function() {
        var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>Tasks</listName> \
                        <viewFields> \
                            <ViewFields> \
                               <FieldRef Name='Title' /> \
                           </ViewFields> \
                        </viewFields> \
                    </GetListItems> \
                </soapenv:Body> \
            </soapenv:Envelope>";

        $.ajax({
            url: "_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
            complete: processResult,
            contentType: "text/xml; charset=\"utf-8\""
        });
    });

    function processResult(xData, status) {
        $(xData.responseXML).find("z\\:row").each(function() {
            var liHtml = "<li>" + $(this).attr("ows_Title") + "</li>";
            $("#tasksUL").append(liHtml);
        });
    }
</script>

<ul id="tasksUL"/> 

On the first line the jQuery library is loaded from googlecode.com. To make this your, your client browser needs to have Internet access of course. Alternativly you can host the jQuery library yourself (see my previous examples) or even load the jQuery library in every page using the SmartTools.jQuery component. After that a function is attached to the jQuery document ready event. In this function the SOAP envelope message is constructed (the soapEnv variable). If you’d like to see the code getting list items from another list than the Task list, you’d have to change the listName element. The second part POST-ing the SOAP envelope to the web service by using jQuery’s ajax function. When the web service comes back with the result, the processResult method is called. In this function a loop is created over every row element (in the namespace z). Notice that "z:row" escapes in Javascript to "z\\:row". For every row element a new li HTML element is added to the ul element with ID tasksUL. And that’s it! You can see the result in the screenshot below.

In my previous post I showed how to make a call to SharePoint’s Lists.asmx web service with the jQuery library to retrieve information about the Lists and Document Libraries that are available on a specific SharePoint Site. In the comments of that post, one of the readers asked if it would be possible to create a new item in a List using the same technique. Of course this is possible, you just need to make use of the UpdateListItems web method (yeah, the name of that method is not very intuitive). Here is a quick example!

First let’s create the UI (in this example I'll use a basic Site Page) to allow the user to enter a Title for the new task, and a button to do the action.

<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderMain">
    <p>
        Task Title:
        <input id="newTaskTitle" type="text" />
        <input id="newTaskButton" type="button" value="Create Task" />
    </p>
</asp:Content>

Next let’s create a Javascript function that will create a new item in a Task list. In the Javascript function I’m declaring two variables that will contain the XML which will be sent to the SharePoint Lists.asmx web service. The first variable (I called it batch) contains the CAML to create a new item. For simplicity the CAML only provides a value for the Title field, add more fields if you’d like. The second variable (called soapEnv) is the SOAP Envelope XML which wraps the batch XML. Notice that in the SOAP Envelope the name of the list is mentioned in which we’re going to create a new item (in this case the Task list). Finally the jQuery ajax function is used to POST the data to the Lists.asmx web service. (If you test this code make sure you update the url option with the URL of your site).

function CreateNewItem(title) {
    var batch =
        "<Batch OnError=\"Continue\"> \
            <Method ID=\"1\" Cmd=\"New\"> \
                <Field Name=\"Title\">" + title + "</Field> \
            </Method> \
        </Batch>";

    var soapEnv =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?> \
        <soap:Envelope xmlns:xsi=\"
http://www.w3.org/2001/XMLSchema-instance\" \
            xmlns:xsd=\"
http://www.w3.org/2001/XMLSchema\" \
            xmlns:soap=\"
http://schemas.xmlsoap.org/soap/envelope/\"> \
          <soap:Body> \
            <UpdateListItems xmlns=\"
http://schemas.microsoft.com/sharepoint/soap/\"> \
              <listName>Tasks</listName> \
              <updates> \
                " + batch + "</updates> \
            </UpdateListItems> \
          </soap:Body> \
        </soap:Envelope>";

    $.ajax({
        url: "
http://yoursite/_vti_bin/lists.asmx",
        beforeSend: function(xhr) {
            xhr.setRequestHeader("SOAPAction",
            "
http://schemas.microsoft.com/sharepoint/soap/UpdateListItems");
        },
        type: "POST",
        dataType: "xml",
        data: soapEnv,
        complete: processResult,
        contentType: "text/xml; charset=utf-8"
    });
}

The jQuery ajax function call has a complete option which points to a function, in this function you can process the result as follows:

function processResult(xData, status) {
    alert(status);
}

The status parameter is a string which can be for example success or error. Finally in the ready event of the document, we'll hook up the click event of the button so the CreateNewItem function is called, with the value of the textbox as the parameter.

$(document).ready(function() {
    $("#newTaskButton").click(function() {
        CreateNewItem($("#newTaskTitle").val());
    });
});
 

That’s it! If you put all the code in a simple Site Page, upload the page to a Document Library in a SharePoint site, and now you can create Task list items by only using Javascript! The sample code can be downloaded in the following zip file. The zip file also contains the jQuery library which you can upload to the same Document Library if it isn't already loaded with the help of the SmartTools.jQuery component for example.

If you read this blog you probably know that besides the web user interface, SharePoint also exposes some interfaces which you can use from code: the SharePoint object model and the SharePoint web services. The object model of SharePoint can only be used by code/applications that are running on a SharePoint server in your Server Farm, so you can’t use the object model on client machines. The SharePoint web services can be used of course across a network boundary, that’s what they are built for! In this post I’m going to show you how you can access the out-of-the-box SharePoint web services by making use of the jQuery Javascript library. First let’s see what you can do with this technique: download this zip file that contains an ASPX page (a basic Site Page without any code behind), and the jQuery Javascript library (in case you don’t have it already). Upload the two individual files (not the zip file) in the root of a Document Library in any of your SharePoint sites. You can do this by making use of the web user interface; you don’t have to touch anything on the server itself. When done, just click on the link of the uploaded ASPX and you’ll see following page:


 
Probably you’re not really impressed but think about the fact that this page is just an ASPX file you’ve uploaded through the web user interface, there is absolutely no code behind involved (which would have been blocked by SharePoint’s default security settings). The details of the SharePoint lists are loaded by making use of Javascript code that calls the web SharePoint lists.asmx web service.

So how do you call a SharePoint web service in Javascript code; well you can use the XmlHttpRequest object and write lots of boring code, or you can make use of a Javascript library that wraps this XmlHttpRequest object and exposes a nice and easy interface. In this demo I’ll use the jQuery Javascript library, so the first thing that you’ll need to do is to make sure the page is loading that library:

<script type="text/javascript" src="jquery-1.3.2.min.js" mce_src="jquery-1.3.2.min.js"></script>

If you already configured your SharePoint site so the jQuery library is loaded (for example by making use of the SmartTools.jQuery component), you can skip this line of course. 

When the page is loaded, the Lists web service (e.g. http://yoursite/_vti_bin/lists.asmx) of SharePoint needs to be called; this can be accomplished by making use of the jQuery’s ajax method. This method can post the necessary SOAP envelope message to the Lists web service. The XML of the SOAP envelope can easily be copied from the .NET web service test form of the desired web method (e.g. http://yoursite/_vti_bin/lists.asmx?op=GetListCollection). In the code below, a call to the GetListCollection web method is made when the page is loaded. The complete parameter of the ajax method is actually a pointer to another Javascript function (which we’ll implement later on) that will be called asynchronously when the web service call is done.  Don’t forget to update the url parameter with your SharePoint site’s URL!

$(document).ready(function() {
    var soapEnv =
        "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
            <soapenv:Body> \
                <GetListCollection xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                </GetListCollection> \
            </soapenv:Body> \
        </soapenv:Envelope>";

    $.ajax({
        url: "
http://yoursite/_vti_bin/lists.asmx",
        type: "POST",
        dataType: "xml",
        data: soapEnv,
        complete: processResult,
        contentType: "text/xml; charset=\"utf-8\""
    });
});

As I already mentioned, the processResult function is called when the response XML of the web service call is received. In this method a loop is created which will iterate over every List element of the response XML. For every List element a <li></li> element is added to the element with the ID attribute set to data.

function processResult(xData, status) {
    $(xData.responseXML).find("List").each(function() {
        $("#data").append("<li>" + $(this).attr("Title") + "</li>");
    });
}

This data element is the actual <ul></ul> list in HTML:

<ul id="data"></ul>

When you put everything together in a Site Page, this is the result:


 
In the zip file mentioned in the beginning of this post, you can find an extended version of the processResult function which will display some additional metadata for every list (like the ID, ItemCount etc). The entire contents of basic version of the Site Page built in this post goes as follows:

<%@ Page Language="C#" MasterPageFile="~masterurl/default.master" %>

<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderAdditionalPageHead">

    <script type="text/javascript" src="jquery-1.3.2.min.js" mce_src="jquery-1.3.2.min.js"></script>

    <script type="text/javascript">
        $(document).ready(function() {
            var soapEnv =
                "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                    <soapenv:Body> \
                        <GetListCollection xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        </GetListCollection> \
                    </soapenv:Body> \
                </soapenv:Envelope>";

            $.ajax({
                url: "
http://yoursite/_vti_bin/lists.asmx",
                type: "POST",
                dataType: "xml",
                data: soapEnv,
                complete: processResult,
                contentType: "text/xml; charset=\"utf-8\""
            });

        });

        function processResult(xData, status) {
            $(xData.responseXML).find("List").each(function() {
                $("#data").append("<li>" + $(this).attr("Title") + "</li>");
            });
        }
    </script>

</asp:Content>
<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderMain">
    <ul id="data"></ul>
</asp:Content>
<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea">
    List Details
</asp:Content>
<asp:Content runat="server" ContentPlaceHolderID="PlaceHolderPageTitle">
    List Details
</asp:Content>

My last post about splitting the Top Navigation Bar of a WSS site in two with the help of jQuery, triggered a little discussion (in a good way) in the comments. As a result Christophe from the Path to SharePoint blog wrote some Javascript to accomplish the same thing, without using jQuery at all. Now Christophe started the "Jan Tielens Challenge" on his blog: "I have already talked about Jan in a previous post. End users who visit his blog can certainly feel like a kid in front of a bakery display: so many goodies that are out of reach!
So here is my proposal: if you find on Jan’s site a tool you’d like to have as an end user, send me the challenge! If a topic gets enough votes, I’ll work on a solution that can be implemented on the client side."

So, make sure you send Christophe all your challenges. Good luck Christophe! :-)

Since I got a fan of the jQuery Javascript library, usually I can’t resist showing off the power of this library in my SharePoint development courses at U2U. For example two weeks ago I was in sunny Cyprus talking about SharePoint, ASP.NET AJAX and jQuery and I told my students something in the line of “with jQuery you can change pretty much anything in the SharePoint UI by just making use of Javascript”. Promptly one of my students asked me if I could show how to break the Top Link Bar of a SharePoint site into two parts. I really like challenges in my SharePoint courses, but I couldn’t conquer this challenge on the spot, it took me a couple of hours in my hotel room to get this to work. Now that I’ve got some time to polish the code a little bit, I want to share the code with you! :-)

First, let’s talk about the issue that this little jQuery script is going to solve: you probably know if you create subsites in SharePoint sites, those subsites are shown (by default) in the Top Link Bar of the parent site. So the more subsite you’ve got the more items this menu is showing. Life is all good until there are too many items to show in the menu so it’s get too big to fit on the screen. You won’t get an error or something like that of course, but the browser will give the menu the space it needs by adding horizontal scrollbar the page. The screenshot below illustrates this behavior: the top link bar has too many menu items making it pretty hard to access for example the Site Actions menu (you need to scroll to the right).

So how can this be solved with the help of jQuery? Well besides a very powerful DOM Selectors API, the jQuery library also has a DOM Maniplation API. This Manipulation API can change the HTML that’s rendered in the browser, by adding elements, removing elements etc. The idea is to write a Javascript function that adds a second Top Link Bar to the page’s DOM. The easiest way to accomplish this is to just copy the existing Top Link Bar entirely:

$("#zz1_TopNavigationMenu").clone(true).insertAfter($("#zz1_TopNavigationMenu")).attr("id", "zz1_TopNavigationMenuCopy");

This jQuery script will:

  1. $("#zz1_TopNavigationMenu")
    select the element with ID zz1_TopNavigationMenu
  2. .clone(true)
    clone that element
  3. .insertAfter($("#zz1_TopNavigationMenu"))
    insert the cloned element after the original menu
  4. .attr("id", "zz1_TopNavigationMenuCopy");
    set the ID attribute to a new value to be able to identify it uniquely

The result of this function is a page that shows two identical menu bars:

Now the only thing that’s left to do is to remove the unnecessary menu items from both menus:
var nrOfItems = ($("#zz1_TopNavigationMenu > tbody > tr > td").length + 1) / 3;
var splitIndex = (Math.round(nrOfItems / 2) - 1) * 3;
$("#zz1_TopNavigationMenu > tbody > tr > td:gt(" + splitIndex + ")").remove();
$("#zz1_TopNavigationMenuCopy > tbody > tr > td:lt(" + (splitIndex + 1) + ")").remove();

The first two lines calculate the number of items in the menu (each menu item consists of three td elements) and the index of the td element where the table should be “split”. The third line removes all the td elements of the original menu with and index higher than the calculated one. The last line removes all td elements of the copied menu with and index lower than the calculated one.

Now, let’s put everything together and get this to work in a real SharePoint site! The first thing to do is to make sure your SharePoint site loads the jQuery library. This can be done in a couple of ways, for example using the jQuery component of the SmartTools for SharePoint project on CodePlex (read my previous blog posts on jQuery in SharePoint for more info). Secondly the Javascript discussed above should be loaded, and once again there are a couple of ways to do this. For production scenarios I’d recommend to build a Feature using  a delegate control that loads the Javascript function (just like the SmartTools jQuery component does), but for testing you can also do this in a plain Content Editor Web Part (or, God forbid, using the SharePoint Designer tool). So just add a Content Editor Web Part to a page of your SharePoint site, and copy/past the following piece of code in it:

<script>
$(document).ready(function() {
  // Calculate where to split the tables of the menus
  var nrOfItems = ($("#zz1_TopNavigationMenu > tbody > tr > td").length + 1) / 3;
  var splitIndex = (Math.round(nrOfItems / 2) - 1) * 3;
  // Make a copy of the TopNavigationMenu
  $("#zz1_TopNavigationMenu").clone(true).insertAfter($("#zz1_TopNavigationMenu")).attr("id", "zz1_TopNavigationMenuCopy");
  // Remove items from original menu
  $("#zz1_TopNavigationMenu > tbody > tr > td:gt(" + splitIndex + ")").remove();
  // Remove items from copied menu
  $("#zz1_TopNavigationMenuCopy > tbody > tr > td:lt(" + (splitIndex + 1) + ")").remove();
});
</script>

When done, the SharePoint page will now display a Top Link Bar, split into two. Make sure you test the code in a WSS site (a Team Site for example), because Publishing sites (e.g. a Collaboration Portal has another menu, see the remark at the end of this post).

Important remark: the code discussed in this article is just an example for demonstration purposes, if you plan to use this code I strongly recommend to test it in various web browsers and check if it meets your performance goals. The identifiers of the Top Link Bar in the code are uses in WSS sites (e.g. a Team Site), SharePoint Publishing sites generate menus with other ID’s and or structures, so you have to tweak the Javascript. For example: the following code works for a Collaboration Portal using the default.master.

$(document).ready(function() {
  // Calculate where to split the tables of the menus
  var nrOfItems = ($("#zz1_TopNavigationMenu *.zz1_TopNavigationMenu_5 > tbody > tr > td").length) / 3;
  var splitIndex = (Math.round(nrOfItems / 2) - 1) * 3;
  // Make a copy of the TopNavigationMenu
  $("#zz1_TopNavigationMenu").clone(true).insertAfter($("#zz1_TopNavigationMenu")).attr("id", "zz1_TopNavigationMenuCopy");
  // Remove items from original menu
  $("#zz1_TopNavigationMenu *.zz1_TopNavigationMenu_5 > tbody > tr > td:gt(" + splitIndex + ")").remove();
  // Remove items from copied menu
  $("#zz1_TopNavigationMenuCopy > tbody > tr > td:lt(2)").remove();

  // Remove first item (current site) from copied men
  $("#zz1_TopNavigationMenuCopy *.zz1_TopNavigationMenu_5 > tbody > tr > td:lt(" + (splitIndex + 1) + ")").remove();
});

More Posts Next page »