Thursday, May 25, 2006

Enabling the 'Edit in Microsoft Word' Functionality for Office 2000/XP Users

Ever since SharePoint v2 was released organizations have struggled with the Office 2003-dependent features of the product. The primary source of frustration are those functions that should work for all users but don’t – like the ‘Edit in [Application]’ link in document libraries. Users with Office 2000/XP get the dreaded “” error when trying to access files using this option, while Office 2003 users can continue working without interruption. While there are several workarounds for this missing functionality, all of them require Office 2000/XP users to perform different steps to access a document than those users who have Office 2003 installed on their system.

Fortunately, there is a way to overcome this problem. The ‘Edit in …’ option is exposed in the core JavaScript file OWS.JS located in the 60\TEMPLATE\LAYOUTS\1033 folder on each front-end web server. On or about line 1440 is the following function:

function editDocumentWithProgID2(strDocument, varProgID, varEditor)
{
var objEditor;
if (strDocument.charAt(0) == "/" strDocument.substr(0,3).toLowerCase() == "%2f")
strDocument = document.location.protocol + "//" + document.location.host + strDocument;
try
{
objEditor = new ActiveXObject(varEditor + ".2");
if (!objEditor.EditDocument2(window, strDocument, varProgID))
alert(L_EditDocumentRuntimeError_Text);
window.onfocus = RefreshOnNextFocus;
return;
}
catch (e)
{
}
try
{
objEditor = new ActiveXObject(varEditor + ".1");
window.onfocus = null;
if (SzExtension(strDocument) == "ppt" && varProgID == "")
varProgID = "PowerPoint.Slide";
if (!objEditor.EditDocument(strDocument, varProgID))
alert(L_EditDocumentRuntimeError_Text);
SetWindowRefreshOnFocus();
return;
}
catch (e)
{
alert(L_EditDocumentProgIDError_Text);
}
}

This function takes three parameters that define the document’s location, program ID, and the application which created it. The critical code in this function is found within the two Try…Catch blocks starting on line 1445. The first block applies to Office 2003 users (the “.2” designation appended to the variable varEditor indicates O2K3 clients), while the second is applicable to Office 2000/XP clients. This is the portion of the function that must be altered to enable the ‘Edit in …’ option for these users.

The original code identifies the application type and determines the correct application to launch based on settings in the DOCICON.XML file. For Office 2000/XP clients, the script must instead contain an explicit mapping between file extension and application (UPDATE: There is a way to parse the DOCICON.XML file dynamically to achieve these results but the code is lengthy and cumbersome – the performance trade-offs are probably not worth the effort). To achieve this, simply add a series of if…else if statements to the beginning of the second block for each file type:

if (SzExtension(strDocument) == "doc")
{
objEditor = new ActiveXObject("Word.Application");
objEditor.Visible = true;
objEditor.Documents.Open(strDocument);
}
else if (SzExtension(strDocument) == "xls")
{
objEditor = new ActiveXObject("Excel.Application");
objEditor.Visible = true;
objEditor.Workbooks.Open(strDocument);
}
else if (SzExtension(strDocument) == "ppt")
{
objEditor = new ActiveXObject("PowerPoint.Application");
objEditor.Visible = true;
objEditor.Presentations.Open(strDocument);
}
else if (SzExtension(strDocument) == "vsd")
{
objEditor = new ActiveXObject("Visio.Application");
objEditor.Visible = true;
objEditor.Presentations.Open(strDocument);
}
else
{
objEditor = new ActiveXObject(varEditor + ".1");
window.onfocus = null;
if (SzExtension(strDocument) == "ppt" && varProgID == "")
varProgID = "PowerPoint.Slide";
if (!objEditor.EditDocument(strDocument, varProgID))
alert(L_EditDocumentRuntimeError_Text);
}
SetWindowRefreshOnFocus();
return;

The script will now check the file extension when the ‘Edit in …’ option is clicked and, if it matches one of the ‘if’ or ‘else if’ statements, open the associated application and load the file. For compatibility, the original code is retained in the final ‘else’ statement. Also, the SetWindowRefreshOnFocus() and return lines have been moved to the end of the function so they remain applicable to the entire function (if not relocated, the window will remain in a wait state after the ActiveX object has been invoked).

There are a couple of caveats to this solution:

1. Each file type must be explicitly declared in the ‘if…else if’ statements. This can be overcome by instead parsing the DOCICON.XML file; however, as noted above this method is a bit cumbersome and the XML settings are not comprehensive (many of the mappings call SharePoint.OpenDocuments instead of the original application, so a clunky series of substitutions must still be made to be effective; overall, this solution is less efficient than simply making the declarations in the code itself).

2. If the SPS Area/WSS site is not in the user’s Intranet zone in IE and the ‘Initialize and script ActiveX controls not marked as safe’ option is not set to ‘Enable’, the user will get a warning dialog each time they select the ‘Edit in …’ option. For organizations that make use of policy files and/or login scripts, these settings can be enabled globally via a policy file on login.

While this is not a substitute for upgrading all Office 2000/XP clients to Office 2003 (users on the older platforms still won’t have the Explorer View functionality or be able to upload multiple files), it will serve as an effective interim measure until the upgrade process can be completed. Afterwards, the script can be reverted back to its original state and the workaround code removed.

Tuesday, May 23, 2006

Extreme SharePoint Design: Hiding List Types in Existing Sites/Areas

It can sometimes be necessary to disable a certain list type within a WSS site or SPS portal area. If, for example, the customer doesn’t wish to provide portal users with the capability to create discussions, that list type should be removed from the list creation page. If you have not already deployed the portal or team site, removing the list is as simple as editing the ONET.XML file for the site definition. But what if you have already deployed a number of sites or areas? Modifying ONET will have no impact on sites or areas that have already been created – only new ones created after the change takes place. To remove a list type from an existing site/area you’ll need to directly modify the list display and creation pages to remove the list type from view.

In this example we’ll hide the Dicussion Board list type. Begin by removing the display of the list type from the Documents and Lists page (spsviewlsts.aspx in SPS and viewlsts.aspx in WSS – BE SURE TO BACK UP ALL FILES BEFORE MODIFYING THEM IN ANY WAY). Both files can be found in the /60/TEMPLATE/LAYOUTS/1033 folder on the front-end web server(s). On or about Lines 166 - 172 (viewlsts.aspx) or Line 175 - 181 (spsviewlsts.aspx) you will find the following block of code:

ArrayList rgDiscussions = new ArrayList();
rgRgs.Add(SPBaseType.DiscussionBoard);
rgRgs.Add(SPListTemplateType.InvalidType);
rgRgs.Add(L_szDisc_Text);
if (!bBaseTypeInited) { rgRgs.Add(L_szNoDisc_Text); } else { rgRgs.Add(L_szNoDisc1_Text); };
rgRgs.Add("?BaseType="+Convert.ToInt32(SPBaseType.DiscussionBoard));
rgRgs.Add(rgDiscussions);

Remove all but the first line of this code by closing the code block then commenting out the remainder of the text:

ArrayList rgDiscussions = new ArrayList();
%>
<!--
rgRgs.Add(SPBaseType.DiscussionBoard);
rgRgs.Add(SPListTemplateType.InvalidType);
rgRgs.Add(L_szDisc_Text);
if (!bBaseTypeInited) { rgRgs.Add(L_szNoDisc_Text); } else { rgRgs.Add(L_szNoDisc1_Text); };
rgRgs.Add("?BaseType="+Convert.ToInt32(SPBaseType.DiscussionBoard));
rgRgs.Add(rgDiscussions);
-->
<%

This will prevent any existing Discussions from being displayed on the Documents and Lists page. It is important to leave the first line intact so the dependent code that relies upon the rgDiscussions array does not throw an error.

Next, perform the same edits on the WSS (create.aspx, lines 194 - 200) and SPS (spscreate.aspx, lines 199 - 205) list creation pages. Save all four pages to their original location (no reset required) and view them in your browser (/_layouts/1033/{filename}.aspx for each site/area). The Discussion Board group is now hidden from view.

There is one caveat: users with sufficient knowledge of SharePoint can still create and view the list type you have hidden by using the URL protocol. For example, the URL to create a new Discussion Board is http://{portal name}/_layouts/1033/new.aspx?ListTemplate= 108&ListBaseType=3 and the list can still be viewed at /Lists/{ListName}/AllItems.aspx; however, for the vast majority of users this method will be sufficient to prevent them from utilizing the hidden list type. It would also be a good idea to delete any existing lists of that type (if possible); or, at least remove any web parts or links that refer to them.

Tuesday, May 16, 2006

SharePoint Forums Released

Sometimes, you just gotta put down your coffee, get up from your computer and dance a little jig. Today is one of those days. Bil Simser has officially released his new SharePoint Forums web parts. And there was much rejoicing and dancing in the streets (there was at my desk, anyway).

Seriously, everyone who has worked with SharePoint for even a day knows what a pain the built-in discussion boards can be. We all owe Bil a debt of gratitude for solving this problem AND making it freely available to the community.

So what are you waiting for? Go get ‘em!

Wednesday, May 10, 2006

SharePoint Resources

Joris Poelmans (JOPX) has an updated list of SharePoint resources, including some great stuff for MOSS 2007. Check it out!

Tuesday, May 02, 2006

MOSS 2007 Upgrade Article

I get a lot of questions regarding upgrading from SPS 2003 to MOSS 2007. So far, there has been very little information on this topic, but Joel Oleson just posted a good article on some of the upgrade options. I am intrigued by the scan option to identify customized sites/pages in a portal but I have a feeling that heavily customized sites (which most of my clients demand) are still going to be a painful process to upgrade.

On that note, if you are doing heavy customizations to site definitions and templates, it's always a good idea to warn the client up front that they will have to re-invest heavily in the upgrade when 2007 ships so they can budget accordingly. As a personal rule of of thumb I try to always present the 'doomsday' scenario as part of the upgrade discussion, meaning for every dollar the client invests today in customizations they should plan on at least a 100% matching investment to upgrade. That might be a bit over the top but personally I'd rather give them the bad news up front so they can make better decisions down the stretch.

SharePoint Developer Guidelines

One of the most common mistakes I see new SharePoint developers make is that they jump right into Visual Studio and start writing code without any real knowledge of the environment their applications will be deployed in. They throw together some code, deploy it within a limited context (usually in WSS) on a local machine, do all their testing with an admin account, and then declare themselves to be SharePoint developers. While this sort of slap-dash approach may be fine if you’re just trying to figure the object model out, it creates bad coding habits that will cause all sorts of headaches the first time your web parts are deployed in an enterprise portal environment.

Here are a few guidelines for producing better code, in less time, with more positive long-term results:

1. Learn the Product

To begin with, developers should understand SharePoint before trying to create the next great web part that changes the world as we know it. Investing the time to learn how portal areas are different from team sites, how the data model works, what the differences are between SPSite and SPWeb objects, how, when and why to do impersonation, how data is stored in and retrieved from lists, and getting a handle on the ins and outs of code access security will be time well spent. Furthermore, one needs to work with SharePoint for a while to have an idea of how users are going to interact with the custom web parts being created. How can a person effectively design a portal application if they’ve never struggled with the default navigation controls, created site collections and subsites, managed portal area security, configured search, created custom views, added calculated fields, configured cross-site groups, or put together a basic data view web part? I’m not saying that developers should be admins – that’s not their job – but they sure need to know what they’re working with before jumping in with both feet.

2. Plan for success

It’s a funny thing, but SharePoint development tends to promote a kind of “code before planning” mentality that rarely exists elsewhere in the enterprise. Developers who wouldn’t normally write a line of ASP.NET code without a dozen use cases jump right into web parts without any kind of plan whatsoever. SharePoint’s inherent advantages – unified navigation, managed presentation layer, built-in security model, user-managed hierarchy – aren’t a substitute for a well-designed application framework; in fact, they often work against the developer to emphasize weak design elements and exacerbate poor application design.

Before writing a single line of code, stop and T-H-I-N-K. What’s the right type of project - web part, server control, event handler, or web service? How will the users deploy the application? Will it be used in both WSS and SPS? Does the code require external files, such as XML and XSL, and, if so, where will they be stored and how will they be accessed? What kind of permissions are necessary to execute the various functions in the code? How will configuration parameters be stored and modified? Does the output require a user control or will the HtmlTextWriter class of RenderWebPart be sufficient? Are success/fail messages required in the GUI or Event Viewer? How will you debug and test the application?

Finally, plan your code just like you would any other enterprise application. Identify your input elements, output parameters, presentation objects, class definitions, and so forth. Map out the structure and navigation hierarchy. Determine data storage requirements. Create a specification for all the supporting elements – lists, libraries, areas, sites, etc. In other words, treat it like a normal development project and avoid the common mistakes that cause web parts to fail under any conditions other than those used in the proof of concept.

3. Know the Object Model

Nothing is more frustrating than seeing an application that works flawlessly in development get kicked back from QA because the code is dependent upon some quirky object model function. Those handy little methods that look like lifesavers in development – IsRootWeb, DoesUserHavePermissions, GetSubwebsForCurrentUser, ParentWeb – can wreak havoc in a production environment (try using IsRootWeb on a top-level portal area and watch your code get blown to smithereens). Even worse, permission-dependent functions, like GetPermissionCollectionFromWeb, don’t work at all for non-admin users, requiring tricky account impersonation techniques.

The best way to avoid these common pitfalls is to test, test, test in the best simulation of a real-world environment possible. Make sure that the development SharePoint server has accounts for at lest each level of built-in security (and any custom security groups required by the application). Deploy code in the Home area, top-level portal areas, subareas, site collections (parent sites) and subsites. Above all, consider how the user *might* implement the code not how you originally intended it to be used.

4. Log Application Events

Debugging code is no easy task in SharePoint; it’s not always possible to run code on the local development server or virtual machine, the standard error messages are cryptic at best and nearly useless in many instances, and asynchronous code execution (event handlers, for instance) will give even the most seasoned .NET developer nightmares. One simple yet effective method for debugging in SharePoint is to write success and fail messages to the event log during development and testing. Using the WriteEntry method of the System.Diagnostics.EventLog class, developers can bypass the SharePoint safemode parser and STSFLTR ISAPI filter and write messages directly to the event log. This is a handy method for isolating code errors during development; just be sure to remove the event log code before moving into production (although it might be a good idea to leave some level of error logging in the application, especially if it is an event handler or other async process).

5. Code Only as a Last Resort

It sounds strange but writing custom code should be the last resort in any SharePoint deployment, large or small. Think about it – code is expensive to write, deploy, and maintain. In most instances, the original developer isn’t going to be around when the code breaks or fails to run after the latest batch of service packs and updates. No matter how good they are, nobody can fully comment a batch of code so that those who come behind them can just pick it up where they left off. But worst of all, custom code cannot be learned, deployed, managed or improved upon by the average portal user.

Out of the box, SharePoint provides a rich application framework that enables users to create dynamic collaboration spaces without any programming. It never ceases to amaze me how many creative solutions users come up with just using the stock web parts and lists. As developers, our first instinct when presented with a problem is to write code to solve it but users don’t think that way; give them a chance to create a solution on their own and you may be surprised what they come up with, especially once they learn how to use Data View Web Parts. Concentrate on writing code only where it’s really needed so developers can spend their valuable time solving really hard problems without getting bogged down writing throw-away web parts and duplicating built-in functions.

6. Lists Rule

The secret to SharePoint’s power and flexibility is the list object. The reason the product has been so successful where others have floundered (think Lotus Notes and PeachTree) is that it empowers users to create their own data-driven applications without even knowing how to spell ‘database’. But lists are more than Databases for Dummies – they can be a developer’s best friend. Need a datatable to store input parameters? Use a list. Need a sortable, filterable grid to display query results? Lists do it automatically. Need to store/retrieve file objects like XML configuration files? Document libraries have it covered. Need an automated method for launching processes in response to user-driven events? Attach an event handler to a document library.

In SharePoint it’s all about using what you have and not recreating the wheel. If the design includes any sort of data input/output requirements, stop and think how a list can serve this purpose before writing to any XML files in the /bin directory or attaching to an external database. Lists can be a tremendous time saver and, even better, can involve the user in the data management process, requiring less code and generating more interactivity.

7. Don’t Fear the Database

I know this is going to be controversial but I’ve never agreed with the misguided mantra promulgated by SharePoint product managers and evangelists of “Don’t touch the database”. What rubbish. Are we in the business of providing value to the customer or giving sermons on “supportability”? Show me one instance of how reading from the database will blow up a portal and I’ll be glad to reconsider – but I have yet to see one (please note that I am talking ONLY about reading from the database – writing to it directly is a really bad idea and should be strenuously avoided). And before you flame me with a zillion emails on the subject, stop and ask yourself why it’s wrong to read from the SharePoint database but OK to read from the MCMS data store? Is there some evil genie guarding _SITE that’s going to awaken and eat all our lists for breakfast if we query the Webs table? And here’s the real kicker – BizTalk can flood the SQL server with SELECT statements while executing a workflow process but my little navigation control is going to bring SharePoint to a screeching halt??? I think not.

The truth of the matter is that the object model only goes so far. Sometimes the right way to get information to the user is to fetch it from the database; sometimes it’s the ONLY way (think full-blown, drop-down, security-trimmed portal navigation, for instance, or rolling up list data across multiple portals/site collections). Of course, a good developer will follow good data access practices, minimizing redundant calls across the network, closing connections in a timely manner, doing advanced sorting and filtering in dataviews, and so forth. The database is to be respected but never feared; in fact, learning how the data model works in SharePoint can help one to develop richer, more efficient and flexible web parts.

Naturally, these techniques are not a comprehensive guide to developing in SharePoint but they are a good starting point for beginners (and a timely refresher for those with a few web parts under their belts). Learn to be a better SharePoint developer and everyone wins – fellow programmers, project managers, administrators, and, most importantly, the users who have to live with our applications on a daily basis.