Changing navigation settings in code

In a previous post I have discussed how to add links to a quick launch list in a SharePoint site. Now I have had to go a step further and update the navigation settings for a site, such as telling to to include pages and sub-sites automatically in the navigation. This is easy enough to change when you know how, it is all based around some site settings, as follows:

   1: SPWeb web;
   2: web.AllProperties["__IncludeSubSitesInNavigation"] = "False";
   3: web.AllProperties["__InheritCurrentNavigation"] = "False";
   4: web.AllProperties["__NavigationShowSiblings"] = "False";
   5: web.AllProperties["__IncludePagesInNavigation"] = "False";
   6: web.Update();

Each of these settings are fairly self explaining, and you can set them all to be either True or False. As always, don’t forget the web.Update() at the end to record you changes to the database.

Change the master page with code

From time to time it might be necessary to change the master page settings of a page with some code (a current example I have is with a feature. when it is activated it changes the current sites master page to a different one). This is easy enough to do through the properties of SPWeb:

   1: SPWeb web;
   2: web.MasterUrl = "/catalogs/masterpage/newfile.master";
   3: web.CustomMasterUrl = "/catalogs/masterpage/newfile.master";
   4: web.AlternateCssUrl = "/Style Library/mystyles.css";

This will work for you, but when you browse to the master page settings in the site, it will still look like it is being inherited from the parent. This is because there is a site setting that needs to be changed to tell the site that the master page is not inheriting. Use this code to make sure this happens:

   1: Hashtable webSettings = web.AllProperties;
   2: webSettings["__InheritsMasterUrl"] = "False";
   3: webSettings["__InheritsCustomMasterUrl"] = "False";
   4: webSettings["__InheritsAlternateCssUrl"] = "False";

The above code updates the site settings so the UI will accurately reflect the settings you have applied. Once this code has run, remember to finish off with a web.Update() statement to commit all the changes your code has made.

Error: Value cannot be null. Parameter name: g

If you work with custom site definitions and do it right, you wont ever see this error come up – me on the other hand, had a bit of a shocker last Friday and chased my tail for half a day trying to figure this one out. Basically you will get this error in your 12 hive log (I think you get "Unknown error on the screen" when you try to create a site from a site definition that has some invalid XML in the WebFeatures section of the ONET.xml file.

A common mistake is the casing of the attributes (remember that XML is case sensitive), so for example <Feature Id="" /> wont work, but <Feature ID="" /> is the one you need, so by having the ID attribute cased wrong, you will see this error.

So the best recommendation I have for dealing with this rather useless error message, go back to the onet.xml file for you site definition, and have a good hard look at the XML to make sure it is all valid.

Nesting Master Pages in MOSS

The project I am on is starting to get into some ‘look and feel’ territory, which means we are of course doing some master pages. What we wanted to achieve was to have a single master page so that the look and feel of the site is defined in a single place, instead of in multiple master pages – but lets be honest, a single master page will rarely cut it for you, especially if you have multiple types of sites that need to look slightly different. This is where master page nesting comes into play.

ASP.NET 2.0 introduced us to master pages, and with it a feature called nesting of master pages. This is exactly what it sounds like, where a master page can have its own master page, so you create a parent-child relationship of sorts between them. Given that WSS and MOSS are just ASP.NET applications at their base level, they too can use nested master pages (although none of the out-of-the-box functionality uses it).

So what we have done is created a single master page that has the sites main header and side navigation on it, and we are using child master pages to define different "layouts" for each page – such as one master page has a right bar down the side, and we have another master page that has controls on it for meeting workspaces, but they all share the same parent master page, so when the look and feel needs to change, chances are it will only need to be done in the one parent file.

The easiest way we found to achieve this was based on a blog post from Ari Bakker, where he demonstrates renaming the content areas with ‘Base’ at the end of them in the parent master page, and in the child master page you add content to the "base" content area, inside which will go the actual content area (as it was in the parent master, before it was renamed with the ‘base’ bit on the end). This worked fantastically, especially for the meeting workspace as we could add the meeting specific controls to the master page (Like the out-of-the-box MWSMaster.master file in every meeting workspace) and still keep the look and feel centralised.

If you are developing look and feel for any MOSS site, I would seriously be considering using nested master pages – they will greatly simplify the maintenance of the site.

Postback from TreeView check boxes

The ASP.NET TreeView control is a handy thing in plenty of places in ASP.NET applications, but when I went to use one in a project I am working on, I discovered that there is no AutoPostback property for the checkboxes that are provided, and considering I am using my TreeView in an UpdatePanel this was a bit of a problem for me – but there is a pretty easy work around for it.

First of all, get this JavaScript onto you page somewhere:

   1: function postBackCheckBox()
   2: {
   3: var o = window.event.srcElement;
   4: if (o.tagName == 'INPUT' && o.type == 'checkbox' && o.name != null && o.name.indexOf('CheckBox') > -1)
   5:     {
   6:         __doPostBack('', '');
   7:     }
   8: }

Then Add this line of code to get your control to use that JavaScript:

   1: myTreeView.Attributes.Add('onclick', 'postBackCheckBox()');

Lastly (and you can ignore this if you are not using a TreeView inside an update panel) you need to add a button that will postback your update panel for you, so add a linkButton to the page, set its text property to be an empty string, and then put this code in to get the script manager to refresh the update panel of the JavaScript.

   1: myScriptManager.RegisterAsyncPostBackControl(myLinkButton);

Also you will need to change the "__doPostBack(”, ”);" line of the JavaScript to be "__doPostBack(‘myButton‘, ”);" where myButton is the client side ID of the link button control.

Once you have done this, fire up your page and check a box in your treeview, it will then postback on its own (or if you did it inside an update panel, the panel will refresh with the new content).

Trigger an UpdatePanel from outside the control

I have been working on a control that is using some AJAX stuff, which is a bit new to me because I have avoided AJAX for a few reasons in the past. Anyway once you get into it, the controls that MS have added to the .NET framework make it so very very easy to get some basic AJAX functionality onto a page.

The main control used for this is the UpdatePanel. When you put one of these bad boys on your page, if something inside the panel causes a postback, it will still do your postback, but only the content up the UpdatePanel will refresh on the screen. That is all handled by the .NET framework, not a single line of JavaScript is written – very cool stuff. (I have read that the JavaScript postback that occurs is pretty fat and isn’t gonna save you a lot of bandwidth, so if you are really out to shrink the load time, then you are probably better looking elsewhere).

Anyway, I had a need to cause the UpdatePanel to update, but I needed a control that wasn’t in the panel to do it. It’s pretty easy to handle, and here is how you do it. Basically you register the control with the ScriptManager, then when the ScriptManager is spitting out the JavaScript for its AJAX post back stuff, your control is included. So if you have put the ScriptManager on the page all you need to do is this in your OnLoad event:

   1: ScriptManager1.RegisterAsyncPostBackControl(myButton);

That’s it, now when the control ‘myButton’ throws a postback, it will do it via AJAX, and the update panel will refresh. Too easy.

Canberra SharePoint user group April Meeting – Security with ForeFront/Customising the Search Centre

This month’s user group meeting is on Wednesday night, and we have two people presenting this month.

Derek Moir from Microsoft will present about Microsoft ForeFront – security for SharePoint. This is a chance for the IT professionals to come in and hear about defending your SharePoint installation from viruses and attacks.

Then, Patrick Tisseghem – an MVP from Brussels will be presenting a session about a number of techniques that are explained in the latest MS Press book "Inside Index and Search Engines: MOSS 2007" regarding the customization of the Search Center. The 100% demo-driven session starts with the proper configuration of the index engine to the full customization of the pages and Web Parts that are part of the Search Center.

As always, the beer and pizza are on the house, so come along, meet some fellow SharePoint enthusiasts, and you will probably learn a thing or two as well. To register for the event, check out the details on the Canberra user group web site at http://www.sharepointusers.org.au/Canberra/Lists/Events%20Calendar/DispForm.aspx?ID=10.1.4&Source=http%3A%2F%2Fwww%2Esharepointusers%2Eorg%2Eau%2FCanberra%2FLists%2FEvents%2520Calendar%2Fcalendar%2Easpx.

Run code when a meeting workspace is created

I’m not sure how common this one is, but I thought I would add it here just in case I ever needed to do it again! Basically the requirement is that we have to perform a specific task every time a new meeting workspace is created. After a little bit of thought, the easiest way to do this is with an event handler, specific the SPListEventReceiver. If you have an event receiver that you attach to your events list with the following code, you can handle new event workspaces being created.

   1: public class MeetingWorkspaceHandler : SPItemEventReceiver
   2: {
   3: public override void ItemUpdating(SPItemEventProperties properties)
   4:     {
   5: if (properties.BeforeProperties["Workspace"] == null)
   6:         {

   7: if (properties.AfterProperties["Workspace"] != null &&                       !String.IsNullOrEmpty(properties.AfterProperties["Workspace"].ToString()))
   8:             {
   9: string url = properties.AfterProperties["Workspace"].ToString().Split(new char[] { ',' })[0];
  10: using (SPSite site = new SPSite(url))
  11:                 {
  12: using (SPWeb web = site.OpenWeb())
  13:                     {
  14: // Do stuff here with web as the meeting workspace
  15:                     }
  16:                 }
  17:             }
  18:         }
  19:     }
  20: }

So with this you could go and perform any task necessary with the meeting workspace. The other option I thought of was to possibly look at feature stapling for the code. So for that to work, I would write a feature with a receiver, and put my code into that. The only problem with that scenario though is that the feature code would run before any of the lists and content of the site were created, and in my particular situation I need to get the IDs of a few lists in the meeting site to record in a database. But if your code was running to add something to the meeting workspace, or to perform an action where you do not need any of the site content to exist, I would probably recommend that you use a stapled feature instead.

Calling PostBack from a pop up window

The scenario for today’s blog post is pretty simple. I have a page that is displaying some data, and it uses a pop-up window to add data to the source, so when the pop up has added the data it needs to close itself and then refresh the parent window. It’s not really a difficult thing to do, lets have a look at the code.

In your parent window, add a LinkButton that has an empty text property. Then in the click event of this button, put in the logic that refreshes your data. When you look at the page, you shouldn’t see the link at all, but if you have a look at the source code, you will see the <A> tag for it is on the page.

In your pop-up window, in whatever method is adding the data to your source, add the following code:

   1: String js = "window.opener.__doPostBack('ControlsUniqueId', '');" + Environment.NewLine;
   2: js += "window.close();" + Environment.NewLine;
   3:  
   4: Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "WindowClose", js, true);

This will tell the parent window to do the post back caused by LinkButton we added to the parent page, so your refreshing logic will run. In place of the "ControlsUniqueId" you should put in the UniqueId property of the LinkButton on the parent page. I pass this as a query string parameter to the pop up window, so it knows what control to call the post back as.

Once this is all in, when you run the code, whenever your pop up runs the code that will save your data, it will also then close and refresh your parent window. It’s that easy! At this point I have only tested this in Internet Explorer, so I can’t say for sure that it will work in other browsers, but if it doesn’t it’s just a matter of finding how to call the postback of the window.opener property.

Also, be aware that if your users have JavaScript disabled (for whatever reason) your window won’t close and the main window will not refresh, so if you are trying to make a completely accessible version of the page you will want to give some thought to how you could achieve this.

List all web applications in a SharePoint farm

Here is one you might comes across from time to time – getting a list of all the SharePoint web applications within the current farm. It’s not a difficult bit of code either:

   1:  using Microsoft.SharePoint;
   2:  using Microsoft.SharePoint.Administration;
   3:   
   4:  SPFarm spf = SPFarm.Local;
   5:  SPWebService spws = spf.Services.GetValue<SPWebService>("");
   6:  foreach (SPWebApplication spwa in spws.WebApplications)
   7:  {
   8:   //do something here with the web application object
   9:  }

You can then get settings relevant to each web application, or get a list of the site collections within the application. All this without knowing the URLs of any site in the environment.