One user control updating another during AJAX Postback? - asp.net

I developed a user control that displays a list of products and it works pretty good. Then I dropped this user control into another user control that allows the user to pick different criteria and the product UC updates to show those products, all pretty slick and with AJAX via UpdatePanel.
All was working just fine... then another requirement came in. The "search" control needs to be separate from the product control (so they can be positioned separately). Initially, I thought this was no problem as I would give the search control a reference to the product control and then it would talk to it via reference instead of directly inside the control (which has been removed).
And they do talk. But the product control loads, but refuses to display.
I checked and it is being passed via reference and not a copy ( as best I can tell ).
There is an updatepanel in the search control. There is an update panel in the product control. And then for good measure, there is an update panel surrounding them both in the actual search aspx page.
I've tried setting the product control update panel to conditional and then fire the .Update() method manually.
What's the secret here?
TIA!
SOLVED
Thanks to Jamie Ide for the tip to use events.
Search Control and Product control still have internal update panels, and NO LONGER have them on this particular page.
Search Control now raises an event OnSearchResultsUpdated and exposes the found items in properties. The page subscribes to this event and takes the properties and passes them to the product control and triggers triggers a .Refresh() method on the product control which simply calls the .Update() on its internal updatepanel.
The Product control, FYI, accepts products in several different flavors. A list of distinct SKUs, a list of product ids, a named collection in our database and finally a given product category.
Our designers need to be able to create a new page, drop the control onto it and set some properties and voila! New site page. They don't want to require a programmer's involvement. So keeping the controls self contained is a requirement. Fortunately all the changes I made still work completely with the other uses of the product control.
THANKS AGAIN SO MUCH!

I don't think there's really enough information to work with here, but my best guess is that the Product control is not getting data bound. You may try calling myProdcutsCtrl.DataBind() from the search control (or something inside the Product control that cause a DataBind() for instance myProductCtrl.Search(value1, value2, value3).
One other thing you might try is removing the UpdatePanels and seeing if things work. Then add them back in once you get core functionality going.
UPDATE: I've gone ahead and put some example code that works here which I believe accomplishes what you want. What follows are snippets for the sake of saving space, but include all code necessary to make it run. Hopefully this will at least give you something for reference.
Things to try:
EnablePartialRendering="true|false" setting it to false will force the natural postbacks and is good for debugging UpdatePanel problems.
Make sure you are seeing Loading... come up on your screen. (maybe too fast depending on your dev computer)
Page.aspx
<%# Register Src="~/Product.ascx" TagPrefix="uc" TagName="Product" %>
<%# Register Src="~/Search.ascx" TagPrefix="uc" TagName="Search" %>
...
<asp:ScriptManager runat="server" ID="sm" EnablePartialRendering="true" />
Loaded <asp:Label ID="Label1" runat="server"><%= DateTime.Now %></asp:Label>
<asp:UpdateProgress runat="server" ID="progress" DynamicLayout="true">
<ProgressTemplate><b>Loading...</b></ProgressTemplate>
</asp:UpdateProgress>
<uc:Search runat="server" ID="search" ProdcutControlId="product" />
<uc:Product runat="server" ID="product" />
Search.ascx
<asp:UpdatePanel runat="server" ID="searchUpdate" UpdateMode="Conditional" ChildrenAsTriggers="true">
<ContentTemplate>
<p>
<asp:Label runat="server" AssociatedControlID="filter">Less than</asp:Label>
<asp:TextBox runat="server" ID="filter" MaxLength="3" />
<asp:Button runat="server" ID="search" Text="Search" OnClick="SearchClick" />
</p>
</ContentTemplate>
</asp:UpdatePanel>
Search.ascx.cs
public string ProdcutControlId { get; set; }
protected void SearchClick(object sender, EventArgs e)
{
Product c = this.NamingContainer.FindControl(ProdcutControlId) as Product;
if (c != null)
{
c.Search(filter.Text);
}
}
Product.ascx
<asp:UpdatePanel runat="server" ID="productUpdate" UpdateMode="Conditional" ChildrenAsTriggers="false">
<ContentTemplate>
<asp:Label runat="server">Request at <%= DateTime.Now %></asp:Label>
<asp:ListView runat="server" ID="product">
<LayoutTemplate>
<ul>
<li id="itemPlaceHolder" runat="server" />
</ul></LayoutTemplate>
<ItemTemplate>
<li><%# Container.DataItem %></li></ItemTemplate>
</asp:ListView>
</ContentTemplate>
</asp:UpdatePanel>
Product.ascx.cs
IEnumerable<int> values = Enumerable.Range(0, 25);
public void Search(string val)
{
int limit;
if (int.TryParse(val, out limit))
product.DataSource = values.Where(i => i < limit);
else
product.DataSource = values;
product.DataBind();
productUpdate.Update();
}
Code does NOT represent best practices, just a simple example!

I'm fairly new to AJAX, but I don't think it's a good idea for user controls to have UpdatePanels. I would also advise you not to have the user controls reference each other; they should communicate through events and methods controlled by their container.
I do something similar with two user controls for a master-details display. The master raises an event when an item is selected from a list, the containing page handles the event and calls a method on the details display to display the selected item. If I remember correctly, my first attempt had UpdatePanels in the user controls and I wasn't able to make that work. Having the user controls inside an UpdatePanel on the page works fine.

If I understand you right you have a layout like this:
Outer UpdatePanel
SearchControl
Search UpdatePanel
ProductControl
Product UpdatePanel
Databound Control
Which one of those update panels is actually being called?
I assume that if you check the network traffic with something like Fiddler or Firebug if you're using Firefox, you aren't seeing any HTML for the product update panel coming back?
Have you tried doing something like:
UpdatePanel productUpdate =
Page.FindControl("Product UpdatePanel") as UpdatePanel;
if (null != productUpdate){
productUpdate.Update();
}

By default, if a postback is made from an UpdatePanel, only that control will be updated/re-rendered (this is called partial page-rendering).
To also update/re-render other UpdatePanels, you have to either:
set their UpdateMode property to Always
add the control that makes the postback to their Triggers collection
Check this page in MSDN for details.

Related

User control retains focus incorrectly after TextChanged postback when UC is contained within a detailsview control

I reference a previous post: Focus lost on partial postback with UserControls inside UpdatePanel
where an excellent solution works perfectly for web-page controls within a form.
However, I have placed my UC inside a detailsview template-field (for Edit+Insert).
The UC contains an UpdatePanel needed to adjust the text-formatting and control's style(s) following the TextChanged event of the UC-textbox (AutoPostback=True) during the Edit-mode and Insert-modes of the DetailsView.
As such, when the DetailsView-control is in Edit-mode, and user changes Text in the UC, the textchanged event is fired and the user-entered value is validated and when OK, the thousounds-separator (comma) are added to the UC-textbox-text, BUT, the focus moves to the next field in the DetailsView and QUICKLY returns back to the UC-control.
This incorrect focus-move(s) does NOT occur when the UC is wrapped in updatepanels as noted in the referenced post since the focus and tabbing order works perfectly outside of the DetailsView control.
Here is the aspx markup for the template-field-EDIT (only).
<asp:TemplateField HeaderText="Initial Mileage" SortExpression="IMilage">
<EditItemTemplate>
<asp:UpdatePanel ID="updpnlIMilage" runat="server" UpdateMode="Conditional" >
<ContentTemplate>
<TGANumeric:GANumeric ID="ucnumIMileage" runat="server"
Caption="Initial Mileage" HideCaption="True" Width="160"
DisplayMask="999,999" InputMask="999999"
Enabled="True" IsRequired="False"
MinNumber="0" MaxNumber="999999"
Text='<%# Bind("IMilage") %>'
TabIndex="0"
/>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="ucnumIMileage" />
</Triggers>
</asp:UpdatePanel>
</EditItemTemplate>
Thanks in advance. Your comments are welcome.
Thanks...J.
So are you saying your control hierearchy (partial) is:
UpdatePanel > TGANumeric:GANumeric > UpdatePanel > TextBox
This is an awful lot of overhead to just format a number with a comma, which is the only reason I see for your posting back. As far as I can tell there is nothing you need from the server, so why post?
Or is there?
My thoughts, lose the update panels, disable the AutoPostback on the Textbox, handle the formatting client side if it must be seen immediately, or leave the formatting to the DetailsView field DataStringFormat when it posts after save.
I'm betting this will clear up any focus issues.
Based on all the comments in this thread, I want to explain the actual root cause of the tabbing misbehavior.
1) There were no coding issues or event-issues with the user-control.
2) There were no coding issues or event-issues with the layering of Master-page, Content-page, Ajax update-panels / nested update-panels, details-view and template-fields containing the user-control.
3) The real culprit was a small snippet of code where the page adjusts the web-controls on the form based on the "state" (status) of the page/form. I manage the adjusting of visible and/or enabling of web-controls in a single subroutine in the code-behind so that all of this enabling/disabling visible/not-visible occurs in one place under a set of CASE-statements.
The actual erroneous snippet of code inside the 'sbSetFormState()'-method was messing with the class-variable 'm_eFormState' that actually caused the update panel to re-fire and thus the tabbing got thrown out of sequence.
This was discovered by the great suggestion from 'fnostro' to remove or add functionality features until the mis-behavior exposes itself.
I mark this topic as resolved/closed.
Again, thanks to fnostro !!!

Why is the ASP.NET Repeater.Items collection empty, when controls are on the screen?

I have an ASP page with the following repeater:
<asp:Repeater runat="server" ID="RegionRepeater"
DataSourceID="SqlDataSourceRegions" EnableViewState="true">
<ItemTemplate>
<tr>
<td valign="top">
<b><%#Eval("description")%></b>
<asp:HiddenField runat="server" ID="RegionID"
Value='<%#Eval("region_id")%>'/>
</td>
<td>
<asp:FileUpload ID="FileUpload" runat="server" Width="368px" />
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
(The repeater is inside a Wizard, inside a content pane).
The code behind is connected to the
protected void Wizard1_NextButtonClick(object sender, WizardNavigationEventArgs e)
event. There are two items on the screen (two rows inside the table). However, when the code tries to read those items, the Items collection is empty!
foreach(RepeaterItem region in RegionRepeater.Items)
{
// Never runs - the RegionRepeater.Items.Count = 0
FileUpload fileUpload = (FileUpload) region.FindControl("FileUpload");
String regionID = ((HiddenField)region.FindControl("RegionID")).Value;
...
Why is the collection empty, when there are controls drawn on the screen?
Thanks a lot for any help; this is starting to drive me nuts.
(BTW: I tried adding/removing the EnableViewState="true" tag)
Have you made sure the repeater has been rebound on the postback?
The ASP.NET Page Life Cycle means that on a postback, you will need to rebind controls like the repeater in order for event handlers to be able to see the data.
Check if you have a Page.DataBind() in the page code behind. That makes your repeater to bind with empty data.
Maybe you do the databinding every time you load the page, do you check that there is no postbacl before you do it ?
I believe Items is only populated when bound. So you have to rebind your data on every page load. This is due to the statless environment of the web; it doesn't remember the data source, but loads the created controls from viewstate that were bound to it.
So on subsequent requests, it loads the control hierarchy but knows nothing about the data source that created the UI unless you rebind again.
HTH.

How to have a link in one placeholder open a ModalPopup in a different placeholder?

I have a page with different placeholders. In one of them, I have a link that I want to open a modal popup in a second placeholder (using the ajaxtoolkit ModalPopupExtender) :
<asp:Content ID="content1" ContentPlaceHolderID="placeholder1" Runat="Server">
<asp:LinkButton ID="link" runat="server" Text="Popup link" />
</asp:Content>
<asp:Content ID="content2" ContentPlaceHolderID="placeholder2" Runat="Server">
<asp:Panel ID="panel" runat="server" Text="Popup content" />
<ajaxToolkit:ModalPopupExtender ID="popup" runat="sever"
TargetControlID="link"
PopupControlID="panel"
/>
</asp:Content>
When doing as above, it fires me an exception, saying that popup cannot find link (which I understand, because they are in two different placeholders).
How can I make this work ? I can think of something find FindControl in the code behind, but I don't really like to use this function, as it is pretty computationally expensive (especially with my nested layout).
One issue is that your TargetControlID and PopupControlIDs are reversed. TargetControlID is the ID of the item that you want to 'Modal Pop', in your case that would be Panel1. The PopupControlID is the ID of the control that would trigger the ModalPopup, in your case that would be "Link"
But you still have a few options if that doesn't work, I have fired a modal located in a different update panel before using the method below. Though not the exact same issue, this workaround may help you (I am assuming you have a script manager on your page).
Create a hidden element in Content2 with ID="hiddenLink"
Set your ModalExtender PopupControlID="hiddenLink"
In the codeBehind for "link" in content1, add an onClick event with the following
ModalPopup1.show()
If you are using updatePanels, this will cause the ModalPopup to display in AJAX fashion without page refresh.But you would still get a full postback worht of data between the client and the server.
Method 2, you could use a javascript function to show to Modal as well. Add a behaviorID="MyModal1" (or whatever you want to call it) to your Modalpopup definition. Then change your link:
<asp:LinkButton ID="link" runat="server" Text="Popup link" OnClientClick="$get('MyModal1').show(); return false;"/>
Note: The return false in the above example prevents the .NET page from performing a postback.

Multiple UpdatePanels and state

I have a user control that has multiple update panels
On Page Load, I load a child user control dynamically onto the PlaceHolder (I do this everytime and don't check for postback)
I want to be able to maintain the state of the changes that a user makes in the Child user control. (The child user control has html controls along with asp.net controls and other custom controls).
The child user control loses the state whenever there's a click on the Button A or B that causes the postback. The mode for UpdatePanels have been set to conditional.
In the OnPageLoad,
I load a child control to the PlaceHolderA
This child control has another user control with a lot of JS. A user can potentially make changes in the child control.
When they click on Button A which is a part of the parent control, the OnPageLoad causes the child control to reload causing it to lose the client side changes that a user has made.
I would like to prevent this from happening.
Here's the HTML structure of the ASPX page
<UpdatePanel1>
<UpdatePanelChild1>
<Button A>
<Button B>
</UpdatePanelChild1>
<UpdatePanelChild2>
<PlaceHolderA>
</UpdatePanelChild2>
When dynamically adding controls you need to re-add them everytime your page loads. I've had to tackle similar difficulties and I do this in two ways. Where dynamic fields are added o the fly I will actually add a hidden control to the page before the user clicks to fill it in or add it themselves. By creating this sort of placeholder the system has tim eto start handling viewstate correctly and this preserves the users changes. The second issue that I hear you mentioning is that there might be issues with chich update panel updates. It's fine to have them set to conditional bu tin your code-behind you can also trigger the updates in teh othe rpanels if you need to from code. It's just updatepanel#.update()
alternatively you can also wrap all the update panels in another panel but I advise against this as it wastes resources.
It looks like you're losing the ViewState for your dynamically-added child control.
This is because you're adding it too late in the page lifecycle: the ViewState is loaded before the Page.Load event.
Try adding your child control in Page.Init instead.
You could use a ScriptManager with a form runat server in your control, instead update panels.
This way you dont have the post backs.
Here a good reference: http://www.asp.net/Ajax/Documentation/Live/overview/ScriptManagerOverview.aspx
Greetings.
Josema.
http://www.learning-workshop.com
Blockquote
//Code in your Asp.Net code
<form runat="server" id="form1">
<asp:ScriptManager id="ScriptManager1" runat="server" >
<Services>
<asp:ServiceReference Path="/WebServices/MyService.asmx"/>
</Services>
</asp:ScriptManager>
</form>
<script language="javascript" type="text/javascript">
//call with namespace "WebSite" included
WebSite.WebServices.HelloWorld(EndHelloWorld);
function EndHelloWorld()
{
//do whatever
}
</script>
</form>
Blockquote
//Code in your webservice class
[ScriptService]
public class MyService : System.Web.Services.WebService
{
[WebMethod]
public void HelloWorld()
{
return "Hello world";
}
}
From the script of my page i will do Ajax against my webservice, and the callback EndHelloWorld you could do whatever without losing state.
Hope it helps...
Kind Regards.
Josema.
It looks like you need to remove the nested UpdatePanels. In a structure like this:
<ParentUpdatePanel>
<ChildUpdatePanel1 />
<ChildUpdatePanel2 />
</ParentUpdatePanel />
ALL of the update panels will ALWAYS update, even if the child UpdatePanels are set to conditional, because the parent is actually the one that is updating. So, a postback event in one of the children will cause the parent to update.
Using that type of pattern is bad in general... if you need events from one UpdatePanel to cause updates in another, you should do this:
<asp:UpdatePanel ID="up1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Button ID="buttonA" runat="server" />
<asp:Button ID="buttonB" runat="server" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdatePanel ID="up2" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:Placeholder ID="ph" runat="server" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="buttonB" EventName="Click" />
</Triggers>
</asp:UpdatePanel>

Trying to self contain pop ups which use the AjaxToolkit ModalPopUpExtender

I have 3 different kinds of ajax popups that need to exist across my site. I was hoping that I could simply create a user control for each one and place the panel and modal popup extender inside each one but this doesn't seem to be working. Has anyone tried this before or do you have a recommendation as to how I can avoid duplicate code for each pop up on different pages? Thanks!
Ah I figured out my issue with the User Control I believe.
The ModalPopUpExtender requires the TargetID property to be set otherwise an error occurs. Since this is sitting in a UserControl I just created a dummy link button that doesn't do anything and I set the property visible to false.
<asp:LinkButton ID="lnkBlank" runat="server" Visible="false" />
<asp:Panel ID="plContainer" style="display: none;" runat="server">
Hello?
</asp:Panel>
<cc1:ModalPopupExtender ID="mpe" runat="server"
BehaviorID="test"
TargetControlID="lnkBlank"
PopupControlID="plContainer" />
Apparently it doesn't appreciate that and the moment I set the visible property to true it started working. Not sure what the reasoning is for a TargetID since, I would think, most pop ups could be called from multiple links about the page. Perhaps I'm still not entirely clear on how this control is supposed to be used.
One option would be to write the popups in a asp.net user control (a .ascx page) and include that on the pages you need the popups. Have a public method in the ascx page that will show the popup, and call it from the parent page when you need to. If you already have a script manager on the parent page, you can't have a second one in the ascx page, but other then that there shouldn't be anything that would stop this from working. Hope this helps!
edit: here's what my modal popup extender control looks like...
<cc1:ModalPopupExtender
ID="mpeClassroom"
BackgroundCssCLass="modalBackground"
runat="server"
CancelControlID="lbClose"
OnOkScript="onOk()"
TargetControlID="Button1"
PopupControlID="pnlClassroom">
</cc1:ModalPopupExtender>
in my code behind page, my method just calls mpeClassroom.Show();
The problem with hidden link as TrgetControlID is that; when u set its visibility as false, server doesn't render it as well. PopExtender then cannot find control on the page.
Instead of setting its visibility to false, try to apply a style with display:none. This should work !

Resources