Rewriting the MvcContrib Grid part 2 – New Syntax
February 22, 2009 – 2:40 pm
This is part two of a series on the new MvcContrib grid component.
- Part 1 – Introduction
- Part 2 – New Syntax
- Part 3 – GridModels and GridRenderes
- Part 4 – Limitations of the WebFormsViewEngine
- Part 5 – The Action Syntax
Edit: 28 Feb 09 I have removed the use of Partial views in the latest MvcContrib trunk for certain areas where it didn’t really make sense. The examples below have been updated to reflect this.
The updated version of the MvcContrib grid has a new syntax that uses method chaining and lambda expressions to build a fluent interface. Here is a grid defined with the new syntax:
<%= Html.Grid(Model.People).Columns(column => { column.For(x => x.Id).Named("Person ID"); column.For(x => x.Name); column.For(x => x.DateOfBirth).Format("{0:d}"); }) .Attributes(style => "width:100%") .Empty("There are no people.") .RowStart(row => "<tr foo='bar'>") %>
Compare this to the same grid defined using the old syntax:
<% Html.Grid(Model.People, new Hash(empty => "There are no people", style => "width:100%"), column => { column.For(x => x.Id, "Person Id"); column.For(x => x.Name); column.For(x => x.DateOfBirth).Format("{0:d}"); }, sections => { sections.RowStart(item => { %> <tr> <!-- some custom html for the row start here --> <% }); %>
Note how grid options (eg Empty) are now specified as part of the fluent interface rather than part of the HTML Attributes dictionary (which I always felt was messy). The use of method chaining also means that it is no longer possible to embed html blocks inside the grid definition (eg RowStart). However, you can still achieve the same functionality using the RowStart method and specifying specifying a lambda that will return the HTML string to output.
The column definitions are almost identical identical.
Paging support
Unlike its predecessor, the new grid does not have any logic for dealing with paging. This has all been moved to a separate Pager component. The old grid rendered the pager in its footer, while with the new grid you’d need to write your page like this:
<%= Html.Grid(Model.People).Columns(column => { column.For(x => x.Id).Named("Person ID"); column.For(x => x.Name); column.For(x => x.DateOfBirth).Format("{0:d}"); }) %> <%= Html.Pager(Model.People) %>
Where’s the code?
The code for the new grid is not in the mvccontrib release, so if you want to play with it you’ll need to download and build the source. Alternatively, I’ve put together a custom build that can be downloaded here for the subversion-impaired.
In my next post, I’ll be focussing on extending the grid by using GridModels and GridRenderers.
29 Responses
You are right, this is a big improvement on the old syntax. One question however: on the RowStart method, do you not think that passing in a string of the name of a partial is a bit magic-string-ish?
@Mark
I was originally going to allow you to specify the codebehind file for a partial, eg RowStart() but as codebehind files are no longer generated since the MVC RC I figured it was best to stick with a string. Is there a different approach you’d suggest instead?
Love the Grid component but I really need something which allows me to use a lambda to render the contents of the cell. For example I have a boolean property on my class which I want to render as a tick/cross graphic so I would love something like
column.For(“Disabled”).RenderAs(p => { %>”<img src=”/images/”/>%>});
Then I can pass in a lambda or a delegate function to render the cell contents.
Thanks, Mark
@Mark Perry,
As mentioned in the post, embedded blocks aren’t supported when using the <%= %> syntax. However, you can achieve the same result like this:
column.For(p => “<img src=’/images/’ />”).Named(“Disabled”).DoNotEncode()
The call to DoNotEncode is necessary as by default the grid will HTML-encode the contents of its cells.
HTH
Nice love it.
One thing I would like to do also but don’t know whether it is possible in asp.net mvc is to have a View called “List” in the shared folder which is a strongly typed “PresentationViewData” then use:
Html.Grid(Model).WithModel(new GridModelFrom())
To return the correct grid mapping. Then I don’t need load of views which essentially repeat the same lines of code. Unfortunately I cannot get the View to accept “T” as a generic.
Not sure whether this is by design in MVC. Just seems a bit silly that I need 20 views for all my list object when I could just use a generic one.
Cheers, Mark
I don’t entirely follow what you’re after – are you saying that you want your View to inherit from ViewPage<PersentationViewData<T>> ? If so, this isn’t possible as views cannot inherit from an open generic type.
If you want to discuss this in more detail, do feel free to drop me an email (jeremy [at] jeremyskinner [dot] co [dot] uk)
Email sent.
I was thinking about alternative approaches and I have to say the more I think about it, the more I think you went down the right route actually. Every other approach I can think of would be a compromise in one way or another.
It would be really great to have an Attributes() method at the column level too. I often just need to add a style attribute to set things like width. It is a pain to have to specify a whole do/action to accomplish this.
I realized after I hit enter that width wasn’t a good example as that would be done in the header and HeaderAttributes is there. In looking at my existing codebase colors would be a better example. For example I have a lineitem total column where the column is a different color from the others.
@Dan
Thanks for the suggestion. I will try and get around to adding this at some point this week.
Jeremy
Can you expand a bit on the pager?
I’m getting errors at the moment on it:
‘System.Web.Mvc.HtmlHelper’ does not contain a definition for ‘Pager’ and the best extension method overload ‘MvcContrib.UI.Pager.PaginationExtensions.Pager(System.Web.Mvc.HtmlHelper, string)’ has some invalid arguments
Usage:
Html.Pager(ViewData.Model.CompanyDeposits)
(html got cut off)
Hi Steve
The datasource for the pager needs to be an instance of IPagination. The easiest way to do this is call the AsPagination extension method from inside your controller action (located in the MvcContrib.Pagination namespace).
There are two overloads for the pager: one takes the IPagination instance directly and the other takes a string key to extract it from the viewdata dictionary (I recommend using the former approach).
I’m planning on doing a post on the pager in more detail – just haven’t got around to it yet.
Feel free to email me directly or post on the mvccontrib mailing list if you have any questions.
Jeremy
oh ok, great – thanks Jeremy
And, fantastic work, I’ve converted a few legacy grids over this morning and I really like what you have done with the rewrite!
Cool, glad you like it
Great work here..Keep good stuff coming..
Have a question..How can we name the Grid..since grid is ultimately generate a table in UI is there anyway we can name that table?
Rukshan,
You can use the ‘Attributes’ method to specify the table’s ID:
<%= Html.Grid(Model).Columns(…).Attributes(id => “someId”) %>
If we are using separate GridModel class like
public class UserGridModel : GridModel
{
public UserGridModel()
{
Column.For(u => Id);
Column.For(u => u.Name);
Column.For(u => u.Email);
}
}
How do we add Html.ActionLink in this case as I don’t see any reference to HtmlHelper available here.
Amitabh,
One option is to take the HtmlHelper in the GridModel’s constructor:
public class UserGridModel : GridModel {
public UserGridModel(HtmlHelper html) {
Column.For(u => html.ActionLink(“foo”, “bar”, “baz”)).DoNotEncode();
}
}
…and then pass the HtmlHelper to it when you declare your grid in the view:
< %= Html.Grid(ViewData.Model).WithModel(new UserGridModel(Html)) %>
Hi Jeremy,
When I attempt to apply attributes to the grid, I receive this error:
“CS0103: The name ’style’ does not exist in the current context”
Line 18: })
Line 19: .Attributes(style = “grid”)
Line 20: .Empty(“There are no entries here.”)%>
What do you think could be the problem? It’s trying to resolve ’style’ as a true variable or reference by the looks of it.
Sorry my mistake, it seems I should have had “=>” as a lambda expression.
Are there any overrides for the Pager helper? I am using partial views and would like to have the action link always set to a particular action and not borrowed from the action which drew the (partial).
Also is there any way to customise the controls?
@Milton,
You can customise the link the pager generates by calling the “Link” method.
The grid can be completely customised by writing your own IGridRenderer implementation (http://www.jeremyskinner.co.uk/2009/02/23/rewriting-the-mvccontrib-grid-part-3-gridmodels-and-gridrenderers/ )
I need an example of how to use the GroupBy and Count methods in a GridModel inheritor. My attempt here doesn’t do anything close to what I want. I wanted to group by the CaseId with a column that had the count of the number of items that had that particular CaseId.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcContrib.UI.Grid;
using MvcContrib.UI.Grid.Syntax;
using MvcContrib.UI.Grid.ActionSyntax;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace MvcApplication1.Models
{
public class DiscoverableGridModel: GridModel
{
public DiscoverableGridModel(HtmlHelper html, AjaxHelper ajax)
{
int disc = Column.GroupBy(g => Column.For(x => x.CaseId)).Count();
Column.For(x => x.CaseName);
Column.For(x => disc).Named(“Discoverables”);
Column.For(“Show Discoverables”).Action(x => html.ActionLink(“Show”, “ShowDiscoverables”, x.CaseId)).DoNotEncode();
}
}
}
@Brannon
I don’t understand what you’re trying to do. Please post your question on the MvcContrib mailing list http://groups.google.com/group/mvccontrib-discuss
[...] remember thinking this API was rather cool when Jeremy Skinner presented the syntax on his blog. The syntax became the topic of discussion in a recent Stack Overflow post – Abuse of C# lambda [...]
Hi Jeremy,
I’ve been trying to specify the “class” attribute of the , but I can’t use the hash syntax:
.Attributes(class => “myCssClass”)
because “class” is a C# reserved word, is there any way to do this? (without having to create an IDictionary)
Thanks!
Hi Benjamin,
You need to use the ‘@’ prefix – .Attributes(@class => “foo”)
Jeremy