Thursday, September 30, 2010

How to build an addable DropDownList (can add items using JavaScript )


Demo:

Sometimes asp.net developers find when they change the items collection in a DropDownList in the client side using JavaScripts in the very next postback they run in to Event Validation error. Yes, this is true, because any attacker can inject malicious items to the item list rather than the proper rendered list, they can break down your site. But the problem is there are some legitimate instances where we intentionally add items the DropDownList in the client side using JavaScripts.

This article provides a solution to add items at the client side without Event Validation errors. Idea is the handle item list in hidden field and mark the hidden field as the actual control. So in a postback, actually item list will not get validated but the hidden field get validated.
 No problem so far, but how we can merge newly added items with excising items? For this, by the time I render the control, I add comma separated list of items as the value of hidden field. Then on the event of adding new items to the list in the client side, inside the JavaScript it appends the newly added items to the hidden field value. So job is almost done, then what we all need to do is, get the hidden field value in the LoadPostBackData event and repopulate the item list. Vola job done.

Control itself it renders the item adding JavaScript event (ready made) so all you need to do is call the JavaScript function with three parameters 

Param1 - value of new item
Param2 - text of new item
Param3 - id of the DropDownList - ClientID
AddListItem(value, text, targetListId);

Markup:
<%@ Register Assembly="ActiveTest" Namespace="ActiveTest" TagPrefix="asp" %>
<%@ Page Language="C#" %>
<html>
<head runat="server">
    <script language="javascript" type="text/javascript">
        function AddItem() {
 
            var value = document.getElementById('<%=txtValue.ClientID %>').value;
            var text = document.getElementById('<%=txtText.ClientID %>').value;
            var targetListId = '<%=addlFreeDownDownList.ClientID %>';
 
            AddListItem(value, text, targetListId);
            return false;
        } 
       
    </script>
</head>
<body>
    <form id="form1" runat="server">
        Add Item - 
        Text: <asp:TextBox runat="server" ID="txtText" /> 
        Value: <asp:TextBox runat="server" ID="txtValue" />
        <asp:Button runat="server" ID="btnAdd" Text="Add" OnClientClick="javascript:return AddItem()" />
        <hr />
        <asp:AddableDropDownList runat="server" ID="addlFreeDownDownList">
            <asp:ListItem>Orange</asp:ListItem>
            <asp:ListItem>Blue</asp:ListItem>
            <asp:ListItem>Red</asp:ListItem>
            <asp:ListItem>Yellow</asp:ListItem>
            <asp:ListItem>Black</asp:ListItem>
        </asp:AddableDropDownList>
        <hr />
        <asp:Button runat="server" ID="btnSave" Text="Save" />
    </form>
</body>
</html>

Control:
public class AddableDropDownList : DropDownList
{
    private string script = @"
    function AddListItem(value, text, target) {
        var list = target + ""List"";
        var option = document.createElement(""option"");
        document.getElementById(list).options.add(option);
        option.text = text;
        option.value = value;
        var target = document.getElementById(target);
        if (target.value == """") target.value = value + ""=="" + text;
        else target.value = target.value + "",:,"" + value + ""=="" + text;
        return false;
    } 
";
    public string ListID
    {
        get
        {
            return this.ClientID + "List";
        }
    }
    protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        string items = postCollection[postDataKey];
        this.Items.Clear();
        if (string.IsNullOrEmpty(items)) return true;
        foreach (string item in Regex.Split(items, ",:,"))
        {
            string[] s = Regex.Split(item, "==");
            if (s.Length != 2)
                throw
                    new Exception("Invalid item the list, list item cannot have == or ,:, combinations");
            this.Items.Add(new ListItem(s[1], s[0]));
        }
        this.SelectedValue = postCollection[postDataKey + "List"];
        return true;
    }
    protected override void Render(HtmlTextWriter writer)
    {
        StringBuilder content = new StringBuilder();
        if (this.AutoPostBack)
        {
            StringBuilder script = new StringBuilder(this.Attributes["onchange"]);
            if (script.Length == 0) script.AppendFormat("javascript:{0}",
                            string.Format("__doPostBack('{0}','');"this.ClientID));
            else
            {
                if (!script.ToString().EndsWith(";")) script.Append(";");
                script.AppendFormat("__doPostBack('{0}','');"this.ClientID);
            }
            this.Attributes.Add("onchange", script.ToString());
        }
        StringBuilder b = new StringBuilder();
        HtmlTextWriter h = new HtmlTextWriter(new StringWriter(b));
        this.Attributes.Render(h);
        content.AppendFormat("<select id=\"{0}List\" name=\"{1}List\"{2}{3}>",
                this.ClientID, this.UniqueID,
                b.Length == 0 ? string.Empty : string.Format(" {0}", b.ToString()),
                !string.IsNullOrEmpty(this.CssClass) ?
                    string.Format(" class=\"{0}\""this.CssClass) : string.Empty);
        foreach (ListItem item in this.Items)
            content.AppendFormat("<option value=\"{0}\"{1}>{2}</option>",
                item.Value, item.Selected ? " selected=\"selected\"" : string.Empty, item.Text);
        content.Append("</select>");
        content.AppendFormat("<input type=\"hidden\" id=\"{0}\" name=\"{1}\"  value=\"{2}\" />",
                this.ClientID, this.UniqueID, this.GetValue());
        writer.Write(content.ToString());
    }
    private string GetValue()
    {
        StringBuilder value = new StringBuilder();
        foreach (ListItem item in this.Items)
        {
            if (value.Length != 0) value.Append(",:,");
            value.AppendFormat("{0}=={1}", item.Value, item.Text);
        }
        return value.ToString();
    }
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.GetType().Name, this.script, true);
    }
}

1 comment:

Onsongo Moseti said...

This is a very good control and it is working the way i want. However, I programmaticaly change attributes at runtime, for example, when i try to disable the control by setting enabld to false, nothing happens. Do i need to everride this behaviour in the code? Please advise.

iPhone Launch Screen Sizes

iPhone Portrait iOS 8 Retina HT 5.5 = 1242 X 2208 Retna HD 4.7 = 750 X 1134 iPhone Landscape iOS 8 Retina HD 5.5  2208 X 1242 iPho...