Rewriting the MvcContrib Grid part 2 – New Syntax
February 22, 2009 – 14:40
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
- Part 6 – Sorting
- Part 7 – Auto-generated columns
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.
44 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
Hi there,
I’m using the current MvcContrib release 2.0.34.0 with MVC2 in VS2010 RC. And I want to use the grid in this constellation. So I’ve fot the following mini-grid:
column.For(o => o.OrderID))
.Attributes(style => “width:100%;”)
.Empty(“foo”);
%>
But when run this grid doesn’t show anything. The HTML source doesn’t show anything at the place where the grid should be (empty string).
Is this a known problem resp. is there a workaround?
Btw: Description of the Grid could be updated, many information don’t match anymore.
~ Matthias
Comment function seems to eat my source…
Here it is again without greater/less symbols:
Html.Grid(Model)
.Columns(column => column.For(o => o.OrderID))
.Attributes(style => “width:100%;”)
.Empty(“foo”);
Matthias,
Please ask support questions on the MvcContrib mailing list – please post your problem there.
I have used the following code –
<%Html.Grid(Model).Columns
(column =>
{%><%– –%>
c.UserID).Named(“User ID”);
column.For(c => c.UserName).Named(“User Name”);
column.For(c => c.Login).Named(“Login”);
%>
The code gives me following error –
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS0246: The type or namespace name ‘User’ could not be found (are you missing a using directive or an assembly reference?)
Source Error:
Line 212:
Line 213: [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
Line 214: public class views_cuser_cuserlist_aspx : System.Web.Mvc.ViewPage<IPagination>, System.Web.SessionState.IRequiresSessionState, System.Web.IHttpHandler {
Line 215:
Line 216: private static bool @__initialized;
Source File: c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\d91dc980\59f8cdfd\App_Web_cuserlist.aspx.493e0889.8i5wv5y9.0.cs Line: 214
Aditya,
This sounds like you’re missing a namespace import rather than a problem with the grid.
If i already have helper which returns me image according with parameter (true o false)
I called it like this
and it is returns me
i saw in post above how to hardcore images but if i need image according with conditions what can i do?
if(item.Availible)
column.For(p => “).Named (“A”).DoNotEncode();
else
column.For(p => “).Named(“A”).DoNotEncode();
any ideas?
If i already have helper which returns me image according with parameter (true o false)
I called it like this
and it is returns me
i saw in post above how to hardcore images but if i need image according with conditions what can i do?
if(item.Availible)
column.For(p => “).Named (“A”).DoNotEncode();
else
column.For(p => “).Named(“A”).DoNotEncode();
cant post tags
Bal,
What is “item” in your example? If this is an additional property on your model then you can use Visible:
column.For(x => …whatever…).Visible(Model.Available)
If by “item” you are actually referring to the item currently being rendered then you should put the condition *inside* the column definition:
column.For(x => x.Available ? x.SomeProperty : x.SomeOtherProperty)
If you have more questions, please post on the mvccontrib mailing list instead. Thanks.
question is here:
http://stackoverflow.com/questions/2911046/images-in-mvccontrib-grid
item.Availible it is boolean value in my model
I did have helper which returning images according with this bool value. Html.GetImage(item.Availible)
if item.Availible true i am getting one image if false another.
I have to Execute client site code when a grid is rendered. What I have to do is clear another grid with Ajax. I have a so called N-N relation two grids are chained together. Is there a way when the grid is rendered to execute an Action after that.
Herman,
I’m not entirely sure I follow what you’re trying to do, but is it not just a case of putting some javascript on the page after the grid declaration?
Is there a way to handle check boxes within the grid, whose selection can be sent back to the controller to, for example, store the selected messages into a custom folder for a mail application like gmail?
Shali,
You can place any arbitrary html inside a grid column (see the section on Custom Columns in the grid documentation).