Site collections are the fundamental building blocks of SharePoint applications. They provide security boundaries and the smallest built-in unit for backup and disaster recovery. Most of the time, the central administration interface is fine for creating site collections. Problems arise when you need to frequently create site collections or allow users to create site collections. More than likely you're going to need a custom solution.
Recently, a client requested just such a custom solution for creating site collections. Not wanting to start from scratch, I found a very good solution from Gary Lapointe here. The solution consists of two main components an application page, for user input, and a web service to create the site collection. It's really very well done and customizable depending on your needs.
Friday, February 5, 2010
Tuesday, January 26, 2010
SharePoint MaskedEdit Field Control
A control that I used over and over in the good old days of VB6 was the masked edit control. I recently came across a jQuery plugin for a masked edit control and wanted to implement it as a custom SharePoint field. Download the code here
What you will need.
jQuery
jQuery Masked Edit Plugin
WSPBuilder
This is masked edit field as seen when adding it to a list. I've defined a small number of masks, you can add more! This interface is built with the next two code blocks. The first code block is the user control that contains the HTML and the server controls. The second code block is the code behind for the user control.
User Control Code Behind
This is the field class. Here I' inheriting form the SPFieldText since it most closely matches what we're trying to produce. This class provides the plumbing for the field. It saves the settings from the user control above, saves the values that users type in, validates the input, provides default display rendering. Luckily, most of this functionality is built into the SPFieldText class or provided by the Visual Studio project.
This is the class that renders the control users interact with in the new and edit forms. It closely resembles a webpart. Here is where the JavaScript is written to the page.
XML used to define the field.
Display View
Edit View
What you will need.
jQuery
jQuery Masked Edit Plugin
WSPBuilder
This is masked edit field as seen when adding it to a list. I've defined a small number of masks, you can add more! This interface is built with the next two code blocks. The first code block is the user control that contains the HTML and the server controls. The second code block is the code behind for the user control.
<%@ Control Language="C#" Inherits="MaskedEditField.jQueryMaskedEditFieldEditor, MaskedEditField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab6ae01ba130938e" compilationMode="Always" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="~/_controltemplates/InputFormControl.ascx" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<wssuc:InputFormControl runat="server" LabelText="Input Masks Explained">
<Template_Control>
<div>A mask is defined by a format made up of mask literals and mask definitions.
Any character not in the definitions list below is considered a mask literal.
Mask literals will be automatically entered for the user as they type and will
not be able to be removed by the user.
<ul>
<li>a - Represents an alpha character (A-Z,a-z)</li>
<li>9 - Represents a numeric character (0-9)</li>
<li>* - Represents an alphanumeric character (A-Z,a-z,0-9)</li>
</ul>
</div>
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl runat="server" LabelText="Input Masks">
<Template_Control>
<table>
<tr>
<td><asp:RadioButton Text="Phone Number" GroupName="MaskedEdit" ID="rPhone" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtPhone" runat="server">(999) 999-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="SSN" GroupName="MaskedEdit" ID="rSSN" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtSSN" runat="server">999-99-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Zip Code + 4" GroupName="MaskedEdit" ID="rZip4" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtZip4" runat="server">99999-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Zip Code" GroupName="MaskedEdit" ID="rZip" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtZip" runat="server">99999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Custom" GroupName="MaskedEdit" ID="rCustom" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="true" ID="txtCustom" runat="server"></asp:TextBox></td>
</tr>
</table>
</Template_Control>
</wssuc:InputFormControl>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="~/_controltemplates/InputFormControl.ascx" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<wssuc:InputFormControl runat="server" LabelText="Input Masks Explained">
<Template_Control>
<div>A mask is defined by a format made up of mask literals and mask definitions.
Any character not in the definitions list below is considered a mask literal.
Mask literals will be automatically entered for the user as they type and will
not be able to be removed by the user.
<ul>
<li>a - Represents an alpha character (A-Z,a-z)</li>
<li>9 - Represents a numeric character (0-9)</li>
<li>* - Represents an alphanumeric character (A-Z,a-z,0-9)</li>
</ul>
</div>
</Template_Control>
</wssuc:InputFormControl>
<wssuc:InputFormControl runat="server" LabelText="Input Masks">
<Template_Control>
<table>
<tr>
<td><asp:RadioButton Text="Phone Number" GroupName="MaskedEdit" ID="rPhone" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtPhone" runat="server">(999) 999-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="SSN" GroupName="MaskedEdit" ID="rSSN" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtSSN" runat="server">999-99-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Zip Code + 4" GroupName="MaskedEdit" ID="rZip4" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtZip4" runat="server">99999-9999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Zip Code" GroupName="MaskedEdit" ID="rZip" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="false" ID="txtZip" runat="server">99999</asp:TextBox></td>
</tr>
<tr>
<td><asp:RadioButton Text="Custom" GroupName="MaskedEdit" ID="rCustom" runat="server"></asp:RadioButton></td>
<td><asp:TextBox Enabled="true" ID="txtCustom" runat="server"></asp:TextBox></td>
</tr>
</table>
</Template_Control>
</wssuc:InputFormControl>
User Control Code Behind
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEditFieldEditor : UserControl, IFieldEditor
{
// Fields
protected RadioButton rPhone;
protected RadioButton rSSN;
protected RadioButton rZip;
protected RadioButton rZip4;
protected RadioButton rCustom;
protected TextBox txtPhone;
protected TextBox txtSSN;
protected TextBox txtZip;
protected TextBox txtZip4;
protected TextBox txtCustom;
private jQueryMaskedEdit fldjQueryMaskedEdit;
public void InitializeWithField(SPField field)
{
this.fldjQueryMaskedEdit = field as jQueryMaskedEdit;
if (this.Page.IsPostBack)
{
return;
}
//when modifying a field check the correct radio button
if (field != null)
{
string prop = fldjQueryMaskedEdit.MyCustomProperty;
if (prop.Equals(txtPhone.Text))
{ rPhone.Checked = true; }
else
if (prop.Equals(txtSSN.Text))
{ rSSN.Checked = true; }
else
if (prop.Equals(txtZip.Text))
{ rZip.Checked = true; }
else
if (prop.Equals(txtZip4.Text))
{ rZip4.Checked = true; }
else
{
rCustom.Checked = true;
txtCustom.Text = prop;
}
}
}
//save the value for the mask from the ascx to the field property
//this is the value the plugin will use to format the masked input
public void OnSaveChange(SPField field, bool bNewField)
{
jQueryMaskedEdit jme = (jQueryMaskedEdit)field;
jme.IsNew = bNewField;
if (rPhone.Checked)
{ jme.MyCustomProperty = txtPhone.Text; }
else
if (rSSN.Checked)
{ jme.MyCustomProperty = txtSSN.Text; }
else
if (rZip.Checked)
{ jme.MyCustomProperty = txtZip.Text; }
else
if (rZip4.Checked)
{ jme.MyCustomProperty = txtZip4.Text; }
else
{ jme.MyCustomProperty = txtCustom.Text.Trim(); }
}
// Properties
public bool DisplayAsNewSection
{
get
{
return false;
}
}
}
}
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEditFieldEditor : UserControl, IFieldEditor
{
// Fields
protected RadioButton rPhone;
protected RadioButton rSSN;
protected RadioButton rZip;
protected RadioButton rZip4;
protected RadioButton rCustom;
protected TextBox txtPhone;
protected TextBox txtSSN;
protected TextBox txtZip;
protected TextBox txtZip4;
protected TextBox txtCustom;
private jQueryMaskedEdit fldjQueryMaskedEdit;
public void InitializeWithField(SPField field)
{
this.fldjQueryMaskedEdit = field as jQueryMaskedEdit;
if (this.Page.IsPostBack)
{
return;
}
//when modifying a field check the correct radio button
if (field != null)
{
string prop = fldjQueryMaskedEdit.MyCustomProperty;
if (prop.Equals(txtPhone.Text))
{ rPhone.Checked = true; }
else
if (prop.Equals(txtSSN.Text))
{ rSSN.Checked = true; }
else
if (prop.Equals(txtZip.Text))
{ rZip.Checked = true; }
else
if (prop.Equals(txtZip4.Text))
{ rZip4.Checked = true; }
else
{
rCustom.Checked = true;
txtCustom.Text = prop;
}
}
}
//save the value for the mask from the ascx to the field property
//this is the value the plugin will use to format the masked input
public void OnSaveChange(SPField field, bool bNewField)
{
jQueryMaskedEdit jme = (jQueryMaskedEdit)field;
jme.IsNew = bNewField;
if (rPhone.Checked)
{ jme.MyCustomProperty = txtPhone.Text; }
else
if (rSSN.Checked)
{ jme.MyCustomProperty = txtSSN.Text; }
else
if (rZip.Checked)
{ jme.MyCustomProperty = txtZip.Text; }
else
if (rZip4.Checked)
{ jme.MyCustomProperty = txtZip4.Text; }
else
{ jme.MyCustomProperty = txtCustom.Text.Trim(); }
}
// Properties
public bool DisplayAsNewSection
{
get
{
return false;
}
}
}
}
This is the field class. Here I' inheriting form the SPFieldText since it most closely matches what we're trying to produce. This class provides the plumbing for the field. It saves the settings from the user control above, saves the values that users type in, validates the input, provides default display rendering. Luckily, most of this functionality is built into the SPFieldText class or provided by the Visual Studio project.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEdit : SPFieldText
{
private static string[] CustomPropertyNames = new string[] { "MyCustomProperty" };
public jQueryMaskedEdit(SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{
InitProperties();
}
public jQueryMaskedEdit(SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{
InitProperties();
}
#region Property storage and bug workarounds - do not edit
/// <summary>
/// Indicates that the field is being created rather than edited. This is necessary to
/// work around some bugs in field creation.
/// </summary>
public bool IsNew
{
get { return _IsNew; }
set { _IsNew = value; }
}
private bool _IsNew = false;
/// <summary>
/// Backing fields for custom properties. Using a dictionary to make it easier to abstract
/// details of working around SharePoint bugs.
/// </summary>
private Dictionary<string, string> CustomProperties = new Dictionary<string, string>();
/// <summary>
/// Static store to transfer custom properties between instances. This is needed to allow
/// correct saving of custom properties when a field is created - the custom property
/// implementation is not used by any out of box SharePoint features so is really buggy.
/// </summary>
private static Dictionary<string, string> CustomPropertiesForNewFields = new Dictionary<string, string>();
/// <summary>
/// Initialise backing fields from base property store
/// </summary>
private void InitProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
CustomProperties[propertyName] = base.GetCustomProperty(propertyName) + "";
}
}
/// <summary>
/// Take properties from either the backing fields or the static store and
/// put them in the base property store
/// </summary>
private void SaveProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
base.SetCustomProperty(propertyName, GetCustomProperty(propertyName));
}
}
/// <summary>
/// Get an identifier for the field being added/edited that will be unique even if
/// another user is editing a property of the same name.
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
private string GetCacheKey(string propertyName)
{
return SPContext.Current.GetHashCode() + "_" + (ParentList == null ? "SITE" : ParentList.ID.ToString()) + "_" + propertyName;
}
/// <summary>
/// Replace the buggy base implementation of SetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public void SetCustomProperty(string propertyName, object propertyValue)
{
if (IsNew)
{
// field is being added - need to put property in cache
CustomPropertiesForNewFields[GetCacheKey(propertyName)] = propertyValue + "";
}
CustomProperties[propertyName] = propertyValue + "";
}
/// <summary>
/// Replace the buggy base implementation of GetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public object GetCustomProperty(string propertyName)
{
if (!IsNew && CustomPropertiesForNewFields.ContainsKey(GetCacheKey(propertyName)))
{
string s = CustomPropertiesForNewFields[GetCacheKey(propertyName)];
CustomPropertiesForNewFields.Remove(GetCacheKey(propertyName));
CustomProperties[propertyName] = s;
return s;
}
else
{
return CustomProperties[propertyName];
}
}
/// <summary>
/// Called when a field is created. Without this, update is not called and custom properties
/// are not saved.
/// </summary>
/// <param name="op"></param>
public override void OnAdded(SPAddFieldOptions op)
{
base.OnAdded(op);
Update();
}
#endregion
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl fieldControl = new jQueryMaskedEditControl(this);
fieldControl.FieldName = InternalName;
return fieldControl;
}
}
public override void Update()
{
SaveProperties();
base.Update();
}
public override string GetValidatedString(object value)
{
if ((this.Required == true) && (value == null))
{ throw new SPFieldValidationException("This is a required field."); }
return base.GetValidatedString(value);
}
public override object GetFieldValue(string value)
{
if (String.IsNullOrEmpty(value))
return null;
return value;
}
public string MyCustomProperty
{
get { return this.GetCustomProperty("MyCustomProperty") + ""; }
set { this.SetCustomProperty("MyCustomProperty", value); }
}
}
}
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEdit : SPFieldText
{
private static string[] CustomPropertyNames = new string[] { "MyCustomProperty" };
public jQueryMaskedEdit(SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{
InitProperties();
}
public jQueryMaskedEdit(SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{
InitProperties();
}
#region Property storage and bug workarounds - do not edit
/// <summary>
/// Indicates that the field is being created rather than edited. This is necessary to
/// work around some bugs in field creation.
/// </summary>
public bool IsNew
{
get { return _IsNew; }
set { _IsNew = value; }
}
private bool _IsNew = false;
/// <summary>
/// Backing fields for custom properties. Using a dictionary to make it easier to abstract
/// details of working around SharePoint bugs.
/// </summary>
private Dictionary<string, string> CustomProperties = new Dictionary<string, string>();
/// <summary>
/// Static store to transfer custom properties between instances. This is needed to allow
/// correct saving of custom properties when a field is created - the custom property
/// implementation is not used by any out of box SharePoint features so is really buggy.
/// </summary>
private static Dictionary<string, string> CustomPropertiesForNewFields = new Dictionary<string, string>();
/// <summary>
/// Initialise backing fields from base property store
/// </summary>
private void InitProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
CustomProperties[propertyName] = base.GetCustomProperty(propertyName) + "";
}
}
/// <summary>
/// Take properties from either the backing fields or the static store and
/// put them in the base property store
/// </summary>
private void SaveProperties()
{
foreach (string propertyName in CustomPropertyNames)
{
base.SetCustomProperty(propertyName, GetCustomProperty(propertyName));
}
}
/// <summary>
/// Get an identifier for the field being added/edited that will be unique even if
/// another user is editing a property of the same name.
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
private string GetCacheKey(string propertyName)
{
return SPContext.Current.GetHashCode() + "_" + (ParentList == null ? "SITE" : ParentList.ID.ToString()) + "_" + propertyName;
}
/// <summary>
/// Replace the buggy base implementation of SetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public void SetCustomProperty(string propertyName, object propertyValue)
{
if (IsNew)
{
// field is being added - need to put property in cache
CustomPropertiesForNewFields[GetCacheKey(propertyName)] = propertyValue + "";
}
CustomProperties[propertyName] = propertyValue + "";
}
/// <summary>
/// Replace the buggy base implementation of GetCustomProperty
/// </summary>
/// <param name="propertyName"></param>
/// <param name="propertyValue"></param>
new public object GetCustomProperty(string propertyName)
{
if (!IsNew && CustomPropertiesForNewFields.ContainsKey(GetCacheKey(propertyName)))
{
string s = CustomPropertiesForNewFields[GetCacheKey(propertyName)];
CustomPropertiesForNewFields.Remove(GetCacheKey(propertyName));
CustomProperties[propertyName] = s;
return s;
}
else
{
return CustomProperties[propertyName];
}
}
/// <summary>
/// Called when a field is created. Without this, update is not called and custom properties
/// are not saved.
/// </summary>
/// <param name="op"></param>
public override void OnAdded(SPAddFieldOptions op)
{
base.OnAdded(op);
Update();
}
#endregion
public override BaseFieldControl FieldRenderingControl
{
get
{
BaseFieldControl fieldControl = new jQueryMaskedEditControl(this);
fieldControl.FieldName = InternalName;
return fieldControl;
}
}
public override void Update()
{
SaveProperties();
base.Update();
}
public override string GetValidatedString(object value)
{
if ((this.Required == true) && (value == null))
{ throw new SPFieldValidationException("This is a required field."); }
return base.GetValidatedString(value);
}
public override object GetFieldValue(string value)
{
if (String.IsNullOrEmpty(value))
return null;
return value;
}
public string MyCustomProperty
{
get { return this.GetCustomProperty("MyCustomProperty") + ""; }
set { this.SetCustomProperty("MyCustomProperty", value); }
}
}
}
This is the class that renders the control users interact with in the new and edit forms. It closely resembles a webpart. Here is where the JavaScript is written to the page.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEditControl : BaseFieldControl
{
private jQueryMaskedEdit field;
private TextBox txtBox;
private string txtVal;
// Used for the linked script file
private const string MaskedInputJS = "jquery.maskedinput-1.2.2.min.js";
private const string MaskedInputScriptKey = "jQueryMaskedInputKey";
private const string IncludeScriptFormat =
@"<script type=""{0}"" src=""{1}""></script>";
public jQueryMaskedEditControl(jQueryMaskedEdit parentField)
{
this.field = parentField;
this.txtBox = new TextBox();
}
//get/set value for custom field
public override object Value
{
get
{
return txtBox.Text;
}
set
{
txtBox.Text = string.Empty;
string txtVal = value as String;
if (txtVal != null)
{
txtBox.Text = txtVal;
}
}
}
protected override void CreateChildControls()
{
if (this.Field == null
|| this.ControlMode == SPControlMode.Display
|| this.ControlMode == SPControlMode.Invalid)
return;
base.CreateChildControls();
txtBox = new TextBox();
this.Controls.Add(txtBox);
}
//Update field value with user input & check field validation
public override void UpdateFieldValueInItem()
{
this.EnsureChildControls();
try
{
this.Value = this.txtBox.Text;
this.ItemFieldValue = this.Value;
}
catch (Exception ex)
{
this.IsValid = false;
this.ErrorMessage = "* " + ex.Message;
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (this.ControlMode == SPControlMode.Edit ||
this.ControlMode == SPControlMode.New)
{ RegisterCommonScript(); }
}
//Function which will register the linked file script and the embedded script
protected void RegisterCommonScript()
{
string location = null;
//include the mask plugin on the page
if (!Page.ClientScript.IsClientScriptBlockRegistered(MaskedInputScriptKey))
{
location = @"/_layouts/";
string includeScript =
String.Format(IncludeScriptFormat, "text/javascript", location + MaskedInputJS);
Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), MaskedInputScriptKey, includeScript);
}
if (!Page.ClientScript.IsClientScriptBlockRegistered(txtBox.ClientID))
{
//this is where the mask plugin is hooked into our field, its all happening on the clients machine
string function =
"<script type=\"text/javascript\">jQuery(function($){$(\"input[id*=" + txtBox.ClientID +
"]\").mask(\"" + field.MyCustomProperty + "\"); });</script>";
Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), txtBox.ClientID, function);
}
}
}
}
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace MaskedEditField
{
public class jQueryMaskedEditControl : BaseFieldControl
{
private jQueryMaskedEdit field;
private TextBox txtBox;
private string txtVal;
// Used for the linked script file
private const string MaskedInputJS = "jquery.maskedinput-1.2.2.min.js";
private const string MaskedInputScriptKey = "jQueryMaskedInputKey";
private const string IncludeScriptFormat =
@"<script type=""{0}"" src=""{1}""></script>";
public jQueryMaskedEditControl(jQueryMaskedEdit parentField)
{
this.field = parentField;
this.txtBox = new TextBox();
}
//get/set value for custom field
public override object Value
{
get
{
return txtBox.Text;
}
set
{
txtBox.Text = string.Empty;
string txtVal = value as String;
if (txtVal != null)
{
txtBox.Text = txtVal;
}
}
}
protected override void CreateChildControls()
{
if (this.Field == null
|| this.ControlMode == SPControlMode.Display
|| this.ControlMode == SPControlMode.Invalid)
return;
base.CreateChildControls();
txtBox = new TextBox();
this.Controls.Add(txtBox);
}
//Update field value with user input & check field validation
public override void UpdateFieldValueInItem()
{
this.EnsureChildControls();
try
{
this.Value = this.txtBox.Text;
this.ItemFieldValue = this.Value;
}
catch (Exception ex)
{
this.IsValid = false;
this.ErrorMessage = "* " + ex.Message;
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (this.ControlMode == SPControlMode.Edit ||
this.ControlMode == SPControlMode.New)
{ RegisterCommonScript(); }
}
//Function which will register the linked file script and the embedded script
protected void RegisterCommonScript()
{
string location = null;
//include the mask plugin on the page
if (!Page.ClientScript.IsClientScriptBlockRegistered(MaskedInputScriptKey))
{
location = @"/_layouts/";
string includeScript =
String.Format(IncludeScriptFormat, "text/javascript", location + MaskedInputJS);
Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), MaskedInputScriptKey, includeScript);
}
if (!Page.ClientScript.IsClientScriptBlockRegistered(txtBox.ClientID))
{
//this is where the mask plugin is hooked into our field, its all happening on the clients machine
string function =
"<script type=\"text/javascript\">jQuery(function($){$(\"input[id*=" + txtBox.ClientID +
"]\").mask(\"" + field.MyCustomProperty + "\"); });</script>";
Page.ClientScript.RegisterClientScriptBlock(typeof(jQueryMaskedEditControl), txtBox.ClientID, function);
}
}
}
}
XML used to define the field.
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
<FieldType>
<Field Name="TypeName">jQueryMaskedEdit</Field>
<Field Name="ParentType">Text</Field>
<Field Name="TypeDisplayName">jQueryMaskedEdit</Field>
<Field Name="TypeShortDescription">jQuery Masked Edit</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="Sortable">TRUE</Field>
<Field Name="AllowBaseTypeRendering">TRUE</Field>
<Field Name="Filterable">TRUE</Field>
<Field Name="FieldTypeClass">MaskedEditField.jQueryMaskedEdit, MaskedEditField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab6ae01ba130938e</Field>
<Field Name="FieldEditorUserControl">/_controltemplates/jQueryMaskedEditFieldEditor.ascx</Field>
<PropertySchema>
<Fields>
<Field Hidden="TRUE" Name="MyCustomProperty"
DisplayName="My Custom Property"
Type="Text">
</Field>
</Fields>
<Fields></Fields>
</PropertySchema>
<RenderPattern Name="DisplayPattern">
<Column />
</RenderPattern>
</FieldType>
</FieldTypes>
<FieldTypes>
<FieldType>
<Field Name="TypeName">jQueryMaskedEdit</Field>
<Field Name="ParentType">Text</Field>
<Field Name="TypeDisplayName">jQueryMaskedEdit</Field>
<Field Name="TypeShortDescription">jQuery Masked Edit</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="Sortable">TRUE</Field>
<Field Name="AllowBaseTypeRendering">TRUE</Field>
<Field Name="Filterable">TRUE</Field>
<Field Name="FieldTypeClass">MaskedEditField.jQueryMaskedEdit, MaskedEditField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab6ae01ba130938e</Field>
<Field Name="FieldEditorUserControl">/_controltemplates/jQueryMaskedEditFieldEditor.ascx</Field>
<PropertySchema>
<Fields>
<Field Hidden="TRUE" Name="MyCustomProperty"
DisplayName="My Custom Property"
Type="Text">
</Field>
</Fields>
<Fields></Fields>
</PropertySchema>
<RenderPattern Name="DisplayPattern">
<Column />
</RenderPattern>
</FieldType>
</FieldTypes>
Display View
Edit View
Labels:
BaseFieldControl,
C#,
jQuery,
SharePoint 2007,
SPFieldText
Saturday, January 23, 2010
Turn Verbose Error Messaging In SharePoint On
An annoying and easily fixed issue you will see on development machines is the really unhelpful message "An unexpected error has occurred." Open your web.config file, found by default here C:\inetpub\wwwroot\wss\VirtualDirectories\80, and make two changes. The brave will not backup the web.config file first !
<customErrors mode="Off" />
<SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
<customErrors mode="Off" />
<SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
Sunday, January 17, 2010
VirtualBox Resize Hard Drive
I've recently upgraded my development and virtual machines to 64bit to start working with SharePoint 2010. Much to my surprise, VirtualPC from Microsoft doesn't support 64bit guest operating systems. VirtualBox from Sun does and I've been really pleased with it so far.
On to the topic at hand. I created a new virtual hard drive and didn't give it enough space. Did quite a bit of Googling before finding an easy way to fix the mess I made for myself. This process really isn't resizing, it's copying and replacing.
1. Create a new virtual hard drive. (See step 3c below - click new)
2. Copy the contents of the old virtual hard drive to the new virtual hard drive.
a. Open up a command window.
b. Change the directory to the VirtualBox directory. The default is
"C:\Program Files\Sun\VirtualBox"
c. Execute this command VBoxManage clonehd --existing "path\old.vdi" "path\new.vdi"
3. Add the new hard drive to the virtual machine.
a. Select Storage
b. Click the folder icon to the right of "Hard Drive:"
c. Add your new hard drive
3. Release/Remove old hard drive on the same screen you added your new hard drive.
4. Make sure the new drive is set as the Primary Master (See step 3b).
5. Start Up your VM.
The additional space will not be recognized by default. Windows 7 and Server 2008 come with disk management software that will let you extend the original partition into the new partition. If you're guest operating system is not one of the above there are a number of free software products available. Look for partition software on download.com.
On to the topic at hand. I created a new virtual hard drive and didn't give it enough space. Did quite a bit of Googling before finding an easy way to fix the mess I made for myself. This process really isn't resizing, it's copying and replacing.
1. Create a new virtual hard drive. (See step 3c below - click new)
2. Copy the contents of the old virtual hard drive to the new virtual hard drive.
a. Open up a command window.
b. Change the directory to the VirtualBox directory. The default is
"C:\Program Files\Sun\VirtualBox"
c. Execute this command VBoxManage clonehd --existing "path\old.vdi" "path\new.vdi"
3. Add the new hard drive to the virtual machine.
a. Select Storage
b. Click the folder icon to the right of "Hard Drive:"
c. Add your new hard drive
3. Release/Remove old hard drive on the same screen you added your new hard drive.
4. Make sure the new drive is set as the Primary Master (See step 3b).
5. Start Up your VM.
The additional space will not be recognized by default. Windows 7 and Server 2008 come with disk management software that will let you extend the original partition into the new partition. If you're guest operating system is not one of the above there are a number of free software products available. Look for partition software on download.com.
Labels:
Resize Hard Drive,
VDI,
VirtualBox
Sunday, January 10, 2010
Add and Remove List Event Handlers with a Feature in C#
Adding event handlers with C# in a feature is sometimes the only way to go. If you want to add an event handler to a single list instance and not a list or content type you have to add it with C#. The code to add event handlers is pretty straight forward and can be seen below.
//assembly name of the enevnt handler class
string asmName = "EventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bdfdd9c76bf90bef";
//event handler class
string eventClass = "EventHandler.EventHandler";
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWeb web = (SPWeb)properties.Feature.Parent;
SPList testList = web.Lists["test"];
testList.EventReceivers.Add(SPEventReceiverType.ItemUpdated, asmName, eventClass);
testList.EventReceivers.Add(SPEventReceiverType.ItemUpdating, asmName, eventClass);
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPWeb web = (SPWeb)properties.Feature.Parent;
SPList testList = web.Lists["test"];
Guid eventReceiverGuid = Guid.Empty;
foreach (SPEventReceiverDefinition receiverDef in testList.EventReceivers)
{
if (receiverDef.Assembly == asmName)
{ eventReceiverGuid = receiverDef.Id; }
}
if (eventReceiverGuid != Guid.Empty)
{ testList.EventReceivers[eventReceiverGuid].Delete(); }
}
Labels:
C#,
Event Handler,
Feature,
SPList
Monday, December 28, 2009
Issue with WSPBuilder Project with Workflow
If you are developing with WSPBuilder and have started a project with a workflow do yourself a big favor.
Edit your .csproj file and add an import statement for the workflow foundation. Find the following line in a C# project or the vb equivalent "<Import Project=”$(MSBuildBinPath)\Microsoft.CSharp.targets” />" and add this line after it.
<Import Project=”$(MSBuildExtensionsPath)\Microsoft\Windows Workflow Foundation\v3.0\Workflow.Targets” />
At a minimum it will allow you to use declarative conditions. If you don't add this you'll have to use code conditions in your while loops.
Edit your .csproj file and add an import statement for the workflow foundation. Find the following line in a C# project or the vb equivalent "<Import Project=”$(MSBuildBinPath)\Microsoft.CSharp.targets” />" and add this line after it.
<Import Project=”$(MSBuildExtensionsPath)\Microsoft\Windows Workflow Foundation\v3.0\Workflow.Targets” />
At a minimum it will allow you to use declarative conditions. If you don't add this you'll have to use code conditions in your while loops.
Labels:
Workflow,
WSpBuilder
Wednesday, December 16, 2009
Gears Turning - Javascript setTimeout
A common challenge is to present the user with a progress bar or animated gif during a long running script. Sounds really easy, before the long running script set the style of an animated gif from none to inline and start the script. We all know what happens next, a frozen gif. The best way to get around this is to use the setTimeout function to allow the browser to return and let the gif move. The example below uses setTimeout to set up a recursive call to give us the needed user feedback.
To try out the sample add a CEWP to a webpart page and paste the included script. If you don't have a SharePoint site, add the script to a html page and get an animated gif here.
To try out the sample add a CEWP to a webpart page and paste the included script. If you don't have a SharePoint site, add the script to a html page and get an animated gif here.
<div id="gears" style="display:none">
<img src="/_layouts/IMAGES/GEARS_AN.GIF"></img></br></br>
</div>
<div id="done" style="display:none">
<span>Long Running Done</span></br></br>
</div>
<input type="button" value="Start Long Process" onclick="longRunning()"></input>
<script type="text/javascript">
var i, length, data
length = 1500;
data = [];
//random numbers to sort
for (i = 0; i < length; i++) {
data[i] = Math.floor(Math.random() * length);
}
function sort () {
i = 0;
function inner() {
var j, value;
//do the sorting
for (j = length; j > i; j--) {
if (data[j] < data[j - 1]) {
value = data[j];
data[j] = data[j - 1];
data[j - 1] = value;
}
}
i++;
//let the browser "breath" so the gears will turn
if (i < length) {
//to see the browser freeze swap these
setTimeout(function(){inner();},0);
//inner();
}
else{
//done hide the gears
document.getElementById("gears").style.display = "none";
document.getElementById("done").style.display = "block";
}
};
inner();
}
function longRunning(){
//show gears
document.getElementById("gears").style.display = "block";
//call the long running process
setTimeout(function(){sort();},2000);
}
</script>
<img src="/_layouts/IMAGES/GEARS_AN.GIF"></img></br></br>
</div>
<div id="done" style="display:none">
<span>Long Running Done</span></br></br>
</div>
<input type="button" value="Start Long Process" onclick="longRunning()"></input>
<script type="text/javascript">
var i, length, data
length = 1500;
data = [];
//random numbers to sort
for (i = 0; i < length; i++) {
data[i] = Math.floor(Math.random() * length);
}
function sort () {
i = 0;
function inner() {
var j, value;
//do the sorting
for (j = length; j > i; j--) {
if (data[j] < data[j - 1]) {
value = data[j];
data[j] = data[j - 1];
data[j - 1] = value;
}
}
i++;
//let the browser "breath" so the gears will turn
if (i < length) {
//to see the browser freeze swap these
setTimeout(function(){inner();},0);
//inner();
}
else{
//done hide the gears
document.getElementById("gears").style.display = "none";
document.getElementById("done").style.display = "block";
}
};
inner();
}
function longRunning(){
//show gears
document.getElementById("gears").style.display = "block";
//call the long running process
setTimeout(function(){sort();},2000);
}
</script>
Labels:
Javascript Recursion Gears IE
Subscribe to:
Posts (Atom)





