In my 1st technical post I'd like to share how I implemented a delicious-like feature in a Sharepoint site (called "Quick Post") that lets users post a file or Url to a Sharepoint document library with one click of a button on any page of the site; the user can tag each post just like with delicious, and the site has tag cloud and searching-by-tags features for users to find info later; the user will get suggestions on existing tags when he tags.
The following is a screen shot of the pop-up window after the user clicks on the Quick Post button in the site:
The type just gives the user a choice on one of the two different document libraries. The location is a toggle between the ASP.NET FileUpload control for file and TextBox control for Url, using Javascript.
With this custom UI, the user is able to post a file or Url to the document library without leaving the page he's on, and in one screen (unlike the built-in multiple Sharepoint pages for adding a file or Url, and any tags in the tag field of the document library); the custom UI also has tag suggestion to maximize tag reuse :).
The Pop-up Window
The pop-up window you see in the screen shot is a DHTML window (which is better than a browser window because of pop-up blocker). I'm all for re-using others' code as much as possible :), so for the pop-up window I used and would recommend this 3rd party widget. Just include the 3rd party widget in your page that pops up the window (in our case the master page since the QuickPost button needs to show up everywhere):
<link rel="stylesheet" href="http://devcentral/_vti_bin/windowfiles/dhtmlwindow.css" type="text/css" />
<script type="text/javascript" src="http://devcentral/_vti_bin/windowfiles/dhtmlwindow.js">
</script>
and have the button click call the following javascript function:
var quickPostWin = null;
function openQuickPostWindow ()
{
quickPostWin = dhtmlwindow.open("quickPostBox",
"iframe",
"http://devcentral/Pages/QuickPost.aspx",
"Quick Post",
"width=650px,height=400px,resize=1,scrolling=1,center=1",
"recal");
if (quickPostWin.onclose == null)
{
quickPostWin.onclose=function(){ //Run custom code when window is being closed (return false to cancel action):
return true;
}
}
}
The content inside the window comes from a ASP.NET user control (.ascx). The easiest way I found to develop custom pages in Sharepoint is to develop it in Visual Studio (so you take advantage of the designer support, code-behind, etc), and host it in a Sharepoint web part: a page viewer web part to host .aspx, or like in this case, a 3rd party smart part web part to host .ascx; the page shown in the DHTML window (http://devcentral/Pages/QuickPost.aspx) is just a Sharepoint page with one smart part web part that hosts a .ascx. Sharepoint still has some way to go in terms of its integration with Visual Studio for development, and this is kind of a stopgap until MSFT builds the functionality of developing custom pages for Sharepoint right into Visual Studio.
AJAX functionality
As seen in the screen shot, Quick Post provides suggestions as the user starts typing in tags, just like delicious. To add AJAX functionality like this, I used the AJAX Control Toolkit from codeplex; it contains quite a few extenders to ASP.NET controls to add AJAX to your web page, and in this case I used the auto-complete extender to the tags text box.
To use AJAX Control Toolkit in Sharepoint, make sure to reference it and include the ScriptManager in the page. Since in our case we want AJAX functionality in every page (as in most cases I think), I just put the following in the master page:
<%@ Register Tagprefix="ajaxToolkit"
Namespace="AjaxControlToolkit"
Assembly="AjaxControlToolkit, Version=3.0.11119.26320,
Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" %>
...
<ajaxToolkit:ToolkitScriptManager ID="ScriptManager1" runat="server" />
Here I'm using AJAX Control Toolkit's implementation of the ScriptManager rather than the one in ASP.NET AJAX, as the former is supposed to be more efficient at sending the javascript down to the client.
The auto-complete extender itself is pretty straight-forward as documented: in my implementation, the web-service responding to the user key strokes in the tags text box just iterates over all records in the document library, extracts and parses the tags from the comma-separated tag field (using Sharepoint API's), and return the tags matching the given prefix.
There's perhaps one tricky thing in the web service. In our Sharepoint site, the user is free to create sub-sites, and those sub-sites can contain library items with tags as well, so to make sure we capture all existing tags from all sites in this site collection, you need to make sure either every user has the permission to iterate over all sites, or elevate the privilege in the web service code somehow, .e.g, by impersonating a user that for sure has permission to do it:
SPUser wssAccount = null;
using (SPSite siteCollection = new SPSite("http://devcentral"))
{
// need to impersonate to iterate over all sub-sites
wssAccount = siteCollection.OpenWeb().AllUsers["corp\\dcentral_wss_svc"];
}
using (SPSite impersonatedSiteCollection = new SPSite("http://devcentral", wssAccount.UserToken))
{
// iterate over all sites, get all the tags, and return matching tags
...
}
The above will also do the trick any time your custom code using the Sharepoint API's needs to run in elevated privilege mode.
Storing Url in a Document Library
Sharepoint document libraries can have multiple content types, and when it does, the user can choose which content type it is when adding a record to a document library. There's a "Link to a Document" content type that's well suited for storing Url's in a document library; in fact it can store link to another document as well. For our Sharepoint site, however, we just need to support storing Url's.
It's however not well documented how to add a document link programmatically, as I had to do for our custom UI. When a user adds a document link to a document library, what happens behind the scenes is that Sharepoint actually creates an aspx file on the fly that does nothing more than redirecting to the document link Url you give, and it's the aspx file that's stored in the document library, just like a document stored in the document library. A rather convoluted way to store links in my opinion, but that's the way the document link content type works.
After bugging him about it, Cliff Green from MSFT wrote up how to programmatically add a document link to a document library in his blog :). Essentially you have a aspx template file that you read into memory each time you add a document link to a document library, after filling in the Url in the template file in-memory.
Conclusion
In this blog post, I've hopefully covered how to have custom pages in Sharepoint (leveraging Visual Studio designer), adding AJAX to your page, and how to have Url's in a document library. In future posts I will share more customizations I've done in Sharepoint. Hope this has been a useful read :).