Introduction
If you have ever developed a WebPart for Windows SharePoint services, then you probably have tried to take advantage of some of the client-side controls, like the OWSDateField
. If you have, then you know how much of a pain in the rear it is to use them.
The problem
There are several issues involved with using the OWS controls that come built-in with SharePoint services that are in the Microsoft.SharePoint.WebControls
namespace:
- If you are going to use a control like the
OWSDateField
, then you must add an OWSForm
control to the WebPart's child control collection. If you do not, then you will not be able to utilize the OWSField
controls.
- If you wish to get the data from the control at the server (in other words, have the data posted back), you must create an
OWSSubmitButton
control and add it to the form. If you do not, any other posting mechanism will not post the user's input back to the server.
This is the problem with OWSSubmitButton
: there is no server side click event. Microsoft will tell you to check the "IsPostBack
" property on the page, but that is not a viable option if you need more than one button that posts back the data, but does different tasks with it.
- None of the OWS form controls in the
Microsoft.SharePoint.WebControls
namespace have the data change events. Even if the data is not posted back, it will not fire an event.
To be blunt, the server-side functionality of the OWS control set is not very robust, yet the client side scripting works quite well.
The solution
For an application I developed in my current position, I needed the aforementioned functionality. However, I found nothing on the Internet that really helped me. Therefore, I dug deep into the server-side and client-side functionality of the OWS control set, and I found an elegant way of fixing the above limitations.
First, I created a base class called OWSBase
that handled all of the data postback and value change event handling for all of the derived controls. Next, I created a set of controls (OWSTextField
, OWSRichTextField
, OWSDateField
, OWSNumberField
, etc.) derived from this base class, and each generated similar client-side JavaScripting that the SharePoint controls did. In the end, I created a submit button (a new OWSSubmitButton
) that causes the form to postback the data and fire a server-side click event.
Here is the code for the OWSBase
class:
public abstract class OWSBase : OWSControl, IPostBackDataHandler
{
public string Display
{
get { return "" + ViewState["Display"]; }
set { ViewState["Display"] = value; }
}
public virtual string Text
{
get { return Value; }
set { }
}
public virtual string Value
{
get { return "" + ViewState["Value"]; }
set { ViewState["Value"] = value; }
}
public bool Required
{
get
{
if (ViewState["Required"] == null)
Required = false;
return (bool)ViewState["Required"];
}
set { ViewState["Required"] = value; }
}
#region IPostBackDataHandler Members
public virtual bool LoadPostData(string postDataKey,
NameValueCollection postCollection)
{
string present = Value;
Value = postCollection[postDataKey];
return (present != Value);
}
public virtual void RaisePostDataChangedEvent()
{
OnValueChanged(EventArgs.Empty);
}
private void OnValueChanged(EventArgs e)
{
if (ValueChanged != null)
ValueChanged(this, e);
}
public event EventHandler ValueChanged;
#endregion
#region Helper Properties
protected string JavaScriptValue
{
get
{
Microsoft.SharePoint.WebControls.
return Value
.Replace(@"\", @"\\")
.Replace("\"", "\\\"")
.Replace("'", "\'")
.Replace("\r", "")
.Replace("\n", "\\n");
;
}
}
#endregion
}
Here is the code for one of the derived controls, OWSDateField
. (Note: This OWSDateField
is not the same as the OWSDateField
in the Microsoft.SharePoint.WebControls
namespace):
public class OWSDateField : OWSBase
{
#region construction
public OWSDateField() { }
#endregion
#region custom properties
public DateTime DateTime
{
get {
return
SPUtility.CreateSystemDateTimeFromXmlDataDateTimeFormat(Value);
}
set {
Value = SPUtility.CreateISO8601DateTimeFromSystemDateTime(value);
}
}
public bool DateOnly
{
get
{
if (ViewState["DateOnly"] == null)
DateOnly = false;
return (bool)ViewState["DateOnly"];
}
set { ViewState["DateOnly"] = value; }
}
public bool HideDescription
{
get
{
if (ViewState["HideDescription"] == null)
HideDescription = false;
return (bool)ViewState["HideDescription"];
}
set { ViewState["HideDescription"] = value; }
}
#endregion
#region overridden properties
public override string Text
{
get
{
if (DateOnly)
return DateTime.ToString("dddd, MMMM d, yyyy");
return DateTime.ToString("dddd, MMMM d, yyyy h:mm tt");
}
}
#endregion
#region overridden methods
protected override void Render(HtmlTextWriter wtr)
{
wtr.Write(
"<script>" +
"fld=new DateField(frm, '{0}', '{1}', '{2}'); " +
"fld.fDateOnly = {3}; " +
"fld.fHideDescription = {4}; " +
"fld.caltype = 1; " +
"fld.fRequired = {5}; " +
"fld.BuildUI(); " +
"</script>",
UniqueID,
Display,
JavaScriptValue,
DateOnly.ToString().ToLower(),
HideDescription.ToString().ToLower(),
Required.ToString().ToLower()
);
}
#endregion
}
Here is the code for the submit button:
public enum OWSButtonType
{
Button,
HyperLink
}
public class OWSSubmitButton : WebControl, IPostBackEventHandler
{
#region properties
public OWSButtonType ButtonType
{
get
{
if (ViewState["ButtonType"] == null)
ButtonType = OWSButtonType.HyperLink;
return (OWSButtonType)ViewState["ButtonType"];
}
set { ViewState["ButtonType"] = value; }
}
public string Text
{
get { return "" + ViewState["Text"]; }
set { ViewState["Text"] = value; }
}
#endregion
#region overridden methods
protected override void OnLoad(EventArgs e)
{
EmitProcessFormScript();
base.OnLoad(e);
}
protected override void Render(HtmlTextWriter writer)
{
switch (ButtonType)
{
case OWSButtonType.HyperLink:
writer.Write(
"<A class='\"{0}\"'
href='http://www.thecodeproject.com/"{1}/"'>{2}</A>",
CssClass,
string.Format(
"javascript:ProcessOwsForm('{0}', 'Click');",
UniqueID),
Microsoft.SharePoint.Utilities.SPEncode.HtmlEncode(Text)
);
break;
case OWSButtonType.Button:
writer.Write(
"<input class='\"{0}\"'
onclick='\"{1}\"' type='\"button\"' value='\"{2}\"'>",
CssClass,
string.Format(
"ProcessOwsForm('{0}', 'Click');",
UniqueID),
Microsoft.SharePoint.Utilities.SPEncode.HtmlEncode(Text)
);
break;
}
}
#endregion
#region IPostBackEventHandler Members
public void RaisePostBackEvent(string eventArgument)
{
OnClick(EventArgs.Empty);
}
#endregion
#region events
public event EventHandler Click;
#endregion
#region virtual methods
protected virtual void OnClick(EventArgs e)
{
if (Click != null)
Click(this, e);
}
#endregion
#region helper methods
private void EmitProcessFormScript()
{
if (!Page.IsClientScriptBlockRegistered(
"OWSProcessFormScript"))
{
Page.RegisterStartupScript(
"OWSProcessFormScript",
"<script> \r\n" +
"function ProcessOwsForm(ctl, argument) \r\n" +
"{ \r\n" +
" if (frm.fPreviewMode) \r\n" +
" { \r\n" +
" var L_cantSave_Text = \"This form cannot be " +
"saved when previewing this page.\"; \r\n" +
" window.alert(L_cantSave_Text); \r\n" +
" return; \r\n" +
" } \r\n" +
" if (frm.FValidate(true)) \r\n" +
" { \r\n" +
" frm.FPostProcess(); \r\n" +
" __doPostBack(ctl, argument); \r\n" +
" } \r\n" +
"} \r\n" +
"</script>"
);
Page.GetPostBackEventReference(this);
}
}
#endregion
}
Included in the download files are the following:
OWSChoiceField
, which uses an Items
property with the ListItem's objects to manage items in the choice, it can either be a dropdown list or a radio button choice field. Use the ChoiceFormat
property to specify this.
OWSTextField
, which can be a single or multi-line text field, use the NumLines
property.
OWSRichTextField
, which can get the HTML encoded text with a nice user interface (uses SharePoint client scripting). Use the NumLines
property.
OWSNumberField
Assumptions
I assume you are familiar with developing WebParts for WSS or SPS. I will not give help with Manifext.xml or DWP files. These controls are meant to be used within the context of a SharePoint WebPart.
About the ZIP file
All that is included in the ZIP file are the source files. There is no project file. You may add these files to an existing project, or you may create a base assembly that you can reference in your WebPart projects.
TestWebPart.cs contains a small example of how to use the controls.
Special considerations
I have only tested these controls when they have been compiled into a class library that has been installed in the Global Assembly Cache, and thus fully trusted by SharePoint. You may need to make changes to the code or .config files if you do not sign your assembly with a strong name and install it in the GAC (using the - globalinstall option with stsadm.exe).