Patrick's SharePoint Blog

SharePoint's Booming world

Posts Tagged ‘Unique Document ID’

Creating a Unique Document ID provider for SharePoint 2010 based on content type

Posted by Patrick Boom on April 29, 2010

SharePoint 2010 introduces the Unique Document ID feature, that assigns an unique ID to each document uploaded in the site collection. As said, the scope is site collection, so the document cannot be found outside the site collection using this service.

Out-of-the-box, SharePoint uses its own provider to assign document ID’s. But what if you want to influence the way the unique ID’s are generated? Well, fortunately, SharePoint 2010 also offers the possibility to create your own provider. In this article though, I would like to take it one step further and make the generation of the ID’s dependant on the list item itself. I will use the content type in this example, but you can use any field of the list item you desire.

My case: I have a company that stores contracts and quotes in the same site collection. I have the following rules regarding the unique identifier of the document, also used for reference in correspondence:

  1. A contract should start with CTR
  2. An offer should start with QUO
  3. Any other should start with DOC
  4. It should be followed by universal date notation, meaning year (4), Month (2), day (2), Hour (2), Minutes (2) and Seconds (2)
  5. Then it should contain the customer number, consisting of 6 digits, obtained from the document properties. If the document does not contain a customer number field, or it is empty, we use 000000 as the customer number.

For example: CTR-20100425163145-123456

Create the custom fields and content types

Before we can create a provider based on content types and fields, we first need to define our content type and the customer number field. Right click your project, select add new item and select Content Type in the template window. Name your content type Contract and derive it from Document. Repeat these steps for the Quote content type.

Also add a new Empty Element and call it Fields. Within the Elements.xml file of the Fields element, include a field definition for the customer number field. See below xml definition:

<?xml version=1.0 encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
  
<Field
       ID={5744d18c-305e-4632-8bd1-09d134f4830d}

       Type=Text

       Name=CustomerNumber

       DisplayName=Customer Number

       Group=Boom.CustomDocumentIdProvider>
    </Field>
</Elements>

 Now that we have our field, we complete the content types to include this field. Other than the name, both content types are the same for the purpose of this blog. See below definition for the Contract content type:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<!– Parent ContentType: Document (0x0101) –>
   <ContentType
      ID=0x010100dc76f46f281449aaa449d8eb30decd03

      Name=Contract

      Group=Boom.CustomDocumentIdProvider

      Description=Contract

      Inherits=TRUE

      Version=0>
     
<FieldRefs>
        
<FieldRef
            ID={5744d18c-305e-4632-8bd1-09d134f4830d}
            Name=CustomerNumber
            DisplayName =Customer Number/>
     
</FieldRefs>
   </ContentType>
</Elements>

 And of course, the definition for the Quote content type:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<!– Parent ContentType: Document (0x0101) –>
   <ContentType
     
ID=0x010100ae26a4fc10bc4810bd234d75bb499864
     
Name=Quote
     
Group=Boom.CustomDocumentIdProvider
     
Description=Quote
     
Inherits=TRUE
      Version=0>
     
<FieldRefs>
        
<FieldRef
           
ID={5744d18c-305e-4632-8bd1-09d134f4830d}
            Name=CustomerNumber
            DisplayName =Customer Number/>
       
/FieldRefs>
   
</ContentType>
</Elements>

Now that we have our content types and fields, we can continue with the custom document id provider.

Create the custom document ID provider

 If we want to implement our provider, we need to extend the following class: Microsoft.Office.DocumentManagement.DocumentIdProvider. There are four methods we need to override to implement our provider:

  1. public override string GenerateDocumentId(SPListItem listItem) à returns the new ID
  2. public override bool DoCustomSearchBeforeDefaultSearch() à specifies whether we do a custom search first rather than using the search framework
  3. public override string[] GetDocumentUrlsById(SPSite site, string documentId) à implements our custom lookup method
  4. public override string GetSampleDocumentIdText(SPSite site) à returns an example of the ID’s that will be generated by our provider

Let us start by creating a new empty SharePoint project in Visual Studio 2010.

Once done, create a class called DocumentIdCache and include the code listed above for the cache object. Secondly, add a class called CustomIdProvider. Let the class inherit from the DocumentIdProvider in the Microsoft.Office.DocumentManagement namespace. For this, add a reference to the Microsoft.Office.DocumentManagement assembly that is located on the .NET tab.

Override the three methods and one property from the base class, so that you have the skeletons in your class as below:

namespace Boom.CustomDocumentIdProvider {
   public class CustomIdProvider : Microsoft.Office.DocumentManagement.DocumentIdProvider {
      public override string GenerateDocumentId(Microsoft.SharePoint.SPListItem listItem)
{
         throw new NotImplementedException();
     
}

     public override string[] GetDocumentUrlsById(Microsoft.SharePoint.SPSite site, string documentId) {
        throw new NotImplementedException();
    
}

     public
override string GetSampleDocumentIdText(Microsoft.SharePoint.SPSite site)
{
        throw new NotImplementedException();
     }

     public override bool DoCustomSearchBeforeDefaultSearch
{
        get {
           throw new NotImplementedException();
        }
     
}
  
}
}

We will implement the easy ones first 😉
In the DoCustomSearchBeforeDefaultSearch property, make sure in returns false. This will instruct SharePoint to use the default search method to find the document. Creating a custom loop method is beyond the scope of the blog, but if you have a better way of finding the document, you can do so in the GetDocumentUrlsById method.
In the GetSampleDocumentIdText method, make sure it returns a string that resembles the pattern you will return for your id’s, for example CTR-20100422153421-123456.
The GetDocumentUrlsById method contains our logic to find documents other than search. Because we use the standard search method, we return a new empty string.

Property and methods should now resemble something like below:

public override string GetSampleDocumentIdText(Microsoft.SharePoint.SPSite site) {
    return “CTR-20100422153421-123456”;
}

public override bool DoCustomSearchBeforeDefaultSearch {
   get {

      return false;
  
}
}

public override string[] GetDocumentUrlsById(Microsoft.SharePoint.SPSite site, string documentId) {
   return new string[0];
}

Great, halfway there J. Now we are going to look at generating the Id’s. The ID is generated in the GenerateDocumentId method. See below code snippet:


public override string GenerateDocumentId(Microsoft.SharePoint.SPListItem listItem)
{
   string customerNumber = “000000”;

   // Get current date and time in universal notation

   string dateString = DateTime.Now.Year.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Hour + DateTime.Now.Minute + DateTime.Now.Second;

   string contentTypeString = null;

   try
{
      // Get the customer number of the document

      if (listItem.Fields.Contains(new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)))
{
         if (listItem[new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)] != null)
           
customerNumber = listItem[new Guid(“5744d18c-305e-4632-8bd1-09d134f4830d”)].ToString();
     
}
      // Obtain the content type
      SPContentType documentType = listItem.ContentType;

      // determine identifier

      switch (documentType.Name) {

         case “Contract”: {
            contentTypeString = “CTR”;
            break;
         }
         case “Quote”: {
            contentTypeString = “QUO”;
            break;
         }
         default : {
            contentTypeString = “DOC”;
            break;
        
}
      }

      string idString = string.Format(“{0}-{1}-{2}”, contentTypeString, dateString, customerNumber);
      return idString;
   } catch (Exception ex) {
      return string.Format(“DOC-{0}-{1}”, dateString, customerNumber);
  
}
}

We first check whether our list item contains a field called CustomerNumber (defined in our solution). If the list item contains the field, we check whether or not a value was entered and if so, obtain the value.

We then continue to get the content type of the list item. Based on the content type’s name, we define the first part of our document id.

Finally, we glue all of it together and return the identifier.

Last thing we need to complete now is register the custom provider with our site collection. Right click the features node in the solution explorer and select Add Feature. Right click the created feature and select Add Event Receiver.
We will attach the custom provider in this receiver. Implement the FeatureActivated and FeatureDeactivating events. See below code snippet.

public
override void FeatureActivated(SPFeatureReceiverProperties properties)
{
   SPSite site = (SPSite)properties.Feature.Parent;

   DocumentId.SetProvider(site, new CustomDocumentIdProvider.CustomIdProvider ());
}

public
override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
   SPSite site = (SPSite)properties.Feature.Parent;

   DocumentId.SetDefaultProvider(site);
}

We register our provider by calling the static DocumentId.SetProvider method, passing an instance of our fresh custom Id provider. In the deactivating method, we reset the provider by using the static DocumentId.SetDefaultProvider method.

Your solution explorer should now resemble the following:

I have included the feature receiver in its own feature, to disconnect the provider registration from the type declarations. Your entire package (wsp) should look like below image:

Deploy and test the solution

 

Right click the project node in the solution explorer and select Deploy Solution. Once deployed, navigate to your site and create a document library that contains both additional content types, like below image:

Now test your provider by adding a document, contract and quote content type. Once the provider job has run, each document should have been assigned with its own identifier, like below:

Because I enabled the Document Id Service after I have created the documents, each document has the same date time stamp, because they are all processed in the same batch. But normally, this is not the case.

Final thoughts

Off course, there are some drawbacks to this implementation. For instance, if multiple normal documents were uploaded and the service was turned on at a later stage, they would have the same ID. However, the scope of this blog is just to show that you could create your own provider and that you could use properties of the document and site to generate the identifiers for the document. Most important aspect to keep in mind is that you have to ensure that the generated ID is unique within the site collection.

I have uploaded the Visual Studio 2010 solution here. Good luck with creating your own!

Advertisements

Posted in SharePoint 2010 | Tagged: , , , | 9 Comments »

Extending the Ribbon with a Send Unique Link Button

Posted by Patrick Boom on April 19, 2010

In one of my previous posts (located here), I discussed the Unique Document ID feature that can be enabled on all document libraries within the site collection. In that post, I also mentioned that it was strange that the E-mail a Link button on the ribbon then still uses the actual url of the document, instead of the virtualized unique url of the document. Why would you have a unique url to a document that you cannot easily send?

So, I decided to add my own button to do so. In the end, it is not that difficult, but a lot of new concepts of SharePoint 2010 can be caught in this single venture. To name a few, Extending the Ribbon interface, the Client Object Model (javascript) and Custom Actions. So, without further due, let us continue.

The Approach

We need to accomplish several things to make all of this work.

  1. Add a button to the ribbon in the context of a document library.
  2. Assign a custom action to execute when the button is clicked.
  3. Obtain the selected item from the document library.
  4. Query the server to get the unique document url of the selected document
  5. Open a mail message containing a description (subject) and the unique url.
  6. Finally, make the button context ‘aware’, so that it is only enabled when a document is selected.

So, let us start with the first part, add a button to click.

Add a button to the ribbon

Let us examine the end state first. We would like a button to be added to the ribbon next to the original E-mail a Link button, like in the image below:

The ribbon is composed using a XML definition that contains three logical parts: Tabs, Groups and Controls. These are hierarchical, meaning that Tabs contain Groups and Groups contain Controls. A tab (for example Documents) or group (for example New) can be static (always available) or contextual.

In our case, we want to add our button (control) to the Share & Track group on the Documents tab. This marks our Location, which we will see later when we start coding. To be exact, the location of our control will be Ribbon.Documents.Share.Controls, where Controls is the container of the group Share on the tab Documents that is located on the ribbon. Sounds logical enough right?

So, when we want to add something on the ribbon (where we can also create our own tabs, groups and contextual controls), we first need to find out the location. Based on the convention, it would be easy enough to figure out, but sometimes the naming is not exactly equal to that displayed in the UI. The following msdn article mentions all default locations for the ribbon, but an excessive overview is beyond the scope of this blog. http://msdn.microsoft.com/en-us/library/bb802730.aspx

Let’s start coding 😉 Open Visual Studio 2010 (this can also be done with VS 2008, but you must then build the package yourself. Visual Studio has far better build-in support for SharePoint 2010. Create a new empty SharePoint project and give it a name, in my case I use Boom.EmailUniqueLinkButton.

Enter the url of the site you want to debug to and use deploy as a farm solution option. Once done, right click the created project and add a new item to the project. Use the Empty Element type and give it a meaningful name, in my case EmailUniqueLinkButton.

Right click again on the project and add SharePoint “Layouts” Mapped Folder. This folder will contain the resources that will be used by our button, in our case the javascript. Also notice that a subfolder with the name of the project is automatically added beneath the Layouts folder. If not, you should add it manually. Right click the feature beneath the feature node (feature 1) and select rename. Rename it to a meaningful name, in my case Boom.EmailUniqueLink. If desired, you can change the default title in the properties also. The default scope of our feature is Web. We will keep it at that scope. When done, our solution will look like below image.

The elements.xml file will contain the xml definition needed to add our button to the ribbon. Open the file and add the following node to the xml (as child of Elements):

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   <CustomAction
      
Id="Ribbon.EmailUniqueLink"
      
Location="CommandUI.Ribbon"
      
RegistrationId="101"
      
RegistrationType="List">
      
<CommandUIExtension>
         
<CommandUIDefinitions>
         
</CommandUIDefinitions>
         
<CommandUIHandlers>
         
</CommandUIHandlers>
      
</CommandUIExtension>
  
</CustomAction>
</Elements>
 

This needs some clarification. Beneath Elements, we define a CustomAction, which has the following attributes:

  • Id – This will uniquely identify our action, can be anything, but is usually something that could easily be related to the ribbon.
  • Location – This indicates where we want to add our custom action, in our case to the CommandUI.Ribbon. Mark though that this is not yet the location of our button! That will follow later.
  • RegistrationType – Although marked in the documentation as optional, it is not. RegistrationType indicates the type to which this action is bound. Could be List, FileType, ContentType or ProgId. In out case, it is List.
  • RegistrationId – Although marked in the documentation as optional, it is not. RegistrationId indicates the type id to which this action is bound. In our case the list type id (101 = Document Library). In case it was a content type, this would contain the content type id.

Below the CustomAction element, we can define a CommandUIExtension, which contains a CommandUIDefinitions and CommandUIHandlers. The former specifies what will be added, the latter specifies what happens when the control is used (in our case clicked).

Include a CommandUIDefinition node below CommandUIDefinitions. Add an attribute Location. The location will dictate where the control will be added. In our case, Ribbon.Documents.Share.Controls._children. Note the ‘_children’ behind Controls, which indicates to SharePoint that a new child should be added to the Controls collection. Now, finally we can add our button. Include the following xml beneath the CommandUIDefinition node.


<Button
  
Id="Ribbon.Documents.Share.EmailUniqueLink"
  
Command="Ribbon.Documents.Share.EmailUniqueLink"
  
Sequence="15"
  
Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png"
  
Image16by16Top="-16"
   Image16by16Left="-88"
  
Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png"
   Image32by32Top="-128"
   Image32by32Left="-448"
  
Description="Sends the unique link to the document by email"
  
LabelText="E-mail Unique Link"
  
ToolTipTitle="E-mail Unique Link"
  
ToolTipDescription="Sends the unique link to the document by e-mail"
  
TemplateAlias="o1"
/>

Before we get to the attributes, I wanted to reuse the image that was used for the original E-mail button. Partly because it would save me work, but more over because I am terrible in creating images and icons 😉 Secondly, I needed to check the Sequence, which defines the order in which the controls are displayed on the ribbon. As I wanted my new button to appear next to the original one, the sequence should be higher than the first, but lower than the following control. To check this, we can take a look at the default Ribbon definition located at 14\Template\Global\XML\CMDUI.xml. Search for Ribbon.Documents.Share.Controls, which defines the controls in the group we want to add to. There you can find the original EmailItemLink button that is the basis of our control.

<Button
  
Id=Ribbon.Documents.Share.EmailItemLink
  
Sequence=10
  
Command=EmailLink
  
Image16by16=/_layouts/$Resources:core,Language;/images/formatmap16x16.png
   Image16by16Top=-16
   Image16by16Left=-88
  
Image32by32=/_layouts/$Resources:core,Language;/images/formatmap32x32.png
   Image32by32Top=-128
   Image32by32Left=-448
  
LabelText=$Resources:core,cui_ButEmailLink;
  
ToolTipTitle=$Resources:core,cui_ButEmailLink;
  
ToolTipDescription=$Resources:core,cui_STT_ButEmailLinkDocument;
  
TemplateAlias=o1
/>

Three attributes are very important to notice. Sequence, Command and TemplateAlias. The sequence of the original E-mail button is 10. The next control in the list (AlertMe) has a sequence of 20. We therefore choose 15 as the sequence for our control, so that it would still be possible to squeeze items in to both left and right in the future. The Command attribute defines the name of the command that is executed when clicked. We will define that later. The TemplateAlias defines the template for handling scaling and sizing. In order for our control to act like the other controls in the group, we have to choose the same template. Finally, we copy over the image references so that we can use the images that came out of the box. So let us now discuss all the attributes:

  • Id – Marks the unique id of our control. Usually, the convention is to follow the location naming followed by a meaningful name of the control, in our case EmailUniqueLink.
  • Sequence – The order in which the control should be shown. In our case 15.
  • Command – The name of the command to execute. Again, could be anything, but has to be unique, which is why I again use the naming convention.
  • ImageXX – The several image attributes define the images to use for both 16 px and 32 px and their locations. I used the same as the original one taken from the CMDUI.xml file.
  • Description – Describes the control.
  • LabelText – Defines what is shown beneath the button in the UI.
  • ToolTipTitle/ToolTipDescription– Shows the title and description when hovering over the button.

Now we have the button defined, but unless we define the action, it will do nothing. So, add the following xml snippet in the CommandUIHandlers section.

<CommandUIHandler
  
Command=Ribbon.Documents.Share.EmailUniqueLink
  
CommandAction=javascript:EmailUniqueLink();
  
EnabledScript=javascript:EnableEmailUniqueLink();
/>

Note that the Command attribute equals that defined in the button command attribute. In the CommandAction attribute, we define the action to be taken when pressed. In our case, we call a javascript function called EmailUniqueLink that will do the hard work for us later on. You can also define the function and related code inline in this attribute, but I rather have it in a separate .js file, both for easy debugging and maintenance. The final attribute here is called EnabledScript which defines the script that determines whether the button is enabled or not. The called function has to return a boolean. We will define the function later.

We have now defined the button and the action to be taken, but not yet the location of the javascript functions that will be called. Right click the Boom.EmailUniqueLinkButton folder in the layouts folder of the solution explorer and add a .js file. You can call it anyway you want, just remember the name. I call it Boom.EmailUniqueLinkButton.js. We will talk about the code in there later on. Go back to the Elements.xml file. Add another CustomAction element beneath the Elements node.

<CustomAction
  
Id=Ribbon.Documents.Share.EmailUniqueLink.Script
  
Location=ScriptLink
  
ScriptSrc =/_layouts/Boom.EmailUniqueLinkButton/Boom.EmailUniqueLinkButton.js
/>

Again, the Id marks the unique identifier for this action. In the Location attribute, we specify ‘ScriptLink’ to indicate that it concerns an external file. In the ScriptSrc attribute, we specify the location of our just created javascript file.

Your Elements.xml file should now look like this:

<?xml version=1.0“vencoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<CustomAction
  
Id=Ribbon.EmailUniqueLink
  
Location=CommandUI.Ribbon
  
RegistrationId=101
  
RegistrationType=List>
  
<CommandUIExtension>
     
<CommandUIDefinitions>
        
<CommandUIDefinition
           
Location=Ribbon.Documents.Share.Controls._children>
           
<Button
              
Id=Ribbon.Documents.Share.EmailUniqueLink
              
Command=Ribbon.Documents.Share.EmailUniqueLink
              
Sequence=15
              
Image16by16=/_layouts/$Resources:core,Language;/images/formatmap16x16.png
               Image16by16Top=-16
               Image16by16Left=-88
               
Image32by32=/_layouts/$Resources:core,Language;/images/formatmap32x32.png
               Image32by32Top=-128
               Image32by32Left=-448
              
Description=Sends the unique link to the document by e-mail
              
LabelText=E-mail Unique Link
               
ToolTipTitle=E-mail Unique Link
               
ToolTipDescription=Sends the unique link to the document by e-mail
              
TemplateAlias=o1/>
         
</CommandUIDefinition>
     
</CommandUIDefinitions>
     
<CommandUIHandlers>
        
<CommandUIHandler
           
Command=Ribbon.Documents.Share.EmailUniqueLink
           
CommandAction=javascript:EmailUniqueLink();
           
EnabledScript=javascript:EnableEmailUniqueLink();/>
      
</CommandUIHandlers>
   </CommandUIExtension>
</CustomAction>
<CustomAction
  
Id=Ribbon.Documents.Share.EmailUniqueLink.Script
  
Location=ScriptLink
  
ScriptSrc =/_layouts/Boom.EmailUniqueLinkButton/Boom.EmailUniqueLinkButton.js/>
</Elements>

We have now defined our button and performed the necessary wiring to make it respond to actions. Now let’s look at the code that will use the Client Object Model to get the unique document id link. Don’t worry, we are almost there 😉

Using the Client Object Model to get List Item properties

SharePoint 2010 adds the Client Object Model to our toolbox, which we can use through native code, Silverlight or javascript. (ECMAScript). Discussing the inner workings of the client object model is beyond the scope of this blog. We will only use the model to get the properties of the list item.

Many of the internet examples cover the Silverlight or native side of the client object model. I will use the ECMAScript method to get the properties of the list item and use it to generate the mail message.

So let us take a look at the javascript code. Open the added js file and include function bodies for the methods defined in the Elements.xml file, like below snippet.
function EmailUniqueLink() {
}

// Delegate that is called when server operation is complete upon success.
function onQuerySucceeded(sender, args) {
}

// Delegate that is called when server operation is completed with errors.
function onQueryFailed(sender, args) {
}

// Method to enable/disable the e-mail unique button on the ribbon.
function EnableEmailUniqueLink() {
}

// This method will contain most of the code needed to request the unique url to the document

The first method contains most of the code. The onQuerySucceeded and onQueryFailed methods are delegates that are needed because the client object model in javascript works asynchronously. The last method is needed to specify when the button is enabled.

When using the client object model, we always have to obtain the client context first. The client context contains the proxies that are used to connect to the server. Second thing we need to know is that the client object model works with batches. This means that we load the object we want returned in the context and then execute it first. Include the following code snippet in the EmailUniqueLink method.


// First get the context and web
var ctx = SP.ClientContext.get_current();
this.web = ctx.get_web();// Get the current selected list, then load the list using the getById method of Web (SPWeb)
var listId = SP.ListOperation.Selection.getSelectedList();
var sdlist = this.web.get_lists().getById(listId);

In this code snippet, we first obtain the client context and load the web. Then we use the SP.ListOperation.Selection object to get the current list. This will return the GUID of the list, which we then use to get the list reference through the web object. Please mark though that the web and list objects are not populated yet. We again use the Sp.ListOperation.Selection object to get the current selected item in the list, which will return an object containing an id property. Using that id, we can request the list item from the list. To have the list item populated and thus get our unique document id, we need to load it in the context and then execute the query. As said, this can only be done asynchronously. This is done by calling executeQueryAsync and pass the success and fail methods respectively. Another thing to note here is that we use this when we need to access the variable outside the method. Include the following snippet in the onQuerySucceeded method.


// Request url by using the get_item method. It will return the Url field type, which has a Url property.
var url = this.listItem.get_item('_dlc_DocIdUrl').get_url();
// Request the name of the document.
var title = this.listItem.get_item('FileLeafRef');
// Open a new e-mail in the default mail program.
window.open('mailto:?subject=Emailing%3A%20'+ title + '&body=' + url);


Once the asynchronous call is done, we can access the properties of the listitem by calling the get_item() method passing the name of the field you wish to return, in this case the ‘_dlc_DocIdUrl‘ that contains the unique link. Because that returns a SPFieldUrl type like object, we call get_url() to get the actual url. To complete the mail message, we also request the name of the file and then call the mailto: to open the message in an e-mail client.
In this case, we requested the entire listitem object from the server. We can also specify which properties to return so we can limit bandwidth use and increase performance. The way to do this is by adding the desired properties as parameters in the load method of the context, in our case _dlc_DocIdUrl and FileLeafRef, like below snippet.


// Only request name and DocIdUrl

ctx.load(this.listItem, '_dlc_DocIdUrl', 'FileLeafRef');


I prefer to load the entire object during debugging, so I can inspect the entire object (using developer tools >IE7).

// Get the currently selected item of the list. This will return a dicustonary with an id field
var items = SP.ListOperation.Selection.getSelectedItems(ctx);
var mijnid = items[0];

// Request the list item from the server using the getItemById method. This will load all properties.
// If needed, one could pre-request the fields to be loaded to preserve bandwidth.
this.listItem = sdlist.getItemById(mijnid.id);
// load the item in the context for batch operation.
ctx.load(this.listItem);

//Execute the actual script on the server side. Specify delegates to handle the response.
ctx.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));

The failed body is not of much interest, although you could add code there to shown that it went wrong and why, as it gets the arguments containing the error passed. Add the following snippet to the failed method.


alert('failed ' + args.toString());


That is all that is needed to create the e-mail and send the unique url. Last method only enables the button when there is actually an item selected. Add the following snippet to the EnableEmailUniqueLink method:


// request number of selected items.
var items = SP.ListOperation.Selection.getSelectedItems();
var count = CountDictionary(items);
// only return true is a single item is selected.
return (count == 1);


Above is quite self-explanatory I guess. I get the number of selected items and only return true if the number of selected items equals 1. The entire javascript file should now resemble the following:


/*
====================================================================================================================
File: Boom.EmailUniqueLinkButton.js
Description: Contains supporting javascript functions to allow the unique link of a document to be sent by e-mail
Date:19-04-2010
Author: Patrick Boom
====================================================================================================================
*/

// This method will contain most of the code needed to request the unique url to the document
function EmailUniqueLink() {
   // First get the context and web
   var ctx = SP.ClientContext.get_current();
   this.web = ctx.get_web();
   // Get the current selected list, then load the list using the getById method of Web (SPWeb)
   var listId = SP.ListOperation.Selection.getSelectedList();
   var sdlist = this.web.get_lists().getById(listId);
   // Get the currently selected item of the list. This will return a dicustonary with an id field
   var items = SP.ListOperation.Selection.getSelectedItems(ctx);
   var mijnid = items[0];
   // Request the list item from the server using the getItemById method. This will load all properties.  
   // If needed, one could pre-request the fields to be loaded to preserve bandwidth.
   this.listItem = sdlist.getItemById(mijnid.id);
   // load the item in the context for batch operation.
   ctx.load(this.listItem);
   //Execute the actual script on the server side. Specify delegates to handle the response.
   ctx.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}

// Delegate that is called when server operation is complete upon success.
function onQuerySucceeded(sender, args) {
   // Request url by using the get_item method. It will return the Url field type, which has a Url property.
   var url = this.listItem.get_item('_dlc_DocIdUrl').get_url();

   // Request the name of the document.

   var title = this.listItem.get_item('FileLeafRef');

   // Open a new e-mail in the default mail program.
   window.open('mailto:?subject=Emailing%3A%20' + title + '&body=' + url);
}

// Delegate that is called when server operation is completed with errors.
function onQueryFailed(sender, args) {
   alert('failed ' + args.toString());
}

// Method to enable/disable the e-mail unique button on the ribbon.

function EnableEmailUniqueLink() {
   // request number of selected items.
   var items = SP.ListOperation.Selection.getSelectedItems();
   var count = CountDictionary(items);

   // only return true is a single item is selected.

   return (count == 1);
}

Only thing left to do is compile and deploy the code. If you followed all steps correctly you should see a button next to the E-mail a Link button and when you click it (and you have the Document ID feature enabled in the Site features), an e-mail should open with the unique link in the body, like below image:

That’s it, watch the fruit of all you hard labor. In this post, we covered quite some material, like customizing the ribbon and using the client object model to examine the selected list item. Hope you like it! You can download the entire VS solution from here. Leave a comment if you have a couple of minutes. Much appreciated!

See ya…

Posted in SharePoint 2010 | Tagged: , , , , , | 50 Comments »

The Unique Document ID feature

Posted by Patrick Boom on April 16, 2010

Enjoyed some time off due to the birth of my 3rd child Emma and finally found some time to get away from the diapers and constant nursering. 😉 And what a time to be back.

SharePoint Server 2010 is almost RTM, so an in-depth investigation of this new version of one the most important Microsoft products is long overdue.

In the following blog posts, I will review some of the new features of SharePoint Server 2010. Targeted far more at the Enterprise, SharePoint Server 2010 aims to remove some of the headaches that SharePoint 2007 had for both end users and developers, while maintaining its basic power, easy information sharing.

SharePoint Server 2010 introduces a lot of new features and functionalities. Some of them are improvements of existing functionality, some are completely new. Clear from a first glance though is that SharePoint Server 2010 has grown up and matured. Enhancements and new features make the new version far more scalable in large enterprises, one of the shortcomings of SharePoint 2007.

Unique Document ID feature

When using SharePoint as a document management solution, one of the shortcomings of SharePoint 2007 was the absence of a unique identifier for the document, regardless of its location. Sure, beneath the service, SharePoint 2007 did assign a GUID (Global Unique Identifier) to a document upon upload or creation, but there were no means to retrieve a document based on this GUID if you did not now the document library it was located in.

While SharePoint 2007 focused on libraries and lists, SharePoint 2010 moves more to sites (collections) and site pages. Several enhancements allow the site collection to share its information across the sites located in the collection.

The Unique Document ID feature can be enabled on site collection level, using site collection features. Once enabled, all documents placed within document libraries on the site collection will also receive a unique identifier field containing the ID of the document.

Based on search

The lookup of documents based on their ID is based on the Search functionality of SharePoint. First, the runtime will call the search provider to find the document. If search returns no documents, the provider calls a custom method for an alternative lookup. Because of the search dependency, when you move a document, it will not be found based on his ID unitl the next incremental crawl. Definitly something to take into account.

Configurable

When enabling the unique ID feature, we can also influence the way SharePoint assigns these unique identifiers. Unfortunately, these options are quite limited, as we can also specify text that each identifier should start with. However, because of the site collection scope, this problem is to overcome.

If you do not feel comfortable with the way SharePoint generates the ID’s, or you have specific rules or conventions to follow when generating these ID’s, it is good to know that you can create your own document ID provider. The way to do this is to create a subclass of the Microsoft.Office.DocumentManagement.DocumentIdProvider class and implement his methods. You can also specify that you want your own lookup method to be to called first, instead of the search. Using a site collection feature receiver, you can hook up this custom provider to the site collection by calling

DocumentId.SetProvider(properties.Feature.Parent as SPSite,new MyNewAndImprovedDocumentIdProvider());

Sending links

So, each document now haves a unique ID. Great, so how can we utilize that? When viewing the properties of a document now, you will find an additional field Document ID containing the unique ID of the document. This is also a link, pointing to the unique document ID service that can look up the document.

The link points to an aspx page located in the layouts folder called DocIdRedir.aspx. To recover a document based on the ID, just call http://yoursite/_layouts/DocIdRedir.aspx?ID=documentID
Interesting though is that when you press the Send a link button, the mail containing the link still contains the original url, pointing to the current location of the document. Obviously, when moving the document, the link would not be valid anymore.

The Ribbon

With SharePoint 2010, the ribbon is introduced in the online suite space. The ribbon and its contents will be subject for discussion in another blog post, but interesting now is that we can modify the ribbon items. We can therefore include a button that also send a link, but sends the permanent link, rather than the original one. I would have expected such a button to be there by default, but hey, we also need some work don’t we?

Till next time.

Posted in SharePoint 2010 | Tagged: , | 24 Comments »

 
%d bloggers like this: