FindControl: Recursive DFS, BFS, and Leaf to Root Search with Pruning October 24, 2011
Posted by codinglifestyle in ASP.NET, C#, CodeProject, jQuery.Tags: ASP.NET, BFS, DFS, extension methods, FindControl, jQuery, pruning, tree
add a comment
I have nefarious reason for posting this. It’s a prerequisite for another post I want to do on control mapping within javascript when you have one control which affects another and there’s no good spaghetti-less way to hook them together. But first, I need to talk about my nifty FindControl extensions. Whether you turn this in to an extension method or just place it in your page’s base class, you may find these handy.
We’ve all used FindControl and realized it’s a pretty lazy function that only searches its direct children and not the full control hierarchy. Let’s step back and consider what we’re searching before jumping to the code. What is the control hierarchy? It is a tree data structure whose root node is Page. The most common recursive FindControl extension starts at Page or a given parent node and performs a depth-first traversal over all the child nodes.
Search order: a-b-d-h-e-i-j-c-f-k-g
/// <summary> /// Recurse through the controls collection checking for the id /// </summary> /// <param name="control">The control we're checking</param> /// <param name="id">The id to find</param> /// <returns>The control, if found, or null</returns> public static Control FindControlEx(this Control control, string id) { //Check if this is the control we're looking for if (control.ID == id) return control; //Recurse through the child controls Control c = null; for (int i = 0; i < control.Controls.Count && c == null; i++) c = FindControlEx((Control)control.Controls[i], id); return c; }
You will find many examples of the above code on the net. This is the “good enough” algorithm of choice. If you have ever wondered about it’s efficiency, read on. Close you’re eyes and picture the complexity of the seemingly innocent form… how every table begets rows begets cells begets the controls within the cell and so forth. Before long you realize there can be quite a complex control heirarchy, sometimes quite deep, even in a relatively simple page.
Now imagine a page with several top-level composite controls, some of them rendering deep control heirachies (like tables). As the designer of the page you have inside knowledge about the layout and structure of the controls contained within. Therefore, you can pick the best method of searching that data structure. Looking at the diagram above and imagine the b-branch was much more complex and deep. Now say what we’re trying to find is g. With depth-first you would have to search the entiretly of the b-branch before moving on to the c-branch and ultimately finding the control in g. For this scenario, a breadth-first search would make more sense as we won’t waste time searching a complex and potentially deep branch when we know the control is close to our starting point, the root.
Search order: a-b-c-d-e-f-g-h-i-j-k
/// <summary> /// Finds the control via a breadth first search. /// </summary> /// <param name="control">The control we're checking</param> /// <param name="id">The id to find</param> /// <returns>If found, the control. Otherwise null</returns> public static Control FindControlBFS(this Control control, string id) { Queue<Control> queue = new Queue<Control>(); //Enqueue the root control queue.Enqueue(control); while (queue.Count > 0) { //Dequeue the next control to test Control ctrl = queue.Dequeue(); foreach (Control child in ctrl.Controls) { //Check if this is the control we're looking for if (child.ID == id) return child; //Place the child control on in the queue queue.Enqueue(child); } } return null; }
Recently I had a scenario where I needed to link 2 controls together that coexisted in the ItemTemplate of a repeater. The controls existed in separate composite controls.
In this example I need to get _TexBoxPerformAction’s ClientID to enable/disable it via _ChechBoxEnable. Depending on the size of the data the repeater is bound to there may be hundreds of instances of the repeater’s ItemTemplate. How do I guarantee I get the right one? The above top-down FindControl algorithms would return he first match of _TextBoxPerformAction, not necessarily the right one. To solve this predicament, we need a bottom-up approach to find the control closest to us. By working our way up the control hierarchy we should be able to find the textbox within the same ItemTemplate instance guaranteeing we have the right one. The problem is, as we work our way up we will be repeatedly searching an increasingly large branch we’ve already seen. We need to prune the child branch we’ve already seen so we don’t search it over and over again as we work our way up.
To start we are in node 5 and need to get to node 1 to find our control. We recursively search node 5 which yields no results.
Next we look at node 5’s parent. We’ve already searched node 5, so we will prune it. Now recursively search node 4, which includes node 3, yielding no results.
Next we look at node 4’s parent. We have already searched node 4 and its children so we prune it.
Last we recursively search node 2, which includes node 1, yielding a result!
So here we can see that pruning saved us searching an entire branch repeatedly. And the best part is we only need to keep track of one id to prune.
/// <summary> /// Finds the control from the leaf node to root node. /// </summary> /// <param name="ctrlSource">The control we're checking</param> /// <param name="id">The id to find</param> /// <returns>If found, the control. Otherwise null</returns> public static Control FindControlLeafToRoot(this Control ctrlSource, string id) { Control ctrlParent = ctrlSource.Parent; Control ctrlTarget = null; string pruneId = null; while (ctrlParent != null && ctrlTarget == null) { ctrlTarget = FindControl(ctrlParent, id, pruneId); pruneId = ctrlParent.ClientID; ctrlParent = ctrlParent.Parent; } return ctrlTarget; } /// <summary> /// Recurse through the controls collection checking for the id /// </summary> /// <param name="control">The control we're checking</param> /// <param name="id">The id to find</param> /// <param name="pruneClientID">The client ID to prune from the search.</param> /// <returns>If found, the control. Otherwise null</returns> public static Control FindControlEx(this Control control, string id, string pruneClientID) { //Check if this is the control we're looking for if (control.ID == id) return control; //Recurse through the child controls Control c = null; for (int i = 0; i < control.Controls.Count && c == null; i++) { if (control.Controls[i].ClientID != pruneClientID) c = FindControlEx((Control)control.Controls[i], id, pruneClientID); } return c; }
Now we have an efficient algorithm for searching leaf to root without wasting cycles searching the child branch we’ve come from. All this puts me in mind jQuery’s powerful selection capabilities. I’ve never dreamed up a reason for it yet, but searching for a collection of controls would be easy to implement and following jQuery’s lead we could extend the above to search for far more than just an ID.
Pass a Name Value Pair Collection to JavaScript August 8, 2011
Posted by codinglifestyle in ASP.NET, CodeProject, Javascript.Tags: ASP.NET, Javascript, jQuery
1 comment so far
In my crusade against in-line code I am endevouring to clean up the script hell in my current project. My javascript is littered these types of statements:
var hid = <%=hidSelectedItems.ClientId%>; var msg = <%=GetResourceString('lblTooManyItems')%>;
Part of the cleanup is to minimize script on the page and instead use a separate .js file. This encourages me to write static functions which take in ids and resources as parameters, allows for easier script debugging, and removes all in-line code making maintenance or future refactoring easier.
While moving code to a proper .js file is nice there are times we might miss the in-line goodness. Never fear, we can build a JavaScript object containing properties for anything we might need with ease. This equates to passing a name/value pair collection to the JavaScript from the code behind. Take a look at this example:
ScriptOptions options = new ScriptOptions(); options.Add("ok", GetResourceString("btnOK")); options.Add("oksave", GetResourceString("btnOkSave")); options.Add("cancel", GetResourceString("btnCancel")); options.Add("viewTitle", GetResourceString("lblAddressEditorView")); options.Add("editTitle", GetResourceString("lblAddressEditorEdit")); options.Add("createTitle", GetResourceString("lblAddressEditorCreate")); options.RegisterOptionsScript(this, "_OptionsAddressEditorResources");
Here we’re using the ScriptOptions class to create an object called _OptionsAddressEditorResources we can access in our script. Now let’s see these options in use:
function fnAddressEditDialog(address, args) { //Define the buttons and events var buttonList = {}; buttonList[_OptionsAddressEditorResources.ok] = function() { return fnAddressEditOnOk(jQuery(this), args); }; buttonList[_OptionsAddressEditorResources.oksave] = function() { return fnAddressEditOnOkSave(jQuery(this), args); }; buttonList[_OptionsAddressEditorResources.cancel] = function() { jQuery(this).dialog("close"); }; //Display the dialog jQuery("#addressEditorDialog").dialog({ title: _OptionsAddressEditorResources.editTitle, modal: true, width: 535, resizable: false, buttons: buttonList }); }
Above we see the jQuery dialog using the resources contained within the _OptionsAddressEditorResources object.
So this seems simple but pretty powerful. Below is the ScriptOptions class which simply extends a Dictionary and writes out the script creating a named global object. Good luck cleaning up your script hell. Hopefully this will help.
/// <summary> /// Class for generating javascript option arrays /// </summary> public class ScriptOptions : Dictionary<string, string> { /// <summary> /// Adds the control id to the options script /// </summary> /// <param name="control">The control.</param> public void AddControlId(WebControl control) { this.Add(control.ID, control.ClientID); } /// <summary> /// Registers all the key/values as an options script for access in the client. /// </summary> /// <param name="page">The page</param> /// <param name="optionsName">Name of the options object</param> public void RegisterOptionsScript(Page page, string optionsName) { if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), optionsName)) { StringBuilder script = new StringBuilder(string.Format("var {0} = new Object();", optionsName)); this.Keys.ToList().ForEach(key => script.Append(string.Format("{0}.{1}='{2}';", optionsName, key, this[key]))); page.ClientScript.RegisterStartupScript(page.GetType(), optionsName, script.ToString(), true); } } }
CustomValidator and the ValidationSummary Control April 26, 2010
Posted by codinglifestyle in ASP.NET, jQuery, Uncategorized.Tags: jQuery, validators
3 comments
ASP.NET validators can be tricky at times. What they actually do isn’t particularly hard, but we have all had issues with them or quickly find their limits when they don’t meet our requirements. The CustomValidator control is very useful for validating outside the constraints of the pre-defined validators: required fields, regular expressions, and the like which all boil down to canned javascript validation. CustomValidators are brilliant as you can write your own client-side functions and work within the ASP.NET validation framework. They are also unique in that they allow for server-side validation via an event.
However, there is a common pitfall when used in combination with the ValidationSummary control. Normally, I would avoid using ShowMessageBox option as I believe pop-ups are evil. However, where I work this is the norm and the problem is the CustomValidator’s error isn’t represented in the summary popup.
When the ASP.NET validators don’t live up to our requirements we really must not be afraid to poke around Microsoft’s validation javascript. It contains most of the answers to the questions you read about on the net (to do with ASP.NET validation… it isn’t the new Bible/42). Quickly we identify the function responsible for showing the pop-up. ValidationSummaryOnSubmit sounds good, but as the name implies it occurs only on submit. However my validator failed after submit and now I need the popup to show what errors occurred. I could see from the script window that this function could be called but programmatically registering the startup script wasn’t working. So I used a jQuery trick to call the function after the DOM had loaded.
So drumroll please, there is the information you want to copy and paste in to your CustomValidator event:
if (!args.IsValid)
{
ScriptManager.RegisterStartupScript(this, this.GetType(), “key”, “$(function() { ValidationSummaryOnSubmit(‘MyOptionalValidationGroup’)});”, true);
}
Now my server-side validation will bring up the ValidationSummary messagebox.