jQuery'ish Selector for WebControls [recursive FindControl]

Yesterday I wrote about "Selectors for WebControls - Querying into the Control hierarchy on the serv...
jQuery'ish Selector for WebControls [recursive FindControl]Yesterday I wrote about "Selectors for WebControls - Querying into the Control hierarchy on the server" and since then I've refined the concept pretty much.



Very often you end up writing a lot of "really ugly foreach code" when you are databinding WebControls like the ASP.NET Repeater and the GridView. This is because you don't have the controls within these statically typed into your .ASPX page. What's even worse is that this is one of the few places where Mono and .Net seriously diverges because they build their Control hierarchies from .ASPX markup different.

.Net will for instance always inject a Literal control for *every-single* white-space area you've got in your markup while Mono will not. Like for instance the code below...


<asp:Panel ... >
<Button ... />
</asp:Panel>


...will build this on .Net;

Panel
Literal
Button


While it will on Mono build this;

Panel
Button


This makes code like this;

protected void BtnInsideRepeaterClicked(object sender, EventArgs e)
{
Button btn = sender as btn;
(btn.Parent.Controls[someIntegerValue_1_or_0] as Label).Text = "some text value";
}

...fissle on Mono or .Net depending on the value you assign to "someIntegerValue_1_or_0"! If you assign 1 it will work only on .Net and if you assign 0 it will work only on Mono...!


ASP.NET WebControls version of "Sizzle" to the rescue ;)


Sizzle is a project started by John Resig, the creator of jQuery to create a CSS selector and it's *extremely* hyped in these days. This is because all the JavaScript DHTML libraries are currently discussing putting it into the core of their selector engines.

Therefore we need a version for WebControls on ASP.NET - kind of like a "jQuery CSS selector engine for ASP.NET" ;)

RaSelector, the WebControl version of CSS selectors ("Sizzle" for ASP.NET - server-side)

/*
* Copyright 2008 - Thomas Hansen thomas@ra-ajax.org
* This code is licensed under the MIT X11 license
*
*/

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;

namespace RaSelector
{
/**
* Helper class for doing selector queries on Controls to retrieve specific
* Controls in Page hierarchy. Kind of like a CSS selector only for the server-side.
* This class can be very handsome in scenarios where you have GridViews or
* Data Repeators which are dynamically databound and you for some reason cannot
* use the ID directly or something but still need to traverse server-side Control
* hierarchy to find a specific control...
*/
public static class Selector
{
/**
* Recursively searches Control hierarchy for matches for Predicate and returns it as T
*/
public static T SelectFirst<T>(Control from, Predicate<Control> predicate)
where T : Control
{
if (predicate(from))
return from as T;
foreach (Control idx in from.Controls)
{
T tmpRetVal = SelectFirst<T>(idx, predicate);
if (tmpRetVal != null)
return tmpRetVal;
}
return null;
}

/**
* Recursively searches Control hierarchy for the first control that
* matches the type of T and returns it as T
*/
public static T SelectFirst<T>(Control from)
where T : Control
{
return SelectFirst<T>(from,
delegate(Control idx)
{
return idx is T;
});
}

/**
* Recursively search Control hierarcy for ALL controls that
* matches the given Predicate and returns them as T
*/
public static IEnumerable<T> Select<T>(Control from, Predicate<Control> predicate)
where T : Control
{
if (from is T)
{
if (predicate(from))
yield return from as T;
}
foreach (Control idx in from.Controls)
{
foreach (T idxInner in Select<T>(idx, predicate))
{
yield return idxInner;
}
}
}

/**
* Recursively search Control hierarcy for ALL controls that
* matches the type of T and returns them as T
*/
public static IEnumerable<T> Select<T>(Control from) where T : Control
{
return Select<T>(from,
delegate(Control idx)
{
return idx is T;
});
}
}
}



So how does it work?


Imagine the first code sample in this blog turning into this;

protected void BtnInsideRepeaterClicked(object sender, EventArgs e)
{
RaSelector.SelectFirst<Button>((sender as Control).Parent).Text = "some text value";
}

Pretty cool, huuh...! ;)

And the above will work BOTH on Mono *and* .Net versions of ASP.NET. Without any "hacks" to get the index right. This is because it will just silently let the Literal control pass and not return anything before it finds the first (breadth first search) control which is of type "Button"...

And the above sample is *REALLY* simple. Often you find yourself doing things like this;

LinkButton btn = null;
foreach (Control idx in ctrl.Controls)
{
if (idx is Panel)
{
foreach (Control idx2 in idx.Controls)
{
if (idx2 is LinkButton && (idx2 as LinkButton).CssClass == "come_css_class")
btn = idx2 as LinkButton;
}
}
}


*13* lines of code to get to ONE control...!!!

The complete sample above can in fact be reduced into this by using the RaSelector class;

LinkButton btn = RaSelector.SelectFirst<LinkButton>(ctrl);


Which reduces your code by more then 90%...!

Pretty cool, huuh...! ;)

Stay tuned, this stuff is coming out on Friday ;)

Addendum;
As I Google the web it occurs to me that a LOT of people are having troubles with this, but they refer to it as recursive FindControl so therefore I updated the header of this blog to include that sentence too :)


.t


Published Thu 4.Dec 08 - viewed 902 times - bookmarked 0 times

/.~ polterguy.blogspot.com


Header of Comment
Comment

Commenting as...


  • IDWed 2.Jun 10Anonymous Coward(-2)
    • Re: IDMon 7.Jun 10polterguy.blogspot.com(3)
2 comments in article...





@ra_ajax_thomas
There are 174 articles, 163 comments and 7 registered users around here