ASP.NET Server Control based on RadComboBox - Postback issue
I am trying to create a custom control that extends the RadComboBox from Telerik to create a Dropdown Checkbox List with default templates. The plan is to use the control in several places so I wanted to consolidate all of the logic in one spot.
However I am experiencing a couple of weird issues on postback. If you check a couple of items and then hit the Apply button the correct items are selected, but the text on the checkbox is different. Then on the next postback I get the error Multiple controls with the same ID 'i2' were found. FindControl requires that controls have unique IDs.
Attached is the custom control. Any help is appreciated.
C# Code:
/// <summary>
/// Private Header template class for the DropdownCheckboxList
/// </summary>
class CheckboxListFooterTemplate : ITemplate
{
#region Public Methods
public void InstantiateIn(Control container)
{
string footer = "<input type=\"submit\" value=\"Apply\" />";
container.Controls.Add(new LiteralControl(footer));
}
#endregion Public Methods
}
/// <summary>
/// Private Header template class for the DropdownCheckboxList
/// </summary>
class CheckboxListHeaderTemplate : ITemplate
{
#region Public Methods
public void InstantiateIn(Control container)
{
string header = "<input type=\"button\" value=\"Check All\" onclick=\"CheckAll("{0}", true)\" />";
header += " <input type=\"button\" value=\"Uncheck All\" onclick=\"CheckAll("{0}", false)\" />";
container.Controls.Add(new LiteralControl(string.Format(header, container.Parent.ClientID)));
}
#endregion Public Methods
}
/// <summary>
/// Template class for the DropdownChecklistBox
/// </summary>
class CheckboxListTemplate : ITemplate
{
#region Constants
//this div will stop the list from closing as a listitem is clicked
const string head = "<div onclick=\"StopPropagation(event)\" class=\"combo-item-template\">";
const string tail = "</div>";
#endregion Constants
#region Private Methods
/// <summary>
/// Bind the data to the checkbox
/// </summary>
/// <param name="sender">Checkbox to bind data to</param>
/// <param name="e"></param>
private void checkbox_DataBinding(object sender, EventArgs e)
{
CheckBox target = (CheckBox)sender;
RadComboBoxItem item = (RadComboBoxItem)target.BindingContainer;
string itemText = (string)DataBinder.Eval(item, "Text");
target.Text = itemText;
}
#endregion Private Methods
#region Public Methods
/// <summary>
/// Create the checkbox list items in the template
/// </summary>
/// <param name="container">Container that the control will be added</param>
public void InstantiateIn(Control container)
{
CheckBox checkbox = new CheckBox();
checkbox.ID = "chkList";
checkbox.Attributes.Add("onclick", string.Format("onCheckBoxClick(this, \"{0}\")", container.Parent.ClientID));
container.Controls.Add(new LiteralControl(head));
checkbox.DataBinding += new EventHandler(checkbox_DataBinding);
container.Controls.Add(checkbox);
container.Controls.Add(new LiteralControl(tail));
}
#endregion Public Methods
}
//todo: complete summary
/// <summary>
/// based on telerik demo: http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/templates/defaultcs.aspx
/// </summary>
[DefaultProperty("Text")]
[ToolboxData("<{0}:DropdownCheckboxList runat=server></{0}:DropdownCheckboxList>")]
public class DropdownCheckboxList : RadComboBox, INamingContainer
{
#region Private Properties
string SelectedText
{
get
{
StringBuilder values = new StringBuilder(SelectedItems.Count);
foreach (RadComboBoxItem item in SelectedItems)
values.Append(item.Text + ", ");
if (values.Length > 0)
return values.ToString().Remove(values.Length - 2, 2);
else
return EmptyMessage;
}
}
#endregion Private Properties
#region Public Properties
public RadComboBoxItemCollection SelectedItems
{
get
{
CheckBox chk = null;
RadComboBoxItemCollection selectedItems = new RadComboBoxItemCollection(this);
foreach (RadComboBoxItem item in Items)
{
chk = (CheckBox)item.FindControl("chkList");
if (chk != null && chk.Checked)
selectedItems.Add(item);
}
return selectedItems;
}
}
//todo: summary
public override string SelectedValue
{
get
{
StringBuilder values = new StringBuilder(SelectedItems.Count);
foreach (RadComboBoxItem item in SelectedItems)
values.Append(item.Value + ", ");
if (values.Length > 0)
return values.ToString().Remove(values.Length - 2, 2);
else
return "";
}
set
{
if (value != null)
SelectedValues = new List<string>(value.Split(','));
}
}
//todo: summary
public List<string> SelectedValues
{
get
{
List<string> selectedValues = new List<string>();
foreach (RadComboBoxItem item in SelectedItems)
{
selectedValues.Add(item.Value);
}
return selectedValues;
}
set
{
RadComboBoxItem item = null;
CheckBox chk = null;
foreach (string val in value)
{
item = Items.FindItemByValue(val.Trim());
if (item != null)
{
chk = (CheckBox)item.FindControl("chkList");
if (chk != null)
chk.Checked = true;
}
}
}
}
#endregion Public Properties
#region Protected Methods
protected override void CreateChildControls()
{
if (base.HeaderTemplate == null)
base.HeaderTemplate = new CheckboxListHeaderTemplate();
if (base.ItemTemplate == null)
base.ItemTemplate = new CheckboxListTemplate();
if (base.FooterTemplate == null)
base.FooterTemplate = new CheckboxListFooterTemplate开发者_JS百科();
base.CreateChildControls();
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string resourceName = "CustomControls.DropdownCheckboxList.js";
ClientScriptManager cs = this.Page.ClientScript;
cs.RegisterClientScriptResource(typeof(CustomControls.DropdownCheckboxList), resourceName);
Text = SelectedText;
}
#endregion Protected Methods
}
Javascript Code:
//based on telerik demo: http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/templates/defaultcs.aspx
var cancelDropDownClosing = false;
function StopPropagation(e) {
//cancel bubbling
e.cancelBubble = true;
if (e.stopPropagation) {
e.stopPropagation();
}
}
function onDropDownClosing() {
cancelDropDownClosing = false;
}
function CheckAll(comboBoxId, value) {
var combo = $find(comboBoxId);
//get the collection of all items
var items = combo.get_items();
//enumerate all items
for (var i = 0; i < items.get_count(); i++) {
var item = items.getItem(i);
//get the checkbox element of the current item
var chk1 = $get(combo.get_id() + "_i" + i + "_chkList");
chk1.checked = value;
}
}
function onCheckBoxClick(chk, comboBoxId) {
var combo = $find(comboBoxId);
//holds the text of all checked items
var text = "";
//holds the values of all checked items
var values = "";
//get the collection of all items
var items = combo.get_items();
//enumerate all items
for (var i = 0; i < items.get_count(); i++) {
var item = items.getItem(i);
//get the checkbox element of the current item
var chk1 = $get(combo.get_id() + "_i" + i + "_chkList");
if (chk1.checked) {
text += item.get_text() + ", ";
values += item.get_value() + ", ";
}
}
//remove the last comma from the string
text = removeLastComma(text);
values = removeLastComma(values);
if (text.length > 0) {
//set the text of the combobox
combo.set_text(text);
}
else {
//all checkboxes are unchecked
//so reset the controls
combo.set_text("");
}
}
//this method removes the ending comma from a string
function removeLastComma(str) {
return str.replace(/,$/, "");
}
This line in InstantiateIn(Control container)
is primary cause of the problem:
checkbox.ID = "chkList";
This line makes every checkbox have the same id - they should be unique.
So it might look more like this
checkbox.ID = Container.ID + SomeUniqueString;
I created a project with the code you provided and duplicated the error with only one control on the page. (there were other errors in the javascript also, but I was able to ignore those.)
I could see no easy way to create the unique ids so that you could know what they were to do the find. So instead of this:
chk = (CheckBox)item.FindControl("chkList");
I tried this:
foreach (var o in item.Controls)
{
if (o is CheckBox)
{
chk = (CheckBox) o;
}
}
It eliminated the error and allowed me to select more than one item. However, this code is not ideal - it makes the assumption that there is only one check box. You would be better off making sure the ids are unique. The approach I would take would be to base the id on the value of the combo box item.
If the selectable items are fixed (the user can't add new items through the combo box) you might try using a RadMenu instead. We have a very simialr control, but we use RadMenu. We simply set the image on the menu item to indicate selection status, and we keep track of selected items within our control.
精彩评论