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!

Monday, October 20, 2008

Revisiting ddwrt and FormatDate

I have had some feedback on the my posting in May 2007 about the ddwrt FormatDate function, including a request to see additional formats.

Therefore I had a play with some reflection code, and have created a little command line application that can be used to see the available date formats output by the ddwrt utility.

Note that this code could be used to experiment with the output of some of the other ddwrt functions in the Microsoft.SharePoint.WebPartPages.DdwRuntime library - to see a list of the other functions, open up the Microsoft.SharePoint dll in Reflector.

One other tip - a list of the LCID values is available in this post on my blog


using System;

using System.Collections.Generic;

using System.Globalization;

using System.Linq;

using System.Reflection;

using System.Text;

 

namespace DDWRT.FormatDate

{

    class Program

    {

        static void Main(string[] args)

        {

            try

            {

                CultureInfo ci = new CultureInfo(1031);

                Console.WriteLine(RunDDWRT("05-09-2007 11:00", ci));

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.ToString());

            }

            Console.ReadLine();

        }

 

        public static string RunDDWRT(string szDate, CultureInfo ci)

        {

            string fullName = "Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c";

 

            StringBuilder sb = new StringBuilder();

 

            //Load the SharePoint Assembly

            Assembly assem = Assembly.Load(fullName);

 

            // Reference the DDWRT namespace

            Type type = assem.GetType("Microsoft.SharePoint.WebPartPages.DdwRuntime", true, true);

 

            //Find the sought method in DDWRT (the "FormatDateTime" method)

            MethodInfo methodInfo = null;

            MethodInfo[] methods = type.GetMethods();

            foreach (MethodInfo method in methods)

            {

                //Uncomment the next line to see a list of all the methods available in the ddwrt class

                //sb.AppendLine(string.Format("  {0}", method.Name));

 

                if (method.Name == "FormatDate")

                    methodInfo = method;

            }

 

            if (methodInfo != null)

            {

                object objectInstance = Activator.CreateInstance(type);

 

                sb.AppendLine("ddwrt.FormatDate Test");

                sb.AppendFormat("\r\n");

                sb.Append("Locale LCID:");

                sb.AppendFormat("\t");

                sb.Append(string.Format("{0} ({1})", ci.LCID.ToString(), ci.Name));

                sb.AppendFormat("\r\n");

                sb.Append("Date to format:");

                sb.AppendFormat("\t");

                sb.Append(szDate);

                sb.AppendFormat("\r\n");

                sb.AppendFormat("\r\n");

 

                sb.Append("FormatFlag");

                sb.AppendFormat("\t");

                sb.Append("Formatted Date");

                sb.AppendFormat("\r\n");

                sb.AppendFormat("\r\n");

                for (long formatFlag = 1; formatFlag < 16; formatFlag++)

                {

                    try

                    {

                        string formattedDateTime = (string)methodInfo.Invoke(objectInstance, new Object[] { szDate, ci.LCID, formatFlag });

                        sb.Append(formatFlag);

                        sb.AppendFormat("\t\t");

                        sb.Append(formattedDateTime);

                        sb.AppendFormat("\r\n");

                    }

                    catch

                    {

                        sb.Append(formatFlag);

                        sb.AppendFormat("\t\t");

                        sb.Append("--------");

                        sb.AppendFormat("\r\n");

                    }

                }

            }

            return sb.ToString();

        }

 

    }

}

Wednesday, October 1, 2008

Displaying the Raw Search Results

Here's a little tidbit I usually end up searching for when manipulating search results - so it must be time to link to it here:
How to: View Search Results XML Data
(Also see Customize the People Search Results)

This gives a rundown on the XSL to use to show the raw XML that is returned to a search results web part (such as the "CoreResultsWebPart").

Basically, use the following:
<xsl:stylesheet version="1.0" xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes">
<xsl:template match="/">
<xmp><xsl:copy-of select="*"></xmp>
</xsl:template>
</xsl:stylesheet>

Custom Web Part Pages Template Woes

Seems to have been plenty of people that have tried to extend the list Web Part Page templates made available on the spcf.aspx page. And it seems that the approach outlined in the Microsoft article "Creating Custom Web Part Page Templates for Microsoft SharePoint Products and Technologies" just doesn't work with SharePoint v3.

The approach of adding an additional option to the list of templates in a custom custspcf.aspx page seems to fail (with the "Invalid URL Parameter" message) for the following reason: according to the WSS SDK, the NewWebPage RPC method accepts a value for the WebPartPageTemplate of between 1 and 8.

That must explain why adding a ninth template to the list fails - the call to the NewWebPage method OWSSVR.DLL doesn't expect a number above 8. For the background, here is a discussion on OWSSVR by Joel Oleson.

So I guess the options if different Web Part Page templates are required are to either
  • Replace the standard ones that are located in 12\TEMPLATE\1033\STS\DOCTEMP\SMARTPGS, or to
  • Create a completely custom template selection page - but I am finding that there are some real oddities in the names of pages that appear in search results when web part pages are created in a custom manner.

Another area that needs attention in vNext!