Introducing – MVC Tables

As briefly mentioned here I’ve finally finished a workable version of a sortable, pageable, filterable table framework for ASP.Net MVC. I’ve creatively dubbed this project “MVC Tables”.

I’ve put the code over at GitHub and there is a live example running on the Northwind OData service at http://mvctables.azurewebsites.net/

MVC Tables works with any IQueryable or IEnumerable to render collections of models into configurable table markup.

Quick Example

    public class Person
    {
        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public int Age { get; set; }
    }

Configuration

The following configuration will define a table displaying FirstName, LastName, Age and a hidden input with the Person.Id

    public class PersonTable : MvcTable<Person>
    {
        public override void Configure(IStaticTableConfiguration<Person> config)
        {
            config
                .SetAction("Index", "Customers")
                .HiddenColumnFor(c => c.Id)
                .DisplayForColumn(c => c.FirstName)
                .DisplayForColumn(c => c.LastName)
                .DisplayForColumn(c => c.Age);
        }
    }

The SetAction() method defines the action method responsible for serving the AJAX requests from the MVC Tables JavaScript framework.
This is a fairly basic readonly configuration. Editable columns are available via EditorForColumn(). This method renders  <input/> tags with name attribute values that are compatible with MVC model binding.

Other column definitions include

  • ActionLinkForColumn()
  • DropdownForColumn()
  • HiddenForColumn()
  • PartialForColumn() – (Renders a PartialView into a <td/>)
  • AddColumn() – Takes a lambda that defines an expression whose result will be rendered into the cell.

Each Column definition method has an override allowing for further customization of that column.

    public class PersonTable : MvcTable<Person>
    {
        public override void Configure(IStaticTableConfiguration<Person> config)
        {
            config
                .SetAction("Index", "Customers")
                .HiddenColumnFor(c => c.Id, cfg => cfg.Hide())
                .DisplayForColumn(c => c.FirstName)
                .DisplayForColumn(c => c.LastName, 
                                  cfg => cfg
                                            .IsSortable(false)
                                            .SetHeaderText("Surname")
                                            .SetCellCssClass("name-column"))
                .DisplayForColumn(c => c.Age);
        }
    }

These MVCTable configurations are bootstrapped from global.asax like this:

    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            ConfigureMvcTables.InTheSameAssembly.As<MvcApplication>();
        }
    }

Controller Infrastructure

Each table instance requires an Action Method to serve data back to the client.
It would normally look something like this:

    public class CustomersController : Controller
    {
        public ActionResult Index(TableRequestModel request)
        {
            IQueryable<Customer> customers = _customerRepository.Query();
            return TableResult.From(customers).Default(request);
        }
    }

Note that passing IQueryable to the TableResult.From() method allows the MVC Tables framework pass sorting/filtering/paging responsibilities to your underlying IQueryable provider (Database/OData etc).. Using an instance of IEnumerable will work, but may have a performance cost when doing sorting etc via LINQ to Objects in memory.

Show me the Markup!

@{
    var table = Html.MvcTable(() => new PersonTable());
}
<div id="Result">
    <h4>Customers Table</h4>
    @table.Table()
    @table.Pagination()
</div>

Note the Pagination() method. This will render a pagination control for your table.

Configuration Pagination

The below shows the default pagination configuration.

    public class PersonTable : MvcTable<Person>
    {
        public override void Configure(IStaticTableConfiguration<Person> config)
        {
            config
                .SetAction("Index", "Customers")
                .ConfigurePagingControl(p => p.
                                                SetActiveClass("active")
                                                .SetFirstPageText("<<")
                                                .SetLastPageText(">>")
                                                .SetPreviousPageText("<")
                                                .SetNextPageText(">"))
                .HiddenColumnFor(c => c.Id, cfg => cfg.Hide())
                .DisplayForColumn(c => c.FirstName)
                .DisplayForColumn(c => c.LastName, 
                                  cfg => cfg
                                            .IsSortable(false)
                                            .SetHeaderText("Surname")
                                            .SetCellCssClass("name-column"))
                .DisplayForColumn(c => c.Age);
        }
    }

So download the code and the sample project and have a look. There is a nuget package (thanks to Mike) also available here: https://www.nuget.org/packages/MvcTables

Advertisements

15 thoughts on “Introducing – MVC Tables

  1. Pingback: MVC Tables | markt.

  2. Hi, Thank-you very much for this project, I love everything about it, and it rocks!

    I do have one problem though. I hope you can help!

    I need to Filter data on page-load.

    Ie. Viewing an Order, you display all the order details and a filtered list of Orderlines that is already filtered to only show the current order’s Order-lines.

    At the moment if you have the filter parameter on your viewmodel, that is passed to the Async Action on the controller, all values come through as null until the filter value is actually changed.
    but in this case the user will never change any filter values as he always needs to see only the current Order’s Order-Lines

    thank you.

    • Hi Gerrie,

      Can you post your controller action here please? It seems to me (right off the bat) that you should do the filtering in your action method…

      public ActionResult GetOrderLines(OrderLinesRequest requst, TableRequestModel tableRequest)
      {
      var ctx = new MyDbContext();
      var lines = ctx.OrderLines.Where(line => line.OrderId == request.OrderId); // This Where clause should do the filtering.
      return TableResult.From(lines).Build(tableRequest);
      }

      Hope this helps.

      • Hi mtranter, thank you for the reply, this is exactly what i want to do but this is not working for me.
        the model that is passed to the controller in my case model.CaveID comes through as 0 (zero) unless i put a textbox on the view, and change the ID in the textbox to something else, then the autoupdate fires and the new ID is successfully passed to the model parameter in the controller.

        this is my MVCtables controller:

        public ActionResult ListVisitHistories(TableRequestModel request,VisitHistoriesViewModel model)
        {
        /*
        model.CaveID value is 0 instead of 1
        unless value is manualy changed bythe user to say 2,
        then it comes through as 2
        */
        IQueryable vh = db.VisitHistories.Where(p => p.CaveID == model.CaveID);
        return TableResult.From(vh).Build(request);
        }

        the page that the table is displayed on is loaded from the index action of the controller:

        public ActionResult Index(int id)
        {
        var vm = new VisitHistoriesViewModel();
        /* value of id is 1 here. all visitHistory records should be filtered to where
        vm.CaveID == 1 */
        vm.CaveID = id;
        return View(vm);
        }

        the MvcTable class for this table:

        public class VisitHistoryTable : MvcTable
        {
        public override void Configure(IStaticTableConfiguration config)
        {
        config.SetAction(“ListVisitHistories”, “VisitHistories”)
        .SetCssClass(“table table-striped”)
        .SetDefaultPageSize(5)
        .HiddenColumnFor(c => c.CaveID, cfg => cfg.Hide())
        .HiddenColumnFor(c => c.VisitHistoryID, cfg => cfg.Hide())
        .DisplayForColumn(c => c.VistitDate);
        }
        }

        finaly my view is something like this:

        ————————————————————

        model CaveRegister.Web.Models.VisitHistoriesViewModel
        @using CaveRegister.Web.MVCTableModels;
        @{
        ViewBag.Title = “Visit History”;
        Layout = null;
        var table = Html.MvcTable(() => new VisitHistoryTable());
        }

        Visit History

        @Html.ActionLink(“Create New”, “Create”, new { id = Model.CaveID })

        only once i change the value of this textbox below does the CaveID get to the controller as something other than zero.
        but i would like it to already filter on the CaveID when the page was rendered.

        @table.TextBoxFilter(“CaveID”, new { text=Model.CaveID})
        @table.Table()
        @table.Pagination()

        thank you for your time, i really appreciate it.

      • Hi Mark thank you for the fix, i still have one problem however.

        The page now loads correctly filtered, but if you use the pager to go to page 2, the filter value is lost again.

        unless if you put a filtertextbox on the page in a form, and do a submit and then after the submit the paging works with the filter.

  3. Hi Mark,

    I’ve just updated from 1.0.20 to 1.0.49 and my tables no longer work – any ideas why?

    All I wanted to do was set the page size but it looks like the .SetDefaultPageSize wasn’t available in the old version.

    Cheers,
    Pete

  4. I wanted to apply the css class into the ul tag that it’s inside the div container on pagination control, but I didn’t find anything yet on the code of how to do it, Could you please give a clue?, by the way excellent project and thanks for sharing on!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s