When you add a new site
column into the site columns gallery in a site collection, or you add a
new column into a list, you base that column based on a bunch of
preexisting field definitions. For example, you could choose to create
the new column as a single line of text, multiple lines of text, be
driven by Metadata, or even be driven by external data. When making that
choice, SharePoint prompts you with a user interface with many radio
buttons. This is shown in Figure 1.
Those various choices in those
radio buttons are referred to as field types. You can write your own
field type which is referred to as a custom field type.
The custom field type I intend
to write will present the end user with a google auto suggest like
experience. The values will be driven from a list, and to keep things
simple you're going to create a list based on the Custom List
definition. The name of the list would be "Songs, " and you will put
some of my favorite songs in this list. When the user begins to type
"Aer" in this textbox, the system will prompt the user with Aerosmith's
"Dude looks like a lady" or "Crazy," and Jefferson Aeroplane's "Need
somebody to love." Obviously, I have prepopulated these and other such
songs in the "Song" list beforehand. Typing the first three characters
in this textbox executes a CAML query on the "Songs" list, and these
results are somehow shown as a list on the client side.
To achieve this, you will have
to write a custom field type. In order to write this custom field type,
you could take many approaches. You could use the ClientObject model and
jQuery or you could write the JavaScript yourself.
Start by creating a new empty
SharePoint project in Visual Studio 2010, call it "SongTypeField". In
this project, add a reference to the AjaxControlToolKit.dll that you
just downloaded from codeplex. Writing a custom field type requires you
to deploy a file at SharePointRoot\Template\XML. This file must have a
filename that starts with fldTypes_*.xml. Whenever SharePoint starts, or
whenever the IIS application pool starts or restarts, all these xml
files in this directory are read, and they contain the field type
definitions for all available fields in SharePoint. In your project, add
a mapped folder for TEMPLATE\XML and inside that folder drop in a file
called fldTypes_AutoComplete.xml. The contents of this file can be seen
in Listing 1.
Example 1. Contents of fldTypes_AutoComplete.xml
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
<FieldType>
<Field Name="TypeName">AutoCompleteField</Field>
<Field Name="ParentType">Text</Field>
<Field Name="TypeDisplayName">Auto Complete Song</Field>
<Field Name="TypeShortDescription">Auto Complete Song</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="ShowInListCreate">TRUE</Field>
<Field Name="ShowInSurveyCreate">TRUE</Field>
<Field Name="ShowInDocumentLibraryCreate">TRUE</Field>
<Field Name="ShowInColumnTemplateCreate">TRUE</Field>
<Field Name="FieldTypeClass">
SongTypeField.AutoCompleteField, SongTypeField, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c1944463f56d857a
</Field>
</FieldType>
</FieldTypes>
|
You can view the full possible schema for this file on MSDN. In Listing 6-1,
I had to find some basic attributes of the new custom field type I
intended to code. Whenever I am authoring a custom field type, I have to
specify a FieldTypeClass. This attribute tells SharePoint with the
actual implementation of my custom field type is. The implementation for
AutoCompleteField can be seen in Listing 2.
Example 2. AutoCompleteField Implementation
public class AutoCompleteField : SPFieldText
{
public AutoCompleteField(
SPFieldCollection fields, string fieldName) :
base(fields, fieldName) { }
public AutoCompleteField(
SPFieldCollection fields,
string typeName, string displayName) :
base(fields, typeName, displayName) { }
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl fieldControl = new AutoCompleteFieldControl();
fieldControl.FieldName = InternalName;
return fieldControl;
}
}
}
|
As you can
see, I'm overriding the FieldRenderingControl property and I am
returning an instance of the AutoCompleteFieldControl class. The
AutoCompleteField control class is where the actual implementation of my
field type goes. This can be a pure server control or it can be
something that loads a user control. The AutoCompleteFieldControl class
will directly or indirectly inherit from the BaseFieldControl base
class.
In this particular
implementation, since you intend to use the Ajax toolkit, it is a lot
easier to write this code as a user control rather than a server
control. Therefore, the implementation of the AutoCompleteFieldControl
can be seen in Listing 3.
Example 3. Implementation for the AutoCompleteFieldControl
public class AutoCompleteFieldControl : BaseFieldControl
{
protected TextBox autoCompleteField;
public override object Value
{
get
{
EnsureChildControls();
return autoCompleteField.Text;
}
set
{
EnsureChildControls();
autoCompleteField.Text = this.ItemFieldValue as String;
}
}
protected override void CreateChildControls()
{
if (this.Field == null || this.ControlMode == SPControlMode.Display)
return;
base.CreateChildControls();
autoCompleteField = TemplateContainer.FindControl("autoCompleteField") as TextBox;
}
protected override string DefaultTemplateName
{
get
{
return "AutoCompleteFieldTemplate";
}
}
}
|
In Listing 3,
there are a number of interesting things. Let's start from the bottom.
There is a property DefaultTemplateName. This should return the ID of
the SharePoint:RenderingTemplate control that contains the actual
implementation of the user interface of my custom feed control. This
SharePoint:RenderingTemplate is in any of the ASCX files in the
SharePointRoot\ControlTemplates virtual directory.
Therefore, add the
ControlTemplates SharePoint mapped folder in your project and add an
AutoCompleteField.ascx file in this folder. When adding this as ASCX,
there are two important things to watch out for. First, that this ASCX
cannot have a code behind, and second, this ASCX needs to live in the
ControlTemplates directory not in a sub directory under
ControlTemplates. This new ASCX and the code in Listing 6-3 together provide the actual implementation of the custom field type. The code for the ASCX can be seen in Listing 4.
Example 4. AutoCompleteField.ascx Implementation
<SharePoint:RenderingTemplate ID="AutoCompleteFieldTemplate" runat="server">
<Template>
<asp:TextBox runat="server" ID="autoCompleteField" Width="100%" autocomplete="off" />
<ajaxToolkit:AutoCompleteExtender
runat="server"
BehaviorID="AutoCompleteEx"
ID="autoComplete1"
TargetControlID="autoCompleteField"
ServicePath="/_vti_bin/SongTypeField/AjaxToolkitMusic.svc"
ServiceMethod="GetTopSongs"
MinimumPrefixLength="1"
CompletionInterval="1000"
EnableCaching="true"
CompletionSetCount="10"
ShowOnlyCurrentWordInCompletionListItem="true"
FirstRowSelected="true">
</ajaxToolkit:AutoCompleteExtender>
</Template>
</SharePoint:RenderingTemplate>
|
Do note that I omitted the assembly, register and import tags from Listing 4
for brevity. You can find those in the associated code download. Now to
understand how the custom field definition works, compare Listing 4 and Listing 3 in tandem. Note that Listing 4 contains a textbox called autoCompleteField. You will find a protected textbox by the name of autoCompleteField in Listing 3
as well. Also, in the CreateChildControls method of the
AutoCompleteFieldControl, you will see the value of this textbox being
set as shown in the following:
autoCompleteField =
TemplateContainer.FindControl("autoCompleteField") as TextBox;
Once this value has been set, in Listing 3
with in the AutoCompleteFieldControl, override the "Value" property to
set and get the value of this autoCompleteField, which is also the value
for this custom field type.
Now focus your attention on the rest of Listing 4.
In Listing 4, you also see an entry for the AutoCompleteExtender.
This is a standard control in the Ajax toolkit, which fetches various
values as a string array from a WCF service, and shows them in a google
suggest/autocomplete like behavior under the target textbox. As you can
see from the various properties on the AutoCompleteExtender in Listing 4,
you are querying a service located at
/_vti_bin/SongTypeField/AjaxToolkitMusic.svc, and you are querying a
method called GetTopSongs.
Therefore, you need to implement a
WCF service in your project as well, which will be located at
/_vti_bin/SongTypeField/AjaxToolkitMusic.svc and will have a method
called GetTopSongs, which will return a string array. Add the new class
in your project and call it AjaxToolkitSongQuery.cs. The code for the
WCF service implemented in AjaxToolkitSongQuery.cs can be seen in Listing 5.
Example 5. AjaxToolkitSongQuery WCF Service
[ServiceContract(Namespace = "winsmarts")]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
public class AjaxToolkitSongQuery
{
[OperationContract]
public string[] GetTopSongs(string prefixText, int count)
{
List<string> songs = new List<string>();
XmlDocument camlDocument = new XmlDocument();
camlDocument.LoadXml(
@"<Where>
<Contains>
<FieldRef Name='Title' />
<Value Type='Text'>[prefixText]</Value>
</Contains>
</Where>".Replace("[prefixText]", prefixText));
SPWeb web = SPContext.Current.Site.RootWeb;
SPQuery query = new SPQuery();
query.Query = camlDocument.InnerXml;
SPListItemCollection items = web.Lists["Songs"].GetItems(query);
IEnumerable<string> sortedItems =
from item in items.OfType<SPListItem>()
orderby item.Title
select item.Title;
songs.AddRange(sortedItems);
return songs.ToArray();
}
}
|
As you can see from Listing 6-5,
the WCF Service expects to run under the asp.net compatibility mode. It
exposes a method called GetTopSongs, which returns a string array of
all matched top songs. Internally, it executes a simple CAML query to
fetch the actual objects. It then uses a linq query to create a
projection of the data that you are interested in and then it then
returns that as a string array.
Since this class is a part of
your SharePoint project, and thus a part of the associated DLL, it will
get deployed to the GAC. Now all you need to create is an endpoint so
this service is accessible.
Create an endpoint for the WCF Service by adding a SharePoint
map folder at ISAPI, and under the ISAPI\SongTypeField folder add two
files.
First, the endpoint, call it AjaxToolkitMusic.svc (note that this name matches what you specified in Listing 4). The contents for this file are as follows:
<%@ Assembly Name="SongTypeField, Version=1.0.0.0,
Culture=neutral,PublicKeyToken=c1944463f56d857a"%>
<%@ ServiceHost Service="SongTypeField.AjaxToolkitSongQuery" %>
Obviously, the public key token you generate might be different.
Secondly, the web.config file which provides all the configuration information for this endpoint can be seen in Listing 6.
Example 6. Configuration Information for the AjaxToolkitMusic.svc Service Endpoint
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="customWebHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="SongTypeField.AjaxToolkitSongQuery">
<endpoint address=""
behaviorConfiguration="SongQueryBehaviorAjaxToolkit"
bindingConfiguration="customWebHttpBinding"
binding="webHttpBinding"
contract="SongTypeField.AjaxToolkitSongQuery"/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="SongQueryBehaviorAjaxToolkit">
<enableWebScript/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
|
As you can see from Listing 6-6,
you're choosing to expose the service over webHttpBinding and are
requiring the NTLM credential to be sent before the service can be run.
Also, you have required aspNetCompatibilityEnabled to true.
There's one last thing that
needs to be done. The Ajaxcontroltoolkit.dll that you add it towards the
beginning of the project also needs to be deployed to the GAC. Double-click the package designer,
click the advanced tab, and choose to "Add an Existing assembly".
Deploy the Ajaxcontroltoolkit.dll assembly in the bin\debug or
bin\release folder.
Your final project structure should look like the one shown in Figure 2.
Next, build and deploy your SharePoint project.
Create a list based on the
custom list template and call it "Songs". This list contains only the
title column, so enter a few of your favorite songs. The list looks like
the one in Figure 3.
Next, create another list
based on the custom list template and call it "My Favorite Songs". In
this list, choose to add a new column. You should see your "Auto
Complete Song" custom field type as one of the kinds of columns you can
create. This can be seen in Figure 4.
After you have created the
column, try creating a new list item in this "My favorite songs" list.
You should see and autocomplete like user interface show up inside
SharePoint, as shown in Figure 5.
The same user interface will
also surface when you're trying to edit an item. Obviously a custom
field type has many other possibilities. Now that you understand how to
write a basic custom field type, you can refer to MSDN and learn how to
add things such as custom validations. Also, do you think you can write
the same custom field type using jQuery and the Client Object Model ? Of
course, you can! I'll give you a starter hint, the jQuery script
registration and the ClientObject model ScriptLink control registration
will go in the CreateChildControls method of the
AutoCompleteFieldControl.
Also, check and see if you
can use this custom field control in a site column. Can you use it in a
site column ? Can you use it in a content type ? I think you'll find
that the answers to these questions are yes. And while you can easily do
this through the browser, it is useful to learn how you can do this as a
.WSP as well.
Next,
let's look at how you can create a site column and an associated content
type using this field type that you just created through a Visual
Studio project.