Friday, September 26, 2008

Getting the current position of a contentquery result

I am using a content query web part and want to have the first record appear differently than the other records that come after it. In order to do this you need something like the xsl position() function to determine if you are at the first element or not. You can't use position() from within your ItemStyles.xsl template though (it always returns a 1). So you need to make two quick changes to get this working.

First edit ContentQueryMain.xsl
  1. find the OuterTemplate.CallItemTemplate template
  2. Find this block:
    <xsl:when test="@Style='NewsCategoryItem'">
    <xsl:apply-templates select="." mode="itemstyle">
    [fix will go here]
    </xsl:apply-templates>
    </xsl:when>
  3. Add this to the spot I've indicated above:
    <xsl:with-param name="CurPos" select="$CurPosition" />
  4. Save ContentQueryMain.xsl

Now edit ItemStyles.xsl
  1. Add the following as the first item in your template:
    <xsl:param name="CurPos" />
Now you can refer to the $CurPos value whenever you need to do something different based on the position.

Thursday, September 25, 2008

SharePoint solution deployment getting stuck

I was deploying a solution into our production farm and found that after adding my solution and trying to deploy it (from Central Admin), the the timer job just got stuck on 'Deploying...'. I eventually discovered that one of our app servers had a problem with the Timer Service and shut down. I restarted the timer service and the solution finished deploying.

Now it's time to figure out why the Timer Job failed...

SharePoint VHD Expansion

In my experience, a typical standalone MOSS installation on a Server 2003 machine will use at least 12-14Gb of space, not counting any other add-ons you're using (antivirus, etc). Unfortunately for those of us who use Virtual Server/Virtual PC for SharePoint development purposes, the default drive setup is a fixed-size 16Gb IDE drive, which doesn't leave much wiggle room. If you, like me, saw the 16 gigs during the installation and said "oh, that should be enough", and are now eating your words, this post is for you.

Virtual Server/Virtual PC doesn't have a built-in way to resize .vhd files, but fortunately there are a few options available. Instead of using a somewhat pricey tool like Ghost, there are a few free alternatives. My favorite is appropriately named VhdResizer, and is available here [vmtoolkit.com]. After its installed, just point it at your old .vhd (shut down the vm first) and plug in the name/path of where you want the newer, bigger one. Allocating space for the new drive might take a while (particularly if you're using a slow usb drive). Hang on to your old .vhd, just in case.

After the new .vhd has been created we have to resize the partion to use the additional space. To do this, you'll need to fire up a command prompt and go to your Microsoft Virtual Server\Vhdmount directory. After you get there, use the vhdmount and expand tools and these instructions to resize the partition.

BTW, this applies to any .vhd file, but since most SharePoint devs use virtual environments, I thought I'd add it here to make it easy to find.

A few things to note: If you can't find the command line tools, you might have a older version of Virtual Server. Just upgrade to at least 2005 SP1 and you should find what you need. Also, you might ask why you can't just use dynamically sized disks when you create your VM. The answer is... you can, but they'll be slower. So if you're running your vm's from a portable drive or on a slow laptop hard drive, they'll be reeealy slow. Fixed-size disks will give you a slight speed boost.

Thursday, September 18, 2008

Publishing Page Layout DateTimeField formatting

I wanted to have my DateTimeField show up with a different format than the 'short date' value that it defaults to. Unfortunately you can't just add a date format string to a field property to make this work. I've seen a few alternative ways to do this involving extending the DateTimeField control. But I didn't like those options and felt the way I finally figured out was a little nicer (in my opinion).

Here's what I did:

1. Add a code behind page to your page layout and wire it up. If you don't know how to do this watch this short video by Andrew Connell. The 2nd part has to do with Page Layouts but you do the exact same thing as he does in the first part with the master page.

2. Add an 2 EditModePanels to your page layout like so:

<PublishingWebControls:EditModePanel runat="server" ID="DateEditModePanel">
  <SharePointWebControls:DateTimeField ID="ArticleDateControl" runat="server" FieldName="NewsArticleDate" />
</PublishingWebControls:EditModePanel>
<PublishingWebControls:EditModePanel runat="server" ID="DateDisplayModePanel" PageDisplayMode="Display">
  <asp:Label runat="server" ID="lblFormatDate" />
</PublishingWebControls:EditModePanel>

3. Add the following control references to your code-behind class

protected Label lblFormatDate;
protected DateTimeField ArticleDateControl;

4. Add the following code in your code behind page:

protected override void OnLoad(EventArgs e)
{
if (this.ArticleDateControl != null)
{
string dateString = "";
try
{
dateString = Convert.ToDateTime(this.ArticleDateControl.ItemFieldValue).ToString("MMMM d, yyyy");
}
catch
{
dateString = "";
}
lblFormatDate.Text = dateString;
}
}


5. Do the dance of joy.

Tuesday, September 16, 2008

Publishing Page Layouts and Images

I recently was working on a project and was trying to get a RichImageField in a page layout working. I ran into a speed bump which was my RichImageField was displaying the html markup for my image tag rather than the image itself.

After pouring over the internet, I finally got it to work after I realized that I didn't include the 'SourceID' attribute in my field columns (SourceID="http://schemas.microsoft.com/sharepoint/v3").

Thursday, September 11, 2008

Content Query Web Part

Sadly there is no built in cross-site list view web part. This seems like a pretty big oversight to me, but there are 3rd party tools that do the trick. However if you want that functionality from out of the box components you have to use a content query web part.

Using the content query part is fairly straightforward but there are two things you must remember:

  1. The columns you are filtering on have to be site columns
  2. If you find some of your list fields aren't available to your itemstyle.xsl, it's likely you'll have to modify the web part's xml. In order to do this complete the following steps.
  • Click the verb menu of the web part and choose to export the part to your file system
  • Open the .webpart file on your file system and find the 'CommonViewFields' property and add your field and its type.
  • Delete the old webpart from your page and reimport the webpart (click Add a web part to this zone then use the advanced settings to import, the import option is in the dropdown by the little triangle in the advanced settings)
  • Now your field will be accessible by itemstyles.xsl code.

Monday, September 8, 2008

Custom list with content type item check-in throws "Value does not fall.." error

When checking in an item in a custom list, I was being presented with the following error:

Value does not fall within the expected range. at Microsoft.SharePoint.SPFieldCollection.GetFieldByInternalName(String strName, Boolean bThrowException) at Microsoft.SharePoint.SPFieldCollection.GetFieldByInternalName(String strName) at Microsoft.SharePoint.SPListItem.get_MissingRequiredFields() at Microsoft.SharePoint.ApplicationPages.Checkin.OnLoad(EventArgs e) at System.Web.UI.Control.LoadRecursive() at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

After much head-scratching and nightmarish debugging, I found that the answer was very simple: Do not have the "Required" attribute set to true in your content type definition, but rather specify this in your custom list schema.

Hope this helps!

Thursday, September 4, 2008

Programmatically Update Page Layouts

I updated my page layouts that I deployed as a feature, but found that I could not overwrite the Layout File. The solution was to add a new page layout and then you would have to associate the new layout with every page on your site that used it.

Obviously with a site of any size you'd have a lot of manual work to get this done, so doing it programmatically makes for a much better (and thorough) approach. After you've got your new page layouts, you could do something like this (in your event receiver class):

public override void FeatureActivated(SPFeatureReceiverProperties properties) {
//replace current page layouts with new page layouts on all pages

SPWeb web = SPContext.Current.Web;

SwapPageLayout(web, "FullWidthContentWithTitleV1.aspx", "FullWidthContentWithTitleV2.aspx");
}

private void SwapPageLayout(SPWeb web, string oldPageLayoutName, string newPageLayoutName)
{
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);

PageLayout[] layouts = pubWeb.GetAvailablePageLayouts();
PageLayout newLayout = null;
PageLayout oldLayout = null;

foreach (PageLayout layout in layouts)
{
if (layout.Name == newPageLayoutName)
{
newLayout = layout;
}

if (layout.Name == oldPageLayoutName)
{
oldLayout = layout;
}
}

DoPageLayoutSwap(web, oldLayout, newLayout);
}

private void DoPageLayoutSwap(SPWeb web, PageLayout oldLayout, PageLayout newLayout)
{
string checkInComment = "PageLayout Feature automatically updated this page layout";

if (PublishingWeb.IsPublishingWeb(web))
{
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);



foreach (SPListItem listItem in pubWeb.PagesList.Items)
{
if(PublishingPage.IsPublishingPage(listItem))
{
PublishingPage page = PublishingPage.GetPublishingPage(listItem);
if (page.Layout.Name == oldLayout.Name)
{
page.CheckOut();
page.Layout = newLayout;
page.Update();
page.CheckIn(checkInComment);
SPFile pageFile = page.ListItem.File;

pageFile.Publish(checkInComment);

if(listItem.ModerationInformation.Status.Equals(SPModerationStatusType.Pending))
pageFile.Approve(checkInComment);
}
}
}
}

//visit children
foreach (SPWeb childWeb in web.Webs)
{
DoPageLayoutSwap(childWeb, oldLayout, newLayout);
}
}