Introduction
It is difficult to find a decent color picker control for ASP.NET. However, there are plenty of pure JavaScript color picker controls. I decided to take one of them and convert it into an ASP.NET web server control. As a base, I took the dhtmlgoodies advanced color picker.
Project Setup
First, let's do New Project ->ASP.NET Server Control. By default, the name of the project would be the same as the name of the default namespace. I called my project WebControls
, and renamed ServerControl1.cs to ColorPicker.cs. I changed default namespace to Karpach.WebControls
, as well as assembly name. Then, I added to the project the images, JavaScript, and styles supplied by dhtmlgoodies.
Then, I clicked on each file's properties and changed the Build Action from Content to Embedded Resource. I also renamed some of the files.
AssemblyInfo.cs
I registered all the resources like this:
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.SliderHandle.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.TabCenterActive.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.TabLeftActive.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.TabLeftInactive.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.TabRightActive.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.TabRightInactive.gif",
"img/gif")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Images.ColorPickerIcon.jpg",
"img/jpeg")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Styles.ColorPicker.css",
"text/css")]
[assembly: System.Web.UI.WebResource("Karpach.WebControls.Javascript.ColorPicker.js",
"text/js")]
As you might have noticed, System.Web.UI.WebResource
, the first parameter has the following signature:
[Assembly Name].[Folder].[File Name]
This is very important, since it is not documented even in MSDN.
ColorPicker.cs
In order to enable Validators for your control, you need to specify ValidationProperty
attribute of your server control class.
[DefaultProperty("Color"),ValidationProperty("Color")]
I modified ToolboxData
to look like this:
[ToolboxData("<{0}:ColorPicker runat="server">")]
Next, I wanted a custom icon in the Visual Studio Toolbox. After ToolboxData
, I added the following line:
[System.Drawing.ToolboxBitmap(typeof(ColorPicker),"Images.color_picker_icon.jpg")]
where the first parameter is the type of the control and the second parameter is the icon file name used in AssemblyInfo.cs.
Originally color picker had two JavaScript files: color_functions.js and js_color_picker_v2.js. I combined them in one file, ColorPicker.js. Those files had a bunch of functions. I combined everything in one JavaScript class, exposed some public
properties and one public
function ShowColorPicker
.
function ColorPicker(options)
{
this.FormWidgetAmountSliderHandleImage = options.FormWidgetAmountSliderHandleImage;
this.TabRightActiveImage = options.TabRightActiveImage;
this.TabRightInactiveImage = options.TabRightInactiveImage;
this.TabLeftActiveImage = options.TabLeftActiveImage;
this.TabLeftInactiveImage = options.TabLeftInactiveImage;
this.AutoPostBack = options.AutoPostBack;
this.AutoPostBackReference = options.AutoPostBackReference;
this.PopupPosition = options.PopupPosition;
this.ShowColorPicker = function(inputObj, formField)
{
}
}
After some feedback, I had to fix JavaScript in order to support ASP.NET AJAX UpdatePanel
and ModalPopup
extender.
Then, I needed to load the stored resources from the DLL into JavaScript class. The best event for this is OnPreRender
:
string colorFunctions = Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Javascript.ColorPicker.js");
Page.ClientScript.RegisterClientScriptInclude("ColorPicker.js", colorFunctions);
string script = string.Format(@"
var colorPicker_{0} = new ColorPicker({{
FormWidgetAmountSliderHandleImage : '{1}',
TabRightActiveImage : '{2}',
TabRightInactiveImage : '{3}',
TabLeftActiveImage : '{4}',
TabLeftInactiveImage : '{5}',
AutoPostBack : {6},
AutoPostBackReference : ""{7}"",
PopupPosition : {8}
}});
", ClientID
, Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.SliderHandle.gif")
, Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.TabRightActive.gif")
, Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.TabRightInactive.gif")
, Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.TabLeftActive.gif")
, Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.TabLeftInactive.gif")
, AutoPostBack?"true":"false"
, Page.ClientScript.GetPostBackEventReference(this,"")
, (int)PopupPosition
);
Page.ClientScript.RegisterStartupScript(Page.GetType(),
String.Format("InitColorPicker_{0}", ClientID), script, true);
if (!DesignMode && Page.Header != null)
{
RegisterCSSInclude(Page.Header);
}
Where RegisterCSSInclude
is the following helper method:
private void RegisterCSSInclude(Control target)
{
bool linkIncluded = false;
foreach (Control c in target.Controls)
{
if (c.ID == "ControlPickerStyle")
{
linkIncluded = true;
}
}
if (!linkIncluded)
{
HtmlGenericControl csslink = new HtmlGenericControl("link");
csslink.ID = "ControlPickerStyle";
csslink.Attributes.Add("href", Page.ClientScript.GetWebResourceUrl
(typeof(ColorPicker), "Karpach.WebControls.Styles.ColorPicker.css"));
csslink.Attributes.Add("type", "text/css");
csslink.Attributes.Add("rel", "stylesheet");
csslink.EnableViewState = false;
target.Controls.Add(csslink);
}
}
Then I overrode the Render
event of WebControl
class in order to render the control HTML.
using (PlaceHolder plh = new PlaceHolder())
{
if (DesignMode || Page.Header == null)
RegisterCSSInclude(plh);
Table table = new Table();
table.CellPadding = 0;
table.CellSpacing = 0;
table.Rows.Add(new TableRow());
table.Rows[0].Cells.Add(new TableCell());
table.Rows[0].Cells.Add(new TableCell());
table.Rows[0].Cells.Add(new TableCell());
table.Rows[0].Cells[1].Style.Add(HtmlTextWriterStyle.PaddingRight, "5px");
HtmlGenericControl txt = new HtmlGenericControl("input");
txt.EnableViewState = false;
txt.Attributes.Add("maxlength", "15");
txt.Attributes.Add("size", "15");
txt.Attributes.Add("value", Color);
txt.Attributes.Add("id", ClientID);
txt.Attributes.Add("name", UniqueID);
txt.Attributes.CssStyle.Value = "height:17px;padding:2px;";
table.Rows[0].Cells[0].Controls.Add(txt);
HtmlGenericControl colorBar = new HtmlGenericControl("div");
colorBar.EnableViewState = false;
colorBar.Attributes.CssStyle.Add(HtmlTextWriterStyle.Height, "21px");
colorBar.Attributes.CssStyle.Add(HtmlTextWriterStyle.Width, "5px");
colorBar.Attributes.CssStyle.Add("border", "solid 1px #7f9db9");
colorBar.Attributes.CssStyle.Add(HtmlTextWriterStyle.BackgroundColor, Color);
table.Rows[0].Cells[1].Controls.Add(colorBar);
HtmlInputImage btn = new HtmlInputImage();
btn.Src = Page.ClientScript.GetWebResourceUrl(typeof(ColorPicker),
"Karpach.WebControls.Images.ColorPickerIcon.jpg");
btn.Attributes.Add("onclick", string.Format("colorPicker_{0}.ShowColorPicker
(this,document.getElementById('{1}'));return false;", ClientID, ClientID));
btn.Attributes.CssStyle.Add(HtmlTextWriterStyle.ZIndex, "1");
HtmlGenericControl container = new HtmlGenericControl("div");
container.EnableViewState = false;
container.Controls.Add(btn);
container.Attributes.CssStyle.Add(HtmlTextWriterStyle.Position, "static");
container.Attributes.CssStyle.Add(HtmlTextWriterStyle.Display, "block");
table.Rows[0].Cells[2].Controls.Add(container);
plh.Controls.Add(table);
plh.RenderControl(output);
}
There are a few ways how you can save postback value of color picker. However I think the best way is to implement IPostBackDataHandler
interface.
public bool LoadPostData(string postDataKey,NameValueCollection postCollection)
{
String presentValue = Color;
String postedValue = postCollection[postDataKey];
if (presentValue == null || !presentValue.Equals(postedValue))
{
Color = postedValue;
return true;
}
return false;
}
public virtual void RaisePostDataChangedEvent()
{
OnColorChanged(EventArgs.Empty);
}
public void OnColorChanged(EventArgs e)
{
if (ColorChanged != null)
ColorChanged(this, e);
}
LoadPostData
method will be called during each postback, when postback form has token with key this.UniqueID
. As you can see above, the rendered input tag has name attribute this.UniqueID
.
ColorPicker
properties are stored in ControlState
by overriding LoadControlState
and SaveControlState
:
protected override void LoadControlState(object savedState)
{
Color = (string)(savedState as object[])[0];
AutoPostBack = (bool)(savedState as object[])[1];
PopupPosition = (PopupPosition)(savedState as object[])[2];
}
protected override object SaveControlState()
{
object []saveState = new object[3];
saveState[0] = Color;
saveState[1] = AutoPostBack;
saveState[2] = PopupPosition;
return (object)saveState;
}
You need to invoke the RegisterRequiresControlState
method to register control for participation in control state, otherwise LoadControlState
and SaveControlState
won't fire.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
Build.proj
Now, the final touch: Minification of JavaScript and CSS. I used Yahoo YUI compressor and Microsoft MSBuild. Here is the final MSbuild file:
="1.0"="utf-8"
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildCommunityTasksPath>..\References</MSBuildCommunityTasksPath>
<ProjectName>WebControls</ProjectName>
</PropertyGroup>
<Target Name="Compress">
<Message Text="Create temp files ..." />
<Copy SourceFiles=".\$(ProjectName)\Javascript\ColorPicker.js"
DestinationFiles=".\$(ProjectName)\Javascript\ColorPicker.js.full"/>
<Copy SourceFiles=".\$(ProjectName)\Styles\ColorPicker.css"
DestinationFiles=".\$(ProjectName)\Styles\ColorPicker.css.full"/>
<Exec Command=
"java -jar yuicompressor-2.4.2.jar --type js .\$(ProjectName)\
Javascript\ColorPicker.js.full >.\$(ProjectName)\Javascript\ColorPicker.js"/>
<Exec Command=
"java -jar yuicompressor-2.4.2.jar --type css .\$(ProjectName)\Styles\
ColorPicker.css.full >.\$(ProjectName)\Styles\ColorPicker.css"/>
</Target>
<Import Project=".\References\MSBuild.Community.Tasks.targets" />
<Target Name="Build" DependsOnTargets="Compress">
<Message Text="Building Project" />
<MSBuild Projects="./$(ProjectName)/$(ProjectName).sln"
Properties="Configuration=Release;Platform=Any CPU" />
</Target>
</Project>
Now you even don't need Visual Studio to compile DLL. All that you need is .NET 2.0 installed and then the following console script will do the compilation:
%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe Build.proj /t:Build
Per requests below, I added PopupPosition
property, so you can specify position of ColorPicker
popup (top left, top right, bottom left, bottom right - default).
I also added AutoPostBack
property and ColorChanged
event.