The elegant Action Syntax in the MVCContrib Grid gives us the Empty()
method. However, the default behavior of MvcContrib.UI.Grid.GridRenderer<T>.RenderHeader()
is to hide the table column headers when the grid is empty. Is there a way to show headers when data is not present that does not require a major refactoring?
Now I have heard of hiding the headers by default and hard-coding something but this is not cool to me.
By the way, this is what is happening under the hood (in MvcContrib.UI.Grid.GridRenderer<T>
):
protected virtual bool RenderHeader()
{
//No items - do not render a header.
if(! ShouldRenderHeader()) return false;
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, 开发者_C百科Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return true;
}
protected virtual bool ShouldRenderHeader()
{
return !IsDataSourceEmpty();
}
protected bool IsDataSourceEmpty()
{
return DataSource == null || !DataSource.Any();
}
You can override the ShouldRenderHeader()
method of the HtmlTableGridRenderer
class.
public class AlwaysRenderHeaderRenderer<T>
: HtmlTableGridRenderer<T> where T : class
{
protected override bool ShouldRenderHeader()
{
return true;
}
}
<%= Html.Grid(Model).RenderUsing(new AlwaysRenderHeaderRenderer<TypeOfModel>()) %>
A side effect of this approach is that when the grid is empty, an empty table body will be rendered instead of a message. Any text provided to Empty()
is ignored. This wasn't a problem for me since I'm manipulating the table on the client side with JavaScript anyway, but be warned.
When inheriting from HtmlTableGridRenderer
you can also override RenderEmpty
to eliminate the problem Brant ran into.
protected override void RenderEmpty()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
RenderHeadEnd();
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
In your custom Grid Renderer (subclass GridRenderer<T>
) use overrides like these:
/// <summary>
/// Renders the <c>table</c> header.
/// </summary>
/// <returns>
/// Returns the negative of the results
/// of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>.
/// </returns>
/// <remarks>
/// The return value of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>
/// in this override has no effect on whether the Grid header is rendered.
///
/// However, this return value is used
/// by <see cref="GridRenderer<T>.Render"/>
/// to run <see cref="GridRenderer<T>.RenderItems"/>
/// or <see cref="GridRenderer<T>.RenderEmpty"/>.
/// </remarks>
protected override bool RenderHeader()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return !base.IsDataSourceEmpty();
}
…
protected override void RenderEmpty()
{
RenderBodyStart();
WriteNoRecordsAvailable(base.Writer, this._numberOfTableColumns);
RenderBodyEnd();
}
Note that WriteNoRecordsAvailable()
is my custom method so it can be ignored.
Finally:
/// <summary>
/// This private member is duplicated
/// in order to override <see cref="GridRenderer<T>.RenderHeader"/>.
/// </summary>
readonly ViewEngineCollection _engines;
…
/// <summary>
/// Initializes a new instance of the <see cref="CrmHtmlTableGridRenderer<T>"/> class.
/// </summary>
/// <param name="engines">The engines.</param>
public CrmHtmlTableGridRenderer(ViewEngineCollection engines)
: base(engines)
{
_engines = engines;
}
Can't you just comment out the check to see if it should render the header. Am I missing something or do you want it to always display the header?
If so then comment out that line.
//if(! ShouldRenderHeader()) return false;
I haven't looks at all the code but from your code snippet it seems like that should work.
A combination of David's and Brant's answers:
protected override bool ShouldRenderHeader()
{
return true;
}
// Oddly Render relies on ShouldRenderHeader to return IsDataSourceEmpty
// so RenderItems will always be called.
protected override void RenderItems()
{
if (IsDataSourceEmpty())
RenderEmpty();
else
base.RenderItems();
}
protected override void RenderEmpty()
{
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
精彩评论