Wednesday, January 11, 2006

SharePoint User Interface Customization

Heather has a great new article on customizing the SharePoint user interface. Like her, I wish I could give you a step-by-step guide on how to do it but there's just no simple solution (here are a few tips to get you started ). In Heather's words:

Customizing SharePoint is like riding a bike, it is very hard to tell someone how to do it, and that person really just has to try, fall down, try again, and they will figure it out.

I couldn't agree more.

Tuesday, January 10, 2006

Extreme SharePoint Design: Custom MySite Titles, Part 2

In a previous post I demonstrated how to create custom titles on MySite pages that display the site owner's name and link back to the public view if the viewer is just a reader and the private view if the viewer is the site owner. The example used the Title property of the SPControl.GetContextWeb(Context) method to retrieve the site owner's name from AD. This works fine if the correct fields in AD have the right information in the right format (i.e First Name, Middle Initial, Last Name) but not so well if they don't. In many cases, the system returns the user name in the Windows default format of [Domain]\[Username].

Fortunately, SharePoint user profiles contain the user's full name, which is rendered in the <SPSWC:PersonalSpaceMainHeading> control; unfortunately, this information is not directly accessible via the object model and, like the Title property, the name is displayed in the 'Last Name, First Name' format. To further complicate matters, the PersonalSpaceMainHeading control insists on displaying an icon that indicates whether or not the user is logged into the system, even if the RenderPawn option is set to 'False'.

To circumvent these issues and insure that the user name is rendered as 'First Name, Last Name' in all instances, we'll first hide the <SPSWC:PersonalSpaceMainHeading> control on the page, then access it's innerText property to get the user name. Then we'll parse the user name into the correct format and apply logic to determine whether the user is a reader or site owner and then write out the proper link.

First, create a blank user control (server and client-side code are blocked from execution in a standard portal page) with the following registrations:

<%@ Control Language="c#" AutoEventWireup="false" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Store the control in the /bin directory of your SharePoint virtual server. For this example, the control is named 'spsTitle_MySite.ascx'.

Next, create the container code for the display of the user name. For this application, we are simply enclosing the user name in a table with two cells: one that reads 'Personal Site' and one that displays the user name. Place the <SPSWC:PersonalSpaceMainHeading> control into a hidden DIV so it can be accessed later in the code:

<table align="left" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="5"></td>
<td valign="middle" class="Mysite">Personal Site</td>
<td valign="middle" class="Mysite_Title"><div id="PersonalSpaceMainHeading" style="display:none"><SPSWC:PersonalSpaceMainHeading RenderPawn="false" runat="server" /></div></td>
</tr>
</table>

Next, combine a server-side 'If...Then' statement with client-side Javascript which extracts the innerText of the PersonalSpaceMainHeading control and renders the user name and correct link:

<% if (SPControl.GetContextWeb(Context).CurrentUser.LoginName == SPControl.GetContextWeb(Context).Author.LoginName) { %>
<script>
var strFullName = document.getElementById("PersonalSpaceMainHeading").innerText;
var intLength = strFullName.length;
var intLocation = strFullName.indexOf(", ");
if (intLocation == -1)
{
strTitle = strFullName;
}
else
{
strTitle = strFullName.substring(intLocation + 1) + " " + strFullName.substring(0, intLocation);
}
document.write("<a href='/mysite/default.aspx'>" + strTitle + "</a>");
</script>
<% } else { %>
<script>
var strFullName = document.getElementById("PersonalSpaceMainHeading").innerText;
var strUrl = window.location.href;
var intLength = strFullName.length;
var intLocation = strFullName.indexOf(", ");
if (intLocation == -1)
{
strTitle = strFullName;
}
else
{
strTitle = strFullName.substring(intLocation + 1) + " " + strFullName.substring(0, intLocation);

}
document.write("<a href='" + strUrl + "'>" + strTitle + "</a>");
</script>
<% } %>

The first part of the 'If...' statement checks to see if the current user name matches the login name of the site author. Since personal sites are created by the user, the Author will always be the site owner. If the name matches, the subsequent javascript extracts the user name, parses out the comma and reverses the last name and first name (the full name is displayed in the 'Last Name, First Name' format by default). It then creates a hyperlink to the default (private view) of the personal site.

If the current user is not the author, the second javascript performs the same string manipulation then writes out the hyperlink using the public view URL (i.e. "/mysite/public.aspx?accountname=[UserID]"). This method will also work on shared and private libraries, personal pages, and lists, sending the user to the proper home page depending upon their role.

Now we'll need to call the user control from DEFAULT.ASPX and PUBLIC.ASPX. First, add a registration for your new control to the top of each page:

<%@ Register Tagprefix="UC" Tagname="spsTitle_MySite" src="/bin/spsTitle_MySite.ascx" %>

Next, call the control from the body of the page:

<td align="left" valign="middle">
<UC:spsTitle_MySite id="spsTitle_MySite" runat="server"></UC:spsTitle_MySite>
</td>

The final output looks something like this (you can modify the styles to render the link however you wish):


Monday, January 09, 2006

New SharePoint Discussion Forum Web Part

Bil Simser has a new post describing a Discussion Forum web part he's working on. I can't tell you how many times I get asked about a replacement for the built-in discussion functionality. This is great news. I can't wait to get my 'grubby little hands' on it when it's finished.

Way to go, Bil!

Friday, January 06, 2006

Customizing the Built-In SharePoint Menus

In previous posts (here and here) I’ve shown you how to build your own custom menus to relocate the standad Actions and Views menus. Great if you’ve got the time and the need to do so but what if all you want is a quick menu solution that looks and works just like the Modify Shared Page menu? Well, Gert van Bael has the answer. In this post he shows you how to duplicate the existing menu code to create a custom drop-down that supports nested menus.

Great tip, Gert!

Update: Dustin has a previous post on the same topic here.

Don't Fire the Messenger

Recently I had a conversation with a prospective client in which it became apparent that a sales engineer had given them bad advice. It fell to me to explain the shortcomings of the SE’s recommendations, as gently as possible, to an IT manager who had only a very short time frame to impelement a large, customized SharePoint solution. He didn’t take it very well, so needless to say, I don’t think I’ll be getting the engagement.

I see this happen quite frequently and it always ends up with the consultant being the bad guy. I hate to break this to the world at large but SE’s aren’t always right; in fact, I used to be an SE and I’ll admit to being wrong on several occasions. The problem with the Account Manager/SE arrangement is that the two primary contacts the end user is engaged with have little, if any, real-world field experience. Their job is to sell licenses and be generalists; they have neither the time nor the motivation to be specialists with in-depth product knowledge gained by sweating it out in the trenches. I can tell you first hand that most SE’s work with the products they promote only in lab environments and have very little time for problem solving or immersive knowledge. I humbly admit to getting much of my knowledge during my SE days from product documentation and lab trials; I know for a fact that this is how it’s usually done.

Before all my SE friends flame me to death, understand that I’m not pointing fingers; it’s not your job to know this stuff, it’s my job - that’s why you sell the product and I make it sing and dance. Unfortunately, the client takes the SE’s words as gospel. On those rare occassions where the SE gets it wrong, I’m in a no-win situation - I have to tell the client that the solution is not going to work. There is no way to overcome the client’s response of “Well, that’s how Microsoft/Oracle/Cisco/IBM/Whomever said to do it” unless you have a long-standing relationship with that client. I can’t afford to nod my head and go along with it just to get the gig – not only will it destroy my credibility with the client and eliminate any chance to do business with them again, it also means that I won’t be able to use that project as a reference (and we consultants live and die by our references).

So if you’re a consultant caught in a similar situation, what do you do? In my mind, there’s no question. You politely and respectfully tell the client why, based on your experience and knowledge, you think what they’ve been told is wrong. You also preface it by saying that you yourself may be wrong and there’s only one way to find out for sure - test the recommendations to see if they work. But you can’t dance around the subject or equivocate; doing so will eventually end your consulting career. You probably won’t get the assignment, but you will have a clear conscience and, when the client does try to implement the misguided suggestions and fails, your stock will go way, way up (no, they won’t call you in to fix it, but they might call you for the next project - just be sure to avoid the irresistable urge to say “I told you so”).

Thursday, January 05, 2006

The Great Metadata Debate

Dustin Miller kicked over the anthill when he ranted against subfolders in document libraries. Bil Simser concurs and stirred up some more dust with his post on the subject. Of course, some folks take exception to all this, preferring to do things the old-fashioned way, but the real question is: what good are subfolders, anyway? Do they help us categorize information? No, they do not. Do they help users find documents easier? Absolutely not. Are they intuitive? Not really. Do users know what to do with them? Ah, and there's the rub - users are comfortable with folders and they know how to use them. Now ain't that a bummer???

While Dustin and Bil are most certainly correct - metadata is by far the best way to categorize and display information in SharePoint - it turns out that there's a lot to be said for giving users something they're used to amidst a sea of change. Think about it for a minute: we toss around terms like 'metadata', 'lists', 'views', 'libraries', 'categories', 'content', 'areas', etc. like they're second nature to everyone but the truth is they only mean something to US. Your average user doesn't understand any of this and, what's worse, they don't even care. Here's a neat little trick - want to immediately turn a room full of professionals into a group of slack-jawed zombies? Use the word 'metadata' three times in one minute and see what happens. They instantly tune you out. I've seen it happen many times.

It may just be that we're asking people to digest more than they have an appetite for. Not only do they have to get used to all this SharePoint mumbo-jumbo but now they have to give up their folders too? For Pete's sake, it took them five years to learn what a network share is and now everything goes back into one directory? I swear I can hear the gears in their brains grinding to a halt when I get to this portion of the training.

Rarely do we have time to fully train (and re-train) every user before the new portal goes live. And even if we did, it's just too much for them to absorb. So we make a judgement call and weigh the options - do we have all these nice little categories that please our analytical minds or do we have immediately productive users? Every client I've ever had opts for the latter and rightly so - no organization can afford that much disruption.

Here's the other problem with metadata that nobody's talking about: it takes a lot of time to make it work right. Not only do we have to decide upon our categories but then we have to create lookups, add list definitions, modify views, group, sort, filter and generally yank, pull, twist, poke and wiggle until it all looks right. Then we need another library to do the same thing - but just a little bit different, of course - and we're at it again, like an army of SharePoint worker bees, buzzing around our little libraries being busy, busy, busy. Meanwhile the users are putting documents back into folders on the network 'cause that's what they know and all this SharePoint stuff is just too hard.

So what do we do? WE know metadata is good but user's don't. WE know it streamlines indexing and retrieval but they could care less. WE know folder structures are the worst way to organize information since, well, folder structures but nobody's listening. A bit of the ol' rock-and-a-hard-place, isn't it?

Well, here's an idea. Give 'em both. Give your users metadata AND folders. Let 'em store stuff the way they always have (at least for now) but also enforce metadata rules. Create standard list definitions and views for each document library that contain all the basic metadata fields that map directly to Word/Excel/etc. so they can fill out the fields in Office and don't have to fiddle with web forms (if you haven't done that yet, you're missing the boat. Read this immediately). Customize the base defs/views only when you have to. Let users upload docs into whatever folder structure they desire until it comes time to ween them off; say six months, or even a year, when they can hear 'metadata' without their eyes glazing over. You can always move the docs out of folders and into the base library later on (I know, I know, some links will be broken but your views will still be intact).

In other words, feed them the elephant one bite at a time so they get used to the taste then invite them to the banquet. Users will be much happier and they'll actually stick with it since the new has now become the old. You'll still have a contingent of naysayers who'll put up a fight when it's time to yank their beloved folders out but by that time they'll be a distinct minority instead of a vocal majority. It'll save you a lot of headaches and them a lot of frustration. That's a win-win if I ever heard one.

UPDATE: For more on metadata and how to use it, see Ian Morrish's post on Sharepoint and metadata.

UPDATE 2: Daniel McPherson links back to an older post on the same subject. His real-life example is dead on.

Extreme Sharepoint Design: Modifying User-Defined Web Part Pages

User-defined web part pages allow web designers and portal members to create isolated pages with the same basic functionality as a standard area or site page. Because they are stored in document libraries instead of the area or site collection hierarchy, they can be created, deleted, and modified without affecting portal navigation, taxonomy or search results. They serve as an effective, low-impact method for enabling user creativity.

Customizing user-defined web part pages is not quite as tricky as overhauling a complete site definition but they do present the designer with a few unique challenges. For one thing, there are several types in every definition and each one is slightly different. For another, they have a few built-in limitations that will cause problems if you don't know what to watch out for. Coordinating the look and feel of these pages with the overall portal design will improve consistency and encourage users to create their own pages without impacting the portal architecture.

User-defined web part pages are stored in the DOCTEMP\SMARTPGS directory of each site definition, named in ascending numerical order (SPSTD1.ASPX, SPSTD2.ASPX, and so on). Each numbered file is directly related to an option in the Web Part Page Creation Wizard (SPCF.ASPX) section titled "Layout". The 'Choose a Layout Template' select box contains eight options for page layout, from a simple, single-column design to a complete, multi-zone web part page. Should you choose to edit Web Part Page Creation wizard page to add or remove items to/from this list, you will notice that the options are out of order in the select box code (line 251 of SPCF.ASPX):

<SELECT id="onetidWebPartPageTemplate" name="WebPartPageTemplate" size="8" onchange="DoTemplateOptionChange()">
<OPTION value="1">Full Page, Vertical</OPTION>
<OPTION value="3">Header, Left Column, Body</OPTION>
<OPTION value="4">Header, Right Column, Body</OPTION>
<OPTION value="2" selected="true">Header, Footer, 3 Columns</OPTION>
<OPTION value="5">Header, Footer, 2 Columns, 4 Rows</OPTION>
<OPTION value="6">Header, Footer, 4 Columns, Top Row</OPTION>
<OPTION value="7">Left Column, Header, Footer, Top Row, 3 Columns</OPTION>
<OPTION value="8">Right Column, Header, Footer, Top Row, 3 Columns</OPTION>
</SELECT>

The options relate to each page in the site definition as follows:

Option 1 -> spstd1.aspx -> Full Page, Vertical
Option 2 -> spstd3.aspx -> Header, Left Column, Body
Option 3 -> spstd4.aspx -> Header, Right Column, Body
Option 4 -> spstd2.aspx -> Header, Footer, 3 Columns
Option 5 -> spstd5.aspx -> Header, Footer, 2 Columns, 4 Rows
Option 6 -> spstd6.aspx -> Header, Footer, 4 Columns, Top Row
Option 7 -> spstd7.aspx -> Left Column, Header, Footer, Top Row, 3 Columns
Option 8 -> spstd8.aspx -> Right Column, Header, Footer, Top Row, 3 Columns

In addition, each option has an animated GIF of the same name associated with it that provides a thumbnail view of the page design. These images are located in the 60\TEMPLATES\LAYOUTS\1033 directory. Display of the thumbnails is controlled by the following script at the top of SPCF.ASPX:

function DoTemplateOptionChange()
{
var index = document.frmWebPage.WebPartPageTemplate.selectedIndex;
document.frmWebPage.PreviewImage.src = strImagePath + "spstd" + document.frmWebPage.WebPartPageTemplate.options[index].value + ".gif";
document.frmWebPage.PreviewImage.alt = document.frmWebPage.WebPartPageTemplate.options[index].text;
}

These images may be replaced with a custom animated gif that reflects the new page design or directed to another file name by editing the DoTemplateOptionChange() function.

Modifying the pages themselves is done in much the same manner as any other definition file - start with the default code and add or remove tags and HTML code as necessary - but there are a few issues to bear in mind when working with web part pages:

1. Web part zones in the page body (excluding the TitleBar zone; see below) are contained within a specific set of table cells which are referenced by a script that determines whether or not to display the cell based on it's contents (or, more accurately, the lack thereof). To recreate this functionality, be sure to place your web part zones inside the following cell:

<td id="_invisibleIfEmpty" name="_invisibleIfEmpty" valign="top" width="100%">

It is also necessary to include the following script somewhere on the page (the default location is just after the closing row tag in the parent table for the web part zones):

<script language="javascript">if(typeof(MSOLayout_MakeInvisibleIfEmpty) == "function") {MSOLayout_MakeInvisibleIfEmpty();}</script>

2. By default, the TitleBarWebPart is added to each web part page when it is rendered. This web part displays the page title and 'Modify Shared Page' menu link. This web part is NOT specified in any SCHEMA.XML file and cannot be disabled. Furthermore, it's target is the TitleBar zone and this zone must exist on the page or an error will occur after page creation. The zone can be located anywhere on the page (it does not have to exist in the "_invisibleIfEmpty" table cell). To hide it from view, it can be placed within a container DIV whose 'display' style is set to 'none', but doing so will require the addition of the Settings Link control (<WebPartPages:SettingsLink runat="server" />) in the title area.

3. If the portal design incorporates custom menus, those menus changes will cascade down to the web part pages. Incorporate any definition-specific menu options into the corresponding web part pages and remove any elements with localized dependencies (such as <SharePoint:RelatedTasks>, which depends upon the <Toolbar Type="RelatedTasks"> section in SCHEMA.XML) or context-sensitive options.

4. Web part pages in each definition are similar but not exactly the same. Take note of the different controls used in portal definition pages and STS definition pages; for example, pages in SPSPERS use <SPWC:PersonalSpaceNavigation> while pages in STS use <Sharepoint:Navigation>. Similarly, the HTML layout code may change from page to page, with DIV's, Tables, and other elements in unusual locations. As with any site definition modifications, when copying and pasting code be aware of the differing page registrations for each definition type and any tags which that definition relies upon for proper page rendering (like <SPSWC:PersonalSpaceNavigation runat="server" /> on personal site pages).

To implement web part page modifications, simply copy the modified files to the target server(s). Since there are no XML files associated with this page type, changes will be seen immediately without having to reset IIS.

UPDATE: Dennis has a great post on adding your own custom templates to the 'Create' page in SPS/WSS.

The Blog Editor Blues

Riddle me this: Why is it so hard to find a blog editor that does everything most bloggers want in a single package? After fiddling for weeks with every editor out there I’m ready to pull my hair out by the roots. Some give you decent editing options but weak post management. Others are good at managing previous posts but don’t have nearly enough formatting options for new posts. Some are good at text, others at HTML. Do the people that develop these apps actually spend any time managing a blog? I’m thinking not.

Maybe I’m asking for too much. Here’s my list of requirements:

  1. Consistent formatting - A paragraph tag should be a paragraph tag no matter what. For some reason every editor handles tags differently; what looks right in one rich text designer looks terrible in Blogger and then gets even further garbled by Feedburner. Why can’t HTML just be HTML?

  2. Automatic Text Replacement - Is it really so hard to catch a less than/greather than symbol and replace it with properly encoded chracters while I’m typing or as soon as I cut and paste? We can figure out how to turn a computer into a virtual Bobby Fischer but we can’t get on-the-fly replacement to work? What the heck am I missing here?

  3. Post Status Handling - If I mark something draft that means I want it to be a draft when it’s uploaded not a regular post. When I switch it from draft to publish status it should post immediately without further action on my part.

  4. Outlook-Style View - I should be able to see previous posts in a pane on the left and the body of the post on the right. That way I can switch back and forth between posts without having a bunch of windows open.

  5. Search - I’m forever linking back to previous posts and looking for information. I should be able to search those posts to find what I want and link to it with a single click. Speaking of...

  6. Link URLs - Linking to posts should be easy; a single click and I’m done. The editor should insert the proper permalink from the blog address. This must be part of the same rocket science as text replacement because it doesn’t work right in any editor. I always seem to end up with some goofy address that has no bearing on reality. What gives?

  7. Tags and Keywords - I want to set up a list of keywords and have the editor automatically catch those words in my post and insert the proper Technorati tags. No further effort on my part required. When I tag a word manually, the editor should find subsequent instances of that word and insert the same tag on it’s own.

This post is being written in Anconia RocketPost, which does a fair job but won’t replace my text on the fly (even though it says it will), doesn’t have a split-pane view, doesn’t handle post status properly (even though the options are there) and still garbles up my HTML between editing and posting. I’ve also tried BlogJet, w.Bloggar, Quamana and Ecto, which all fall short in various areas.

So I’m putting out a plea to the community at large. What do you use? Have you found anything that meets all these requirements? Am I just in outer space or what? Leave a comment and steer me in the right direction...

Wednesday, January 04, 2006

SharePoint RPC Methods

I was browsing through Heather's list of SharePoint resources when I found this link to an RPC method that I was unaware of. That led to this article that documents the various RPC methods and provides even more links to other information.

If I'm not mistaken, isn't this just the sort of "associative trail" that Vannevar Bush had in mind all along???

UPDATE: Bil Simser has a great post on this topic that is a must read. Go check it out!

Top Ten Tricks for Customizing SharePoint

In the spirit of helping new SharePointers along (see this post from Bil and this one from yours truly), here's a Top Ten list to start the New Year off on the right foot. For anyone about to dive into heavy SharePoint customization, this is a good list to keep handy (or to use for lining your cat's litter box, whichever you find more appropriate).

1. Simple HTML
Keep your HTML layout code as simple as possible. Use the minimum number of formatting elements (DIV’s, SPANs, Tables, etc.). SharePoint creates a tremendous amount of extraneous code at runtime; the less you add, the less chance for something unexpected to happen. Also, you’ll be cutting and pasting a LOT of code; fewer lines means fewer mistakes.

2. User Controls
Reuse as much code as possible by placing repetitious elements into user controls. Rather than repeating the layout code that defines web part zones and code areas on page after page, place it in an ASCX file, register the file location at the top of the page, and reference it in the page body. This creates a template-like effect for site definition pages in which you only have to modify one file to have a global effect. An added benefit is that code which cannot be executed within presentation files can be executed within a user control.

3. Custom User Menus
SharePoint menus take up a great deal of valuable screen real estate. There’s nothing magical about the left-hand navigation area; extract the menu elements and place them into collapsing DIV’s or pop-outs. While they’ll never be perfect, a basic set of menu layouts can free up 125 – 300 pixels of screen width. Place them in user controls to simplify maintenance.

4. Nested Style Sheets
SharePoint style sheets are a confusing maze of endless CSS code. Nest common code elements in your custom style sheets just like you would with HTML; create parent styles, indent each child style, and keep everything together in logical groupings. Copy existing styles from OWS.CSS and SPS.CSS into your custom sheet to overwrite the defaults and maintain compatibility.

5. Minimal Site Definitions
There are few occasions where more than one or two custom site definitions are necessary (in addition to the required SPS, SPSMSITE, SPSPERS, SPSSITES and STS, all of which can be replaced with a custom definition by modifying the WEBTEMP files). Each definition exponentially increases the amount of customization and future maintenance. Use the bare minimum number of definitions to meet the requirements.

6. Server Controls
Custom code elements that do not require user manipulation should be implemented as server controls instead of web parts. The layout is easier to design with fewer web part zones, the code is much more compact, and render times are much faster. Store them in the /Bin directory along with the user controls for layout.

7. Preserve Zone Names
Some functions, such as ‘Edit in DataSheet’ require a specific zone to be present on the page. To avoid erratic behavior, preserve the default zone names whenever possible but change their location to meet your requirements. This also reduces the amount of edits to the various SCHEMA.XML files.

8. Comments Before Deletions
Before deleting existing code, comment it out and test it with your modifications. Once you have a working page, go back and delete the extraneous elements one by one. There are some pages that absolutely must have certain elements in place – even if they’re hidden - or they won’t render properly. You’ll save yourself a great deal of frustration by working backwards through commented code than trying to add it back in later.

9. Assumption = Frustration
Never assume that the modifications you make in one definition will work in another. This is especially true of SPS and its deviations; the root portal definition is quite different from the subarea definitions. Diff the original files extensively before copying changes to a new definition. This rule applies in spades to administration pages, where there are multiple shared code structures and many one-off files.

10. Make CAML Your Friend
Much of what drives SharePoint lives in ONET.XML and SCHEMA.XML. The more extensive the modifications, the more you’ll need to work with these files. Learn the ins and outs of CAML and how to tweak it to meet your needs. Although at first intimidating, it’s nothing more than a structured XML superset. Begin by adding list definitions and move from there into creating custom lists and mastering views.

Happy SharePointing!!!