Setting Telerik’s HTML5 Kendo UI Grid’s Height to 100% when Binding to Remote Data

Update 06/24/2013 – Telerik, Kendo UI Team (Dimo Dimov) has reached out to me (see block quote below), and has taken the time to provide the best practice on how to handle the 100% height for the Kendo UI Web Grid http://jsfiddle.net/dimodi/SDFsz/, please use this approach vs. the approach in the blog post, thanks for visiting…!

I am Dimo Dimov from the Telerik / Kendo UI team. A client recently provided this page as an example of a 100% high Kendo UI Grid. I reviewed the code and the comments and it seems that a lot of people would benefit from some clarifications how percentage heights work.

First of all, please take a look at the following jsFiddle demo, which is similar to the jsFiddle demo above, but is better and simpler in a couple of ways:

http://jsfiddle.net/dimodi/SDFsz/

The example discussed in this blog post is also similar, but has one drawback – it uses hard-coded “magic numbers” in order to workaround the presumably disobeyed web standard requirement regarding percentage heights. Let me explain this requirement in more detail using the new jsFiddle demo as a starting point.

We have the following DOM tree:

First of all, the rule of thumb is that an element with a percentage height should have a parent with explicit height. The rule applies recursively until an element with a pixel height is reached, or until the HTML element is reached, which should have a height as well. This is because percentage heights are always calculated with regard to the set height of the parent element.

Now let’s get back to our example. The Grid should be 100% high and we set such a style. This implies that the parent element () should have an explicit pixel or percentage height. It has none of those, so we apply a 100% height to it. Now we go up and reach the element. It should also have an explicit height, otherwise the and Grid percentage heights will be ignored. So we apply a 100% height to the element as well. Now we have three nested elements that are 100% high and these heights start working. Since 100% high elements should not have vertical borders, margins or paddings, we remove those, as shown in the jsFiddle demo.

With more complex DOM trees the logic is the same – you start with the given element that needs to expand and go up the tree and apply 100% height styles (or any other values) until an element with a pixel height is reached, or until the element is reached. If you have a sequence of nested 100% high elements, removing just one of those heights will cause all the others to stop working. The most common reason for 100% heights to not work is that there is an element with no height, which breaks the sequence of 100% high nested elements.

One thing more to keep in mind is that 100% high elements cannot have siblings, otherwise they will overflow their parent.

This blog post uses the opposite approach compared to the one I describe – the height is calculated from “the outside to the inside”. The only case, in which such an approach makes sense is when the Grid or some of its parent elements have siblings, so 100% heights cannot be applied that easily. Also, this approach can work with parent elements with missing heights, because it relies on the height of the browser window, but I would not recommend this appoach.

Thank you.

I’ve been somewhat wresting with how to set the KendoUI Grid’s height to 100% so that it could fill a user’s screen as much as possible so that it would only show it’s vertical scrollbar only when absolutely needed e.g. when a user’s actual browser real estate is shorter in height than the grid’s content (rows). Those of us that have wired up the Grid binding to remote data e.g. OAuth, JSON, etc.. have been required to explicitly set the Grid’s height.

I’ve visited the Telerik’s forums and googled, unfortunately no working real solution, hence this blog post.

http://www.kendoui.com/forums/ui/grid/dynamic-grid-height.aspx
http://jsfiddle.net/dimodi/4eNu4/33/

If we don’t set the height, the Grid will render as follows:

Note: No height was set for the Grid configuration and it appears as if the Grid has no records when indeed our service is returning records it’s just the Grid’s content div’s height is 0px because we did not explicity set the height, and if we try to set it to a percent 100%, the Grid does not render at all.


    $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            detailTemplate: kendo.template($("#template").html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });

If explicity set the height, the Grid will render as follows:

Note: Line 10, where we are explicitly setting the height, however notice that we have plenty of screen real estate under grid, and if we could get the Grid could take advantage of that empty space we wouldn’t need the scrollbar.


    $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            height: 500,
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            detailTemplate: kendo.template($("#template").html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });

So a quick way we can remedy this is to calculate how much vertical space we have and set the maximum height of the Grid ourselves. By taking the Html document height and offsetting it factoring in our layout e.g. header and footer.


    $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            height: $(document).height() - 350,
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            detailTemplate: kendo.template($("#template").html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });

Great! Our Grid now renders and takes up the full vertical real estate within the browser on the intial load

Last Problem, now when we resize the browswer, the Grid is NOT automatically resizing itself

No problem, we will add a method for resizing the grid and wire this up to the browser resize event with jQuery.


  $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            height: 250,
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            detailTemplate: kendo.template($("#template").html()), detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });
    
    function resizeGrid() {
        var gridElement = $("#grid");
        var dataArea = gridElement.find(".k-grid-content");

        var newGridHeight = $(document).height() - 350;
        var newDataAreaHeight = newGridHeight - 65;

        dataArea.height(newDataAreaHeight);
        gridElement.height(newGridHeight);

        $("#grid").data("kendoGrid").refresh();
    }
    
    $(window).resize(function () {
        resizeGrid();
    });

Now, when grid first loads

Note: Looks great, Grid occupies all the available vertical real estate it can

When user resizes Grid (maximizes browser vertically)

Note: Still Looks great, Grid automatically resizes itself to occupy the new vertical space within the browser after the resize.

Happy Coding…!

Download sample application:

https://skydrive.live.com/redir.aspx?cid=949a1c97c2a17906&resid=949A1C97C2A17906!375&parid=949A1C97C2A17906!361

Telerik’s HTML5 Kendo UI (Grid, Detail Template, TabStrip) with MVC3 and JSON

Update (4/13/2012): I’ve posted a continuation of this blog which includes server side sorting and filteration complete with solution download – Telerik’s HTML5 Kendo UI Grid with Server Side Paging, Sorting & Filtering with MVC3, EF4 & Dynamic LINQ.

After some relentless googling for for a complete example of using Telerik’s new HTML5 Kendo UI Grid and MVC3 with server-side paging, I realized there isn’t an example anywhere on how to implement this including the Kendo UI documentation on Telerik’s site. Reason being is Telerik is really trying to set a strong message that the Kendo UI suite of controls is not coupled to any backend technology, they are really just pure HTML5 controls, all wired up with jQuery that makes request to your choice of restful like services e.g. oAuth (yes, Kendo UI controls supports oAuth out of the box).

So in short they will run pretty much on any browser with mobile gestures built in. I’ve been working with DevExpress ASP.NET WebForms and MVC controls for the past few years and I must admit that their controls, as well as many other 3rd party server-side control suites including Telerik’s can make your application somewhat bulky.

I think this pure HTML5 and jQuery direction from Telerik is a great idea, giving you the power of nice lean controls without the large footprint and clunkiness that your traditional 3rd party server side controls carry and last but not least let's face it, the timing is perfect, everyone is coding client side apps with jQuery especially when using MVC!

I created an empty MVC3 application and wired everything up in the Home/Index.cshtml view, let’s take a look at what’s all required to get a basic Kendo UI Grid up and running.

Add your Javascript and CSS (optional) references


<link href="@Url.Content("~/Content/KendoUi/kendo.common.css")" rel="stylesheet"/>
<link href="@Url.Content("~/Content/KendoUi/kendo.default.css")" rel="stylesheet"/>
<script src="@Url.Content("~/Scripts/KendoUi/kendo.all.js")" type="text/javascript"></script>

Add your Kendo UI Grid jQuery code to instantiate, configure the Grid


<script language="javascript" type="text/javascript">

    $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            height: 400,
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });

Note: the transport property is wired up to our MVC route: controller: ProductsController action: GetAll.

Now, let’s look at what the Grid is sending over the wire when requesting the payload for the Grid to bind by firing up an instance of Fiddler.

So from this we see that the Kendo Grid is making a GET request passing in parameters [take], [skip], [page], [pagesize]. Which now helps us frame up the controller, we now know that we have to provide a controller that accepts a GET request which takes in those parameters in order for us to do nice server side paging, meaning we will only ever return a max of 5 records for the current page you are viewing on the grid instead of returning all rows to the web server or in this case to the client browser and then do our paging processing there, minimizing the payload from our SQL box our web server. With that being said let’s wire up our ProductsController and while we are here, we will go ahead our implement our action for our detail grid which I will go over in just a bit.

Action for the master grid


        public JsonResult GetAll(int skip, int take, int page, int pageSize, string group)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var products = myDatabaseContext.Products
                .OrderBy(p => p.Name)
                .Select(p => new ProductViewModel.Product
                                 {
                                     Completed = p.Completed,
                                     CompletedBy = p.CompletedBy,
                                     CreatedBy = p.CreatedBy,
                                     Name = p.Name,
                                     ProductId = p.ProductId,
                                     ProductType = p.ProductType.Name,
                                     ProductDetails = p.ProductDetails,
                                     Status = p.Status,
                                     Updated = p.Updated,
                                     UpdatedBy = p.UpdatedBy
                                 });

            return Json(
                new ProductViewModel
                    {
                        Products = products.Skip(skip).Take(take),
                        TotalCount = products.Count()
                    },
                JsonRequestBehavior.AllowGet);
        }

Note: I’m using EF 4.3 (which you can get with NuGet, this was chosen because of the super fast prototyping ability you get with EF’s CodeFirst approach, CodeFirst was actually introduced in 4.1, 4.3 has some extra cool Migration features), so if you download the sample app and you’re wondering why there isn’t an *.edmx file, thats why! 🙂

Action for the detail grid


        public JsonResult GetProductDetails(int skip, int take, int page, int pageSize, string group)
        {
            var myDatabaseContext = new MyDatabaseContext();

            var productDetails = myDatabaseContext.ProductDetails
                .OrderBy(p => p.ProducDetailtId);

            return Json(
                new ProductDetailsViewModel
                    {
                        ProductDetails = productDetails.Skip(skip).Take(take),
                        TotalCount = productDetails.Count()
                    },
                JsonRequestBehavior.AllowGet);
        }

Note: Rule of thumb, if you can’t accurately predict how many records will be returned in a service call that returns a collection, always implement pagination! You always want to have the smallest footprint in terms of payload going across the wire, whether it be the payload from your SQL box to your web server, or your web server to your browser or both.

You don’t want:

  • A non-performant app because of large payloads
  • Consumption of all the memory/cpu resources on your web box, for pagination processing on a large collection. Just think if your application is getting thousands of request and your web server is now responsible for pagination processing for all of them.
  • Consumption of all the memory/cpu resources on the client workstation on a large collection for pagination processing

So with that begin said perform pagination processing at the earliest stage, which is on your SQL box, apologize if I’m over emphasizing this, however, I’ve seen this bad practice implemented one to many times.

Notice, the [group] parameter in our action method above (which we are not using at the moment), if you enable server side grouping on the Kendo Grid, it will pass the field (column) that you chose to group on so that you can also perform server side grouping!

Let’s run the application

Great! We have the grid all wired up with server side paging in probably less than 10 minutes, all we had to do was paste in some jQuery configuration code, write an action that returns a list of products with skip and take which is trival with EF4 or pretty much anything that implements IQueryable.

Now let’s revisit wiring up the detail grid which is ProductDetails

Adding a Kendo template for the detail area


<script type="text/x-kendo-template" id="template">
    <br/>    
    <div class="tabstrip">
        <ul>
            <li class="k-state-active">Product Details</li>
        </ul>
    <div>
        <div class="productDetails"></div>
    </div>
    </div>
    <br/>
</script>

This is pretty much the Kendo practice, which is providing a template that the Kendo libraries can interpret and inject into other places, in our case it will read this template and inject it into the Detail area of our grid.

Note: I’m using another Kendo UI control which is the TabStrip control to display a tab,
not neccessary however looks great so I added it to our detail view. 🙂

Add the following (highlighted) to our original jQuery config for our master Grid


    $(document).ready(function () {
        $("#grid").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetAll", dataType: "json"} },
                schema: { data: "Products", total: "TotalCount" }
            },
            height: 400,
            pageable: true,
            columns: [
                    { field: "ProductId", title: "ProductId" },
                    { field: "ProductType", title: "ProductType" },
                    { field: "Name", title: "Name" },
                    { field: "Created", title: "Created" }
                ],
            detailTemplate: kendo.template($("#template").html()), 
            detailInit: detailInit,
            dataBound: function () {
                this.expandRow(this.tbody.find("tr.k-master-row").first());
            }
        });
    });


Note:

  • line 18: specify the Kendo template to use for the detail area
  • line 19: wiring up the detailInit event
  • line 20, 21: wiring up the dataBound event, simply expand the first rown after data is bound for the very first time

Implement the detailInit event


    function detailInit(e) {
        var detailRow = e.detailRow;
        detailRow.find(".tabstrip").kendoTabStrip();

        detailRow.find(".productDetails").kendoGrid({
            dataSource: {
                type: "json",
                serverPaging: true,
                pageSize: 5,
                transport: { read: { url: "Products/GetProductDetails", dataType: "json"} },
                schema: { data: "ProductDetails", total: "TotalCount" }
            },
            height:200,
            columns: [
                { field: "Key", title: "Key" },
                { field: "Value", title: "Value" }
            ]
        });


Note:

  • line 3: inject the Kendo UI TabStrip in the div with Id: tabstrip from our kendo template that we used as our detailTemplate earlier
  • line 10: set service call to controller: ProductsConroller, action: GetProductDetails
  • line 11: map our properties from our entity to properties Kendo Grid, e.g. the Grid expects that your collection is mapped to [data] and that your total record count for that request is mapped to [total]

Notice that in the transport property we are setting the url to the MVC route: controller: Products action: GetProductDetails. Now with that being said we are only getting the precise payload and also retrieving it only on demand or lazy loading if and only if when the user clicks on a particular row to see the details of that row. This is very nice and lean request, not the typical behaivor you get when implementing a traditional ASP.NET WebForms 3rd party control where you have to load and instantiate the entire control tree, rebind almost every parent that leads up to the detail control, so if you haven’t already made the leap to ASP.NET MVC, you better start!

Let’s run the app and see the detail grid in action

There are plenty of resources on the all the configurations you can do on the client side for the Kendo UI Grid, however what I mentioned earier is that there is a lack of any artifacts on how to wire things up on the server side in our case which was MVC3.

Here are a couple of links if your interested in more of the client side configuration including events and API for the Kendo UI Grid.

Happy coding…! 🙂