jQuery data API and the ASP.net ListControl
August 31, 2016
.net Programming, Website Design
I am a big fan of jQuery and the data API makes client-side programming much easier since any HTML element can be decorated with custom attributes that contain bits of data. Even using ASP.net WebForms, like I still do for most projects (I know, I know, MVC is Microsoft's flagship and what they are touting as the way to go, but until I can code as fast or faster using its toolset I will continue to use WebForms - with little or no ViewState), adding data attributes is usually quite easy.
However, the list-based controls (DropDownList, CheckBoxList, RadioButtonList) are tough since there is no easy way to decorate each item. You can easily add data bits to the wrapper, but that doesn't do much good as most of the time the data that is truly needed pertains to each individual item. The code below, however, makes adding data attributes to the items almost trivial.
DataApiBindableAttribute
The first place to start is to define how each data attribute will be created. That is the job of the DataApiBindableAttribute class. It specifies the name to output to HTML and the name of the runtime object's property to use for the actual value.
/// <summary>
/// Represents a single bindable jQuery data attribute
/// </summary>
public
class
DataApiBindableAttribute
{
/// <summary>
/// Gets or sets the attribute name (with or without the data- prefix)
/// </summary>
public
string
Name
{
get
;
set
;
}
/// <summary>
/// Gets or sets the name of the field to pull the value from
/// </summary>
public
string
DataValueField
{
get
;
set
;
}
}
DataApiBindableAttributeCollection
Now that we've defined a single attribute, we need to be able to store a collection of them should we need to include multiple attributes. And in this case, our collection also does the work of adding the jQuery data API attributes to each list item. Through the power of Reflection we can query each object that is bound to the List control, extract the necessary value(s) and add the required data attribute(s).
/// <summary>
/// A list of attributes that can be added via data binding
/// </summary>
public
class
DataApiBindableAttributeCollection : List<DataApiBindableAttribute>
{
/// <summary>
/// Binds this collection to a ListControl
/// </summary>
/// <param name="List"></param>
public
void
BindToList(ListControl List)
{
IEnumerable<
object
> en = List.DataSource
as
IEnumerable<
object
>;
if
(en !=
null
)
{
Dictionary<DataApiBindableAttribute, PropertyInfo> props =
null
;
int
itmIndex = 0;
foreach
(
object
o
in
en)
{
if
(props ==
null
)
{
PropertyInfo[] allProps = o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
props =
new
Dictionary<DataApiBindableAttribute, PropertyInfo>();
foreach
(DataApiBindableAttribute attr
in
this
)
props.Add(attr, allProps.Where(pi => pi.Name.Equals(attr.DataValueField, StringComparison.OrdinalIgnoreCase)).FirstOrDefault());
}
ListItem itm = List.Items[itmIndex++];
foreach
(DataApiBindableAttribute attr
in
props.Keys)
{
if
(props[attr] ==
null
)
continue
;
object
val = props[attr].GetValue(o);
if
(val ==
null
)
continue
;
itm.Attributes.Add($
"{(attr.Name.StartsWith("
data-
") ? "
" : "
data-
")}{attr.Name}"
, val.ToString());
}
}
}
}
}
The keys here are:
- Grab a PropertyInfo array of all public instance properties that have a getter (i.e, declared as public object thing {get;}. They can also have a setter, but we don't care.
- As each property is grabbed, we check its name to see if it matches any DataValueField in the list. If so, we associate the DataApiBindableAttribute with the PropertyInfo via a Dictionary.
- Finally, as we loop through each in the data source, we grab the corresponding ListItem, and, if the DataSource object's value is non-null we add the data attribute to the Attributes collection so that the ASP.net engine can render it for us.
Example
Now let's see this in action. We will first define a City class that is used for data binding purposes. Then we will create some data, create a jQuery data attribute and bind both to the list.
public
class
City
{
public
int
ID
{
get
;
set
;
}
public
string
CityName
{
get
;
set
;
}
public
string
PostalCode
{
get
;
set
;
}
}
public
class
Test
{
public
Test()
{
DataApiBindableAttributeCollection attrs =
new
DataApiBindableAttributeCollection();
attrs.Add(
new
DataApiBindableAttribute { Name =
"zip"
, DataValueField =
"PostalCode"
});
List<City> dataSource =
new
List<City>();
dataSource.Add(
new
City { ID = 1, CityName =
"Lakewood"
, PostalCode =
"44107"
});
dataSource.Add(
new
City { ID = 2, CityName =
"Westlake"
, PostalCode =
"44145"
});
dataSource.Add(
new
City { ID = 1, CityName =
"Bay Village"
, PostalCode =
"44140"
});
DropDownList list =
new
DropDownList();
list.DataValueField =
"ID"
;
list.DataTextField =
"CityName"
;
list.DataSource = dataSource;
list.DataBind();
attrs.BindToList(list);
using
(HtmlTextWriter writer =
new
HtmlTextWriter(
new
System.IO.StringWriter()))
{
list.RenderControl(writer);
writer.Flush();
Console.Write(((System.IO.StringWriter)writer.InnerWriter).ToString());
}
}
}
The resulting HTML will be:
<
select
>
<
option
value
=
"1"
data-zip
=
"44107"
>Lakewood</
option
>
<
option
value
=
"2"
data-zip
=
"44145"
>Westlake</
option
>
<
option
value
=
"3"
data-zip
=
"44140"
>Bay Village</
option
>
</
select
>
Wrapping Up
This example shows that with a few lines of code, understanding the power of Reflection and a bit of knowledge of how the ASP.net rendering system works it is actually quite easy to modernize ListControl-based ListItems and make them play nicely with jQuery's data API.