Aug 10th 2008 09:17 am
Binding MS Ajax Extenders to nested Controls
The MS Ajax Extender model encourages us to bind html elements to extender properties via clientId. To help make this more declarative, the ajax control toolkit provides property attributes to do this mapping between server side control property and client side javascript accessor methods:
[IDReferenceProperty(typeof(WebControl))]
[DefaultValue("")]
[ExtenderControlProperty]
[ClientPropertyName("popupElement")]
[ElementReference]
[ExtenderControlProperty]
public string PopupControlID { � }
In this case, the framework will call a method on the javascript extender called “set_popupElement” and pass it the result of $get(”value from PopupControlId on the server side”).
This method works well until you want to bind the extender against elements contained in a different naming container. For example, suppose you create a custom extender to populate a popup panel depending on what link a user clicks upon.
Assume the popup panel control is in fact an asp.net panel. Since panel implements INamingContainer, the custom extender will not locate any elements inside it because the extender’s parent control (the one whose childControls collection it searches) is not the panel itself. Since FindControl() is not recursive, when the extender tries to find the controls inside the panel by Id, it will be unable to locate them.
One limited workaround is to move the extender inside the asp.net panel. This will work until the extender has to resolve controls both inside and outside the popup panel.
Another workaround is to set the element ids in the code behind.
MyExtender.PopupControlId = this.PopupPanel.FindControl(”elementToBindAgainst”);
this can be quite tedious depending on the number of controls you want to bind against and is less intuitive than the declarative approach.
Assuming the extender inherits from AjaxControlToolkit.ExtenderControlBase, a more flexible method is to override the ResolveControlProperty method as follows:
public string SearchContainerPaths { get; set; }
private bool searchControlsInitialized;
private System.Collections.Generic.List<Control> searchContainers = new System.Collections.Generic.List<Control>();
protected void ResolveControlProperty(object sender, ResolveControlEventArgs e)
{
if (string.IsNullOrEmpty(e.ControlID))
{
return;
}
if (!this.searchControlsInitialized) //check tosee if we've built the "probe" collection of containers to search
{
char[] splitChar = new char[] {’.'};
string[] controlPaths = this.SearchContainerPaths.Split(new char[] {’,'});
foreach (string path in controlPaths) {
string[] subPaths = path.Split(splitChar);
Control ctl = this.Parent;
foreach (string controlId in subPaths)
{
ctl = ctl.FindControl(controlId);
if (ctl == null)
{
throw new ArgumentException(”Cannot find path:” + path);
}
}
this.searchContainers.Add(ctl);
}
searchControlsInitialized = true;
}
Control tmpCtl = null;
foreach (Control control in this.searchContainers)
{
tmpCtl = control.FindControl(e.ControlID);
if (tmpCtl != null)
{
e.Control = tmpCtl; //the control is found, set it and exit.
break;
}
}
if (tmpCtl == null) //unable to find control in map paths, throw an exception.
{
throw new ArgumentException(”Cannot find controlId: ” + e.ControlID);
}
}
By setting SearchContainerPaths we can now probe for any controls outside the immediate naming container e.g. SearchContainerPaths=”panel1.panel2,modalPanel” will search the controls collection of panel2 (contained in panel1) and the controls collection of modalPanel.
This sample gives an example of this using the modalPopupBehavior and a customExtender to bind the popup panel.
No Comments yet »