I've implemented a simple control which renders an abbr
tag in order to use the jQuery TimeAgo plugin.
public class TimeAgoControl : System.Web.UI.WebControls.WebControl
{
[Bindable(true)]
[DefaultValue(null)]
public string Iso8601Timestamp
{
get { return (string)ViewState["Iso8601Timestamp"]; }
set { ViewState["Iso8601Timestamp"] = value; }
}
[Bindable(true)]
[DefaultValue(null)]
public override string ToolTip
{
get { return (string)ViewState["ToolTip"]; }
set { ViewState["ToolTip"] = value; }
}
public override void RenderControl(HtmlTextWriter writer)
{
writer.AddAttribute("class", "timeago");
writer.AddAttribute("title", Iso8601Timestamp);
writer.RenderBeginTag("abbr");
RenderContents(writer);
writer.RenderEndTag();
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.WriteEncodedText(ToolTip);
}
}
This works fine. I can use the above control in a GridView TemplateField
and that also works fine:
<asp:TemplateField>
<ItemTemplate>
<my:TimeAgoControl runat="server" Iso8601Timestamp='<%# Item.LastImportDate.ToString("s") %>'
ToolTip='<%# Item.LastImportDate.ToString("f") %>' />
</ItemTemplate>
</asp:TemplateField>
Naturally, I'd rather not have to type all of the above every time I want to use the control in a GridView, so I thought I'd abstract the above logic into a custom BoundField
where I simply pass the name of the DataField
containing the DateTime
value to be rendered. Simple enough, extend from System.Web.UI.WebControls.BoundField
and override the InitializeDataCell
method, add a control to the DataControlFieldCell
therein, and attach an handler to the DataBinding
event. It works and generates the correct markup:
<td><abbr class="timeago" title="Wednesday, January 29, 2014 16:17">about 23 hours ago</abbr></td>
But when the page does a PostBack, the Control is gone and all I'm left with is
<td>29.01.2014 16:17:17</td>
Please note that the page uses no Ajax features: it contains no UpdatePanels nor ScriptManagers.
I've researched quite a bit, finding this unanswered question as well as this other question which states that one must override the ExtractValuesFromCell
method, which in my case is never called. Here's my implementation
public class TimeAgoBoundField : System.Web.UI.WebControls.BoundField
{
protected override void InitializeDataCell(System.Web.UI.WebControls.DataControlFieldCell cell, System.Web.UI.WebControls.DataControlRowState rowState)
{
base.InitializeDataCell(cell, rowState);
cell.Controls.Add(new TimeAgoControl());
cell.DataBinding += (sender, e) =>
{
var c = (DataControlFieldCell)sender;
//how do I get the TimeAgoControl within this scope? c.Controls.Count is 0
var dateTimeValue = (DateTime?)DataBinder.GetPropertyValue(DataBinder.GetDataItem(c.NamingContainer), this.DataField);
c.Controls.Add(new TimeAgoControl
{
Iso8601Timestamp = dateTimeValue.HasValue ? dateTimeValue.Value.ToLocalTime().ToString("s") : this.NullDisplayText,
ToolTip = DateTimeValue.HasValue ? dateTimeValue.Value.ToLocalTime().ToString("f") : this.NullDisplayText
});
};
}
}
Note that if I add the control not in the DataBind
event handler but rather in InitializeDataCell
itself as suggested here, the Controls
collection is empty (as a consequence, giving the control an ID and attempting to use FindControl
fails returning null). Obviously the DataBind
event is not called on postback, but given that ViewState
is active at this stage of the lifecycle, I would have expected the control to be persisted on postback.
Any pointers would be greatly appreciated. Thanks in advance.