How to scroll the detail row of a Kendo Grid into view on select

I decided I liked the idea of only having one detail pane at a time in my web grid. It gets too cluttered having a number of them open. I got feedback from my users that when selecting a row towards the end of the screen, they had to then scroll to see all the details.

I did a simply time cost analysis and instead of changing the UI to show the data in a popup, it was faster and cheaper just to scroll down the page. Simple, eh.

Here is the script to do a scroll when a row is selected in the Kendo Grid to ensure the detail pane is visible.

  function engagementRowSelected(e) {

            var eventTarget = (event.target) ? $(event.target) : $(event.srcElement);

            var grid = $("#engagementsContainer").data("kendoGrid");
            var gridSource = grid.dataSource;
            var selectedRow = this.select()[0];

            var position = $(selectedRow).position();
            $(window).scrollTop(position.top);

        }

I left out all the code that does the selection, and forcing to display only one row at a time. If that is something people want to see or use, I can post that as a simple demo later.

Happy coding!

Fix: Kendo Grid Template gives error “Unable to get value of the property ‘replace’: object is null or undefined”

I got the lovely unhelpful error “Unable to get value of the property ‘replace’: object is null or undefined” from the Kendo Grid when I added a template to a column. This was a completely useless error that led me to staring at the javascript stack and walking through the minified code. Which, by the way, is very painful to look through obfuscated minified code.

After entirely too long analyzing the code, I finally figured out the root cause. I had an ever so small typo in my template retrieval declaration and it was returning back a null template. Why on earth didn’t the Telerik guys NULL check this and fire a friendly error? All of two lines of code would have saved me some trouble.

See, javascript is entirely too friendly to typing mistakes.

Here is the errant code:

    {
                             title: "Planning", width: "100px",                                       
                             template: kendo.template($("myPortfolio_PlanningTemplate").html())
                         },

Here is the fixed code.

    {
                             title: "Planning", width: "100px",                                       
                             template: kendo.template($("#myPortfolio_PlanningTemplate").html())
                         },

Painful lesson in remember to put in the hashtag for ID lookups. Easy to forget when in a hurry.

Happy Coding!

How to add complex headers to a Kendo grid using simple jQuery javascript

I was trolling through the horrible documentation provided by Telerik for the kendo grid. I needed to create a complex header. Basically, a two row header so that I could group columns in a visually meaningful way.

simple example:

I spent far too much time reading how it couldn’t be done. A light bulb finally hit me, this is html. Yeah, flexibility abounds if you can be clever. I simply opened up the old browser developer tool and looked at the generated html for the grid.I then realized, this is a simple table, why not just inject another table row.

So, here is the easy way to add another row to your kendo grid and make a nice complex header. You can use this technique to go to town on adding additional features to your grid as well.

//create grid.

function BuildMyActionsList(tasks) {
            var list = $("#tasksContainer");
            list.empty();

            list.kendoGrid(
                {
                    dataSource: {
                        data: tasks,
                        schema: {
                            model: {
                                id: "Id",
                                fields: {
                                    Name: { type: "string" },                                    
                                    LessThanEGAs: { type: "number" },
                                    GreaterThanEGAs: { type: "number" },
                                    LessThanRisks: { type: "number" },
                                    GreaterThanRisks: { type: "number" },
                                    LessThanCNs: { type: "number" },
                                    GreaterThanCNs: { type: "number" },                                    
                                    ReportSignDate: { type: "date" }
                                }
                            }
                        },
                        //     pageSize: 20
                    },
                    dataBound: addExtraStylingToTasksGrid,
                    filterable: true,
                    scrollable: true,
                    sortable: true,
                    columns: [
                        {
                            field: "Name", title: "Engagement", width: "175px",
                            filterable: {
                                extra: false,
                                operators: {
                                    strings: {
                                        startswith: "Starts with",
                                        eq: "Is equal to",
                                        neq: "Is not equal to"
                                    }
                                }
                            }
                        },                        
                        {
                            field: "LessThanEGAs", title: "EGA's", width: "80px"
                        },
                        {
                            field: "GreaterThanEGAs", title: "CN's", width: "80px"
                        },
                        {
                            field: "LessThanRisks", title: "Risk's", width: "80px"
                        },
                        {
                            field: "GreaterThanRisks", title: "EGA's", width: "80px"
                        },
                        {
                            field: "LessThanCNs", title: "CN's", width: "80px"
                        },
                        {
                            field: "GreaterThanCNs", title: "Risk's", width: "80px"
                        },
                        {
                            field: "ReportSignDate", title: "Report Signing", width: "125px", filterable: false                            
                        }
                    ]
                });

            list.find("thead").first().prepend("<tr><th></th><th class='lessThanDaysGrouping' colspan='3'>&lt;= 2 Days</th><th  class='greaterThanDaysGrouping' colspan='3'>&gt; 7 Days</th><th></th></tr>");

        }

Notice , the line at the end. This is where we search the table for the header and then prepend the row. Just make sure your new row definition aligns with the same column structure as your table. If you need help with this, search on html tables on google.

Line of code that does the magic:

  list.find("thead").first().prepend("<tr><th></th><th class='lessThanDaysGrouping' colspan='3'>&lt;= 2 Days</th><th  class='greaterThanDaysGrouping' colspan='3'>&gt; 7 Days</th><th></th></tr>");

 

Happy Coding!

 

How to display a complex type/list in a Kendo UI Grid client side using a simple template

I had the need to display multiple values from a list that was attached to each row. Simply put, I had a list of Roles the user could be in for each record. I wanted to display them delimited. Turns out, not that hard.

I at first was not loving the idea of using the Telerik kendo grid. I preferred to roll my own html using javascript alone. Since this is a large team based project, I decided to use a more consistent approach and utilize the Kendo library. Painful at first, since the documentation sucks more than an industrial vacuum.

The beauty lies in the power of the template. Telerik is nice enough to let you use powerful javascript in the templates, which makes it wonderful to configure and tailor each cell to your specifications. For this post, the need was to write a pipe delimited list to the cell of all the role names.

Here is the magic. The template I created. You obviously need to tailor for your field names.

    <script id="rolesColumnTemplate" type="text/x-kendo-tmpl">    

        # for(var iRoleIndex=0;iRoleIndex< Roles.length;iRoleIndex++){ #
        # if(iRoleIndex==0){  #
        #: Roles[iRoleIndex].Name #
        # } else { #
        #: " | " + Roles[iRoleIndex].Name #
        # } } #
    </script>

You need to bind it to a column which is also easy.

  columns: [

                        {
                            field: "Roles", title: "Roles", width: "80px",
                            template: kendo.template($("#rolesColumnTemplate").html()),
                            filterable:
                                {
                                    operators: {
                                        string: {
                                            eq: "Is equal to",
                                            neq: "Is not equal to"
                                        }
                                    }
                                }
                        }
                    ]

Notice how easy that was. Well, easy once you know it.  Not easy if you had to figure it out on your lonesome.

Happy Coding!

How to set specific filter options with the Kendo UI Grid in Javascript!

I spent about an hour trying to decipher the cryptic documentation that Telerik provides. It’s really difficult to use, especially with hundreds of method on one page and not even organized. Clicking each property should give access to its member properties and be navigable heirarchicaly. Well, its not.

Anyway, rant over. I was trying to limit the options for a filter on one of my columns. Seems easy enough, right? Well, I kept pasting the stupid example code in and nothing happened.

It turns out, that you can indeed set column specific filter options. Which made me happy, but you need to configure the filter options based on the data type of the column. Yeah, stupid to me. If I am including the names, which are generic across most of the types, this should have been easier.

Take note of the member property you set. See, one instance is called strings. One is called number. I love the inconsistency with the pluralization too, so fun.

So, here is an example of a string column.

 {
                            field: "EngagementName", title: "Engagement", width: "175px",
                            filterable: {
                                        extra: false,
                                        operators: {
                                            strings: {
                                                    startswith: "Starts with",
                                                    eq: "Is equal to",
                                                    neq: "Is not equal to"
                                            }
                                        }
                                    }
                        },

Here is an example of a number(int) column.

{
                            field: "SystemType", title: "Type", width: "80px",
                            template: function(dataItem) {
                                return systemTypeToString(dataItem.SystemType);
                            },
                            filterable:
                                {
                                    ui: systemTypeFilter,
                                    operators: {
                                        number: {
                                            eq: "Is equal to",
                                            neq: "Is not equal to"
                                        }
                                    }
                                }
                        },

Look through the docs and you will see there is a date, enums, etc. Just make sure to match that property name to the type of the column.

In case that confused you, here is a full column definition with filters.

 
  columns: [
                        {
                            field: "EngagementName", title: "Engagement", width: "175px",
                            filterable: {
                                        extra: false,
                                        operators: {
                                            strings: {
                                                    startswith: "Starts with",
                                                    eq: "Is equal to",
                                                    neq: "Is not equal to"
                                            }
                                        }
                                    }
                        },
                        { field: "EntityName", title: "Entity",  filterable: false},
                        {
                            field: "SystemType", title: "Type", width: "80px",
                            template: function(dataItem) {
                                return systemTypeToString(dataItem.SystemType);
                            },
                            filterable:
                                {
                                    ui: systemTypeFilter,
                                    operators: {
                                        number: {
                                            eq: "Is equal to",
                                            neq: "Is not equal to"
                                        }
                                    }
                                }
                        },
                        {
                            field: "DateAssigned", title: "Assigned", width: "125px", filterable: false,
                            template: function(dataItem) {
                                //var taskDate = new Date(dataItem.DateAssigned.match(/d+/)[0] * 1);
                                var taskDate = new Date(dataItem.DateAssigned);
                                var offset = new Date().getTimezoneOffset();
                                    taskDate.setMinutes(taskDate.getMinutes() - offset);
                                      return  humaneDate(taskDate);
                                }
                        }
                    ]

Happy coding!

EntityDataSource and Relationships, why oh why did you not bring along my relationship?

I spent about 3 hours cursing up a storm over some data binding I was doing using the EntityDataSource against my Entity Framework context. I was feeling lazy and using the declarative data sources to bind against my grid for some automatic operations.  So, should be easy right? Slap on the data source, bind to the grid, add the other data source for the lookups and bind that to the column.

So why was my column always showing the wrong value? Hmm….It was always the first value in the list? hmm…So I thought my grid was defective and went down the route of beating it up. The kicker was I could edit the record, and the newly set value would go into the database. It would never reflect on the grid though. This was rediculously annoying.

I finally gave up and created a new data source and ran my own Linq on the context to pull the objects. Thats when the Ah HA! moment came. I typed my INCLUDE off the context to pull the relationships in.

WAIT! You tell me. Wasn’t that on the data source setup wizard screen when you said pick all the properties? NO. No it isn’t. All the relationships are missing as selectable items on that screen. I guess the EntityDataSource is not usable in these scenarios then, right?

Wrong. After banging your head on the wall a few more times, you gingerly go to the properties window of the EntityDataSource object you created. You delicately go to the Include property and type in the name of the property.

What a pile of crap! I spend 3 hours and multiple rewrites to figure out I could have typed the 8 characters in on the property window and been good to go.

Well, it all works now. I just wish that option would have been in the wizard. If I am going the lazy route with declarative bindings, I want to be able to go full lazy.