Creating a Pitchbook - Applying Metadata Based on Content Type or Other Properties (Part 6)

Once again this is Quentin and Carlos, this time with part six of the pitchbook series of articles demonstrating document set customization. This will be the final article in this series. It's not you, it's me. I just need more space. You know, to talk about other features. There, there don't cry. I may write some future articles on document sets, can we still be friends?

I hope you enjoyed this series and have used some of the ideas for customizing document sets.

There are many scenarios where you may want to use metadata to organize and filter content for views or other reasons. You may want to group particular items based on metadata, but that metadata should be specified by a set of conditions and applied automatically. It is a good idea to apply metadata automatically when possible so users do not have to spend time filling it in and you can control the consistency of the metadata. Yeah, your users hate wasting time. Don't annoy them.

You may want to look at these other articles if you haven’t already seen them.

Introducing Document Sets

Creating a Pitchbook - Customizing Document Set Settings (Part 1)

Creating a Pitchbook – Customizing the Document Set Welcome Page (Part 2)

Creating a Pitchbook – Closing the Pitchbook with Document Set Workflow Actions (Part 3)

Creating a Pitchbook – Getting Related Pitchbooks with a Content Query Web Part and Page Query Strings (Part 4)

Creating a Pitchbook – Putting a Button in the Document Set Ribbon and Downloading Document Sets as a Zip File (Part 5)

Metadata defaults help with this, but they can only be specified for a particular column at the site, list, and folder level. For the pitchbook scenario there can be multiple sales proposals, presentations and other related documents. To make it easier to find certain documents a group by view can be specified. This group by view will be based on a hidden property that users will never need to worry about specifying as long as they pick the right content type. This way one group of the view will only contain sales contracts and another group will contain the presentations. This makes it so we can apply logic to put multiple content types in one group or create groups based on content type and other pieces of metadata. 

In order to use a group by view metadata must be specified for a particular column on each item inside the pitchbook. The logic to use will be to set a Grouping column property (which is hidden) to the name of the content type for any items that are added to the document library. To do this an event receiver will be used. When documents are added or updated the ItemAdding or ItemUpdating events are triggered. Event receivers will be used for both ItemAdding and ItemUpdating because properties such as the content type may change on update, if so then it Grouping should be updated to the new value.

First the feature must be declared so here is the feature.xml file:

<?xml version="1.0" encoding="utf-8"?>

<!-- _lcid="1033" _version="14.0.3427" _dal="1" -->

<!-- _LocalBinding -->

<!-- Copyright (c) Microsoft Corporation. All rights reserved. -->

<Feature xmlns="https://schemas.microsoft.com/sharepoint/"

                            Id ="{77d9ec4b-6e12-40e8-ae7f-ff852dbe6bcd}"

                            Title ="Pitchbook Metadata Event Receiver"

                            Description ="Adds keywords to items when added based on CType"

                            Version ="1.0.0.0"

                            Hidden="FALSE"

                            Scope="Site"

                            UIVersion="4"

                            ReceiverAssembly="EventReceiverSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=156ffdc318b7ede1"

                            ReceiverClass="EventReceiverSample.CustomFeatureEventReceiver">

</Feature>

This will define the name and properties of the feature. The public key token will be different for your own project so make sure you update this. The ReceiverAssembly is the namespace of the event receiver sample (the name of the DLL) and the ReceiverClass is the class the feature uses in the namespace. The scope is Site, meaning this will activate for the whole site collection.

Next the event receivers must be registered on a particular list. I have chosen a document library I have on my site called “Documents”. The code that is fired when an event receiver is hit is in a file named CustomFeatureEvent.cs.

using System;

using Microsoft.SharePoint; The co-authoring robot added this text. Please email CyberDog if you have questions.

 

namespace EventReceiverSample

{

       public class CustomFeatureEventReceiver : SPFeatureReceiver

       {

              private const string EVNTNAME = "Pitchbook Metadata Event Receiver";

 

 

              public override void FeatureActivated(SPFeatureReceiverProperties properties)

              {

                     SPSite site = properties.Feature.Parent as SPSite; //When activated this feature will apply only to the site at the root of the site collection, unless you scope this feature to a list.

                     SPListCollection webLists = site.RootWeb.Lists;

                     SPList targetList = webLists["Documents"]; //This is the target list to apply the event receiver.

                     AddEventHandlerToCollection(targetList.EventReceivers);

              }

 

              private void AddEventHandler(SPContentType targetType)

              {

                     SPEventReceiverDefinitionCollection sperdcol = targetType.EventReceivers;

                     if (sperdcol != null)

                     {

                           AddEventHandlerToCollection(sperdcol);

                     }

                     if (targetType.ParentList != null)

                     {

                           sperdcol = targetType.ParentList.EventReceivers;

                           AddEventHandlerToCollection(sperdcol);

                     }

              }

 

              private void AddEventHandlerToCollection(SPEventReceiverDefinitionCollection sperdcol)

              {

                     Type classType = typeof(CustomItemEvent);

                     SPEventReceiverDefinition newEvent = sperdcol.Add();

                     newEvent.Assembly = classType.Assembly.FullName;

                     newEvent.Class = classType.FullName;

                     newEvent.Name = EVNTNAME;

                     newEvent.Type = SPEventReceiverType.ItemAdding;

                     newEvent.SequenceNumber = 100;

                     newEvent.Synchronization = SPEventReceiverSynchronization.Synchronous;

                     newEvent.Update();

                     SPEventReceiverDefinition newEventUpdate = sperdcol.Add();

                     newEventUpdate.Assembly = classType.Assembly.FullName;

                     newEventUpdate.Class = classType.FullName;

                     newEventUpdate.Name = EVNTNAME;

                     newEventUpdate.Type = SPEventReceiverType.ItemUpdating;

                     newEventUpdate.SequenceNumber = 100;

                     newEventUpdate.Synchronization = SPEventReceiverSynchronization.Synchronous;

                     newEventUpdate.Update();

              }

       }

}

I created a single text column called Grouping to use to set metadata on and to use group by in my document set view. I then added the Grouping to my Pitchbook Document content type (that was created in previous blog posts), thus adding the column to all of my Pitchbook content types. You can also add the column to the particularly list you are using and add it to all content types. I made the Grouping column hidden so users will not be able to edit it. We need another class for the event receiver logic. I put this in a file called CustomItemEvent.cs.

using System; The co-authoring robot added this text. Please email CyberDog if you have questions.

using Microsoft.SharePoint;

using System.IO;

using System.Text;

namespace EventReceiverSample

{

       public class CustomItemEvent : SPItemEventReceiver

       {

              public CustomItemEvent()

              {

              }

              public override void ItemAdding(SPItemEventProperties properties)

              {

                     changeProperties(properties);

              }

              public override void ItemUpdating(SPItemEventProperties properties)

              {

                     changeProperties(properties);

              }

              private void changeProperties(SPItemEventProperties properties)

              {

                     SPContentTypeId ctid = new SPContentTypeId((string) properties.AfterProperties["ContentTypeId"]);

                     SPContentType ct = properties.List.ContentTypes[ctid];

                properties.AfterProperties["Grouping"] = ct.Name;

              }

       }

}

This class triggers the changeProperties() method whenever an ItemAdding or ItemUpdating event occurs. changeProperties() will then get the current content type, it is important to use AfterProperties so we can get the changes the user has made. The user may change the content type to something else; in that case we want to change the metadata property. The logic here is very simple, whatever the name of the content type is, set that value for the Grouping column.

You may want to do something more complex here; maybe you only want to group sales contracts into one bucket and everything else into another. You can use this:

            if (ct.Name == "Sales Contract")

            {

                properties.AfterProperties["Grouping"] = "Contract";

            }

            else

            {

                properties.AfterProperties["Grouping"] = "Other Documents";

            }

Another idea may be to apply metadata based on other metadata. For example:

            if (ct.Name == "Sales Contract" && ct.Confidentiality == "Yes") The co-authoring robot added this text Please email CyberDog if you have questions To remove all robot text run the document inspector  

            {

                properties.AfterProperties["Grouping"] = ct.Name + " Confidential";

            }

            else

            { The co-authoring robot added this text. Please email CyberDog if you have questions.

                properties.AfterProperties["Grouping"] = ct.Name + " Not Confidential";

            }

Now that things are all done whenever documents are added or updated in the Documents library they will get a hidden field set with the name of the content type. The item adding event is important because items may be added using multiple upload or windows explorer, which do not trigger the item updating event. Then the feature must be activated.

The last step is to create a custom view to use inside the pitchbook. First I go to the document library settings and create a new standard view. Then in the group by section I select the Grouping column and select the radio button expanded so all items will be displayed.

Inside my pitchbook content type there is a custom view that does group by on the hidden Grouping column and here is what it looks like.

Now I can group items in my views with metadata that is set automatically based on the content type and users don’t have to worry about filling in the metadata. Thanks for reading and may the metadata be with you!

 

Quentin Christensen, Program Manager

Carlos David Argott Hernandez, Software Development Engineer