Why is my ListView losing data on postback? - c#

I'm sure there's a really simple explanation for this but I've been tearing my hair out too long now so I appeal to SO for the sake of my sanity!
It's this simple: I have a ListView which is bound when the page first loads (and not again on postback). It's part of a form. On the first load I see the list of items but when I postback I see the EmptyDataTemplate. Is this correct behaviour? I'm sure that ListView as a data binding control should persist its list over postbacks. Here's the code, first front end:
<asp:ListView ID="boxes" runat="server" ItemType="Model.Generic.ILookupEntity<int>" EnableViewState="true">
<LayoutTemplate>
<div>
<ul class="contact-list checkbox-row">
<li id="itemPlaceholder" runat="server" />
</ul>
</div>
</LayoutTemplate>
<ItemTemplate>
<li>
<label>
<asp:CheckBox ID="cb" runat="server" />
<asp:HiddenField ID="value" runat="server" Value="<%# Item.Key %>" />
<span><%# Item.Value %></span>
</label>
</li>
</ItemTemplate>
<EmptyDataTemplate>
<p>No data</p>
</EmptyDataTemplate>
</asp:ListView>
<asp:CheckBoxList ID="boxtemp" runat="server" />
Then back end:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!IsPostBack)
{
var items = new List<Model.Generic.LookupEntity<int>>()
{
new Model.Generic.LookupEntity<int>(1, "One"),
new Model.Generic.LookupEntity<int>(2, "Two"),
new Model.Generic.LookupEntity<int>(3, "Three"),
new Model.Generic.LookupEntity<int>(4, "Four"),
};
boxes.DataSource = items;
boxes.DataBind();
boxtemp.DataSource = items;
boxtemp.DataTextField = "Value";
boxtemp.DataValueField = "Key";
boxtemp.DataBind();
}
}
I've added the boxtemp CheckBoxList as a test. This one retains its data on postback. But the ListView doesn't. What am I doing wrong here?
UPDATE 1
What's even weirder is that if I add the following code and debug, the variable test references the value 4 even on postback. But when the posted page is rendered I still see the EmptyItemTemplate.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
var test = boxes.Items.Count;
}
UPDATE 2
Having had it confirmed that this is not expected behaviour I have now found the cause I think. The ListView is inside a user control which is calling this.DataBind() on PreRender. I presume this is causing all data bound controls inside to re-bind on postback. The weird thing is that if I handle the ListView's OnDataBinding and OnDataBound events, on post back I can observe that the list still contains 4 items during both events. But is still rendered displaying the EmptyDataTemplate. Given this scenario, and assuming that the parent control's DataBind call is required, can anyone suggest the best resolution?

Related

Data not bound for ListView on postbacks

I am writing some message panel as a user control. The code looks like this (shortened for clarity)
protected void Page_PreRender(object sender, EventArgs e)
{
BindMessages(MessageType.Error, ErrListView);
}
private void BindMessages(MessageType type, ListView target)
{
List<string> messages = Session.PopMessages(type);
target.DataSource = messages;
target.DataBind();
}
ascx:
<asp:ListView runat="server" ID="ErrListView">
<ItemTemplate><li><%# Container.DataItem %></li></ItemTemplate>
</asp:ListView>
The code gets executed on each request (initial page load and postback) as it should, and the messages come out of the SessionState correctly. However, if the request is a postback, the messages are not actually updated (as if the DataBind() would not happen).
Anyone got a clue whats going on?
The reason I failed here was that my content did not contain an UpdatePanel. Changing my ascx file to include said control resolved my issues.
<asp:UpdatePanel runat="server" ID="StatusUpdatePanel">
<ContentTemplate>
<ul Class="Errors">
<asp:ListView runat="server" ID="ErrListView">
<ItemTemplate><li><%# Container.DataItem %></li></ItemTemplate>
</asp:ListView>
</ul>
</ContentTemplate>
</asp:UpdatePanel>

Telerik RadListView inside asp.net update panel does not refresh

I've been banging my head against the wall for the last couple of days trying to get this scenario to work.
I have an <asp:UpdatePanel> on a page which has an external trigger and contains a Telerik RadListView:
<asp:UpdatePanel runat="server" ID="RationUpdatePanel" UpdateMode="Conditional" ChildrenAsTriggers="true">
<ContentTemplate>
<telerik:RadListView ID="RationListView runat="server"
DataSourceId="RationDataSource"
OnItemCanceling="ItemCancelingHandler"
OnItemEditing="ItemEditingHandler"
OnItemUpdating="ItemUpdateHandler">
<LayoutTemplate>
<table>
<thead>
<tr>...</tr>
</thead>
<tbody>
<tr id="itemPlaceHolder" runat="server"/>
</tbody>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>
<asp:LinkButton ID="EditButton" runat="server" CausesValidation="False" CommandName="Edit" Text="Edit" ToolTip="Edit" />
<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" ToolTip="Delete" />
</td>
<td>...</td>
</tr>
</ItemTemplate>
<EditItemTemplate>
<tr>
<td>
<asp:LinkButton ID="SaveButton" runat="server" CausesValidation="False" CommandName="Save" Text="Save" ToolTip="Save" />
<asp:LinkButton ID="CancelButton" runat="server" CausesValidation="False" CommandName="Cancel" Text="Cancel" ToolTip="Update" />
</td>
<td>...</td>
</tr>
</EditItemTemplate>
</telerik:RadListView>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="UseRationButton" EventName="Click" />
</Triggers>
</UpdatePanel>
Code behind looks like this:
protected void RationListView_ItemEditing(object sender, RadListViewCommandEventArgs e)
{
((RadListViewDataItem)e.ListViewItem).Edit = true;
}
protected void RationListView_ItemCanceling(object sender, RadListViewCommandEventArgs e)
{
RationListView.ClearEditItems();
((RadListViewDataItem)e.ListViewItem).Edit = false;
}
Clicking on the EditButton or CancelButton produced the expected events and the ObjectDataSource routine to rebind the list is called, yet the update panel is not refreshed. Calling RationUpdatePanel.Update() from the event handlers does not change the situation. The code works properly outside of an update panel: the mode changes and the buttons from the other template are displayed.
In either scenario clicking on the external trigger causes the list to be displayed.
Update: I added a couple of javascript callbacks to the PageManager class in the browser for beginRequest and endRequest. These just display an alert when the event occurs.
The page contains several elements which basically look like this (details omitted):
<div>
<div>
<asp:DropDownList ID="RationDdl"/>
<asp:Button ID="UseRationButton"/>
</div>
<div>
<asp:DropDownList ID="AnimalGroupDdl"/>
<asp:UpdatePanel ID="AnimalWeightUpdatePanel"/>
<ContentTemplate>
<asp:DropDownList ID="AnimalWeightDdl"/>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</div>
<div>
<telerik:RadListBox>
</telerik:RadListBox>
</div>
<div>
<asp:UpdatePanel ID="FeedPreviewUpdatePanel">
<ContentTemplate>
<asp:Repeater>
</asp:Repeater>
</ContentPanel>
</asp:UpdatePanel>
</div>
<div>
<!-- this contains the update panel shown above -->
</div>
The UseFeedRationButton in the first div is an external trigger for the RationUpdatePanel. AnimalGroupDdl is an external trigger for AnimalWeightUpdatePanel. The handler for the SelectedIndexChanged event on AnimalGroupDdl populates AnimalWeightDdl with available weight classes for the
selected group. It has no interaction with RationUpdatePanel or FeedPreviewUpdatePanel.
Here is the markup for UseRationButton:
<asp:Button ID="UseRationButton" runat="server"
Text="Use This Ration"
OnClick="UseRationButton_Click"
CausesValidation="true"
ValidationGroup="UseRation"
UseSubmitBehavior="false">
</asp:Button>
And here is the code behind:
protected void UseRationButton_Click(object sender, EventArgs e)
{
string text = this.RationDdl.SelectedItem.Text;
this.RationNameLabel.Text = text;
this.RationDataSource.SelectParameters["RationId"].DefaultValue = this.RationDdl.SelectedValue;
}
There are two scenarios I have been testing:
1) Choose a ration from RationDdl and click UseRationButton
2) Choose an animal group and then execute scenario 1.
In scenario 1 an async postback is initiated by the client side asp.net code and the button event is handled server side. The RationListView is populated but the client side endRequest code is never executed (the alert is not displayed). Subsequent clicks on the controls in the RadListView trigger an async postback but the state of the list is not changed and the client side endRequest code is not executed.
In scenario 2 an async postback is initiated, the event is handled on the server side and AnimalWeightDdl is populated. The client side endRequest code is executed in this case (the alert IS displayed). Subsequent to this selecting a ration and clicking on the use ration button initiates a postback, the list view is populated and the endRequest code is executed. At his point clicking on buttons in the list view causes an async postback to be initiated, the state of the list is changed and the endRequest code is executed.
My working theory is that something is causing the state of the page request manager class on the client to become fubared, but I can't figure out what might be causing it.
Firstly , If you use telerik rad control , I suggest you to change your asp:button to telerik:RadButton .
Here is example
<telerik:RadButton ID="v" runat="server" Text="Use This Ration"
AutoPostBack="true"
ValidationGroup="UseRation" OnClick="UseRationButton_Click">
</telerik:RadButton>
protected void UseRationButton_Click(object sender, EventArgs e)
{
string text = this.RationDdl.SelectedItem.Text;
this.RationNameLabel.Text = text;
this.RationDataSource.SelectParameters["RationId"].DefaultValue = this.RationDdl.SelectedValue;
}
Note that , you should add AutoPostBack="true" into your radButton .
And this links , MSDN Reference1 and MSDN Reference2 will show details of using update Panel .
The research I did late Friday and Saturday morning provided the answer to this one.
I had some code attached to the Sys.WebForms.PageRequestManager endRequest event which updated a text box which contained a temporary title for a new ration. The code looked like this:
///
/// reregister the change handlers after reloading the animal weight ddl and create a
/// temporary name for the ration based on animal choice and weight.
///
function animalWeightDdlOnLoad() {
var ddl = document.getElementById("<%= AnimalWeightDdl.ClientID %>");
//
// add event handler in a browser agnostic fashion
//
if (ddl.addEventListener) {
ddl.addEventListener("change", animalWeightDdlOnChange);
}
else if (ddl.attachEvent) {
ddl.attachEvent("onchange", animalWeightDdlOnChange);
}
else {
ddl.onchange = animalWeightDdlOnChange;
}
animalWeightDdlOnChange();
}
animalWeightDdlOnChange() referenced the options list of the animal weight ddl:
///
/// create a temporary name for the ration
///
function animalWeightDdlOnChange() {
var ddl1 = document.getElementById("<%= AnimalGroupDdl.ClientID %>");
var ddl2 = document.getElementById("<%= AnimalWeightDdl.ClientID %>");
var text = document.getElementById("<%= RationNameText.ClientID %>");
text.value = ddl1.options[ddl1.selectedIndex].text + ' ' + ddl2.options[ddl2.selectedIndex].text + ' - Copy';
}
However if the list had not yet been populated the reference to options caused a fault which halted endRequest processing. Rather than having this code run on every async request I changed it to be a handler for the change event on the animal group ddl, like this:
function animalGroupDdlOnSelectedChanged() {
//
// add a handler for the endRequest event which will be triggered by
// fetching the contents of the weight ddl.
//
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(animalWeightDdlOnLoad);
}
Then in animalWeightDdlOnLoad() I added a line to remove the handler once the request had
run. This ensured that the change handler for the weight ddl would only run once the list had been populated. Problem solved. But just in case, I added a check on the length of the animal weight ddl's options list.
So even seemingly innocent javascript can mess up your async request handling in the browser.

How to get selected items of Listbox control?

I am developing an application in ASP.NET & c#. I have a Grid view in my page. In that I have a Listbox control and by default Enabled property set by false. I am trying to enable & disable that control from javascript / jQuery on Checkbox control.
My ASP Code:
<asp:GridView ID="gvWhom" runat="server" AutoGenerateColumns="False" DataSourceID="odsWhom"
GridLines="None" Width="100%" OnRowDataBound="gvWhom_RowDataBound">
<Columns>
<asp:TemplateField>
<HeaderTemplate>
</HeaderTemplate>
<ItemTemplate>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr style="height: 25px;">
<td width="20%">
<asp:CheckBox ID="chkCriteria" runat="server" Text='<%#Eval("TargetType") %>' />
</td>
<td width="80%">
<asp:ListBox ID="lbCriteria" runat="server" CssClass="ddl" Width="150px" Height="50px"
Enabled="false" SelectionMode="Multiple"></asp:ListBox>
</td>
</table>
</ItemTemplate>
<HeaderStyle Height="2px" />
</asp:TemplateField>
</Columns>
</asp:GridView>
Javascript Code:
function disableDropDown(chkCriteria, lbCriteria) {
if (document.getElementById(chkCriteria).checked) {
document.getElementById(lbCriteria).removeAttribute('disabled');
}else{
document.getElementById(lbCriteria).value = -1;
document.getElementById(lbCriteria).disabled = true;
}
return true;
}
C# Code:
protected void gvWhom_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
ListBox lbCriteria = (ListBox)e.Row.FindControl("lbCriteria");
CheckBox chkCriteria = (CheckBox)e.Row.FindControl("chkCriteria");
chkCriteria.Attributes.Add("onchange", "javascript:return disableDropDown('" + chkCriteria.ClientID + "', '" + lbCriteria.ClientID + "')");
}
}
Using above code I am able to enable / disable the Listbox control. But when I enable the control and select multiple items and submitting the form I am not able to find selected items of Listbox.
How can I fetch selected items of Listbox? or Is there any other way to enable - disable the listbox control and get the selected items in c# code?
This is mostly a guess, but it's worth a try...
You're disabling the control on the server-side here:
<asp:ListBox ID="lbCriteria" runat="server" CssClass="ddl" Width="150px" Height="50px"
Enabled="false" SelectionMode="Multiple"></asp:ListBox>
That Enabled="false" may be indicating to ASP.NET that it doesn't need to track this control, that changes won't be made to it. That might be why it's not getting the data back when you post back to the server.
Let's try keeping the control enabled from the server's (ASP.NET's) perspective, and maintaining the enabling/disabling in the UI entirely on the client's (JavaScript's) side. Essentially removing ASP.NET's built-in framework from making decisions for you regarding what data to track.
Start by enabling the control in the server-side control markup:
<asp:ListBox ID="lbCriteria" runat="server" CssClass="ddl" Width="150px" Height="50px"
SelectionMode="Multiple"></asp:ListBox>
The rest is handled by JavaScript. You already have some JavaScript code which you say enables/disabled the control on the client-side for you, correct? So all you need to do now is call that same JavaScript for every instance of the control when the page first loads in the browser to disable the controls client-side. This is the part you want:
document.getElementById(lbCriteria).value = -1;
document.getElementById(lbCriteria).disabled = true;
Now, it looks like you're binding your JavaScript events from the server-side code, which is where I won't be able to help as much because that method isn't particularly intuitive to me. (It's mixing JavaScript and C#, which feels a bit backwards.) So I'm not entirely sure the approach you'd take there.
Essentially you want to invoke those two lines of JavaScript for every instance of the control, and you want it to happen as soon as the page loads in the browser.
In jQuery I'd do something like this:
$(function () {
$('select.ddl').attr('disabled', 'disabled');
});
This would run when the DOM is loaded and would add the disabled attribute (with a value of "disabled" to every select with the class ddl.

Binding generic list of objects to ListView and then updating the bound list

I've got a ListView that is bound to a generic list of objects that I've created.
<asp:ListView ID="lvwConfig" runat="server">
<ItemTemplate>
<br />
<div class="title_small">
<asp:Label ID="lblName" runat="server" Text='<%#Eval("Name")%>'/>
</div>
<asp:TextBox ID="iFirstValue" runat="server" MaxLength="8" Text='<%#Eval("FirstValue")%>'></asp:TextBox><br />
<asp:TextBox ID="iSecondValue" runat="server" MaxLength="8" Text='<%#Eval("SecondValue")%>'></asp:TextBox><br />
<asp:TextBox ID="iThirdValue" runat="server" MaxLength="8" Text='<%#Eval("ThirdValue")%>'></asp:TextBox><br />
</ItemTemplate>
</asp:ListView>
protected void btnSave_Click(object sender, EventArgs e)
{
//Loop through each item in the listview
for (int i = 0; i < lvwSMSConfig.Items.Count(); i++)
{
//Some code to check to see if the value was updated
//If it was, call UpdateItem
lvwSMSConfig.UpdateItem(i,true);
}
}
protected void lvwSMSConfig_ItemUpdating(Object sender, ListViewUpdateEventArgs e)
{
TextBox iFirstValue= (TextBox)lvwSMSConfig.Items[e.ItemIndex].FindControl("iFirstValue");
TextBox iSecondValue= (TextBox)lvwSMSConfig.Items[e.ItemIndex].FindControl("iSecondValue");
TextBox iThirdValue= (TextBox)lvwSMSConfig.Items[e.ItemIndex].FindControl("iThirdValue");
myObjectList[e.ItemIndex].FirstValue= iFirstValue.Text;
myObjectList[e.ItemIndex].SecondValue= iSecondValue.Text;
myObjectList[e.ItemIndex].ThirdValue= iThirdValue.Text;
}
The above code (modified some bits for public posting) works just fine, however I am unsure if this is the best way to achieve my goal. Is there a more direct rout that I should be taking?
I would say, no this isn't the "easiest" way.
Have you considered using a datasource control? Something such as a ObjectDatasource or SQLDataSource control prove very helpful in such cases.
There are other templates you can use for instance, an Update should use the EditItemTemplate. I did a quick search to get you a link to some more detailed information about how to best utilize the Listview control. You will notice for instance you wont have to do FindControl calls. Link

Accessing Code Behind for a Control in a Master Page

I need to display a control consistently across a set of pages. So I'm using a MasterPage to do that in ASP.NET/C#. However I also need to programmatically access this control, mostly provide options to view/hide depending on whether the controls checkbox is clicked.
Here is the Source for the MasterPage
<div id="verifyInitial" runat="server">
<asp:CheckBox ID="chkInitialVerify" runat="server"
oncheckedchanged="chkInitialVerify_CheckedChanged" />
I agree that my initial data is correct.
</div>
<div id="verifyContinuous" runat="server">
<asp:CheckBox ID="chkContinuousVerify" runat="server"
oncheckedchanged="chkContinuousVerify_CheckedChanged" />
I agree that my continuous data is correct
</div>
Now in the code behind I want to perform the following operations. Basically if a person clicks on the checkbox for the initial div box, then the initial box disappears and the continous box shows up. However it seems that the code behind for the MasterPage does not activate whenver I click on the checkbox. Is that just the way MasterPages are designed? I always thought you could do add some sort of control functionality beyond utilizing the Page Load on the Master Page.
protected void chkInitialVerify_CheckedChanged(object sender, EventArgs e)
{
verifyContinuous.Visible = true;
verifyInitial.Visible = false;
}
protected void chkContinuousVerify_CheckedChanged(object sender, EventArgs e)
{
verifyContinuous.Visible = false;
}
If you're expecting the two controls to trigger a change for that page immediately then you'll need to set the AutoPostBack property to true for both of them:
<div id="verifyInitial" runat="server">
<asp:CheckBox ID="chkInitialVerify" runat="server" oncheckedchanged="chkInitialVerify_CheckedChanged" AutoPostBack="true" />
I agree that my initial data is correct.
</div>
<div id="verifyContinuous" runat="server">
<asp:CheckBox ID="chkContinuousVerify" runat="server" oncheckedchanged="chkContinuousVerify_CheckedChanged" AutoPostBack="true" />
I agree that my continuous data is correct
</div>
Otherwise, you need an <asp:button /> or some other control on the page to trigger a postback and cause your event handlers to run. The button, or other control, can either be on your masterpage or on your content page, the choice is entirely yours.

Categories

Resources