>
Blog
Book
Portfolio
Search

1/29/2011

10807 Views // 0 Comments // Not Rated

Document Integration With SharePoint 2010 / Office 2010 / Silverlight 4

I've noticed over the years that a lot of different types of windows popup whenever I click on a document in SharePoint. Back in the "2003" days, you'd be lucky if anything popped up at all. Then in 2007, when the whole publishing paradigm started to solidify in people's minds, we began to see different options when we accessed different documents...especially Office documents.

With the 2010 release (of both client and server), Office integration has only deepened. I mean, even Visio has been invited to the Office/SharePoint integration party. Nowadays, this type of functionality that was new and cool in 2007 has come to be expected. The workflow of checking something out from your browser in SharePoint, having it open in Word, and then saved, tagged, and checked back in all in the same session is really cool.

Several factors determine the experience your browser will give you upon integrating with Office: document library settings (around versioning, check in/out, etc.), the actual version of Office installed, domain-joined-ness, and of course browser platform, version, and security. This is why sometimes the file just opens in the browser, sometimes you get a security warning, sometimes you get a popup asking if you want to open the file in "read only" or "edit" mode, and sometimes Office comes to life, opens the document, and handles all the SharePoint integration from there.

What's interesting is that none of this magic is implemented with response headers, content types, MIME types, HTTP handlers or modules, or any managed code. Nope. It's all just a little bit of good old fashioned JavaScript. If you browse to a document library with Firefox and Firebug the page, you'll see that upon inspecting the hyperlink corresponding to each document there is a lot of JavaScript going on. However, the piece that we're interested in is the event handler wired into the onclick method of the aforementioned anchor tag, which calls something called "DispEx."

This method is defined in core.js, which is nestled in the layouts/1033 subdirectory of the SharePoint Root (aka the 14 hive). (It's no longer called "the hive," probably because the files inside are not actually bees.) Core.js is "obfuscated" and to be honest, not worth decoding. Whatever this method does (I'd guess something with cookies, if you put a gun to my head) it makes all the beautiful goo between SharePoint and Office remain sticky.

So if you want to mimic SharePoint's functionality, create an anchor tag, set the "href" attribute equal to the URL of the document, hook "onclick," and call "DispEx." (I'll provide an example later in this post.)

But what if we're in Silverlight? Normally, when we're working with Silverlight and SharePoint, it's SharePoint that owns the page, while Silverlight is contained in a webpart to provide some bling. So you simply use the Silverlight HTML bridge and call System.Windows.Browser.HtmlPage.Window.Eval(string), passing in the proper string of JavaScript.

What I meant by SharePoint "owning" the page is that the ASPX is using a customization of a raw SharePoint MasterPage, and core.js will be available. But this isn't always the case. My current project is a collaboration tool for our client's design team, and the UI has a lot of heavy animation and styling, as well as very stringent performance requirements. Furthermore, the majority of the users are on Macs using FireFox and Safari.

For these and other reasons, we went with a straight Silverlight page, where the only HTML is the object tag used to host the plugin. Everything else is XAML. SharePoint is the backend, used for image and file storage, search, administration, etc. (Yes: a SharePoint app with no SharePoint UI!) So, we can't simply call methods in core.js, since our page doesn't even live in a SharePoint web application, and therefore no SharePoint client code is pulled down.

So what we did is create a SharePoint 2010 feature that deploys an application page to the _layouts folder, along with a JavaScript file with a helper method (since I've found that calling jQuery (which we'll need later) from Silverlight to be a little wonky). This page contains the aforementioned magical anchor tag that mimics the HTML and JavaScript that SharePoint employs when rendering document library views. It works by taking the URL to the document in as a query string parameter, rendering the anchor tag, and then using jQuery to programmatically click it when the page loads.

A disclaimer before I should you some code: this might seem a bit over-engineered. This is because of a weird nuance with the way DispEx works. When you close the Office application that it opens, the browser refreshes the page automatically. My guess is that this is to update the metadata of the document in the case where you changed something client-side. This is problematic, because if the page refreshes, my jQuery runs again, and Office will launch another time!

Fortunately, Page.IsPostback will only be false the first time the document is launched, so we can use this to circumvent the refresh issue. Note: I've seen some postings about a weird JavaScript method to disable the refreshing behavior, but couldn't get it to work (probably because we're in an application page). Read more about that here.

First, let's look at the code behind for our application page:

Code Listing 1

  1. protected void Page_Load(object sender, EventArgs e)
  2. {
  3. //force document to only open once
  4. if (!this.Page.IsPostBack)
  5. this.Page.ClientScript.RegisterHiddenField("hidOpen", true.ToString());
  6. }

All this does is inform the client when it should open the document. Next, let's move on the HTML. I used the OOTB layout you get when you create a new application page, so I excluded a lot of the template markup from the following listing. All we're interested in are the "PageHead" and "Main" content placeholders.

Code Listing 2

  1. <asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
  2. <SharePoint:ScriptLink ID="slJQuery" Language="javascript" Name="jquery-1.4.2.min.js" Defer="true" runat="server" />
  3. <SharePoint:ScriptLink ID="sliNB" Language="javascript" Name="iNB.OfficeIntegration/iNB.js" Defer="true" runat="server" />
  4. </asp:Content>
  5. <asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
  6. <a id="lnkDocument" style="visibility: hidden;" onclick="return DispEx(this,event,'TRUE','FALSE','FALSE','SharePoint.OpenDocuments.3','1','SharePoint.OpenDocuments','','','1','1','0','0','0x7fffffffffffffff','','')" href="<% =this.Page.Request.QueryString["Url"] %>" />
  7. </asp:Content>

As you can see, Line #'s 2 and 3 merely pull down jQuery and our custom JavaScript file. Line #6 contains our anchor tag, and the call to DispEx. Scroll to the right of the line, and note how I used spaghetti code to inject the query string value for the URL into the href attribute.

Finally, let's look at the JavaScript file, which only contains one method that hooks the client side page load event:

Code Listing 3

  1. $(document).ready(function()
  2. {
  3. //initialization
  4. var trigger = $("#hidOpen");
  5. //determine if we are opening the document or closing the window
  6. if (trigger.val() == "True")
  7. $("#lnkDocument").click();
  8. else
  9. window.close();
  10. });

Like I said, all this does is click the link only the first time to page is loaded, instead of after each refresh. Notice the window.close() call in Line #9. The first implementation of Silverlight/SharePoint/Office integration I came up with involved popping our application page up in a new window. This line "cleaned up" our application page as Office loaded, but absolutely wouldn't work on our test Mac. Besides, this was a Silverlight app, where every drop shadow and Visual State was crafted with care; why get sloppy and start having empty browser windows spamming around?

Instead, we shoved a hidden iframe right next to Silverlight's history iframe. If you do this, make sure to keep your iframes inline with the closing Silverlight object tag, otherwise you'll see an otherwise-inexplicable gap of white space at the bottom of your page. Figuring that out took an hour of my life that I'll never get back. Make sure you give your iframe an id, since we'll be referring to it programmatically.

Finally, let's look at how Silverlight plugs into this. If you use a Silverlight Hyperlink control and set the NavigateUri property to the absolute URL of an Office document, clicking it will open the file in the targeted frame with no integration features enabled. So instead of using the NavigateUri property to do our bidding, we bound the click Command to a method in our view model, (if you're thinking Prism/MVVM you're thinking correctly) and the CommandParamer to the URL of the document. For more on MVVM command binding, read this. Our view model's command implementation takes the document URL from the CommandParameter and passes it to the following utility method:

Code Listing 4

  1. public static void EditDocument(string rawUrl)
  2. {
  3. //get the site collection's url
  4. string url = rawUrl.Substring(0, rawUrl.IndexOf("/Assets/"));
  5. //pass url to office integrator
  6. url = string.Format("{0}/_layouts/iNB.OfficeIntegration/OfficeIntegrator.aspx?Url={1}", url, rawUrl);
  7. //load into iframe
  8. HtmlPage.Window.Eval(string.Format("EditDocument('{0}');", url));
  9. }

Line #4 uses a "safe hack" (pursuant to our requirement that all document libraries are named "Assets") to grab the relative site-collection-rooted URL of the document. This then gets appended as the query string parameter value to a longer URL pointing to the custom application page. Finally, this entire mess is passed to a JavaScript method called EditDocument, which simply sets the "src" attribute of our iframe using jQuery. Setting the iframe's source in this manner keeps Page.IsPostback honest server side.

The rest just flows: the iframe reloads itself, the application page is assembled and renders, jQuery clicks the link, and Office opens the document, integrated and beautiful. I really dig these piece-wise architectures, where a bunch of one-liners from different technologies work together behind the scenes to drive something. However, I still find myself a little annoyed that, even though we're dealing with cool new technologies like Silverlight 4, Visual Studio 2010, Office 2010, and SharePoint 2010, it's still ultimately JavaScript that's making it all churn.

Oh well; at least it works. Have fun integrating!

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.


Loading...