Well it's been a long time since my last post and I though I'd better get my arse into gear and share some of the stuff I've been working on. As part of some development work I was doing for a colored SharePoint Calendar I had the need to allow users to select colors to have different types of Events displayed in and couldn't find a decent way of accomplishing this with the built in components of SharePoint, so I decided that this was an ideal candidate for a custom field type. Having made what I think is a useful addition to SharePoint I thought I'd share it with you. First I'll give you a little flavour of what it looks like before we dive into the code and how it all hangs together
Here's how the field first looks in a New or Edit form
Here's some of the color choices available
And this is what it looks like once you've made some choices
So thats what the field looks like when your using it but how does it display you might be wondering, well here's how you'd see it in a list view
Yep lovely but not very practicle is it, the real power comes when you use this value either within code or the xslt of the DataView WebPart, then you can produce some quite stunning results.
custom xslt using the field values
Custom calendar views using the field values through code
So Now that you've seen what you can do with it let's get into the code itself. In this example we have 5 files
- SPColorsPickerFieldValue which inherits from SPFieldMultiColumnValue and holds the values and describes the properties for our field
- SPColorsPickerField which inherits from SPFieldMultiColumn and is our field
- SPColorsPickerFieldControl which inherits from BaseFieldControl and is the brains of the operation, basically the code behind for our ASCX
- ControlTemplates_SPColorsPicker.ascx which defines a SharePoint RenderingTemplate element that tells it how to render our field in New or Edit mode
- fldtypes_SPColorsPicker.xml which basically describes our field to SharePoint, telling it where it can be used, what it's called, what the assembly is called & also how it should be rendered in a list view
So how do we create a project for this custom field you ask. Well just create a Class Library project within VS2005 & add references for Microsoft.SharePoint and Microsoft.SharePoint.Security which you'll find in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI.
You also need to sign the assembly with a strong name key. Then you'll need to add classes for the field, field value & field control. Each of these classes needs a unique guid attribute. Here's the 3 classes you need
SPColorsPickerFieldValue (holds the values and describes the properties for our field)
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Runtime.InteropServices;
namespace SPColorsPicker
{
[
CLSCompliant(
false)]
[
Guid(
"7B5DF376-E7DB-4353-AD51-7C5B69C675D6")]
[
Serializable]
public class SPColorsPickerFieldValue : SPFieldMultiColumnValue
{
//how many properties are we storing
private const int numberOfFields = 2;
//required constructor
public SPColorsPickerFieldValue()
: base(numberOfFields)
{
}
//required constructor
public SPColorsPickerFieldValue(
string value)
: base(value)
{
}
//property for storing the font color to use
public string FontColor
{
get
{
if (
string.IsNullOrEmpty(
this[0]))
return "Black";
else
return this[0];
}
set { this[0] = value; } }
//property for storing the background color to use
public string BackgroundColor
{
get
{
if (
string.IsNullOrEmpty(
this[1]))
return "White";
else
return this[1];
}
set { this[1] = value; } }
}
}
. . .
SPColorsPickerField (our field)
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace SPColorsPicker
{
[
CLSCompliant(
false)]
[
Guid(
"01FB13ED-33F3-4caa-8CCF-42DAE083291F")]
public class SPColorsPickerField : SPFieldMultiColumn
{
//required constructor
public SPColorsPickerField(SPFieldCollection fields,
string fieldName)
: base(fields, fieldName)
{
}
//required constructor
public SPColorsPickerField(SPFieldCollection fields,
string typeName,
string displayName)
: base(fields, typeName, displayName)
{
}
//tell it which control to use for rendering this field
public override BaseFieldControl FieldRenderingControl
{
[SharePointPermission(SecurityAction.LinkDemand, ObjectModel =
true)]
get{
BaseFieldControl fieldControl = new SPColorsPickerFieldControl();
fieldControl.FieldName = this.InternalName;
return fieldControl;
}
}
//returns the value of this field
public override object GetFieldValue(
string value)
{
if (
String.IsNullOrEmpty(value))
return null;
return new SPColorsPickerFieldValue(value);
}
}
}
. . .
SPColorsPickerFieldControl (the brains)
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.WebControls;
using System.Web.UI.WebControls;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Web.UI;
using System.Drawing;
namespace SPColorsPicker
{
[
CLSCompliant(
false)]
[
Guid(
"F44DCD6A-D657-42e8-853A-ABC0713773C2")]
public class SPColorsPickerFieldControl : BaseFieldControl
{
protected DropDownList ddlBackground;
protected DropDownList ddlFont;
protected TextBox tbSampleText;
private SPColorsPickerFieldValue fieldValue;
protected
override string DefaultTemplateName
{
//Get the RenderingTemplate in the ascx file
get { return "ColorsPicker"; }
}
public
override object Value
{
get
{
EnsureChildControls();
if (
this.ControlMode != SPControlMode.Display)
{
GetValues();
}
return fieldValue;
}
set{
EnsureChildControls();
fieldValue = value as SPColorsPickerFieldValue;
}
}
private
void GetValues()
{
//set this instances value to whats been selected
fieldValue.FontColor = ddlFont.SelectedItem.Text;
fieldValue.BackgroundColor = ddlBackground.SelectedItem.Text;
}
private
void SetValues()
{
//set the controls values to whats currently stored in the field for an existing item
ddlFont.SelectedItem.Text = fieldValue.FontColor;
ddlBackground.SelectedItem.Text = fieldValue.BackgroundColor;
tbSampleText.ForeColor = Color.FromName(fieldValue.FontColor);
tbSampleText.BackColor = Color.FromName(fieldValue.BackgroundColor);
}
protected
override void OnInit(
EventArgs e)
{
//either get the value from an existing item or set it as a new value
if (ControlMode == SPControlMode.Edit || ControlMode == SPControlMode.Display)
{
if (
this.ListItemFieldValue !=
null)
fieldValue = this.ListItemFieldValue as SPColorsPickerFieldValue;
else
fieldValue = new SPColorsPickerFieldValue();
}
if (ControlMode == SPControlMode.New)
{
fieldValue = new SPColorsPickerFieldValue();
}
base.OnInit(e);
}
protected
override void OnLoad(
EventArgs e)
{
base.OnLoad(e);
if (!Page.IsPostBack && ControlMode != SPControlMode.Display)
{
//fill the DropDowns with the list of available colors
List<string> allColors = new List<string>(Enum.GetNames(typeof(System.Drawing.KnownColor)));
List<string> systemColors = new List<string>();
foreach (
MemberInfo member
in (
typeof(System.Drawing.SystemColors)).GetProperties())
{
systemColors.Add(member.Name);
}
foreach (
string color
in allColors)
{
if (!systemColors.Contains(color))
{
this.ddlBackground.Items.Add(color);
this.ddlFont.Items.Add(color);
} }
//register scripts to update the preview client side
ClientScriptManager cs = this.Page.ClientScript;
if (!cs.IsClientScriptBlockRegistered(
this.GetType(),
"ChangeColors"))
{
StringBuilder csText = new StringBuilder();
csText.Append(" <SCRIPT type=text/javascript>");
csText.Append("function changeTextColor(dropdown, textboxID)");
csText.Append("{");
csText.Append("var x = document.getElementById(textboxID);");
csText.Append("var myindex = dropdown.selectedIndex;");
csText.Append("var SelValue = dropdown.options[myindex].value;");
csText.Append("x.style.color = SelValue;");
csText.Append("}");
csText.Append("function changeBackgroundColor(dropdown, textboxID)");
csText.Append("{");
csText.Append("var x = document.getElementById(textboxID);");
csText.Append("var myindex = dropdown.selectedIndex;");
csText.Append("var SelValue = dropdown.options[myindex].value;");
csText.Append("x.style.backgroundColor = SelValue;");
csText.Append("}");
csText.Append("</SCRIPT> ");
cs.RegisterClientScriptBlock(this.GetType(), "ChangeColors", csText.ToString(), false);
}
SetValues();
}
}
protected
override void CreateChildControls()
{
if (Field ==
null)
return;
base.CreateChildControls();
if (ControlMode == SPControlMode.Display)
return;
//set the local vaiables
ddlFont = (DropDownList)TemplateContainer.FindControl("ddlFont");
ddlBackground = (DropDownList)TemplateContainer.FindControl("ddlBackground");
tbSampleText = (TextBox)TemplateContainer.FindControl("tbSampleText");
if (!Page.IsPostBack)
{
//add attributes to call the javascript functions when we choose different colors from the dropdown box's
//to update the preview client side
string textboxID = this.tbSampleText.ClientID;
ddlBackground.Attributes.Add("OnChange", string.Format("changeBackgroundColor(this,'{0}');", textboxID));
ddlFont.Attributes.Add("OnChange", string.Format("changeTextColor(this,'{0}');", textboxID));
}
}
//control how the field is rendered in display mode, doesn't overide list view display which is controlled
//by the DisplayPattern in the fldtypes xml file
protected override void RenderFieldForDisplay(HtmlTextWriter output)
{
StringBuilder sbldr =
new StringBuilder();
sbldr.Append(
"
Sample Text
");
output.Write(sbldr.ToString());
}
}
}
. . .
I've commented the code so you can see what's going on so i'm not going to go into more detail here.
Compile your class library & install it in the GAC Now you can create the ascx file you'll need
ControlTemplates_SPColorsPicker.ascx
<%
@ Control Language="C#" Debug="true" %>
<%
@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%
@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
Namespace="Microsoft.SharePoint.WebControls" %>
<SharePoint:RenderingTemplate ID="ColorsPicker" runat="server">
<
Template>
<
table>
<
tr>
<
td class="ms-formlabel">
Background Color</td>
<
td>
<asp:DropDownList ID="ddlBackground" Width="200px" runat="server">
</asp:DropDownList></td>
</tr>
<tr>
<
td class="ms-formlabel">
Text Color</td>
<
td>
<asp:DropDownList ID="ddlFont" Width="200px" runat="server">
</asp:DropDownList></td>
</tr> </table>
<br />
<asp:TextBox ID="tbSampleText" runat="server" Font-Bold="True" ReadOnly="True">SAMPLE TEXT
</asp:TextBox> </Template> </SharePoint:RenderingTemplate>
. . .
Then copy the ascx file to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES
Open your class library using Lutz Roeder's Reflector to get hold of the assembly name which you'll need in the next file you need to create
fldtypes_SPColorsPicker.xml
xml version="
1.0"
encoding="
utf-8"
?>
<FieldTypes>
<
FieldType>
<
Field Name="
TypeName"
>Colors
< FONT>Field>
<Field Name="ParentType">MultiColumn< FONT>Field>
<Field Name="TypeDisplayName">Colors Picker< FONT>Field>
<Field Name="TypeShortDescription">Pick Background and Font Colors< FONT>Field>
<Field Name="UserCreatable">TRUE< FONT>Field>
<Field Name="ShowInListCreate">TRUE< FONT>Field>
<Field Name="ShowInDocumentLibraryCreate">TRUE< FONT>Field>
<Field Name="ShowInColumnTemplateCreate">TRUE< FONT>Field>
<Field Name="FieldTypeClass">SPColorsPicker.SPColorsPickerField, SPColorsPicker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3a4554e68e310c34< FONT>Field>
<RenderPattern Name="DisplayPattern">
<
Switch>
<
Expr>
<Column/>
< FONT>Expr>
<Case Value="">
< FONT>Case>
<Default>
<
HTML>
0" HTMLEncode="TRUE"/>
<HTML>; background-color: ]]>< FONT>HTML>
<Column SubColumnNumber="1" HTMLEncode="TRUE"/>
<HTML>">Sample Text
]]>< FONT>HTML> < FONT>Default> < FONT>Switch> < FONT>RenderPattern> < FONT>FieldType> < FONT>FieldTypes>
. . .
Replace the value for FieldTypeClass with your assembly name then this file needs to be copied into C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML
Now all you need to do is either recycle the application pool or do an IISReset to have this new field type available to your SharePoint Lists
Well I've run out of time to show you how to use the value from within SharePoint designer (I'm sure blogging this took longer than actually creating the field) but I'll post a new article on that very soon.