简体   繁体   中英

ASP.NET dual listboxes updated client-side with JQuery, and how to retrieve the results server-side

I've been trying to get an ASP.NET WebForms page up and running that has dual listboxes. This is a standard paradigm where there are two side-by-side listboxes, with available items on the left, selected items on the right, and buttons that move the items from one side to another.

I want all the work to be done on the clientside, via JQuery (or whatever). And I want to be able to retrieve the selected items upon postback.

I've run into all sorts of hurdles. Here's a few:

1) When the postback occurs, I receive this error:

Invalid postback or callback argument. Event validation is enabled using in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

Turing "EnableEventValidation" off is risky. But "ClientScriptManager.RegisterForEventValidation" is confusing.

2) Assuming I get to the point of actually getting the postback to occur, my listbox doesn't contain the selected items.

A common recommended solution for this is to copy the selected values into a hidden field before the postback. Seriously? There's got to be a better way.

3) And of course, all the details for how the client-side scripting should work is confusing also, although not nearly as bad as the first two items.

So what's the solution? I've posted the answer below.

According to http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/ , it's not only OK to ask and answer your own question, it's encouraged. So that's what I'm doing.

Here's my working solution, and after this I will go into details. Please note that I'm by no means an expert in web development or Javascript -- far from it. So take all of this with a grain of salt, and always remember that I'm not responsible if your code somehow manages to execute a HCF code.

<script>
    function addAllItems() {
        $("#<%= lbAvail.ClientID %> option").appendTo("#<%= lbSelected.ClientID %>");
        enableControls();
    }

    function addSelectedItems() {
        $("#<%= lbAvail.ClientID %> option:selected").appendTo("#<%= lbSelected.ClientID %>");
        enableControls();
    }

    function removeAllItems() {
        $("#<%= lbSelected.ClientID %> option").appendTo("#<%= lbAvail.ClientID %>");
        enableControls();
    }

    function removeSelectedItems() {
        $("#<%= lbSelected.ClientID %> option:selected").appendTo("#<%= lbAvail.ClientID %>");
        enableControls();
    }

    function enableButtons(listBoxControlId, buttonAllControlId, buttonSelectedContolId) {
        var count = $("#" + listBoxControlId + " option").length;
        document.getElementById(buttonAllControlId).disabled = count <= 0;

        count = $("#" + listBoxControlId + " option:selected").length;
        document.getElementById(buttonSelectedContolId).disabled = count <= 0;
    }

    function enableControls() {
        enableButtons("<%= lbAvail.ClientID %>", "<%= btnAddAll.ClientID %>", "<%= btnAddSelected.ClientID %>");
        enableButtons("<%= lbSelected.ClientID %>", "<%= btnRemoveAll.ClientID %>", "<%= btnRemoveSelected.ClientID %>");
    }

    function selectAll() {
        $("#<%= lbSelected.ClientID %> option").prop("selected", true);
    }
</script>

<div style="margin-top: 20px;">
    <table>
        <tr>
            <td>
                <asp:ListBox ID="lbAvail" runat="server" style="min-width: 150px" Height="150" SelectionMode="Multiple" 
                             onchange="enableControls();" ondblclick="addSelectedItems();"/>
            </td>
            <td style="padding-left: 10px; padding-right: 10px;">
                <table>
                    <tr>
                        <td>
                            <asp:Button Width="35" ID="btnAddAll" Text="&raquo;" OnClientClick="addAllItems(); return false;" UseSubmitBehavior="False" runat="server"/>
                        </td>
                    </tr>
                    <tr>
                        <td style="padding-top: 5px">
                            <asp:Button Width="35" ID="btnAddSelected" Text=">" OnClientClick="addSelectedItems(); return false;" UseSubmitBehavior="False" runat="server"/>
                        </td>
                    </tr>
                    <tr>
                        <td style="padding-top: 5px">
                            <asp:Button Width="35" ID="btnRemoveSelected" Text="<" OnClientClick="removeSelectedItems(); return false;" UseSubmitBehavior="False" runat="server"/>
                        </td>
                    </tr>
                    <tr>
                        <td style="padding-top: 5px">
                            <asp:Button Width="35" ID="btnRemoveAll" Text="&laquo;" OnClientClick="removeAllItems(); return false;" UseSubmitBehavior="False" runat="server"/>
                        </td>
                    </tr>
                </table>
            </td>
            <td>
                <asp:ListBox ID="lbSelected" runat="server" style="min-width: 150px" Height="150" SelectionMode="Multiple"
                             onchange="enableControls();" ondblclick="removeSelectedItems();"/>
            </td>
        </tr>
    </table>

    <div style="margin-top: 10px;">
        <asp:Button ID="btnSubmit" CssClass="btn btn-primary" Text="Submit" runat="server" OnClientClick="selectAll();"/>
    </div>
</div>

...

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        lbAvail.Items.Add(new ListItem("Monday", "Mon"));
        lbAvail.Items.Add(new ListItem("Tuesday", "Tues"));
        lbAvail.Items.Add(new ListItem("Wednesday", "Wed"));
        lbAvail.Items.Add(new ListItem("Thursday", "Thur"));
        lbAvail.Items.Add(new ListItem("Friday", "Fri"));
        lbAvail.Items.Add(new ListItem("Saturday", "Sat"));

        lbSelected.Items.Add(new ListItem("Sunday", "Sun"));

        ScriptManager.RegisterStartupScript(this, GetType(), "CallEnableControls", "enableControls()", true);
    }
    else
    {
        string[] selectedValues = Request.Form.GetValues(lbSelected.UniqueID);
        // ...
    }
}

protected override void Render(HtmlTextWriter writer)
{
    // Register all Available items as valid items in the Selected listbox.
    foreach (ListItem item in lbAvail.Items)
    {
        ClientScript.RegisterForEventValidation(lbSelected.UniqueID, item.Value);
    }

    // When a user removes an item from the Selected listbox, we put it into the
    // Available listbox, so we have to register it here, or we'll get an error.
    foreach (ListItem item in lbSelected.Items)
    {
        ClientScript.RegisterForEventValidation(lbAvail.UniqueID, item.Value);
    }

    base.Render(writer);
}

Here's some explanations:

Remember the "Invalid postback or callback argument" error that I got after moving items from one listbox to the other, then posting the page? This error is actually a clue to the fact that ASP.NET does indeed include the modified listbox contents in the postback data.

In order to access the modified listbox items, all we need is this line:

string[] selectedValues = Request.Form.GetValues(lbSelected.UniqueID);

This gives us an array of the values in the Selected listbox. We don't need to use a hidden field or textbox or whatever. Since the data is being sent back to us, we might as well use it. Note that these are the values, not the display strings.

One important note: Only the selected items in a listbox are sent back. So up in the markup code, you'll see that when the Submit button is clicked, I call some javascript code that selects all the items in the Selected listbox (I don't care what's in the Available listbox).

So how do we prevent the "Invalid postback or callback argument" error, so we can actually use this data? We have to tell ASP.NET what values are valid for each listbox. We do this using "ClientScript.RegisterForEventValidation", in the "Render()" method.

ASP.NET already knows that the values we originally placed in each listbox are valid values to return. So all we have to do is register the other values that might be returned. That's what I'm doing in the "Register" method above.

As for the Javascript code, it should be fairly self-explanatory. Just remember it's actually calling some JQuery functions, so you obviously have to include Jquery.

I hope this helps. It took me a while to figure it all out, and like I said, I couldn't find one place with all the answers. Of course, as soon as I post this, 15 people will point out where I could have found this information easily, but that's life.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM