views:

1333

answers:

1

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(&quot;{0}&quot;, true)\" />";
  header += "&nbsp;<input type=\"button\" value=\"Uncheck All\" onclick=\"CheckAll(&quot;{0}&quot;, 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();

  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(/,$/, "");
    }
A: 

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.

slpbq