Sunday, March 11, 2012

CascadingDropdown and Viewstate in Dynamic UserControls

I'm not sure I understand the problem exactly, but if you peek at the way CascadingDropDown uses ClientState (asp:HiddenField) to pass information back-and-forth, that might help?
The problem is twofold:

1. Cascading dropdowns with 3 dropdowns with the first being a parent of the other two, the last (child) and the first (parent) dropdown is the only one to have its values persisted after postback (dropdown 2 is reset)
2. A cascading dropdown in an update panel in a usercontrol does not have any of its values posted back (the dropdown is reset)

I will set up some test pages and post again if reqired.

/Glenn
I forgot to mention in my reply that this is in dynamically created controls as I mentioned in my first post
A test page that we can run to see the problem would be very helpful, thanks!

One thing I would suggest is to load the controls on the page initialization. This takes place before .NET restores viewstate to the controls and I believe the load function takes place after. I don't know if this is the problem or if it will help with both problems. It's just a suggestion.


Quite a bit of code follows, but it should make sense.
Also, please not I am quite new to asp 2.0 and I absolutely hate dynamic controls.

file: CarsService.asmx

<%@. WebService Language="C#" Class="CarsService" %>
using System.Collections.Generic;
using System.Web.Services;
using AtlasControlToolkit;

[WebService(Namespace ="http://tempuri.org/")][WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]public class CarsService : WebService{ [WebMethod] public CascadingDropDownNameValue[] GetDropDownContents(string knownCategoryValues, string category) { List<CascadingDropDownNameValue> faked = new List<CascadingDropDownNameValue>(); for (int i = 0; i < 10; i++ ) { faked.Add(new CascadingDropDownNameValue(knownCategoryValues + ":" + category + " " + i.ToString(), i.ToString()));
}
return faked.ToArray();
}
}

file : DriverPanel.ascx

<%@. Control Language="C#" AutoEventWireup="true" CodeFile="DriverPanel.ascx.cs" Inherits="Engine_DynamicTest_DriverPanel" %>
<div style="border:solid 1px blue;">
<asp:TextBox ID="persistTest" runat="server" />
<div class="demoarea">
<div class="demoheading">
CascadingDropDown Demonstration</div>
<atlas:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<table>
<tr>
<td>Make </td>
<td>
<asp:DropDownList ID="DropDownList1" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
<tr>
<td>Model </td>
<td>
<asp:DropDownList ID="DropDownList2" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
<tr>
<td>Color </td>
<td>
<asp:DropDownList ID="DropDownList3" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
</table>
<br />
<asp:Button ID="Button1" runat="server" Text="I want this car" OnClick="Button1_Click" />
<br />
<br />
<asp:Label ID="Label1" runat="server" Text="[No response provided yet]"></asp:Label>
</ContentTemplate>
</atlas:UpdatePanel>
<atlasToolkit:CascadingDropDown ID="CascadingDropDown1" runat="server">
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList1" Category="Make" PromptText="Please select a make" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" />
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList2" Category="Model" PromptText="Please select a model" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" ParentControlID="DropDownList1" />
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList3" Category="Color" PromptText="Please select a color" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" ParentControlID="DropDownList2" />
</atlasToolkit:CascadingDropDown>
</div>
<asp:LinkButton ID="DeleteLink" CausesValidation="False" runat="server">Delete</asp:LinkButton>
</div>
file: DriverPanel.ascx.cs

using System;
using System.Web.Services;
using System.Web.UI;
using AtlasControlToolkit;

public partialclass Engine_DynamicTest_DriverPanel : UserControl
{
public event EventHandler DriverDeleted;

protected override void OnInit(EventArgs e)
{
DeleteLink.Click +=new EventHandler(DeleteLink_Click);
}

protected void Page_Load(object sender, EventArgs e)
{

}

public virtual void OnDriverDeleted(EventArgs e)
{
if (DriverDeleted !=null)
{
DriverDeleted(this, e);
}
}

void DeleteLink_Click(object sender, EventArgs e)
{
OnDriverDeleted(e);
}

protected void Button1_Click(object sender, EventArgs e)
{
// Get selected valuesstring make = DropDownList1.SelectedValue;
string model = DropDownList2.SelectedValue;
string color = DropDownList3.SelectedValue;

// Output result string based on which values are specifiedif (string.IsNullOrEmpty(make))
{
Label1.Text ="Please select a make.";
}
else if (string.IsNullOrEmpty(model))
{
Label1.Text ="Please select a model.";
}
else if (string.IsNullOrEmpty(color))
{
Label1.Text ="Please select a color.";
}
else { Label1.Text =string.Format("You have chosen a {0} {1} {2}. Nice car!", RemoveValueText(color), RemoveValueText(make), RemoveValueText(model));
}
}

/// <summary>
/// Removes the text " (value)" used in the sample data set to show the use of values
/// </summary>
/// <param name="s">string to modify</param>
/// <returns>modified string</returns>private string RemoveValueText(string s)
{
return s.Replace(" (value)","");
}

public string PersistedValue {
set {
persistTest.Text =value;
}
}
}

file: default.aspx

<%@. Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Engine_DynamicTest_Default" EnableEventValidation="false" %>
<%@. Register TagPrefix="Panel" TagName="Driver" Src="DriverPanel.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<atlas:ScriptManager ID="Atlas" EnablePartialRendering="true" runat="server" />
<form id="form1" runat="server">
<div class="demoarea">
<div class="demoheading">
CascadingDropDown Demonstration</div>
<atlas:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<table>
<tr>
<td>Make </td>
<td>
<asp:DropDownList ID="DropDownList1" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
<tr>
<td>Model </td>
<td>
<asp:DropDownList ID="DropDownList2" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
<tr>
<td>Color </td>
<td>
<asp:DropDownList ID="DropDownList3" runat="server" Width="170">
</asp:DropDownList>
</td>
</tr>
</table>
<br />
<asp:Button ID="Button1" runat="server" Text="I want this car" OnClick="Button1_Click" />
<br />
<br />
<asp:Label ID="Label1" runat="server" Text="[No response provided yet]"></asp:Label>
</ContentTemplate>
</atlas:UpdatePanel>
<atlasToolkit:CascadingDropDown ID="CascadingDropDown1" runat="server">
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList1" Category="Make" PromptText="Please select a make" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" />
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList2" Category="Model" PromptText="Please select a model" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" ParentControlID="DropDownList1" />
<atlasToolkit:CascadingDropDownProperties TargetControlID="DropDownList3" Category="Color" PromptText="Please select a color" ServicePath="CarsService.asmx" ServiceMethod="GetDropDownContents" ParentControlID="DropDownList2" />
</atlasToolkit:CascadingDropDown>
</div>
<hr />
<div style="border:solid 1px red;">
<asp:PlaceHolder ID="phDrivers" runat="server" />
</div>
<asp:button id="AddDriver" text="Add" runat="server" causesvalidation="false" /
</form>
</body>
</html>
file: default.aspx.cs
using System;
using System.Collections;
using System.Web.UI;

public partialclass Engine_DynamicTest_Default : Page
{
private class Driver
{
public int number;
public string clientid;
}

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
AddDriver.Click +=new EventHandler(AddDriver_Click);

}

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DriverList =new ArrayList();
AddDriverPanel();
}
else {
foreach (Driver driverin DriverList)
{
Engine_DynamicTest_DriverPanel dPanel = LoadDriver();
driver.clientid = dPanel.ClientID;
/*dPanel.SetDeleteHandler(new EventHandler(Driver_DriverDeleted));
phDrivers.Controls.Add(Page.LoadControl(dPanel.GetType(), null));
dPanel.SetDeleteHandler(new EventHandler(Driver_DriverDeleted));*/ } } }void Driver_DriverDeleted(object sender, EventArgs e)
{
//DriverList.Remove(sender);string clientID = ((Engine_DynamicTest_DriverPanel)sender).ClientID;
Driver delete =null;
foreach (Driver driverin DriverList)
{
if (driver.clientid == clientID)
{
delete = driver;
break;
}
}
if (delete !=null)
{
DriverList.Remove(delete);
}
phDrivers.Controls.Remove((Engine_DynamicTest_DriverPanel)sender);
}

void AddDriver_Click(object sender, EventArgs e)
{
AddDriverPanel();
}

private Engine_DynamicTest_DriverPanel LoadDriver()
{
Engine_DynamicTest_DriverPanel dPanel = ((Engine_DynamicTest_DriverPanel)Page.LoadControl("DriverPanel.ascx"));
phDrivers.Controls.Add(dPanel);
dPanel.DriverDeleted +=new EventHandler(Driver_DriverDeleted);
return dPanel;
}

private void AddDriverPanel()
{
Engine_DynamicTest_DriverPanel dPanel = LoadDriver();
dPanel.PersistedValue = dPanel.ClientID;
Driver driver =new Driver();
driver.clientid = dPanel.ClientID;
driver.number = DriverList.Count;
DriverList.Add(driver);
}

private ArrayList DriverList
{
get {return (ArrayList)Session["driverList"];
}
set { Session["driverList"] =value;
}
}

#region UserControllocalprotected void Button1_Click(object sender, EventArgs e)
{
// Get selected valuesstring make = DropDownList1.SelectedValue;
string model = DropDownList2.SelectedValue;
string color = DropDownList3.SelectedValue;

// Output result string based on which values are specifiedif (string.IsNullOrEmpty(make))
{
Label1.Text ="Please select a make.";
}
else if (string.IsNullOrEmpty(model))
{
Label1.Text ="Please select a model.";
}
else if (string.IsNullOrEmpty(color))
{
Label1.Text ="Please select a color.";
}
else { Label1.Text =string.Format("You have chosen a {0} {1} {2}. Nice car!", RemoveValueText(color), RemoveValueText(make), RemoveValueText(model));
}
}

/// <summary>
/// Removes the text " (value)" used in the sample data set to show the use of values
/// </summary>
/// <param name="s">string to modify</param>
/// <returns>modified string</returns>private string RemoveValueText(string s)
{
return s.Replace(" (value)","");
}
#endregion}


I am aware the line
driver.number = DriverList.Count;
Is not very useful. It should not affect the demonstration.

Note how the values in the textboxes (feel free to change the values) are persisted across posts, but the CascadingDropdowns do not.

I am not even 100% certain this is the way to do things, as the documentation I have read for dynamic controls say to put the re-load in page_load and the viewstate will be restored for the control when it is added - the viewstate is saved and skipped on Init and reinstated when the control is added back to the control tree.

As for the problem with a CDD not being able to have 1 parent for 2 children, the problem seems to be in the javascript with this line (ca 187 in CascadingDropDownBehavior.js):

if (e.childDropDown && !gettingList) { e.childDropDown._onParentChange(); }
which implies 1 child only. There should be an array to add to and itterate through. If nobody is going to post a fix, I may do so, but JS is not my strong point either :P
The change proposed by BGRhoades above (specifically, moving the contents of Page_Load into OnInit) seems to do the trick for me as well. His/her posting outlines the reasons this works, too. Please let me/us know if there's some other problem that remains. Thanks!

David Anson:

The change proposed by BGRhoades above (specifically, moving the contents of Page_Load into OnInit) seems to do the trick for me as well. His/her posting outlines the reasons this works, too. Please let me/us know if there's some other problem that remains. Thanks!

Ah! Well, unfortunately, the UserControl includes some UpdatePanels and runs under nested master pages and as such some controls complain viciously that they want to be loaded in a control that runs under a form with runat=server when I try to load them dynamically under OnInit.

This is obviously the case, but it whinges anyway.
It would seem that an old version of the framework/toolkit creapt in, but after implementing my changes in conjunction with the demo pages, recompiling the project brought the changes over and it all works as expected. (control are loadin in OnInit, viewtsate is restored, etc.. life is good)

Thanks all for the help

pre select the cascading dropdwon control

preserve viewstate on dynamic cascading dropdown control

http://gandhirohan.blogspot.com/2007/09/cascadingdropdown-control.html

No comments:

Post a Comment