Introduction
When a developer wants to display a list of items with the ability to select some of them, there are not many options available, actually it comes to only two:
- List box
- Check box list
Both have some disadvantages.
The list box control selection is just highlighting of a row, which is a visually weak type of selection, multiple selection is not intuitive and is not convenient for the end user.
Check box list has a strong visual selection type but takes a lot of space if the list is long enough.
It would be nice to use something like a drop-down list, but unfortunately the drop-down list control does not support multiple-item selection.
The combination of the drop-down list and check box list would be ideal for this purpose.
So when the time had come and the web portal that I developed required to have such selection list, I started to do my homework and look for a suitable solution on the Internet.
I found a few, but was not satisfied with the complexity and overall amount of code to implement them.
I also found a nice JQuery extension, but it was a pure client side solution and I had something else in mind.
I decided to develop my own server side control.
I realized that it would be JavaScript driven, because I want to implement the client side click event to open and close the drop-down list.
My JavaScript of choice would be JQuery, which is very popular nowadays.
I also realized that CSS will be involved to style this control the way I wanted.
So the entire solution will be:
- Server side control
- Reference to JQuery library
- CSS (either a file of just a style tag)
- One image (an arrow down)
Server Side Control
Obviously, this control will inherit from CheckBoxList
control.
I added three public
properties (Title
, ImageURL
, and OpenOnStart
) and overrode the Render
procedure.
For my convenience when rendering the control, I added the reference to JQuery library inside the Render
procedure, but you can do it differently.
So this is the source:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CodeProjectWeb
{
public class CheckBoxListDropDown : CheckBoxList
{
public string Title { get; set; }
public string ImageURL { get; set; }
public bool OpenOnStart { get; set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
if (string.IsNullOrEmpty(this.CssClass))
this.CssClass = "ddlchklst";
string divFirstRow = @"
<div>
{0} <img id=""{1}"" style=""float: right;"" src=""{2}"" />
</div>";
string ulTag = "<ul style=\"display:{1}\" id=\"{0}\" >";
string chkbox = "<input id=\"{0}\" name=\"{1}\"
type=\"checkbox\" value=\"{2}\"{3} />";
string label = "<label for=\"{0}\">{1}</label>";
string jqueryToggleFunction = @"
<script type=""text/javascript"">
$(document).ready(function () {{
$(""#{0}"").click(function () {{
$(""#{1}"").toggle(""fast"");
}});
$("".{2} li:even"").css(""background-color"", ""#efefef"")
}});
</script>";
writer.WriteLine("<script type='text/javascript'
src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'>
</script>");
writer.WriteLine(string.Format("<div class=\"{0}\">", this.CssClass));
writer.Write(string.Format(divFirstRow, this.Title + " ",
base.ClientID + "_arrowDown", ImageURL));
writer.WriteLine();
writer.Indent++;
writer.WriteLine(string.Format(ulTag, base.ClientID + "_ul",
OpenOnStart ? "block" : "none"));
for (int index = 0; index < Items.Count; index++)
{
writer.Indent++;
writer.WriteLine("<li>");
writer.Indent++;
writer.WriteLine(string.Format(chkbox,
base.ClientID + "_" + index.ToString(),
base.ClientID + "$" + index.ToString(),
Items[index].Value,
(Items[index].Selected ? " checked" : " ")));
writer.WriteLine(string.Format(label,
base.ClientID + "_" + index.ToString(), Items[index].Text + " "));
writer.Indent--;
writer.WriteLine("</li>");
writer.WriteLine();
writer.Indent--;
}
writer.WriteLine("</ul>");
writer.WriteLine("</div>");
writer.Write(string.Format(jqueryToggleFunction,
base.ClientID + "_arrowDown", base.ClientID + "_ul", this.CssClass));
}
}
}
Testing the Control
ASPX Page
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DropdownCheckcs.aspx.cs"
Inherits="CodeProjectWeb.DropdownCheckcs" %>
<%@ Register Assembly="CodeProjectWeb" Namespace="CodeProjectWeb" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<style type="text/css">
.ddlchklst
{
width: 170px;
border:solid 1px silver;
}
.ddlchklst ul
{
margin:0;
padding:0;
border-top:solid 1px silver;
}
.ddlchklst li
{
list-style: none;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div>
<cc1:CheckBoxListDropDown ID="ddlchklst" runat="server"
Title="Select what you need" OpenOnStart="true" ImageURL="/images/DropDown.PNG">
</cc1:CheckBoxListDropDown>
</div>
<div>
<asp:Button ID="btn" runat="server" Text="Save" OnClick="btn_Click" />
</div>
</form>
</body>
</html>
Please note that you have to register the control:
<%@ Register Assembly="CodeProjectWeb" Namespace="CodeProjectWeb" TagPrefix="cc1" %>
User your own Assembly and Namespace to register.
Use the following image to display the down arrow: Place this image into a convenient location and set the ImageURL
property correctly.
Also this is our CSS:
<style type="text/css">
.ddlchklst
{
width: 170px;
border:solid 1px silver;
}
.ddlchklst ul
{
margin:0;
padding:0;
border-top:solid 1px silver;
}
.ddlchklst li
{
list-style: none;
}
</style>
ASPX.cs
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CodeProjectWeb
{
public partial class DropdownCheckcs : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string[] ds = new string[] { "one", "two", "three",
"four", "five", "six", "seven", "eight", "nine", "ten" };
this.ddlchklst.DataSource = ds;
this.ddlchklst.DataBind();
}
}
protected void btn_Click(object sender, EventArgs e)
{
foreach (ListItem li in this.ddlchklst.Items)
{
Response.Write( li.Value +": " +li.Selected.ToString() + "
");
}
}
}
}
Result
This is the result you will see when you expand the list:
Updated Version of the Control
Added on December 1st, 2010
Having some positive feedback I decided to improve the control, making sure that it really inherits from the CheckBoxList
control. The new version has implemented the following list of properties:
AutoPostBack
BackColor
BorderColor
BorderStyle
BorderWidth
Font-Bold
- etc...
And, as you see from the list of the properties, I do not need a CSS class anymore.
You can get everything by using the formatting properties.
There are still a couple of issues, but it would be unfair for other developers to rob them on the ability to improve somebody's work. :-)
Some properties I decided not to inherit, I don't think it would be useful (CellPadding
, CellSpacing
, etc.).
Here is the code:
public class DropDownCheckBoxList : CheckBoxList
{
public string Title { get; set; }
public string ImageURL { get; set; }
public string JQueryURL { get; set; }
public bool OpenOnStart { get; set; }
public Color AltRowColor { get; set; }
protected override void OnLoad(EventArgs e)
{
if (string.IsNullOrEmpty(ImageURL))
throw new Exception("ImageURL was not set.");
if (string.IsNullOrEmpty(JQueryURL))
throw new Exception("JqueryURL was not set.");
base.OnLoad(e);
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
if (this.ForeColor.IsEmpty)
this.ForeColor = Color.Black;
if (this.AltRowColor.IsEmpty)
this.AltRowColor = Color.GhostWhite;
if (this.BorderStyle.Equals(BorderStyle.NotSet) ||
this.BorderStyle.Equals(BorderStyle.NotSet))
this.BorderStyle = BorderStyle.Solid;
if (this.BorderColor.IsEmpty)
this.BorderColor = Color.Silver;
if (this.BorderWidth.IsEmpty)
this.BorderWidth = Unit.Pixel(1);
if (this.BackColor.IsEmpty)
this.BackColor = Color.White;
StringBuilder sbCss = new StringBuilder();
sbCss.Append("<style type=\"text/css\">");
sbCss.Append(".{0}{{");
if (this.Font.Italic)
sbCss.Append("font-style:italic; ");
if (this.Font.Bold)
sbCss.Append("font-weight:bold; ");
string textDecor = string.Empty;
if (Font.Overline || Font.Underline || Font.Strikeout)
{
sbCss.Append("text-decoration:");
if (this.Font.Overline)
sbCss.Append("overline ");
if (this.Font.Strikeout)
sbCss.Append("line-through ");
if (this.Font.Underline)
sbCss.Append("underline ");
sbCss.Append("; ");
}
if (!ForeColor.IsEmpty)
sbCss.Append("color:" + ForeColor.Name.Replace("ff", "#") + "; ");
if (!Font.Size.IsEmpty)
sbCss.Append("font-size:" + Font.Size + "; ");
if (!BackColor.IsEmpty)
sbCss.Append("background-color: " +
BackColor.Name.Replace("ff", "#") + "; ");
sbCss.Append("width: {1}; ");
sbCss.Append(" border:" + BorderStyle + " " +
BorderWidth + " " + this.BorderColor.Name.Replace
("ff", "#") + "; }}.{0} ul {{overflow:auto; height:{2};
margin:0; padding:0; border-top:solid 1px " +
BorderColor.Name.Replace("ff", "#") + "; ");
sbCss.Append("}} .{0} li {{list-style: none;}}</style>");
string css = sbCss.ToString();
if (string.IsNullOrEmpty(this.CssClass)) this.CssClass = "ddlchklst";
if (Width.IsEmpty) Width = Unit.Pixel(170);
if (Height.IsEmpty) Height = Unit.Pixel(170);
string divFirstRow = @"<div> {0} <img id=""{1}""
style=""float: right;"" src=""{2}"" /> </div>";
string ulTag = "<ul style=\"display:{1}\" id=\"{0}\" >";
string chkBox = "<input id=\"{0}\" name=\"{1}\"
type=\"checkbox\" value=\"{2}\"{3}{4}{5} />";
string attrs = string.Empty;
foreach (string key in this.Attributes.Keys)
{
attrs += " " + key + "=" + "\"" + this.Attributes[key].ToString() + "\"";
}
string label = "<label for=\"{0}\">{1}</label>";
string jqueryToggleFunction = @"<script type=""text/javascript"">
$(document).ready(function () {{ $(""#{0}"").click(function ()
{{ $(""#{1}"").toggle(""fast""); }});
$("".{2} li:even"").css(""background-color"", """ +
AltRowColor.Name.Replace("ff", "#") + "\") }}); </script>";
writer.WriteLine(string.Format(css, CssClass, Width, Height));
writer.WriteLine(string.Format("<script type='text/javascript'
src='{0}'></script>", JQueryURL));
writer.Write(string.Format(jqueryToggleFunction, base.ClientID +
"_arrowDown", base.ClientID + "_ul", this.CssClass));
writer.WriteLine(string.Format("<div class=\"{0}\">", this.CssClass));
writer.Write(string.Format(divFirstRow, this.Title + " ",
base.ClientID + "_arrowDown", ImageURL));
writer.WriteLine();
writer.Indent++;
writer.WriteLine(string.Format(ulTag, base.ClientID + "_ul",
OpenOnStart ? "block" : "none"));
for (int index = 0; index < Items.Count; index++)
{
writer.Indent++;
writer.WriteLine("<li>");
writer.Indent++;
writer.WriteLine(string.Format(chkBox,
base.ClientID + "_" + index.ToString(),
base.ClientID + "$" + index.ToString(),
Items[index].Value,
(Items[index].Selected ? " checked=true" : " "),
(AutoPostBack ? " onclick=\"" + HttpUtility.HtmlEncode
("javascript:setTimeout('__doPostBack(<a href="file:
+ index.ToString() + "\\',\\'\\')', 0)") + "\"" : ""),
attrs
));
writer.WriteLine(string.Format(label, base.ClientID + "_" +
index.ToString(), Items[index].Text + " "));
writer.Indent--;
writer.WriteLine("</li>");
writer.WriteLine();
writer.Indent--;
}
writer.WriteLine("</ul>");
writer.WriteLine("</div>");
}
}
New Implementation
Now you don't need to supply the CSS class, but you must specify the "arrow down" image and reference to the JQuery library:
<cc1:DropDownCheckBoxList ID="DropDownCheckBoxList1" runat="server"
Title="Select..." ImageURL="/images/dropdown.png"
JQueryURL="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js">
</cc1:DropDownCheckBoxList>
Pretty easy, isn't it? Good luck in implementing.
Conclusion
As you can see, this control is easy to implement and you can change the way it looks by changing the Render
procedure and/or CSS class.
If you like this article, please vote for it. It is important because it allows more programmers to utilize this code.