<CharlieDigital/> Programming, Politics, and uhh…pineapples

18Feb/11Off

Most Annoying Thing About SharePoint 2010?

Posted by Charles Chen

I've been bashing my head against a SharePoint 2010 solution package for almost 10 hours now across two days.  I mean god forbid Microsoft provides us poor developers with some useful error messages or even a hint of what's going on inside that crazy contraption.

It seems that other's have also encountered this problem, but I'll summarize here: in SharePoint 2007, when you create a custom list template and list definition, you could associated a custom content type with the list quite easily (or so I thought...more on this in a sec).  Here's an example from my schema.xml file:

<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint"
	Title="Basic List" FolderCreation="FALSE" Direction="$Resources:Direction;"
	Url="Lists/Basic List" BaseType="0" EnableContentTypes="TRUE">
	<MetaData>
		<ContentTypes>
			<ContentTypeRef ID="0x0100C171C000000000000000000000000003"/>
			<ContentTypeRef ID="0x0120" />
		</ContentTypes>

<!-- additional markup omitted -->

I can then use the content type fields in the views without having add the fields or field references separately:

<ViewFields>
	<FieldRef Name="LinkTitleNoMenu"></FieldRef>
	<FieldRef Name="Request_Name" ></FieldRef>
	<FieldRef Name="Request_Status" ></FieldRef>
	<FieldRef Name="Requestor" ></FieldRef>
	<FieldRef Name="Ticket_Project_Number" ></FieldRef>
	<FieldRef Name="Ticket_Group" ></FieldRef>
	<FieldRef Name="Support_Ticket_Number" ></FieldRef>
	<FieldRef Name="Support_Ticket_Name" ></FieldRef>
	<FieldRef Name="Support_Ticket_Status" ></FieldRef>
	<FieldRef Name="Support_Ticket_Res_Date" ></FieldRef>
	<FieldRef Name="Technical_Manager" ></FieldRef>
</ViewFields>

In SharePoint 2010, this doesn't work. Let me rephrase this: it doesn't work 100% correctly.  The default behavior seems to be that the content type will be copied over, but the fields aren't copied over...

Well in fact, Microsoft's documentation states that this is as designed:

When SharePoint Foundation creates a list instance, it includes only those columns that are declared in the base type schema of the list or in the list schema. If you reference a site content type in the list schema, and that content type references site columns that are not included in the base type schema of the list or in the list schema, those columns are not included. You must declare those columns in the list schema for SharePoint Foundation to include them on the list.

What you'll get is the content type will indeed be on the list, but the fields won't be copied over and any views that use those fields will be borked.  This is a really annoying problem.  Really, it is.

The solution is to either build some tool that does an XSL transform on my content types and produces the views (thereby avoiding the necessity of copying the fields over manually) or write some code to fix it after the list has deployed.  But it's problematic because I want to deploy data with my list instance using markup like so:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListInstance
		Title="My Custom Configuration"
		Id="18000"
		FeatureId="ACA62A34-7B27-442D-97C6-55FC3F55A2BF"
		TemplateType="18000"
		Url="Lists/MyCustomConfiguration"
        OnQuickLaunch="TRUE">
      <Data>
          <Rows>
              <Row>
                  <Field Name="ID">1</Field>
                  <Field Name="Title">some.setting.name</Field>
                  <Field Name="Configuration_Value">http://myserver.mydomain.com</Field>
                  <Field Name="Default_Value">http://myserver.mydomain.com</Field>
                  <Field Name="Usage_Description">The URL of the something something server</Field>
                  <Field Name="Property_Name">SomethingSomethingServer</Field>
                  <Field Name="Property_Type">string</Field>
              </Row>
          </Rows>
      </Data>
    </ListInstance>
</Elements>

I need to be able to deploy the content type to the list automatically.  (If you're not deploying default data with your list, the ContentTypeBinding element may do the trick.)

So I fumbled around with this for a day and a half while bitching and moaning about it to anyone who would listen to me.  Did I screw up the content type somehow?  Did I screw up the list template somehow?  Did I mess up an ID somewhere?  I mean, I've done this a million times in the past in a million different solution packages and even in SharePoint 2010...what the heck was different about this package?

Finally (in fact, just moments ago!) it dawned on me that I've always "primed" my content types when deploying them in my previous solutions based on some findings from Martin Hatch!

You do not have to declare custom fields in the schema.xml, as long as you manually update your content type before creating any libraries.

Bingo!

Martin links to a code sample, but the gist of it is pretty simple.

  1. Wire up a feature receiver for your content type feature.
  2. On feature activation, "prime" the content types that have a specific group name
  3. ???
  4. Profit!

Okay, the profiting part is questionable, the the rest of it isn't!

The first part is to wire up the feature for a feature receiver.

<Feature Id="0C4C1210-B056-4AFC-B556-A8BB30E1A9F1"
         Title="My Custom Content Types"
         Description="Installs the content types."
         Hidden="FALSE"
         Scope="Site"
         DefaultResourceFile="core"
         ReceiverAssembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=56f39f66bb4e1d17"
         ReceiverClass="MyAssembly.Components.FeatureReceivers.MetadataPrimer"
         xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="my_contenttypes.xml" />
    </ElementManifests>
    <Properties>
        <Property Key="prime.if.contains" Value="My Custom Content Type"/>
    </Properties>
</Feature>

Note that I've added a feature property here: a key called "prime.if.contains".  This allows me to specify the group names of the content types that I want to prime.  That way, I don't have to prime all of the content types.

I create a base class for my feature receivers:

/// <summary>
///     Abstract base class for custom feature receivers.
/// </summary>
/// <remarks>
///     All custom feature receivers should be updated to inherit from this class for common functionality.
/// </remarks>
public abstract class FeatureReceiverBase : SPFeatureReceiver
{
	/// <summary>
	///     Gets the <c>SPWeb</c>.
	/// </summary>
	/// <remarks>
	///     This method allows the feature receiver to obtain a reference to the current <c>SPWeb</c>
	///     regardless of whether there is an <c>HttpContext</c> (i.e. in the case that the feature
	///     is activated from <c>STSADM</c>).
	/// </remarks>
	/// <param name = "properties">The properties.</param>
	/// <returns>The <c>SPWeb</c> instance from the properties.</returns>
	protected static SPWeb GetWeb(SPFeatureReceiverProperties properties)
	{
		SPWeb web;

		if (SPContext.Current != null && SPContext.Current.Web != null)
		{
			web = SPContext.Current.Web;
		}
		else if (properties.Feature.Parent is SPWeb)
		{
			web = (SPWeb) properties.Feature.Parent;
		}
		else if (properties.Feature.Parent is SPSite)
		{
			web = ((SPSite) properties.Feature.Parent).RootWeb;
		}
		else
		{
			throw new Exception("Unable to retrieve SPWeb - this feature is not site or web-scoped.");
		}

		return web;
	}
}

And then I add the primer class:

/// <summary>
///     <para>Receiver which "primes" the content types by updating each of the custom content types to push the changes down.</para>
///     <para><see cref = "http://mkeeper.spaces.live.com/blog/cns!60F12A60288E5607!278.entry" /></para>
/// </summary>
public class MetadataPrimer : FeatureReceiverBase
{
	private const string _primerKey = "prime.if.contains";

	/// <summary>
	///     Handles the feature activated event by priming content types specified by the primer key property.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureActivated(SPFeatureReceiverProperties properties)
	{
		Console.Out.WriteLine("Activating Metadata Primer");

		// Prime the content types.
		SPWeb web = GetWeb(properties);

		SPContentTypeCollection contentTypes = web.ContentTypes;

		List<string> contentTypeIds = new List<string>();

		// Capture the feature properties.
		Dictionary<string, string> featureProperties = new Dictionary<string, string>();

		foreach (SPFeatureProperty property in properties.Feature.Properties)
		{
			featureProperties[property.Name] = property.Value;
		}

		// Must find the key "prime.if.contains"
		if (!featureProperties.ContainsKey(_primerKey))
		{
			ExceptionFactory.Throw<ArgumentException>(
				"The property prime.if.contains is not defined in the feature.");
		}

		string[] tokens = featureProperties[_primerKey].Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);

		Log("Priming content types containing: {0}", featureProperties[_primerKey]);

		foreach (SPContentType contentType in contentTypes)
		{
			bool prime = tokens.Any(token => contentType.Group.ToUpperInvariant().Contains(token.ToUpperInvariant()));

			if (!prime)
			{
				continue; // EXIT: Next iteration.
			}

			contentTypeIds.Add(contentType.Id.ToString());

			Log("Adding content type \"{0}\" ({1}) for priming", contentType.Name, contentType.Id);
		}

		// Do it in two loops since doing it one causes issues if a parent content type is primed (child content types
		// fail to prime because the parent changes).
		foreach (string id in contentTypeIds)
		{
			try
			{
				SPContentTypeId contentTypeId = new SPContentTypeId(id);

				SPContentType contentType = web.ContentTypes[contentTypeId];

				Log("Priming content type: {0}", contentType.Name);

				contentType.Update(true, false);
			}
			catch (Exception)
			{
				Log("Failed to prime content type with ID: {0}", id);
			}
		}
	}

	/// <summary>
	///     Logs the specified message to both log4net and the console.
	/// </summary>
	/// <param name = "message">The message.</param>
	/// <param name = "args">The args.</param>
	private static void Log(string message, params object[] args)
	{
		Console.Out.WriteLine(message, args);
	}

	/// <summary>
	///     Features the deactivating.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {}

	/// <summary>
	///     Features the installed.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}

	/// <summary>
	///     Features the uninstalling.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
}

(Note: remove or replace the usages of ExceptionFactory -- this is just a custom exception helper that I use)

You can see, the gist of it is that the code will iterate all of the content types at the site, find ones that belong in the given groups, and them prime the content type by calling SPContentType.Update(bool, bool).

So there you have it; it didn't work in 2007 either, but I had completely forgotten that I had written the primer for this express purpose.  With this simple bit of code, you can save yourself the hassle of having to copy fields from your content type to your schema.xml definition file when creating custom list definitions for SharePoint and all of the fields are nicely copied over from your site level content type to your list instance without having to manually modify the schema.xml file for your list.

Filed under: .Net, SharePoint Comments Off
17Feb/11Off

Responsibility in Consultancy

Posted by Charles Chen

As a consultant, I feel strongly about giving sound technical advice to my clients, even if such advice means saying "no" to a client or possibly turning back a larger project for a more pragmatic one. It's about doing the right thing and offering sound technical advice to the best of my knowledge -- not just money, projects, and utilization.

The one personal example that really sticks out for me is the case where Microsoft sold a deal to a hedge fund to build a bulk import system using BizTalk that would have cost them triple the price (once licensing and hardware was factored in) of doing it using SQL Server DTS, which was easier to program, maintain, and more robust in every way (not to mention this company already had SQL Server skillsets in-house).  Luckily, we were able to convince the client that DTS was purposefully designed for carrying out bulk import and transform of data before they committed the cash to BizTalk.

Recently, a friend of mine showed me a project that the Big Consulting Company he works for was delivering to their client, a public library. It looked really good for a public library website...until he dropped the bomb that it was built using Silverlight (and to top it off, he was really proud, too -- as if I was supposed to find it impressive).  I don't think I've ever done a bigger facepalm in my life.

As I've stated in the past, I have a strong disdain for the misuse of Silverlight.  There are certainly scenarios where it should be used for building web sites:

  1. Streaming media
  2. Scalable 2D vector graphics and animation
  3. 3D graphics and animation
  4. Interactive games

And that's it!  Beyond that, if a company wants to use it in their intranet site, it doesn't concern me as much because the environment is more homogeneous and controlled in terms of having the platform to run the Silverlight applications; it's their headache going forward.  Besides, if it's a private, multi-national company, then by all means; if they wish to waste their capital and resources, that's their choice.

However, it is a damn crime to recommend Silverlight to any client building basic web applications that are Internet facing, especially a public library financed by taxpayers.  I mean, people should be fired and embarrassed for offering such terrible advice.  To begin with, few non-Windows devices natively support Silverlight (and even folks on older Windows OSes can't natively run Silverlight apps).  iPad?  iPhones?  Android phones?  Linux based netbooks?  As sales of traditional laptops and desktops decline, it's important to factor in the presence of these newer platforms when designing a publicly facing Internet site.  I would think that this would be even more important for a public library.

Now, if the site were media focused -- like a YouTube -- perhaps it could be forgiven; after all, HTML5 is still a moving target and supported only by newer browser versions.  But this is a public library website that was listing books...It's as bad as websites that still use Java (yes, Java without the "Script") for image galleries or raindrop effects.  It's as bad as websites using Flash for menus and menu rollover animations.

I would be embarrassed to be a part of the company or the team that sold and implemented this deal.  A fucking crime to the taxpayers of the township with me as the perpetrator; no better than stealing money from your neighbors.  I couldn't live with myself for being so evil.

Now, he told me that the client insisted on Silverlight and that it was they who wanted it done in Silverlight.  To me, that makes no difference.  As a consultant, it's my duty to provide sound technical guidance to the best of my knowledge and ability.  If there is a more compatible, cheaper, easier to maintain solution built on a platform with greater longevity that solves the same problem, I will recommend taking that route, even if it takes me out of the running.  It's our job as consultants to consult and to offer sound technical advice.

For you see, the client may not know or care for the difference between Silverlight and HTML5 or jQuery based UIs.  The client may be under the impression that a given UI or bit of functionality is only possible because of Silverlight if that's what they've been sold and demo'd.  The client may not understand the alternative solutions as certainly, for a non-expert, the difference between two types of wood -- for example -- aren't perceivable.  The client may be enamored with one buzzword or technology, but it is our duty and responsibility as consultants (and decent human beings) to tell the truth because I'd like to believe that when I ask a contractor to come to my house for a quote or get a diagnosis from an auto mechanic, he'd do the same for me and give me the low-down to the best of his or her ability and knowledge.

In the end, I was so peeved by what my friend had shown me, I took 30 minutes and rebuilt the same exact functionality that they had implemented in Silverlight using nothing but jQuery and CSS with only 20 lines of JavaScript and 5 lines of CSS after being challenged to do so.

I'm still peeved by this as it's a critical misunderstanding of the Internet ecosystem and managing device compatibility as well as a critical misunderstanding of technology and their suitability for a purpose.  Not to mention that it's a terrible choice for audience accessibility, long term costs, and maintenance.  I really don't want to be upset by the fact that my friend or his team could have purposefully offered bad advice for greater financial returns as that would be a true embarrassment and I only hope that all sides in this come to their senses and ditch Silverlight.

In the end, for me, consultancy is about people and treating customers with respect by offering the best technical advice to one's knowledge.  Even if it costs me my job, I've always believed that I am accountable to my clients and I'm responsible for giving sound technical advice.

Filed under: DevLife, Rants 2 Comments
11Feb/11Off

A jQuery Scrollbar Replacement

Posted by Charles Chen

Click here to see the demo page.

I was looking around recently for a jQuery based scrollbar replacement and spent quite some time trying out a few different libraries that I found in a blog post.

I was left quite disappointed as none of the scripts I tried (jScrollPane, ScrollBarPaper, and malihu's custom content scroller) seemed to work well with my use case.  In particular, none of them seemed to work well with content that was set to 100% height.

Malihu's was promising, but as far as coding goes, it wasn't written to be a proper plugin and needed a lot of cleanup.

So after wasting half a day trying out various scrollbar scripts, I ended up rolling my own.  Here are the specs:

  • Works with fixed or fluid height containers
  • Uses default jQuery UI slider as a scrollbar
  • Does not attempt to mimic full scroll bar behavior (I don't want it to be full height, don't need the up/down arrows)
  • Responds to mousewheel scrolling
  • Easing animation when position changed with a mouse click
  • Small - just 72 lines of code including comments and blank lines

A sample screenshot:

Markup wise, the script only requires that you create a structure like so:

<div id="my-id">
    <!--// The scrollable class is required //-->
    <div class="scrollable">
        <!--// Content goes here //-->
    </div>
</div>

Then you can initialize the scrollbars like so:

$(function(){
	/*
		Settings and defaults:

		scrollbarHeight: "300px", // the height of the scrollbar
		scrollbarSize: "6px", // the size of the scrollbar
		positionX: "5px", // the distance from the right of the container
		positionY: "15px", // the distance from the top of the container
		easing: "easeOutQuart", // the named easing effect from jQuery UI
		pace: 20 // affects the mousewheel increment
	*/
	$("#myIdWhatever, #anotherId").prettyScroll();
	$("#yetAnother").prettyScroll({"scrollbarHeight": "400px"});
});

One CSS definition must be added:

/*--- Your container must have an inner DIV with the class "scrollable" ---*/
.scrollable {
	width: 100%;
	height: 100%;
	overflow: hidden;
}

Two basic CSS overrides can optionally be added to make it more scroll bar like (feel free to toy around with this to get your scrollbar to look however you like):

/*--- Override jQuery styles so that there's no color in the slider track ---*/
.scroll-bar .ui-widget-header { background: #fff !important;}
/*--- Override jQuery styles so that the handle is taller ---*/
.scroll-bar .ui-slider-handle {
	width: 1.1em !important;
	height: 2em !important;
}

Since it uses the default jQuery slider widget, you can just use whatever CSS you would normally use to customize the slider here.

You can download the script and demo here: prettyScroll.zip

Update (6/13/2011):  I noticed that this page actually gets a lot of hits in my Google Analytics so I figured it was worth updating with some new script: prettyScroll.20110613.zip

It has some new code that shows how to scroll to an object that's clicked in the scroll pane.  Consider the following markup:

<div id="content3">
    <div class="scrollable">
        <p>Here is some stuff that's before.</p>
        <p>Another sentence.</p>
        <a id="toggler" href="">toggle</a>
        <p class="after" style="display:none">Lorem ipsum dolor sit amet...</p>
        <!-- shortened for brevity -->
    </div>
</div>

When the link "toggle" is clicked, I want to scroll to the link.  I've cut off the content, but assume that it generates a large body of text that requires scrolling once visible.  You can cause it to scroll to the element by triggering a custom event:

$("#toggler").click(function(e){
    e.preventDefault();

    var $this = $(this);

    $this.siblings(".after").toggle();

    $this.parents(".scrollable").trigger("scrollTo", [$this]);
});
Filed under: Dev No Comments