A common issue I see people run into with Web Forms is the inability for client side changes to LIST elements (ListBox, DropDownList, etc…) to be passed back during POSTBACK. This is due to the how the viewstate is assigned/process, and there is no simple “hack” to bypass this. As for other non-list elements that typically implement a Text property, this isn’t an issue. Here are 3 common ways to get around the limitation in seeing changes to your LIST elements that were made on the client using jQuery/JavaScript.
1. Create a hidden field (that has a text property)
<select size="5" id="users">
<option value="Kelly">Kelly</option>
<option value="Kylee">Kylee</option>
<option value="Nathan">Nathan</option>
</select>
<input type="hidden" id="userList" runat="server" value="Kelly,Kylee,Nathan"/>
Here is how you would add/parse the client side change.
var $users = $('#users');
var $usersList = $('#usersList');
$users.append($('<option></option>').val('New Entry Value').html('New Entry Text'));
var newUserList = '';
var $options = $('#users').children('option');
$.each($options, function(index, value) {
newUserList = newUserList + $options[index].value + ',';
});
$usersList.val(newUserList.substr(0, newUserList.length-1));
** In this example, you maintain a hidden input element that matches the items in the select list. When you change the select list in JavaScript, you change the value of the hidden element. During post back, you read and parse this list to make your updates.
2. Wrap your lists in an update panel
<asp:UpdatePanel ID="UpdateRoles" runat="server">
<ContentTemplate>
<table>
<tr>
<td style="height: 180px; vertical-align: top;">
Active Roles<br />
<asp:ListBox ID="lstAdd" runat="server" Width="300px" OnSelectedIndexChanged="lstAdd_SelectedIndexChanged" Rows="10"></asp:ListBox>
</td>
<td style="height: 180px">
<asp:Button ID="AddRole" runat="server" Text="->" OnClick="AddRole_Click1" />
<br />
<asp:Button ID="RemoveRole" runat="server" Text="<-" OnClick="RemoveRole_Click" />
</td>
<td style="height: 180px; vertical-align: top;">
InActive Roles<br />
<asp:ListBox ID="lstRemove" runat="server" Width="300px" OnSelectedIndexChanged="lstRemove_SelectedIndexChanged" Rows="10"></asp:ListBox>
</td>
</tr>
</table>
</ContentTemplate>
</asp:UpdatePanel>
Here are the events that are fired when you click the Add / Remove buttons the move items between the lists.
protected void AddRole_Click1(object sender, EventArgs e)
{
if (lstAdd.SelectedIndex > -1)
{
lstRemove.Items.Add(lstAdd.SelectedItem);
lstAdd.Items.RemoveAt(lstAdd.SelectedIndex);
}
}
protected void RemoveRole_Click(object sender, EventArgs e)
{
if (lstRemove.SelectedIndex > -1)
{
lstAdd.Items.Add(lstRemove.SelectedItem);
lstRemove.Items.RemoveAt(lstRemove.SelectedIndex);
}
}
** You could fire the events using any type of event, in the sample I highlighting a item in one of the two lists and click Add/Remove to switch the items between the lists.
3. Post back using Ajax
This is currently my preferred approach, since it ride on top of jQuery Ajax and follows my normal design approach to building widgets (ajax load / add / edit / delete). Using this approach you can do real-time updates as they edit the list, or you can post back your entire form including all your list changes without a post back. There are so many options when you start using ajax calls to pass data back and forth between the client and server.
My example below is using a WCF service, you can also use ASMX to do this in earlier versions of .NET. This solution has a lot more steps, but it really encompasses a whole “form submit” vs. propagating your LIST changes to the server.
To start, I also build some simple objects to pass between the client & server. The objects go both ways and are defined as a DataContract in C#.
[DataContract]
public class Member
{
[DataMember]
public int MemberId;
[DataMember]
public string CustomerCode;
[DataMember]
public string DelToCode;
}
[DataContract]
public class Rule
{
[DataMember]
public int RuleId;
[DataMember]
public string Name;
[DataMember]
public bool Enabled;
[DataMember]
public List<Member> Members;
}
One we have an opject to pass, we define a method that will use this object.
[OperationContract]
[WebInvoke(
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.WrappedRequest)
]
bool UpdateRule(Rule rule);
public bool UpdateRule(Rule rule)
{
// Do Update Using Object
}
Next we build a form that will be used with our server, everything on the form can be build during page load or it can be populate via Ajax.
<fieldset class="withborder" id="manageRule">
<legend>Rule Update</legend>
<ol>
<li>
<label class="left">
Rule ID<em>*</em></label>
<input type="text" class="medium disabled" id="ruleId">
</li>
<li>
<label class="left">
Name<em>*</em></label>
<input type="text" class="medium" maxlength="5" id="ruleName">
</li>
<li>
<label class="left">
Enabled<em>*</em></label>
<input type="checkbox" class="checkBox" id="ruleEnabled" checked="checked">
</li>
<li>
<label class="left">
Assigned<em>*</em></label>
<div>
<div class="dropZone available">
<ul class="connectedSortable ui-sortable" id="available" style="">
<li id="MemberId_1" class="ui-state-highlight">Red</li>
<li id="MemberId_2" class="ui-state-highlight">Green</li>
</ul>
</div>
<div class="dropZone included">
<ul class="connectedSortable ui-sortable" id="included" style="">
<li id="MemberId_3" class="ui-state-highlight">Blue</li>
<li id="MemberId_4" class="ui-state-highlight">Yellow</li>
<li id="MemberId_5" class="ui-state-highlight">Orange</li>
</ul>
</div>
</div>
</li>
<li style="text-align: right;">
<input type="button" value="Update Rule" text="Add" id="updateRule">
</li>
</ol>
</fieldset>
Finally, we put it all together with JavaScript function that uses jQuery and JSON.org’s libraries to build and transfer data between the client and sever.
function UpdateRule() {
var rule;
rule.RuleId = parseInt($('#ruleId').val());
rule.Name = $('#ruleName').val();
rule.Enabled = $('#ruleEnabled').is(':checked');
var $includedMembers = $('#included > li');
rule.Members = [];
// This is where you parse / add the values of your list (listbox, li, etc...)
if ($includedMembers.length > 0) {
$.each($includedMembers, function (index, value) {
var id = $includedMembers[index].id.replace('MemberId_', '');
var name = $includedMembers[index].firstChild.data.split(' - ');
var member = { 'CustomerCode': name[0], 'DelToCode': name[1], 'MemberId': id };
rule.Members.push(member);
});
}
$.ajax({
url: '/Service/ExampleWCFService.svc/UpdateRule',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
type: 'POST',
data: JSON.stringify({ 'rule': rule }),
success: function (rule) {
// Do Something On Success
}
});
}
** Don’t forget, you could have your service store this data in the cache/session/etc… if you don’t want to store it real-time. In my sample, I send the whole form to be process and not just the list. This gets rid of the entire post back process.
Each has it’s own unique requirements, the only one I tend not to use anymore is #2 since it requires the addition of the ASP.NET Ajax Library. Having an additional library and added content in a project for this one feature isn’t worth it to me. I know the library has got more compact, but in general it’s quite heavy (size wise), and since I already “always” include jQuery there really isn’t a need for this baggage.