Adventure in SPWonderland

Take apart and put back together

NAVIGATION - SEARCH

Cross-Browser AJAX for SharePoint Lists Part 2

In part 1 we have got to the point where we have called the SharePoint lists.asmx Web Service and retrieved the list of all Lists on our SharePoint site.
We now need to filter down to only those lists that are based on the links list template, this is template type 103, we can use an Xpath expression to filter the items.

// Filter for the Links lists items
links_lists=ajaxsp_GetSoapResponseItems(xmlhttpLists.responseXML, "//sp:List[@ServerTemplate='" + ListTemplate + "']", moz_NSResolver1 );

We loop through the list items and use the getAttribute of the XML Dom node to retrieve the fields we are interested in and concatenate a string of <LI> items which we then set to the innerHTML property of a DIV element.

Each list item on the left is a hyperlink that calls the javascript function GetLinkItems when clicked.
This function is passed three parameters - the list Guid, list Title and the Default View URL which normally is the full list of links.

function GetLinkItems(ListGuid, ListName, ListDefaultView)


To get the list items we need to call the GetListItems method and pass it the listName parameter.

// Create SOAP Request
xmlrequest = SoapPrefix + '<GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">'
xmlrequest = xmlrequest + '<listName>' + ListGuid + '</listName></GetListItems>' + SoapPostfix;

To send the SOAP request we do the same as before only this time we set the GetListItems SOAP method

xmlhttpItems.open("POST", "_vti_bin/Lists.asmx?op=GetListItems ",true); xmlhttpItems.setRequestHeader("Content-Type","text/xml; charset=utf-8"); xmlhttpItems.setRequestHeader("SOAPAction","http://schemas.microsoft.com/sharepoint/soap/GetListItems ");
// Set event handler
xmlhttpItems.onreadystatechange=ajaxsp_GetLinkItemsEvent;
// Post request
xmlhttpItems.send(xmlrequest);

Our GetLinkItemsEvent parses the XML and formats the data into a TABLE element with the ms-summarycustombody style to match the theme of the SharePoint site.
Each hyperlink has the target=”_blank” attribute which causes the links to open in a new window, my preferred method of opening links. This can easily be changed.


Creating a new Item Form

Now we have displayed our list of links we need to look at adding a new item.
In the Web Part after the script blocks I have some HTML that contains a DIV element. The DIV element has a style that sets its position to be absolute and hidden.
Inside the DIV I’ve create a HTML table that sets the layout of the AddNew form.
To render the URL editing and Notes fields I use the built-in functions provided in the SharePoint javascript file ows.js to handle input fields on SharePoint forms.
The complete list of fields type that can be used are:

DateField
URLField
NumberField
BooleanField
NoteField
RichTextField
TextField
FileNameField
GridChoiceField
ChoiceField

I only need to use the URLField and NoteField type.


These functions are called in javascript like this
var fld = new URLField(frm,"URL","URL", "","");
fld.fRequired = true;
fld.IMEMode="";fld.BuildUI();

The URLField function takes parameters of a OWSForm, Internal Field Name, Display Name, Default URL Value , Default Description.
BuildUI takes care of creating the input HTML which is great as it automatically creates two input fields, one for the URL and one for the description.

I create a NoteField object like this.

fld = new NoteField(frm,"Comments","Notes","");
fld.stNumLines = "6";
fld.IMEMode="";
fld.BuildUI();

The NoteField functions parameters are a OWSForm, Internal Field Name, Display Name, Default Note Field Value


Heres the function prototype for creating a OWSForm object:

function OWSForm(stName, fUseDHTMLOverride, stPagePath)

Creating the OWSForm is tricky in that it’s easy to create the OWSForm object in itself but it expects the name of the onpage form used in the WebPart page postbacks, that’s fine but it overwrites the onsubmit function which causes problems for any other component like the Web Part settings toolbar that need to post to the server. The way around this is to pass it a dummy name and then after the call set the Form name to the genuine article.
The second parameter is fUseDHTMLOverride which is used to force the use of DHTML when rendering the controls.
stPagePath is used to locate any extra files needed such as the datepicker’s template htm page.

Heres how its called in code

// Pass a dummy form name so it does not overwrite the Submit event
var _WPQ_frm= new OWSForm("DummyName" , true, "_layouts/");
// Now assign the proper form name it as it’s needed for the fields to locate their values
_WPQ_frm.stName=MSOWebPartPageFormName;

We use the global variable MSOWebPartPageFormName here which is defined in ows.js and available on all WebPartPages and gives the name of the FORM element used for postback. Note we don’t postback ourselves but the OWS Field elements think that they might.

I’ve laid out the new item form with the Javascript to create the edit fields embedded in a HTML table. This table is wrapped in a DIV which is initially hidden. The code to show and center the DIV element is Javascript 101 so I won’t go into details but check the function ajaxsp_DisplayNewForm if you’re interested.

I want to set focus to the Http edit field so we need to a reference to the Edit fields.
We can use getElementById for this but we need to know the name of the field on the form.
The key to this is to understand how the OWS form elements name themselves.

Fields are named by the internal function FrmStFieldNameFactory which does this:
return "OWS:" + name + ":" + stPart;
This concatenates OWS with the name of the field and the subfield name.

The URL field type is composed of two sub fields named URL and Desc. In this case we’re referencing the URL field and the URL subpart of that field.

So to reference the URL sub part of the URL field we can use this code.

var fldURL=document.getElementById("OWS:URL:URL");
if (fldURL) fldURL.focus();

 

Adding a new record via SOAP services

Now that we can get the URL, Description and Notes field values we use the UpdateListItems method of the Lists.asmx web service to add the new record.
This method takes a CAML string to do adds, updates and deletes.
This is an example CAML batch string we send:

<Batch>
<Method ID='1' Cmd='New'>
<Field Name='URL'>http/www.google.com, Description here - note comma and space</Field>
<Field Name='Comments'>My Comment String</Field>
<Method>
</Batch>

The ajaxsp_AddListItem routine assembles this string, places it inside the UpdateListItems SOAP string and sends it to the web service. 
The ajaxsp_AddListItemEvent handles the returned SOAP string which may include a faultstring node if the SOAP call fails or an ErrorText node if the new record fails to add in SharePoint. Once the insert succeeds we hide the new item form and refresh the list.

Thats almost it, there is some extra code where I store the last displayed list in a cookie so if the page refreshes I can display the list items again. This cookie is designed to only last for the browser session.


 

Summary

This has been a deep dive into calling the SharePoint Web Services from client side javascript but the actual methods involved are pretty basic and simple to use, most of the complications and extra code come from handling non IE browsers.

Some of the other ways that Web Services could be used include adding field by field server-side validation, filling comboboxes from SQL Server tables, getting unread email counts information from Exchange via WebDav.

Feel free to download the DWP file and browse the code for yourselves.

LinksLists.dwp (16.2 KB)

Cross-Browser AJAX for SharePoint Lists Part 1

On my personal SharePoint site I use the SharePoint Links type lists a huge amount, I have at least 20 of these and I like to flick between them a lot, having the links to hand is a great productivity booster.
I find the standard navigation just too slow and although the double-click minimize/restore Web Part helps what I really wanted was a windows application usability for these lists.


This is a perfect job for client-side JavaScript making SOAP calls to the SharePoint WebServices using DHTML to display the items. This kind of code now has the nifty name of AJAX although this is really AJAX-lite as the size of our framework is tiny and doesn’t need to handle complex object serialization/deserialization or data binding.

The goal is to have a WebPart that lists the Links based lists (must be an easier way to say that) and with a click display the items on the list, I also wanted to have an easy way to add items into the list. All code would be client-side javascript and also needed to work in Mozilla based browsers such as FireFox as I also use that a lot. Once the initial page has been rendered as normal by SharePoint there are no other postbacks or page refreshes needed so response times are very fast.


The Javascript will be initially be hosted in a Content Editor Web Part but I plan to create a server side WebPart also. Here’s a screen shot.

List of Lists

 

Add a new Item


 
SharePoint already does some AJAX already, albeit in a limited fashion, to handle getting and setting Web Part properties while editing the Properties of Web Parts. It posts to the WebPartPages.asmx SOAP service using the SaveWebPart and GetWebPart SOAP methods. Note the built-in SharePoint SOAP calls are only made if the browser is IE 5 and up. This and the fact that the library calls are hard-coded to use the WebPartPages web service only meant I had to create my own routines.

Soap requests by hand

Ok down to the nitty-gritty.
I’m going to assume you already have some understanding of what SOAP is but basically it is the method of sending an XML formatted request to a URL via HTTP and getting back a XML formatted response.
The first thing our Web Part needs is the list of Links type lists on the SharePoint site we are on.
To do this we will call the GetListCollection method of the Lists.ASMX web Service.
If you add /_vti_bin/lists.asmx page to your site URL you will see the methods this service exposes. Click the GetListCollection method to see the format of the SOAP request it expects.

The function GetLinksList populates the list of links and is the entry point into our code. We hook into the windows load event like this:

if (window.addEventListener)
window.addEventListener('load', _WPQ_AjaxObject.GetLinksList, false);
else
window.attachEvent("onload",_WPQ_AjaxObject.GetLinksList);

Note the use of the _WPQ_ token, this is replaced by our Web Part ID by the CEWP at runtime so avoiding duplicate global variables when there are two instances of the same Web Part on a page.

So we send a POST request to the URL /_vti_bin/Lists.asmx?op=GetListCollection and make sure we have a SOAPAction header set to the correct operation which is http://schemas.microsoft.com/sharepoint/soap/GetListCollection.
This will give use a list of all lists which we can filter for the Links’s type.

The body of the request will look like this:

<?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>
<GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>
</soap:Body>
</soap:Envelope>

This is a very simple request string and notice the majority of the text is the boilerplate SOAP XML envelope that will never change, so in the code I’ve created vars that contain this text called SoapPrefix and SoapPostFix

To submit the request we create a XMLHttpRequest object and use its send method asynchronously, the function that calls the List Web Service looks like this

function ajaxsp_GetLinksList()
{
var xmlrequest;

xmlrequest = aSoapPrefix+ '<GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>'
xmlrequest += SoapPostfix;

if (!xmlhttpLists) xmlhttpLists= GetHTTPObject();

xmlhttpLists.open("POST, "_vti_bin/Lists.asmx?op=GetListCollection",true);
xmlhttpLists.setRequestHeader("
Content-Type","text/xml; charset=utf-8");
xmlhttpLists.setRequestHeader("
SOAPAction","http://schemas.microsoft.com/sharepoint/soap/GetListCollection");
xmlhttpLists.setRequestHeader("Content-Length", xmlrequest.length);


// set the callback event
xmlhttpLists.onreadystatechange=ajaxsp_GetLinksListEvent;
// Send the request
xmlhttpLists.send(xmlrequest);

return true;

}

Two important functions are GetHTTPObject which returns the HttpRequest object for both IE and Mozilla based browsers and the ajaxsp_GetLinksListEvent which does the heavy lifting of processing the returned XML

GetHTTPObject needs to create different objects for IE and Mozilla because Mozilla has a built-in native Javascript object called XMLHttpRequest whereas IE uses the ActiveX XMLHTTP  object contained in MSXML libraries, there a quite a few versions of this library and the ProgID changes with each one but I just use either Msxml2.XMLHTTP  of Microsoft.XMLHTTP which should cover most modern machines with IE5.0 and up, interestingly IE 7 has now gained the native XMLHttpRequest object.


Heres a snippet of the returned XML.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><GetListCollectionResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetListCollectionResult>
<Lists>
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Company Links/AllItems.aspx" ID="{60E01147-6A7A-47F0-8CA1-4D88426280DE}" Title="1 Company Links" Description="" ImageUrl="/_layouts/images/itlink.gif" Name="{60E01147-6A7A-47F0-8CA1-4D88426280DE}" BaseType="0" ServerTemplate="103" Created="20050225 11:43:06" Modified="20060222 09:58:24" LastDeleted="20060220 17:42:54" Version="1" Direction="none" ThumbnailSize="" WebImageWidth="" WebImageHeight="" Flags="4105" ItemCount="16" AnonymousPermMask="" RootFolder="" ReadSecurity="1" WriteSecurity="1" Author="3" EventSinkAssembly="" EventSinkClass="" EventSinkData="" EmailInsertsFolder="" AllowDeletion="True" AllowMultiResponses="False" EnableAttachments="False" EnableModeration="False" EnableVersioning="False" Hidden="False" MultipleDataList="False" Ordered="True" ShowUser="True" />
<List DocTemplateUrl="" DefaultViewUrl="/Lists/Links/AllItems.aspx" ID="{233585BA-5972-40BB-85C8-67F793F22F67}" Title="2 Links" Description="Use the Links list for links to Web pages that your team members will find interesting or useful." ImageUrl="/_layouts/images/itlink.gif" Name="{233585BA-5972-40BB-85C8-67F793F22F67}" BaseType="0" ServerTemplate="103" Created="20041008 14:10:07" Modified="20060224 10:43:07" LastDeleted="20050302 08:40:12" Version="1" Direction="none" ThumbnailSize="" WebImageWidth="" WebImageHeight="" Flags="4105" ItemCount="26" AnonymousPermMask="" RootFolder="" ReadSecurity="1" WriteSecurity="1" Author="3" EventSinkAssembly="" EventSinkClass="" EventSinkData="" EmailInsertsFolder="" AllowDeletion="True" AllowMultiResponses="False" EnableAttachments="False" EnableModeration="False" EnableVersioning="False" Hidden="False" MultipleDataList="False" Ordered="True" ShowUser="True" />


As you can see a large amount of information is returned for each list but I’m most interested in four attributes of each list:

Name –List Name for display
ServerTemplate – for filtering
DefaultViewURL – to allow jumping to the standard SharePoint links page
Hidden - for hiding hidden lists.

The ServerTemplate is used to filter out only those lists that are Links based list which have a list template of 103. I use the constant LISTTEMPLATE_LINKS which is defined in ows.js.

The routines for parsing this XML into DOM nodes brings out the second major difference between IE and Mozilla : IE uses the MSXML ActiveX objects whereas Mozilla has the XML parsing javascript objects built-in.
I’ve encapsulated the conversion of the response XML into a set of DOM nodes into the GetSoapResponseItems routine

// Cross Browser helper to filter the XML results
// Returns array of Node objects
function ajaxsp_GetSoapResponseItems(SoapResponseXML, XPathFilter, NSResolver)
{
var m_Items=[];

// Mozilla
if (window.addEventListener)
{
    var aItems = xmlhttpLists.responseXML.evaluate(XPathFilter, SoapResponseXML, NSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
    for( var i = 0; i < aItems.snapshotLength; i++){m_Items[i] = aItems.snapshotItem(i);}
}
else
{ // IE
    var xmlDom=new ActiveXObject("MSXML2.DOMDocument.3.0");
    xmlDom.async=false;
    xmlDom.loadXML(SoapResponseXML.xml);
    m_Items=xmlDom.selectNodes(XPathFilter.replace("sp:",""));
}

return m_Items;

}

In IE you create a MSXML.DomDocument ActiveX object and use its LoadXML method to load the XML and use selectNodes with an XPath expression to get back the list of items
Mozilla has support for a lot of the DOM Level 3 XPath  (http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html)  which allows XPath expression to be run against the XML data. The responseXML property is actually an object that has parsed the XML but unfortunately it doesn’t have support for selectNodes so you have to use the evaluate function to get a XpathResult and with that you can add the individual Dom nodes to an array.
One wrinkle with Mozilla is that’s its very picky about NameSpaces so you must specifiy a prefix in your XPath filter for default namespaces and also you need a routine that converts the prefix to the full Namespace, there are a couple of ways of doing this but a simple function returning a string is the easiest.

function moz_NSResolver1(NSPrefix)
{
switch(NSPrefix)
{
case "soap" : return "http://schemas.xmlsoap.org/soap/envelope/"; break;
default : return "http://schemas.microsoft.com/sharepoint/soap/";
}
}

As this function needs to vary depending on the XML I pass this in as function pointer in parameter NSResolver.
IE does some guessing for you so we can strip the prefix from the XPath filter.

Now once we have the Links Lists I create a set of ListItems (LI) in an Unordered List (UL) and display them by setting the innerHTML of a DIV element.

Whew, thats enough for one entry, In part 2 I’ll delve into displaying the actual links and creating a form for adding a new item using the client-side OWS Field objects and post the DWP file that contains all the code and which can be imported into your Web Part page.

Add SharePoint Portal Exclude/ Include Rules via Code

If you have ever wondered how you can add or remove items via code that you see on the exclude and include content page then here is some sample code to do that.

As you can see in the screen-shot below I’ve added a new rule (called a Site Restriction in SharePoint terms) that includes a main site and its content and excludes a subsite, these sub filters are called SitePaths. 

 

Using code shown at the end of this entry I've added the w2k3-devclient:5003 site rule.

 

What we need to do is obtain the ISiteRestrictions interface and use its Add method passing it a group name (note this is not the full URL it must be a without the http:// prefix) and true or false indicating whether it is to be included or excluded.

 

MSSITLB.ISiteRestriction Add ( System.String bstrName , System.Boolean fIncluded )

 

The Add method returns a ISiteRestriction object which in turn has a Add method returning a ISitePath2 object which allows us to add Site Paths to fine tune the filtering of the content source.

Some methods of the ISitePath2 interface are

 

string AccountName [ get]
int AuthenticationType [ get]
bool FollowComplexUrls [ get, set ]
bool Included [ get, set ]
bool IncludeSubdirs [ get, set ]
bool SuppressIndex [ get, set ]

 

Curious at the 2 at the end of ISitePath2? Way back in the old days of COM if you wanted to extend an interface with new methods you couldn’t change the original as that was part of the COM contract of invariance of interfaces, so the developers normally just pegged the next number up on the interface name to create the new one. (Update: whoops this is just been added to .net best practice guidelines ;-) )

 

Now the path to actually get a ISiteRestriction interface is a bit torturous mainly because all the functionality for controlling the search engine is done by accessing the SharePoint Portal Search COM libraries via interop and the documentation for these interfaces are completely missing.

Also according to MSFT they are completely unsupported, hmm so the means of controlling your enterprise wide search engine via API’s is unsupported?

This is not good but hopefully completely sorted via a managed interface in v3.

 

For now to get access to the interfaces we need to add a reference to thses libraries on the COM tab - the SharePoint Portal Search CoClasses Type Library which gives us access to SearchAdminClass and also the Microsoft Search Interface Type Library for the other COM interfaces.

  • We instantiate the SearchAdminClass class which gives use access to the Applications property, now Applications in MS Search terminology comes down to the directory that the indexes are stored in. Different applications will have different directories, it’s a way of separating index content.
  • Each portal site has a specific ApplicationName that we get from the PortalSite object.
  • This then allows us to get a reference to the BuilderServer interface which we can get the list of Catalogs. The default catalogs are the famous Portal_Content and Non_Portal_Content.
  • Then finally within each catalog live the Site Restrictions which define the source of the data to be indexed.

Heres the sample code for a simple console application that lists the Catalogs and all the Site Restrictions and Site Paths within them.

As shown in the screen-shot above it also adds some test rules to the Non_Portal_Content catalog so comment that out of you don't want that to happen.

using System;

using Microsoft.SharePoint.Portal.Topology;
using MSSCTLB;
using MSSITLB;

namespace ShowRestrictions
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    class Class1
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            ShowRestrictions();
        }

        private static void ShowRestrictions()
        {
            TopologyManager man=new TopologyManager();
    
            PortalSite site=man.PortalSites[new Uri("http://w2k3-devclient:5001")];

            MSSCTLB.SearchAdminClass sadmin=new SearchAdminClass();
            
            IApplications applications1 = (IApplications) sadmin.Applications;
            IApplication2 application1 = (IApplication2) applications1[site.SearchApplicationName];
            IBuildServer2 buildserver= (IBuildServer2)application1.BuildServer;
            IBuildCatalogs2 catalogs=(IBuildCatalogs2)buildserver.BuildCatalogs;

            foreach(IBuildCatalog3 cat in catalogs)
            {

                Console.WriteLine("Catalog {0}",cat.Name);
                MSSITLB.ISiteRestrictions siterestrictions=(MSSITLB.ISiteRestrictions)cat.Sites;
        
                if (cat.Name=="Non_Portal_Content")
                {
                    MSSITLB.ISiteRestriction NewSearchContent=siterestrictions.Add("w2k3-devclient:5005",true);
                    NewSearchContent.Paths.Add("http://w2k3-devclient:5005/sites/",true);
                    NewSearchContent.Paths.Add("http://w2k3-devclient:5005/sites/dontsearchme/*",false);
                }

                foreach(ISiteRestriction2 restriction in siterestrictions)
                {
                    Console.WriteLine(restriction.Name + "," + restriction.Included);
                    foreach(MSSITLB.ISitePath2 sitepath in restriction.Paths)
                        Console.WriteLine("SitePath : {0},{1}",sitepath.Path,sitepath.Included);
                }    
            }

            Console.ReadLine();
        }
    }
}

Now there is a lot more to the Search classes than this, with them you can control all aspects of the search process and I hope to get some time to do more digging.

 

Colin.

 

 

Color Picker Web Part

Here's something I thought I'd throw out there to demonstrate the much underrated Content Editor Web Part (CEWP). It's a client-side JavaScript Color Picker based on code from http://www.mickweb.com/javascript/colours/websafe.html 

The CEWP allows you to either embed formatted Rich Text into the web page or pure HTML/JavaScript so I could very quickly create a Web Part using the copied javascript. It just took a few mods such as getting rid of the image dependency by using a SPAN to form the cells instead.

The code's not much to talk about its just plain JavaScript creating a HTML <table> with <td>'s for the color cells. One interesting point is that the color table is dynamically generated. This is done by placing an empty block element, <p> in this case, giving it an ID and then set its innerHTML property to the generated table string.

function AttachColorTable(){
document.getElementById("'colourtable'").innerHTML= table_maker(36,6,'Select Colour');
}

This code is called on the load event of the page. I use attachEvent so I don't override any other code that may be using the onLoad event.

window.attachEvent('onload',AttachColorTable);

You can either import the DWP file on a single page or upload to the Site or Virtual Server galleries using instructions given by Todd Bleaker in a cool tip here http://mindsharpblogs.com/todd/archive/2005/08/04/646.aspx

Also check out the Google search Web Part also using the CEWP by Todd http://mindsharpblogs.com/todd/archive/2005/08/04/647.aspx

 

Download : Color Picker1.zip (1.36 KB)

Adding a hidden SharePoint Field in code

I tend to keep an eye on the sharepoint newsgroups mainly because I can jump in and help someone out with something I've had problems with in the past. One question today on the newsgroup was about adding a hidden field to a sharepoint list via the object model. I've hit this before and until you find out whats going on you feel a bit like you're in a maze of twisty passages all alike.

Often you might want an internal tracking flag or number handled via code that you don't want exposed in the edit list UI that some unsupecting user might then delete by accident.

In theory this should simple.

        SPSite spsite=new SPSite(http://localhost:4455);
        SPWeb web=spsite.OpenWeb();

        SPList list =web.Lists["Announcements"];

        string id=list.Fields.Add("NewField",SPFieldType.Boolean,false);
        SPField spfield=(SPField)list.Fields.GetField(id);

        spfield.Hidden=true;
        spfield.Update();

 

But if you try this you'll hit an exception - the reason? The code for the Hidden property checks to see if the property CanToggleHidden property is true and throws an exception if its not.

Fair enough,all we do then is set the CanToggleHidden  property to true and do the update but there is no such property on the object model.

There are two ways around this but its best to know some internal details about the SPField object. This way the internal data for a field is stored is in an XML format like this

<Field DisplayName="NewField" Type="Boolean" Required="FALSE" Name="NewField" ColName="bit4" CanToggleHidden="TRUE" Hidden="TRUE"/>

You can see here the CanToggleHiden and the Hidden properties. Note not all fields will have these extra properties if they are not set. The properties are read from /written to via the .dotnet XML classes.

This field definition is a subset of the whole list schema which is taken from the tp_Fields column of the Lists table in the SharePoint content database. The ColName refers to the column used to store the data in the UserData table. You don't need to add this column by hand its automatically calculated from the field type and number of fields of that type already in use.

Now the SPField object internally gets and set its properties into this XML fragment using some helper functions one of which is SetFieldBoolValue for boolean fields, there is also SetFieldIntValue and SetFieldAttributeValue.

The first way to get around the limitation is to add the field directly as XML using the AddFieldAsXml method on the SPFieldCollection using the XML format as above but leaving out the columnname.

   SPList list =web.Lists["Announcements"];

   list.Fields.AddFieldAsXml("<Field DisplayName=\"NewField\" Type=\"Boolean\" Required=\"FALSE\" Name=\"NewField\" CanToggleHidden=\"TRUE\" Hidden=\"TRUE\"/>");

Thats the supported way but one other interesting method is to toggle the internal CanToggleHidden internal property with reflection.

Add a reference to the System.Reflection namespace and you can do this

   Type type = spfield.GetType(); 
   MethodInfo mi= type.GetMethod("SetFieldBoolValue", BindingFlags.NonPublic | BindingFlags.Instance);
   mi.Invoke(spfield,new object[]{"CanToggleHidden", true});

you can then set the Hidden property via the OM without getting an exception

   spfield.Hidden=true;
   spfield.Update();


Either way both of these are hacks but at least one is supported.
 

Hiding SharePoint Page Elements when Printing

This entry was prompted by a question on a newsgroup about hiding screen elements so you can get the whole contents of a page fitting onto a printout without the top/left nav links.

Personally I don't like the printer friendly buttons you see on web pages mainly because you often find heaps of javascript dynamically altering DOM element's styles or the developer has coded a special version of the page just for the printer.
Sometimes that may be needed but I'd just recently done a website where this feature was requested and came across an aspect of css that I'd missed before. That was the media=print attribute. A few tweaks to the css file to hide navigation divs and margins and voila the printouts came out perfectly.

In CSS 2 you can specify different stylesheets for the different output devices the document may be rendered to, see http://www.w3.org/TR/REC-CSS2/media.html for the full details.
You can either reference a separate css file like this

<LINK rel="stylesheet" type="text/css" href="print.css" media="print">

or you can inline the style like this

@media print { .classname   {display :none; visibility:hidden } }

So to alter how a SharePoint page looks when printed we can either alter the css files that SharePoint uses : ows.css and sps.css with SP Portal : or inline the stylesheet in the page.
We can inline by adding a Content Editor Web Part and using its source edit button to add this text

<style type="text/css">
@media print
{
.ms-bannerframe   {display :none; visibility:hidden }
.ms-nav   {display :none; visibility:hidden }

}
</style>

Go to File-Print Preview and you should see that the top and left nav bars are now hidden.
On the Portal sites you probably need to add the ms-navframe style to the list above as that's used in the left nav bar.

 

Getting Macromedia Flash to talk to Sharepoint

Towards the end of last year our company Flexnet Consultants decided to release a set of free Web Parts that we thought might be useful to have installed in WSS/SPS sites. The first of these was a Flash slideshow based on the pictures held in a SharePoint picture library http://www.flexnetconsult.co.uk/WebParts/FlashSlideShow.htm.

We like Flash a lot as its a great way to liven up dull static pages and the content can be a lot more than 'eye-candy'

 

The first thing you need is some way to create the swf file that Flash is compiled into, in-house we use Flash MX. The actual slideshow view of the pictures wasn't a real problem as that's exactly the kind of thing Flash is good at. We use ActionScript to control the action on the Flash stage. 

The main issue was how to get a list of the pictures held in the library.

We looked at a few possible ways, one would be to pass a list of pictures to Flash using its URL or the FlashVars property, not pretty and not scaleable, the next idea was to talk to the SharePoint Web Services, this would have been ideal except Flash only really got proper support for talking to generic Web Services with the recent version 8 and we wanted to support Flash players version 6 and upwards to allow for the widest audience. We will probably create a version that supports 8 later.

That left us with one final method and it turns out to be a very easy one, the old SharePoint URL protocol available since version 1. The docs for these calls are here http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spsdk11/Client_API/spURLProtocol.asp , there's a lack of useful examples but its pretty easy to use.

You pass parameters via URL to the SharePoint ISAPI DLL called owssvr.dll in the _vti_bin directory and it returns xml information either formated in a CAML style or raw data rowset style. We want raw Data so we put XMLDATA=true in the URL

As an example if you pass http://sharepoint.flexnet.ds/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List={89ECD870-30EE-4E6E-B39B-B2C8CC548213}&XMLDATA=TRUE then you get this XML data back

- <xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
- <s:Schema id="RowsetSchema">
- <s:ElementType name="row" content="eltOnly" rs:CommandTimeout="30">
- <s:AttributeType name="ows_SelectedFlag" rs:name="Selection Checkbox" rs:number="1">
  <s:datatype dt:type="ui1" dt:maxLength="1" />
  </s:AttributeType>
- <s:AttributeType name="ows_DocIcon" rs:name="Type" rs:number="2">
  <s:datatype dt:type="string" dt:maxLength="512" />
  </s:AttributeType>
- <s:AttributeType name="ows_NameOrTitle" rs:name="Name" rs:number="3">
  <s:datatype dt:type="string" dt:maxLength="512" />
  </s:AttributeType>
- <s:AttributeType name="ows_ImageSize" rs:name="Picture Size" rs:number="4">
  <s:datatype dt:type="i4" dt:maxLength="4" />
  </s:AttributeType>
- <s:AttributeType name="ows_FileSizeDisplay" rs:name="File Size" rs:number="5">
  <s:datatype dt:type="i4" dt:maxLength="4" />
  </s:AttributeType>
- <s:AttributeType name="ows_RequiredField" rs:name="" rs:number="6">
  <s:datatype dt:type="string" dt:maxLength="1073741823" />
  </s:AttributeType>
  </s:ElementType>
  </s:Schema>
- <rs:data>
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London01.jpg" ows_ImageSize="180" ows_FileSizeDisplay="10587" ows_RequiredField="Flash Test/London01.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London02.jpg" ows_ImageSize="180" ows_FileSizeDisplay="7675" ows_RequiredField="Flash Test/London02.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London03.jpg" ows_ImageSize="180" ows_FileSizeDisplay="14242" ows_RequiredField="Flash Test/London03.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London04.jpg" ows_ImageSize="180" ows_FileSizeDisplay="6755" ows_RequiredField="Flash Test/London04.jpg" />
  <z:row ows_SelectedFlag="0" ows_DocIcon="jpg" ows_NameOrTitle="London05.jpg" ows_ImageSize="180" ows_FileSizeDisplay="19926" ows_RequiredField="Flash Test/London05.jpg" />
  :row ows_SelectedFlag="1" ows_NameOrTitle="SubFolder1" ows_RequiredField="Flash Test/SubFolder1" />
  </rs:data>
  </xml>
 
 
As you can see the data holds information such as the filesize and the subfolders, this could be used to recurse into the sub folders if you wanted.
 
This format is exactly what we need as Flash has a XML object that can load XML data from a URL.
Example code for this would be

var x=new XML();
x.ignoreWhiteSpace = true;
x.onLoad = LoadDocs;
x.Load("http://sharepoint.flexnet.ds/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List={89ECD870-30EE-4E6E-B39B-B2C8CC548213}&XMLDATA=TRUE");

The XML object will call the LoadDocs function when it has completed the data download.
 
All thats left is to iterate the XML nodes and extract the picture names from the attribute ows_RequiredField
 
function LoadDocs(success)
{
   if (success){
   var eXmlRoot = this.firstChild;

   // allow for /r/n in the XML stream
   var eXmlSchema= eXmlRoot.childNodes[1];
   var eXmlData= eXmlRoot.childNodes[3];
   var eRows= eXmlData.childNodes;

   arrImages=new Array();

   for (i=0; i< eRows.length; i++){
      if (eRows[i].nodeType==1 && eRows[i].nodeName.toLowerCase()=="z:row"){

         //Skip subfolders and only show jpgs
         // to only show jpgs use (eRows[i].attributes.ows_DocIcon == 'jpg')
         if(eRows[i].attributes.ows_SelectedFlag=="0"
         {
            arrImages.push(eRows[i].attributes.ows_RequiredField);
         }
      }
   }

   }
// The list of pictures are now loaded
}
 
Now that we have the list of pictures we can then use ActionScript to rotate through them.
 
Once you have created your Flash SWF the final thing that needs to be done is to render out the OBJECT tag that will host the Flash file in the RenderWebPart event, this will look like this on the SharePoint page:
 
<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" WIDTH=180 HEIGHT=120 id="FlexnetSlides_WPQ4" ALIGN="">
<PARAM NAME=movie VALUE="http://sharepoint/wpresources/FlexnetConsultants.Slideshow/Flexnet-SlideShow1_2.swf">
<PARAM NAME=FlashVars VALUE="ServerBase=http%3a%2f%2fsharepoint%3a80&SiteURL=http%3a%2f%2fsharepoint&PicLib=dcebbba5-5e94-49a1-88b7-03f7625dd8e4&RootFolder=&Transition=Fade&Displaytime=5&Transitiontime=3&ScaleMode=0&MaxCachedImages=30">
<PARAM NAME=menu VALUE=false>
<PARAM NAME=quality VALUE=high>
<PARAM NAME=scale VALUE=noscale>
<PARAM NAME=bgcolor VALUE=#FFFFFF>
<EMBED src="http://sharepoint/wpresources/FlexnetConsultants.Slideshow/Flexnet-SlideShow1_2.swf" menu=false scale=noscale quality=high bgcolor=#ffffff FlashVars="ServerBase=http%3a%2f%2fsharepoint%3a80&SiteURL=http%3a%2f%2fsharepoint&PicLib=dcebbba5-5e94-49a1-88b7-03f7625dd8e4&RootFolder=&Transition=Fade&Displaytime=5&Transitiontime=3&ScaleMode=0&MaxCachedImages=30" WIDTH="180" HEIGHT="120" NAME="FlexnetSlides_WPQ4" ALIGN=""TYPE="application/x-shockwave-flash" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer">
</EMBED>
</OBJECT>
 
The FlashVars method allow you to reference parameters as variables in the ActionScript code e.g. _root.ServerBase and _root.SiteURL
 
Future Web Parts in the pipeline are an improved Slideshow, a PhotoStrip with dynamic pictures (like the Flikr badge) and a analog/digital clock.
 
 

Running both versions of ASP.NET with WSS

I've just been going through the process of upgrading my development SharePoint sites to run on ASP.NET 2.0 and SQL server 2005.

First I installed the WSS Service Pack 2. No problems there.

Then I upgraded one of my sites to run on ASP.NET 2.0 using the nice new IIS property tab and running the stsadm -o upgrade -forceupgrade -url http://portalsiteurl command . The ststadm command updates the web.config in the IIS root directory and it also runs the contents of Web Server Extensions\60\TEMPLATE\SQL\STOREUP.SQL. This adds some new fields to tables and updates a lot of the core stored procedures.

An IISRESET later and I could browse to the WSS sites running on ASP.NET 2.0 with no problems.

The real problem came when I try to access another set of WSS sites that were running on a different IIS virtual server that gave me the dreaded Server Application Unavailable error

 

No panic as I knew from previous experience this is almost always a problem with Web.Config parsing or Application Pool configuration/identity .

Checking the Event Log gave me this

 

 

 

Fantastic, a helpful error message, it pointed me straight to the fact that the same Application Pool was in use by both Virtual Server applications and ASP.NET doesn't support loading a second version of .NET in the same worker process.

The fix is to create a new App Pool for the applications you want to run under ASP.NET 2.0. This would include any .Net applications that share an AppPool not just SharePoint.

 

 

Create a new App Pool based on the existing SharePoint one, give it a meaningful name

 

Assign it to the Virtual Server running SharePoint

an iisreset later and you should be good to go.

I'm so glad I don't try this stuff out on live servers, anymore.

DoubleClick Minimise/Restore for WebParts

One Click Minimize/Restore for WebParts

One anoyance I have with WebPart pages that have a lot of WebParts is the need to click the WebPart menu to select Minimize or Restore when you want a little more screen real estate or you need to expand a previously minimized WebPart.

To solve this niggle I've developed a simple WebPart that using client side Javascript that traps the double click on a WebPart title and toggles the state of the WebPart. It integrates with the SharePoint client side object model so any changes are saved to the site and persists across sessions.

Unzip the DoubleClick WebPart Minimize/Restore DWP file to a directory of your choice

Browse to the SharePoint WebPart page - Click the 'Modify My Page' - 'Add Web Parts' - 'Import menu' option

Click the Browse button and select the DWP file.

Click the upload button - once uploaded you can drag the WebPart to anywhere on the page.

 


Here's a little more detail of the SharePoint client side scripting.

First I needed to get a list of WebParts on a page. Thats easy - every WebPart page that holds WebParts has a global object WPSC that is initialized like this WPSC.Init(document). The object WPSC is defined in IE55up.js/IE50.js/NonIe.js depending on browser. This is the Web Part Page Services Component which handles the client side WebPart infrastructure, it provides services such as loading/saving properties and handles page events see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/spptsdk/html/wpscaOverview_SV01098760.asp  for more details.

OnPage javascript adds every WebPart on the page to its WebPartPage.Parts collection e.g.

WPSC.WebPartPage.Parts.Register('WPQ1','8db7d8d8-b1e0-4e9d-84ff-0b266b8c0beb',document.all.item('WebPartWPQ1'))

To get a list of all the WebParts on the Page you can do this

for(var i=0; i< WPSC.WebPartPage.Parts.Count;i++)
  {
    wp=WPSC.WebPartPage.Parts.Item(i);
}

What we really need is the WebPartQualifierproperty of the WebPart which is the unique ID for each WebPart on the Page, various parts of the WebPart are then suffixed with this identifier e.g WebPartTitleWPQ2 for the title div and WebPartWPQ2 for the actual serverside rendered HTML div container.

So to hook the DblClick event we can modify the inner loop like so

    wpq=WPSC.WebPartPage.Parts.Item(i).WebPartQualifier;
    var oTitle=document.getElementById('WebPartTitle' + wpq);
  
    if (oTitle)
    { 
       oTitle.attachEvent('ondblclick',MinRestoreWP);
    }

Here we're setting the double click event to fire the MinRestoreWP function. this is defined as

function MinRestoreWP()
{
var WPQId, WPObject;
var titleid,  WPQPos;

// Clear the selection the double click produces
document.selection.empty();
window.event.cancelBubble = true;
window.event.returnValue = false;

titleid=window.event.srcElement.parentNode.id;
WPQPos=titleid.lastIndexOf("WPQ");
if (WPQPos>0)
{
   WPQId='WebPart' + titleid.substring(WPQPos, titleid.length);
   WPObject=document.getElementById(WPQId);
   // call the MSO api to do the work
   if (WPObject) MSOLayout_MinimizeRestore(WPObject);
}
 
return false;
}

Here we get the WebPart to collapse or expand by going to the parent element of the Title bar which holds the WebPart title id which we can parse to get the WPQ id. This allows us to get the WebPart object we need.

The expand collapse is handled by a SharePoint function MSOLayout_MinimizeRestore which simply takes a WebPart object and does the hard work of hidding or showing the webpart by changing the WebPart.style.display CSS property. It also logs the change so the property is saved back to the WebPartPages webservice and is remembered if you close the browser and revisit the page.

DoubleClickWebPartMinimizeRestore.zip (1.35 KB)

Welcome

hi and welcome to my blog.

I'm Colin Byrne a SharePoint Architect at Flexnet Consultants based in Surrey UK.

When I was a kid I'd always get myself into trouble by taking apart anything I could, electrical, mechanical it didn't matter, you could follow the trail of screws to the corner where I had successfully taken apart plugs, hoovers, walkmans, irons.

Of course I could never put them back together again but as I was a kid that was someones else problem alas now I'm older its my problem if things don't work after I play with them. Unfortunately for SharePoint i've taken a severe liking to it and the old curiosity is kicking in so I'll be delving into the inner working of SharePoint but along the way hopefuly produce some free WebParts and utilites that can make users and admins life easier.

One of the things I also like to do is make SP sites a lot less static so adding Flash based WebParts with dynamic content is something I like to do. I'll be blogging about developing a SlideShow WebPart using Flash in the future, also new technologies such as AJAX and Atlas, well its not that new SharePoint already uses the XMLHTTP object to save and load WebPart properties but more of that later.

Oh have I mentioned I hate having to do the same thing twice.