Topic
I
have explained in my generic post about Wiki
Page customization within a SharePoint 2010 Team Site that the Wiki Page of
the Team Site are based on an unique template that cannot be customized.
However, there is a way of customizing a Wiki Page by provisioning a page in
a Wiki Library. I do not speak here to use a SharePoint 2010 Feature, but
provisioning a custom prepopulated Wiki Page as you can create a blank Wiki Page
by using the "New Page" menu item within the Site Actions menu or
the "New Wiki Page" menu item in the "New Document" menu item in a Wiki
Page Document Library.
This
process is already present in SharePoint 2010 when you create a new team site
and the product is provisioning the Home Page of the team site or when you
create an new Wiki Library and the product is provisioning the Welcome Page
that is pre populated with explanation about the Wiki Libraries.
As
a picture is worth than long explanations, here is that I propose to show in
this post:
Imgine a new Wiki Page Library where you could find a "Custom
Wiki Page" button in the "New" group of the SharePoint 2010
Ribbon.
Then,
when clicking on this button, a custom dialog, redesigned, would invite you to
create a custom Wiki Page.
When
having typed the title and submited the dialog, you would be redirected to your
new Custom Wiki page prepopulated with the typed title of the page, plus
your name and your photo.
Much
more! When editing the page, you would notice a new Text Layout, that is not
provided by the default SharePoint 2010 Text Layout menu items.
Of
course the page could be Saved properly...
To
be able to mount such a solution, several steps has to be performed:
- First you have to create a new Button within the SharePoint 2010 Ribbon.
- Second, you need to create the custom application page that will provision
the custom Wiki Page in this custom Document Library
So
let us do all this now... but before some little explanations about Content Types
for the Team Site Wiki Pages Library are to be provided
1
- You CANNOT add Content Types to a Team Site Wiki Library
Now
that you have seen what I wanted to do, you might be thinking "wait a minute,
why he has not created a new content type for his custom wiki page so as the
"New" menu of the Wiki Library will be automatically populated with an
item to create a custom page?".
I
tried, tried very much, but as I discovered a first limitation of the Team Site
Wiki Library that does not allow them to create pages based on several
templates, I have now discovered that you cannot add new content types to a Team
Site Wiki Library.
When
you try to add a content type to a Wiki Library you may be surprised because, by
default, a Wiki Library is not configured to support Content Types. It is normal
since the original goal is not to create other document than a native SharePoint
2010 Wiki Page for a Team Site. Here is an excerpt of the Microsoft
documentation on the topic (How to: Add a
Content Type to a List):
[...
Before
you can add a content type to a list definition, you must be sure that the list
is configured to support content types. The first thing to check is the template
from which the list instance is created. If the ListTemplate element has an attribute named DisallowContentTypes and the
attribute is set to TRUE,
then the list does not support content types. If the attribute is set to
FALSE or is missing, then
the list does support content types.
The
next thing to check is whether content types are enabled on the list instance.
The Schema.xml file that defines the list instance has a List element. This element must include an attribute
named EnableContentTypes,
and the attribute must be set to TRUE. Setting the attribute to
TRUE is equivalent to
selecting Yes under Allow management of content types? in the
Advanced
Settings section of List Settings.
..]
So
first, I tried to to created a new Custom Wiki Library with a new
type and changed the schema configuration, then created a new Wiki Page Content
Type and added it to the library, and it worked, this new library accepted a new
Wiki Page content type, and I had a new menu entry in my New Document menu :
Wiki Custom Page - Create a new wiki page.
But
just after that, when trying to create the page, it failed, and I had the
following error message : "You cannot create a Wiki Page in a Library that is
not a Wiki Library".
Ok,
then I tried to create a custom Wiki Page library with the same type (119),
even if it is idiot, since we cannot see the difference after and that
time, although I had configured and test several times the configuration, the
library just did not show the UI entries corresponding to the possibility
to add new Content Types.
So, the conclusion is the following:
You
cannot add new Content Types to the Team Site Wiki Page Libraries in
SharePoint 2010.
You cannot create Wiki Pages in a custom Team Site Wiki
Page Library.
Ok,
no trouble then, I will create my custom pages from a new button within the
Ribbon. ( I also tried many hours to add a new entry in the New Document menu
using a Custom Action feature, even tried to create first a new section in the
menu, but no way. If somebody finds it, let me know...)
2
- Creating a "New Document" custom button in the Ribbon
There
is a lot of documentation now on the Internet about how to create a custom
button in SharePoint 2010 so here is just the code and a few comments:
<?xml version="1.0"
encoding="utf-8"?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="afe52f83-a20e-42d4-8aa7-35d8c5fdfc50"
Title="Ribbon New
Button"
Scope="Web"
Description="Add a new menu button to a Wiki Page
Library to provision custom Wiki Pages"
>
<ElementManifests>
<ElementManifest Location="newButton.xml"/>
</ElementManifests>
</Feature>
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="Ribbon.Library.Actions.AddAButton"
RegistrationId="119"
RegistrationType="List"
Location="CommandUI.Ribbon"
Sequence="5"
Title="New Custom Wiki Page">
<CommandUIExtension>
<CommandUIDefinitions>
<CommandUIDefinition Location="Ribbon.Documents.New.Controls._children">
<Button
Id="Ribbon.Documents.New.NewCustomWikiPage"
Alt="Add a Custom Wiki
Page"
Sequence="5"
Command="CreateCustomWikiPage"
Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-48" Image16by16Left="-16"
Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="0" Image32by32Left="-64"
LabelText="Custom Wiki
Page"
Description="Create a new Custom Wiki
Page"
TemplateAlias="o1"
/>
</CommandUIDefinition>
</CommandUIDefinitions>
<CommandUIHandlers>
<CommandUIHandler
Command="CreateCustomWikiPage"
CommandAction="javascript:OpenCreateWebPageDialog('/_layouts/createCustomWebPage.aspx?List={ListId}');" />
</CommandUIHandlers>
</CommandUIExtension>
</CustomAction>
</Elements>
You
can notice that you have to specify the type of the library in the
RegistrationId, so don not mistake when creating a button for a library.
And
a fantastic thing is that JavaScript supports the old syntax for the parameter:
{ListId}
Do not forget the quotes in the javaScript method.
Now,
very important, how to know if the component installtion has worked or failed.
First, as
you maybe know, you can also add an item to the Ribbon by using a Web Part. So if you check the
html source code of the page, you will see this Web Part that shows the parameters of the native Sharepoint configuration for the Ribbon. You can check this parameters as an example if you want to create a custom control later within the Ribbon.
But if you want to check the installation of a custom control,
there will be also available javascript code to be checked, within the dataExtension, and
the Command UI Handler objects in json format. If you see them, it will be the
confirmation that the installation is successful, and you will also be able to
check that the parameters are correct, if something is going wrong:
3 -
Provisioning the Team Site Wiki Page
3.1
Implementing the custom Web part
So
as the goal of our tutorial was to prepopulate a Wiki Page with some text and a
Web Part that can retrieve the name and the picture of the author, let us first
implement the Web Part.
I
called the assembly MyCompany.Frameworks.SharePoint
I have created a folder
to add a WebParts part to the namespace
And the first Web Part of this
framework is called GetAuthorInfo
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
namespace MyCompany.Frameworks.SharePoint.WebParts
{
[Guid("5aa223b6-fd04-48c1-83e6-9531bf45ef19")]
public class GetAuthorInfo :
System.Web.UI.WebControls.WebParts.WebPart
{
protected override void
CreateChildControls()
{
base.CreateChildControls();
string HtmlOutput = string.Empty, pictureUrl ;
Label label = new Label();
SPWeb myWeb = SPContext.Current.Web;
SPList myUserInfo = null;
string author = SPContext.Current.Item["Created By"].ToString();
author = author.Split(new Char[] { '#'
})[1].ToLower();
HtmlOutput += "Author: " +
author;
myUserInfo = myWeb.Site.RootWeb.Lists["User
Information List"];
var results =
myUserInfo.Items.Cast<SPListItem>().Where(item => Equals(string.Compare(item["Name"].ToString(), author, true), 0));
if (results.Count() > 0)
{
pictureUrl = results.First<SPListItem>()["Picture"].ToString().Split(new Char[] {
',' })[0];
HtmlOutput += "<br/><br/><img
src='" + pictureUrl + "'
/>";
}
label.Text = HtmlOutput;
this.Controls.Add(label);
}
}
}
notice
that if you want the previous code works on SharePoint 2010 you have to
configure it for Linq and I have a post for a minimal configuration :
Use
Linq inside a Script Block of an Application Page - SharePoint minimal
configuration for Linq
Until
now except some minor differences with SharePoint 2007, we were in a known land,
it is now that it becomes interesting since we are going to discover the
SharePoint 2010 wiki pages content new syntax.
So
I will give soon the code of the custom SharePoint 2010 dialog and inside the in
line script code to provision the Wiki Page. But before, some indications:
3.2 -
to create a wiki page file and get the SPListItem corresponding to the
file:
SPListItem listItem =
files.Add(strUrl, SPTemplateFileType.WikiPage).GetListItem(new
string[] { "WikiField" });
3.3 -
to add some content to a wiki page programmatically:
listItem["WikiField"] =
content;
listItem.UpdateOverwriteVersion();
3.4 -
to add a Web part within this content:
Give
it a storageKey
add it to the Web part manager
then
use this syntax to place it within the Wiki page content, based on the storage
key
string str3 =
string.Format(CultureInfo.InvariantCulture,
"<div
class=\"ms-rtestate-read ms-rte-wpbox\" contentEditable=\"false\"><div
class=\"ms-rtestate-read {0}\" id=\"div_{0}\"></div><div
style='display:none' id=\"vid_{0}\"/></div>", new object[] {
storageKey.ToString("D")
});
3.5 -
Structure of the text layout:
This
is the html to store in WikiField field of the WikiPage SPListItem if you want
to obtain the same page than this tutorial.
Notice
the nested DIV with the ms-rte-layoutszone-outer and the ms-rte-layoutszone-inner classes. and the <span id="layoutsData" style="display: none">true,false,2</span>
that
is corresponding to the structure (I think the integer is the number of rows),
but I have not any clue about the boolean. I had to set the second to false
if not my page could not be saved! If somebody finds information about it,
let me know...
<table id="layoutsTable" style="width: 100%">
<tbody>
<tr style="vertical-align:
top">
<td style="width: 100%">
<div class="ms-rte-layoutszone-outer" style="width: 100%">
<div class="ms-rte-layoutszone-inner" style="min-height: 60px;
word-wrap: break-word">
<h1 class="ms-rteElement-H1B" style="margin-bottom: 0px;">
<span><span>Wiki 1
</span></span>
</h1>
<p>
 </p>
<p>
 </p>
<p>
 </p>
</div>
</div>
</td>
</tr>
<tr style="vertical-align:
top">
<td style="width: 100%">
<div class="ms-rte-layoutszone-outer" style="width: 100%">
<div class="ms-rte-layoutszone-inner" style="min-height: 60px;
word-wrap: break-word">
<p>
 </p>
<p>
</p>
<p>
 </p>
<p>
 </p>
<p>
 </p>
<p>
 </p>
<div class="ms-rtestate-read ms-rte-wpbox" contenteditable="false">
<div class="ms-rtestate-read
ae26f6c9-8cf4-438c-8a38-26cd309d0ec6" id="div_ae26f6c9-8cf4-438c-8a38-26cd309d0ec6">
</div>
<div style='display: none' id="vid_ae26f6c9-8cf4-438c-8a38-26cd309d0ec6" />
</div>
<p>
 </p>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<span id="layoutsData" style="display: none">true,false,2</span>
3.6 -
To close the dialog and be redirected to the provisioned page
window.frameElement.navigateParent(ProvisionedPageUrl);
4
- Code of the all dialog page (createCustomWebpage.aspx)
<%@ Page Language="C#" AutoEventWireup="true" Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase"
DynamicMasterPageFile="~masterurl/default.master" %>
<%@ Assembly Name="Microsoft.SharePoint,
Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="MyCompany.Frameworks.SharePoint, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=11b701f527101e0f" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"
Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="System.Text" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint.WebPartPages" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="MyCompany.Frameworks.SharePoint.WebParts" %>
<%@ Import Namespace="System.Web.UI.WebControls.WebParts" %>
<asp:content contentplaceholderid="PlaceHolderAdditionalPageHead" runat="server">
<SharePoint:UIVersionedContent ID="UIVersionedContent1" UIVersion="4" runat="server"><ContentTemplate>
<SharePoint:CssRegistration runat="server" Name="forms.css"
/>
</ContentTemplate></SharePoint:UIVersionedContent>
</asp:content>
<asp:content id="Main" contentplaceholderid="PlaceHolderMain" runat="server">
<script language="C#" runat="server">
SPList myList = null;
SPWeb myWeb = null;
protected override
void OnLoad(EventArgs e)
{
isProvisioningFinished.Value = "false";
myWeb = SPContext.Current.Web;
Guid listGuid = new Guid(Request.QueryString["List"]);
myList = myWeb.Lists[listGuid];
TargetLibrary.Text = "This page will be
created in this library: <br/><div
style='color:#0072bc;padding-top:3px'>" + myList.Title + "</div>";
NameDescLabel.Text = "New page name:
";
}
protected void
provision(object sender, EventArgs e)
{
SPFolder rootFolder =
myList.RootFolder;
SPWeb parentWeb =
myList.ParentWeb;
SPFileCollection files =
rootFolder.Files;
string pageName =
txtPageName.Text;
string strUrl =
rootFolder.ServerRelativeUrl + "/" +
pageName + ".aspx";
if
(!parentWeb.GetFile(strUrl).Exists)
{
SPListItem listItem =
files.Add(strUrl, SPTemplateFileType.WikiPage).GetListItem(new string[] { "WikiField" });
ProvisionWikiPageHomePage(listItem.File, pageName);
ProvisionedPageUrl.Value = strUrl;
isProvisioningFinished.Value = "true";
}
}
protected void
ProvisionWikiPageHomePage(SPFile wikiPage,
string pageName)
{
if (wikiPage == null)
{
throw new ArgumentNullException("wikiPage");
}
SPListItem listItem =
wikiPage.GetListItem(new string[] { "WikiField" });
if (listItem == null)
{
throw new ArgumentException(SPResource.GetString("OnlyInWikiLibraries", new object[0]));
}
SPList parentList =
listItem.ParentList;
SPWeb parentWeb =
parentList.ParentWeb;
string serverRelativeUrl =
parentWeb.ServerRelativeUrl;
if (serverRelativeUrl.EndsWith("/"))
{
serverRelativeUrl = serverRelativeUrl.Substring(0, serverRelativeUrl.Length - 1);
}
SPList list = null;
string str2 = null;
using (SPLimitedWebPartManager manager =
wikiPage.GetLimitedWebPartManager(PersonalizationScope.Shared))
{
str2 = RenderListViewWebPartForWikiPage(manager);
}
StringBuilder builder = new StringBuilder();
StringBuilder builder2 = new StringBuilder();
builder.Append("\r\n
<table id=\"layoutsTable\" style=\"width: 100%\">\r\n
<tbody>\r\n
<tr style=\"vertical-align:top\">");
builder.Append("\r\n
<td style=\"width: 100%\">\r\n
<div class=\"ms-rte-layoutszone-outer\"
style=\"width:100%\">\r\n
<div class=\"ms-rte-layoutszone-inner\"
style=\"min-height:60px;word-wrap:break-word\">\r\n<h1
class=\"ms-rteElement-H1B\"
style=\"margin-bottom:0px;\"><span><span>");
builder.Append(SPHttpUtility.HtmlEncode(pageName));
builder.Append("\r\n
</span></span></h1>\r\n ");
builder.Append("\r\n
<p> </p>\r\n
<p> </p>\r\n
<p> </p>\r\n
</div>\r\n
</div>\r\n
</td>");
builder.Append("\r\n
</tr>");
builder.Append("\r\n <tr
style=\"vertical-align:top\">");
builder2.Append("\r\n
<td style=\"width: 100%\">\r\n
<div class=\"ms-rte-layoutszone-outer\"
style=\"width:100%\">\r\n
<div class=\"ms-rte-layoutszone-inner\"
style=\"min-height:60px;word-wrap:break-word\">\r\n");
builder2.Append("
<p> </p>\r\n
<p>");
builder2.Append("\r\n
</p>\r\n
<p> </p>\r\n
<p> </p>\r\n
<p> </p>\r\n
<p> </p>");
if (!string.IsNullOrEmpty(str2))
{
builder2.Append(str2);
}
builder2.Append("<p> </p>\r\n
\r\n</div>\r\n
</div>\r\n
</td>");
builder.Append(builder2.ToString());
builder.Append("\r\n </tr>\r\n
</tbody>\r\n</table>\r\n
<span id=\"layoutsData\" style=\"display:
none\">true,false,2</span>");
string result = "";
result = builder.ToString();
//to see the generated code (user must have
the write permission on C drive...)
//System.IO.File.WriteAllText("c:\\test.html",result);
listItem["WikiField"] =
result;
listItem.UpdateOverwriteVersion();
}
protected string
RenderListViewWebPartForWikiPage(SPLimitedWebPartManager
lwpm)
{
if (lwpm == null)
{
throw new ArgumentNullException("lwpm");
}
System.Web.UI.WebControls.WebParts.WebPart webPart = new GetAuthorInfo();
webPart.Title = "Author";
webPart.ChromeType = PartChromeType.None;
Guid storageKey = Guid.NewGuid();
webPart.ID = StorageKeyToID(storageKey);
lwpm.AddWebPart(webPart, "wpz", 0);
string str =
storageKey.ToString();
StringBuilder builder = new StringBuilder();
string str3 = string.Format(CultureInfo.InvariantCulture, "<div class=\"ms-rtestate-read ms-rte-wpbox\"
contentEditable=\"false\"><div class=\"ms-rtestate-read {0}\"
id=\"div_{0}\"></div><div style='display:none'
id=\"vid_{0}\"/></div>", new
object[] { storageKey.ToString("D") });
return str3;
}
internal string
StorageKeyToID(Guid
storageKey)
{
if (!(Guid.Empty == storageKey))
{
return ("g_" + storageKey.ToString().Replace('-', '_'));
}
return string.Empty;
}
</script>
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<div style="width:350px; margin-right:auto;
margin-left:auto;">
<br />
<div style="height:3.75em;">
<asp:Label id="TargetLibrary" runat="server" />
</div>
<div>
<br />
<br />
<asp:Label style='padding-bottom:5px;display:block' id="NameDescLabel" runat="server" />
</div>
<div>
<asp:TextBox id="txtPageName" runat="server" />
</div>
<div style="height:3.75em;" class="ms-formvalidation">
<asp:Label id="NameErrorLabel" runat="server" />
</div>
<div>
<asp:LinkButton style="border:solid 1px
#0072bc;padding:2px" runat="server" Text="Provision the page " OnClick="provision"
/>
</div>
<input type='hidden' runat='server' id='isProvisioningFinished' />
<input type='hidden' runat='server' id='ProvisionedPageUrl' />
</div>
<script type="text/javascript">
var isProvisioningFinishedClientID =
'<%=isProvisioningFinished.ClientID%>';
var ProvisionedPageUrlClientID = '<%=ProvisionedPageUrl.ClientID%>';
var isFinished = "false";
var ProvisionedPageUrl = "";
isFinished =
document.getElementById(isProvisioningFinishedClientID).value;
ProvisionedPageUrl =
document.getElementById(ProvisionedPageUrlClientID).value;
if (isFinished == "true") {
isFinished.value = "false";
window.frameElement.navigateParent(ProvisionedPageUrl);
}
</script>
</asp:content>
<asp:content id="PageTitle" contentplaceholderid="PlaceHolderPageTitle" runat="server">
New Custom Wiki Page
</asp:content>
<asp:content id="PageTitleInTitleArea" contentplaceholderid="PlaceHolderPageTitleInTitleArea"
runat="server">
New Custom Wiki Page
</asp:content>