Extreme New Document-button makeover

WE ARE ALL FAMILIAR WITH THE “NEW DOCUMENT” BUTTON IN SHAREPOINT. YOU PROBABLY ALSO KNOW THIS BUTTON HAS A DROPDOWN MENU ALLOWING YOU TO SELECT A TEMPLATE. IT’S GREAT FOR A SMALL NUMBER OF TEMPLATES, UP TO 10, BUT WHAT IF YOU HAVE MORE THAN A THOUSAND TEMPLATES?


We wanted to make a solution that supports a huge amount of templates and make them available in the document libraries. And in order to standardize as much as possible we wanted to force users to only create documents from those templates. Not unimportant, the result needs to be user-friendly and easy to manage. This is quite a challenge but the solution isn’t that difficult.

NewDocumentButtoninSharePoint_Macaw

Figure 1: Out of the box template selector menu

WHAT DOES SHAREPOINT PROVIDE OUT OF THE BOX?

First, let’s see what solution Microsoft has to provide. They have a guide “Add multiple Office templates to a document library” which can be found at http://office.microsoft.com/en-us/sharepoint-online-enterprise-help/add-multiple-office-templates-to-a-document-library-HA102409514.aspx.

What it says in short is:

  • Create a site content type with “Document” as the parent for each template.
  • After a content type is created, upload the template to the content type.
  • Enable management of content types on the list.
  • Add each of the template content types to the library.
  • Repeat steps three and four for each document library.



Implementing the steps described above and then managing the templates, like adding and removing, requires a lot of maintenance effort.

The following screenshot is the result of adding sixteen templates to a document library. As you can see it is not a very user-friendly solution, especially when keeping in mind that we have more than a thousand templates.

NewDocumentButton_Macaw_02

Figure 2: Many templates added to the out of the box template selector menu

You might say that a potential solution would be to split up the collection of templates in separate groups. E.g. HR templates for a HR library and marketing templates for a marketing library. But:

  • It would still be a very long cluttered list for most document libraries.
  • It would be even more difficult to manage, because templates are scattered across multiple document libraries and SharePoint has no clear overview available.
  • All of the templates would still be needed to be added by hand, which is a lot of repetitive work.



The last point could be scripted, but does not remove the large amount of required maintenance for the number of templates.

We also wanted to force the user to select a template. For that we would have to remove or disable the “New Document” button but keep the dropdown, which isn’t possible out of the box.

Based on the findings above, we can conclude that this solution does not meet our requirements, because it’s difficult to maintain and not user-friendly. Meaning we would have to build a custom solution.

SO, WHAT’S THE BETTER SOLUTION?
We shall go ahead and skip the part where we do all the technical and architectural decision-making and just show you the steps to create a successful solution.

This is what we need:

  • A document library to store the templates
  • A user-friendly interface to easily select the desired template from a big collection of templates
  • Replace the “New Document” button with a button that shows the user interface described above

NewDocumentButton_Macaw_03

Figure 3: The solution where we are working to

TEMPLATES DOCUMENT LIBRARY
To centralize the storage for all templates, one document library is created, with the templates organized in folders and subfolders. You can, of course, also categorize it with taxonomy instead of folders.

We made a document library called “Templates” and, for testing purposes, uploaded some of the default Office templates.

NewDocumentButton_Macaw_04

Figure 4: Our templates document library with the default Office templates



The default Office templates can be found at this location: “C:\Program Files (x86)\Microsoft Office\Templates\1033\”.



You can manage multiple files and folders in SharePoint using the Windows Explorer functionality. Go to\\webapplicationurl\DavWWWRootin your Explorer window.
If your site is setup with SSL you will have to add @SSL\\webapplicationurl@SSL\DavWWWRoot



SELECT A TEMPLATE USER INTERFACE

Before developing, make sure you’ve installed the rel="noopener noreferrer" CKS: Development Tools(http://cksdev.codeplex.com). We are going to use some of its features later on.

To start, create an empty SharePoint Project in Visual Studio and choose to deploy as a Farm Solution, as we will use the layouts folder located in the SharePoint Root (14-hive). Create a new Application Page and call it “TemplateSelector.aspx”. This page will be our templates display page, allowing the user to find and open a template. The page has four content placeholders though we will only use PageHead and Main.

The PageHead will be the placeholder where the scripts are placed. We have a piece of JavaScript that uses a JavaScript command calledcreateNewDocumentWithProgID that comes with SharePoint. This command enabled you to open a template with the corresponding Office Application and, more importantly, set a default save location for the document.

You could use the script like this:

CreateNewDocumentWithProgID(
'http://sharepoint/Templates/template.dotx',
'http://sharepoint/Shared Documents/',
'SharePoint.OpenDocuments',
false);

Listing 1: Open template with default SharePoint JavaScript command



This will open Word with the template from http://SharePoint/Templates/template.dotx and store the document in the Document Library at http://SharePoint/Shared Documents/ upon saving.

The code that goes into the PageHead placholder looks like this:



<script language='javascript' type="text/javascript">
// Open template with the corresponding Office app
function OpenNewTemplate(strTemplate, strSaveLocation) {
var strProgID = "SharePoint.OpenDocuments";
createNewDocumentWithProgID(makeAbsUrl(strTemplate),
makeAbsUrl(strSaveLocation), strProgID, false);
window.frameElement.commitPopup();
}
</script>



Listing 2: Open template script

This code is pretty straightforward, except for the window.frameElement.commitPopup which will be explained later in this article.

We also need a bit of code to show the templates, this code will be placed in the Main content placeholder. The content will be divided into two panels. On the left side, a panel that will show the library’s folders in a TreeView and on the right side a ListView that shows the content of the selected folder.

This code goes into the Main placeholder:

<div>
<div id="Navigation" style="position: absolute;
top: 0; left: 0; width: 30em">
<asp:TreeView ID="tvFolders" runat="server">
</asp:TreeView>
</div>
<div id="Content" style="margin-left: 30em;">
<asp:ListView ID="lvTemplates" runat="server"
GroupItemCount="5" DataKeyNames="Title">
<LayoutTemplate>
<table cellpadding="5" runat="server" id="tblTemplates">
<tr id="groupplaceholder" runat="server"></tr>
</table>
</LayoutTemplate>
<GroupTemplate>
<tr id="itemPlaceholderContainer" runat="server"
style="height: 80px;">
<td id="itemPlaceholder" runat="server">
</td>
</tr>
</GroupTemplate>
<ItemTemplate>
<td valign="top" align="center"
style="width: 100px;" runat="server">
<asp:HyperLink ID="TemplateLink" runat="server"
NavigateUrl='<%# Eval("Url") %>' Target="">
<!-- Show template icon... –>
<asp:Image ID="IconImage" runat="server"
ImageUrl='<%# Eval("ImageUrl") %>' />
<br />
<!-- ...with title underneath –>
<%# Eval("Title") %>
</asp:HyperLink>
</td>
</ItemTemplate>
</asp:ListView>
</div>
</div>

Listing 3: Syntax that shows the folder TreeView and the templates ListView



The data sources of the TreeView and ListView will be provided in the code-behind. The DataTable for the ListView will be defined as a class variable.

// datasource for the Template ListView
private readonly DataTable _dataTable = new DataTable();

Listing 4: DataTable for the listview as class variable




In the Page_Load, we execute some code which fills the TreeView and ListView.



if (!IsPostBack)
{
// Instantiate the folder tree if first time on page
SetTemplateLibraryFolders();
}
// Add columns to the datatable
_dataTable.Columns.Add("ImageUrl", typeof(string));
_dataTable.Columns.Add("Title", typeof(string));
_dataTable.Columns.Add("Url", typeof(string));
// if there's no node selected, select the first node,which is the root.
var selectedNode = tvFolders.SelectedNode == null ?tvFolders.Nodes[0].Value :
tvFolders.SelectedNode.Value;

GetTemplatesInSelectedFolder(selectedNode);

// set the templates as the datasource for the template list view
lvTemplates.DataSource = _dataTable;



Listing 5: Code of the Page_Load



The methods to retrieve the templates from a specific folder are shown in listing 6:

/// <summary>
/// Open selected folder of the templates library and fill
/// the data table with the templates in the folder
/// </summary>
/// <param name="folderUrl">Target folder</param>
protected void GetTemplatesInSelectedFolder(string folderUrl){
using (SPSite site = new SPSite(http://mysharepoint))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists.TryGetList("Templates");
if (list != null)
{
SPDocumentLibrary docLib = (SPDocumentLibrary)list;
SPFolder folder = web.GetFolder(folderUrl);
AddFilesToDataTable(web, folder);
}
}
}
}
/// <summary>
/// Add templates to the data source
/// </summary>
/// <param name="docWeb"></param>
/// <param name="folder"></param>
private void AddFilesToDataTable(SPWeb docWeb, SPFolder
folder)
{
foreach (SPFile file in folder.Files)
{
// get template icon
string docIcon = SPUtility.ConcatUrls(
"/_layouts/images/",
SPUtility.MapToIcon(
file.Web,
SPUtility.ConcatUrls(file.Web.Url, file.Url),
"",
IconSize.Size32)
);

// absolute url of the template
string absUrl =
(string)file.Item[SPBuiltInFieldId.EncodedAbsUrl];
// click action
string action;
if (!String.IsNullOrEmpty(
HttpContext.Current.Request.QueryString["DocLib"]
))
{
// get the list from the List query string
Guid? docLibId = new Guid(
HttpContext.Current.Request.QueryString["DocLib"]
);

SPList selectedLibrary = docWeb.Lists.GetList(
docLibId.Value,
true
);

string docLibUrl = selectedLibrary.DefaultViewUrl;
// remove Forms[%] from the view url to get the
// correct document library url
docLibUrl = docLibUrl.Remove(
docLibUrl.LastIndexOf(
"Forms",
StringComparison.Ordinal
)
);
action = string.Format(
"javascript:OpenNewTemplate('{0}', '{1}');",
absUrl,
SPContext.Current.Site.RootWeb.Url + docLibUrl
);
}

AddDataToDataTable(docIcon, file.Name, action);
}
}

/// <summary>
/// Add template to the data source
/// </summary>
/// <param name="docIcon">Template icon</param>
/// <param name="title">Template title</param>
/// <param name="url">Template url</param>
private void AddDataToDataTable(string docIcon,
string title,
string url)
{
DataRow row = _dataTable.NewRow();
row["ImageUrl"] = docIcon;
row["Title"] = title;
row["Url"] = url;
_dataTable.Rows.Add(row);
}

Listing 6: Helper methods to retrieve the templates




You can now test the application page. Our page is displayed in figure 5.
Paarhuis_NewDocumentButton_05

Figure 5: The application page should look something like this

We can now move on to the part where we explain how to access this application page from the document libraries.

SELECT TEMPLATE RIBBON BUTTON
We are actually doing two things here. First, we hide the default “New Document” button and secondly, we show a custom button. For this we use theCustomActions component of the CKS: Development Tools.

  • Go to your Visual Studio Project and add a SharePoint 2010 item called Hide Custom Action and name it “HideNewDocumentButton”.
  • A wizard will now appear, but you can skip this by clicking Finish.
  • We are now looking at the elements.xml file. In here you have to remove<HideCustomAction /> and add the code from listing 7:

<CustomAction

Id="RemoveNewDocumentRibbonButton"

Location="CommandUI.Ribbon"

RegistrationType="List"

RegistrationId="101">

<CommandUIExtension>

<CommandUIDefinitions>

<CommandUIDefinition

Location="Ribbon.Documents.New.NewDocument" />

</CommandUIDefinitions>

</CommandUIExtension>

</CustomAction>

Listing 7: The code of the elements.xml of the HideNewDocumentButton




The reason why we remove the HideCustomAction tag and add a normalCustomAction is because the HideCustomAction can’t hide Ribbon buttons, which is a bit self-contradictory. However, there is a way to hide buttons with a CustomAction. You can create a CustomAction using the same properties and location of the button you wish to hide, but keep the CommandUIDefinitionempty. This way, a new empty button will override the existing button, and empty buttons are not visible in SharePoint.

We want our button to only available for document libraries. This is specified by the RegistrationType and RegistrationId which, for a document library, is “List” and “101” respectively.

We now need to add our own button. This button will open the created application page in a popup. This is also why in the JavaScript on the application page is a line “window.frameElement.commitPopup()”. This closes the popup when a template has been selected.

To achieve this we create a Custom Action and call it “TemplateSelectorButton”. In the wizard you should generate a Guid and name it “New Document”. We can skip the rest by clicking next, next and finish.

This CustomAction consists of two parts: one for the visual appearance of the button and one for the button action.



<CustomAction

Id="Microsoft.SharePoint.StandardMenu.NewMenu.TemplateSelector"

Title="New document"

RegistrationType="List"

RegistrationId="101"

Location="CommandUI.Ribbon">

<CommandUIExtension>



<CommandUIDefinitions>

<CommandUIDefinition

Location="Ribbon.Documents.New.Controls._children">

<Button

Id="Ribbon.Documents.New.TemplateSelectorButton"

Sequence="0"

Command="TemplateSelector_Button"

Image32by32="/_layouts/1033/images/formatmap32x32.png"

Image32by32Top="-0" Image32by32Left="-64"

LabelText="New document"

TemplateAlias="o1"

/>

</CommandUIDefinition>

</CommandUIDefinitions>



<CommandUIHandlers>

<CommandUIHandler

Command="TemplateSelector_Button"
CommandAction="javascript:OpenPopUpPageWithTitle('{SiteUrl}
/_layouts/TemplateSelectorDemo/SelectTemplateDemo.aspx?&amp;
DocLib={ListId}', RefreshOnDialogClose, 1280, 720,
'Template overview)"/>

</CommandUIHandlers>

</CommandUIExtension>

</CustomAction>



Listing 8: The code of the elements.xml of the TemplateSelectorButton


You can see that the Location of the CommandUIDefinition is placing our button inside the New group of the Documents ribbon. We are using the same image as the default “New Document” button. The “TemplateAlias=“o1”” is ensuring that the bigger Image32by32 will be used. In the handler you can see that JavaScript uses the OpenPopUpPageWithTitle command from the SharePoint library to open our application page using the DocLib parameter.



This MSDN article contains all the default SharePoint Ribbon locations:
http://msdn.microsoft.com/en-us/library/ee537543.aspx.



When you deploy these actions with a feature, you can see that the “New Document” button is replaced by a button that looks very similar, but when you click it, you get a popup in which you can select a template.

NewDocumentButton_Macaw_06

Figure 6: The result when clicking the new “New Document” button



BONUS: WORD ADD-IN
An advantage of having the solution in an application page is that you can reach it from anywhere. In our solution we opened the application page as a popup in a document library, but it is also possible to open it in Microsoft Word. In the following elaboration you will see it is fairly easy to make this possible.

In your Visual Studio add a new Word 2010 Add-In project to your solution. Now add an Ribbon (Visual Designer) item to this project. In the ribbon designer you can see in figure 7, you can add a new button from the toolbox.

Paarhuis_NewDocumentButton_07

Figure 7: Ribbon designer in Visual Studio


Double-click this button to create and go to the click handler. In the handler add the following code that opens a browser window showing our application page.IsDlg=true means that we want a plain site, without the SharePoint header, footer and menu.



Process.Start("http://SharePoint/_layouts/TemplateSelectorDemo/
SelectTemplateDemo.aspx?IsDlg=true");


Listing 9: Code to open a window where users can select a template

There is one slight problem now. The application page expects a document library ID to be passed as a parameter in the query string. Unfortunately we aren’t working from a document library. To make this work we will have to add a functionality to open a template, even when no document library ID is available. We do this in the application page by adding an else-statement to the following if-statement which can be found in Listing 6. Listing 10 is displaying the if statement, the else statement can be found in listing 11.

if (!String.IsNullOrEmpty(
HttpContext.Current.Request.QueryString["DocLib"]
))

Listing 10: The if-statement to add an else-statement to

else
{
// if query string DocLib is empty use the default web url
action =
string.Format(
"javascript:OpenNewTemplateThenClose('{0}', '{1}');",
absUrl,
SPContext.Current.Web.Url
);
}



Listing 11: An else-statement so that templates can be opened when no document library ID is present

As you can see, we pass the default SharePoint web URL. This means that the document gets saved in the default Shared Documents of the site.

You also might notice that we are using a different JavaScript command. This is because we would like to close the Internet Explorer window as soon as the template is selected. The JavaScript of this command is displayed in listing 12.



function OpenNewTemplateThenClose(strTemplate, strSaveLocation)
{
var strProgID = "SharePoint.OpenDocuments";
createNewDocumentWithProgID(
makeAbsUrl(strTemplate),
makeAbsUrl(strSaveLocation),
strProgID,
false
);

//this is a trick to close the Internet Explorer window or tab
window.history.go(-1);
top.window.opener = top;
top.window.open('', '_parent', '');
top.window.close();
}



Listing 12: JavaScript that opens a template and then closes the window or tab


When you have deployed the SharePoint solution and run the Word Add-In you can now open a template from Word.

Paarhuis_NewDocumentButton_08

Figure 8: The result of the Add-In in Microsoft Word


CONCLUSION
SharePoint is a great development platform, many component are already available, ready to be used in your project. With a few lines of XML we can build our own ribbon components. The SharePoint JavaScript library supports popups and Office integration, with just a few lines of code.

We quickly came to the conclusion that there was no good solution possible with out of the box SharePoint functionality. The solution that we ended up creating is relatively simple, easy to implement and easy to manage. It uses default SharePoint building blocks to create new functionality that meets all of the requirements.

This article is also published in de Diwug's SharePoint eMagine #7 from the Dutch Information Worker User Group (DIWUG).