*** Edited on January 30, 2012 – Forgot that FireFox does not support innerText, so I replaced all references to our best friend jQuery’s text() method. ***
Slight detour… I know I’m behind on posting part three of my series on wresting with Telerik’s MVC Grid control, but a lot has happened since I posted part two. One is Telerik’s release of Kendo UI. There’s been some concern (myself, included) that Kendo UI may eventually replace the Telerik MVC extensions (since there are plans to include some MVC-specific server-side wrappers), but they’re currently denying that. I still plan on posting that third article for completeness sake, but I’m not 100% sure of the MVC extensions future.
Also, the more I use it, and the more comfortable I am working down at the metal with JavaScript, HTML, CSS, and jQuery (not metal, but still a de facto standard), the less enamored I am of such a library. Sure, it provides a useful layer of abstraction, much in the same way ASP.NET Web Pages provides a layer of abstraction for building entire sites. But as my series of articles implies, if you want go deeper than what the abstraction provides, you end up fighting against the tool, and it no longer feels right.
The Challenge
I came upon such a situation today. Maybe I missed something, but struggling so mightily with what I’d expect to be handled by a simple attribute, makes me feel the tool is just getting in the way. I had a request from a client for a master / detail grid to retain its expansion state between operations, such as adding, editing, and deleting rows at the master level. My initial thought was, “This should be easy — it’s probably just a parameter I need to check.” Yet, my experience with the grid gave me an uneasy feeling that it wasn’t going to be so simple.
The First Attempt
So, after not finding such a simple setting, I did some web searching. Of course, the two main resources found were Telerik’s forums (they do have pretty good support), and StackOverflow. Not much there, aside for some related issues which gave some hints. It made sense to hook into the grid’s client events and API. It seemed obvious that I’d need to hook into the OnDetailViewExpand and OnDetailViewCollapse events to keep track of the master row expansion state. So knowing where to capture this info was easy. But before knowing what to capture, I had to see what’s needed to restore the state after a data refresh.
That’s what cost me hours poring through a deep hierarchy of properties. Here’s the issue: I figured I needed to call the expandRow API method to restore the previously expanded rows. This method requires a jQuery object parameter representing the master row. Here’s their documentation:
var grid = $("#Grid").data("tGrid"); // get the first master table row var tr = $("#Grid tbody > .t-master-row:eq(0)"); // use .t-master-row to select only the master rows // expand the row grid.expandRow(tr);
Ok, simple enough, although this is an example of exposing too much gunk under the hood — without their sample code, you’d have to do a lot of trial and error to figure out exactly which Telerik classes to use in order to select the pieces that make up their controls. Ok, ignoring that, it still should have been straightforward. It seemed the good news was that both OnDetailViewExpand and OnDetailViewCollapse received an event parameter giving us access to a masterRow jQuery object field. Since the expandRow call requires such a parameter, I thought I was set.
Nope. Their example worked fine, selecting the row via $(“#Grid tbody > .t-master-row:eq(0)”). But using the retained masterRow reference, absolutely nothing happened. No JavaScript errors, mind you. Just no expansion.
I spent way too much time inspecting every inch of these two seemingly identical structures, single-stepping through both Telerik’s and jQuery’s JavaScript code, and not seeing anything significantly different. Finally, I decided to take a different approach that we’ll discuss below.
The Successful Solution
As mentioned, we’re going to have to hook into the OnDetailViewExpand and OnDetailViewCollapse grid events so we can capture the state of each row. We’ll also need to hook into the OnRowDataBound grid event, because as we traverse through the rows as we bind, we’re going to expand the rows that were previously expanded. Here’s the section of the Razor view page where we define the event hooks we’re going to make use of:
.ClientEvents(events => events ... ... ... .OnRowDataBound("onRowDataBound") .OnDetailViewExpand("onExpand") .OnDetailViewCollapse("onCollapse"))
We’re going to use an array to track the expanded rows. I wrote a few utility functions to support the array:
- addToArray, which we’ll use on expand, and will only add an element if it doesn’t already exist.
- removeFromArray, which we’ll use upon collapse.
- isInArray, which returns true if the element already exists.
You may know a better way to handle this in JavaScript, but these generic and reusable functions work nicely, so we’ll go with it.
The rest of the code is just as simple:
- Since the innerText of each row should be unique, we’re going to use that as the value we push onto the array in the onExpand handler. We can get this value through a field of the masterRow event argument.
- In the onCollapse handler, we’re going to remove this value from the array. We can also make the same call before deleting a master row from the grid, but I don’t show that here.
- Finally, in the onRowDataBound handler, we first need to grab a reference to the master grid. We check if the innerText value is already in the array, and if so, we make a call to expandRow, passing in the row event argument. For some reason, this works fine, although as I mentioned earlier, using supposedly the same object type (the entire e.masterRow event argument) doesn’t work. *Shrug*
var expandedRows = []; function onExpand(e) { addToArray(expandedRows, $(e.masterRow).text()); } function onCollapse(e) { removeFromArray(expandedRows, $(e.masterRow).text()); } function onRowDataBound(e) { var grid = $("#CustomerGrid").data("tGrid"); if (isInArray(expandedRows, $(e.row).text())) grid.expandRow($(e.row)); } function addToArray(arr, value) { for (var i = 0; i < arr.length; i++) { if (arr[i] === value) return; } arr.push(value); } function removeFromArray(arr, value) { for (var i = 0; i < arr.length; i++) { if (arr[i] === value) { delete arr[i]; return; } } } function isInArray(arr, value) { for (var i = 0; i < arr.length; i++) { if (arr[i] === value) return true; } return false; }
Alternatives
I’m becoming less of a fan of these kind of libraries, where a different coding paradigm is used to try to “simplify” things. In my mind, keeping it simple means keeping to well-known paradigms, unless a new one has tremendous benefit. This is why I’m intrigued by a jQuery plug-in called DataTables. It uses basic JavaScript constructs to specify customizations.
Although not a grid, another powerful jQuery plug-in I’ve been using is a treeview control called DynaTree. Although it can be pretty complex, at least it takes advantage of straightforward, standard JavaScript and jQuery constructs.
Upon cursory review, Kendo UI appears to take a more bare-bones approach as well, taking advantage of HTML5, JavaScript, and CSS, allowing for more control, popular for us ASP.NET MVC fans. I’ll look into this deeper, and perhaps write about it in the future. But for now, I’ve invested a lot in the Telerik MVC library for a large project, so I’m sort of stuck with it.
Nice blog! I share your concerns about abstraction but I have worked with these telerik libraries for the past year. Trust me, in a professional environment they have proved very good, the support that Telerik bring to their customers is simply outstanding and the features they keep adding are fantastic. Check out the graph controls.
This brings me on to your worry about the paradigm.. I don’t really understand this question, what paradigm are you concerned with, is it the fact that there are methods on the Telerik controls you dont like?
Because all the others you quote also have methods.. Perhaps it is the controller syntax of using a GridAction, personally I think it works really well?
Trying to implement almost any of their controls never mind a hierarchy grid as you have just done in pure html and jquery that supports filtering, row editing, inserting/adding rows etc would be simply insane. Are you really serious that you think this library is ‘getting in the way’? I would suggest you try doing without a library and then review your opinion 🙂
The very fact that in a few hours of cludging around with arrays in jquery (cant believe you wrote isInArray, what about jquery or filter?!) you have managed to get this working at all says everything for this library (incidentally I would stick the state in a cookie and?)
I have tried some of those other grids and none came close to the ease of implementation and feature set of telerik.
Thanks for your input, Delly. I was wondering when someone was going to call me out on the isInArray function 😉 You’re right. I should have looked at that.
I think my frustration came through in my posts about the Telerik grid. Yes, it’s saved me a lot of time writing stuff from scratch, but it takes me to a point where there’s another cliff. Then I feel I lose a lot of the time I’ve saved by using the control to begin with. It’s probably an exaggeration — I’m sure I still saved a lot of time, overall.
I didn’t mean that I’d rather try doing what the Telerik grid does from scratch. I was just saying I’d rather try a library such as DataTables (or even KendoUI) that’s built to take advantage of jQuery, JavaScript, and CSS. The problem I’m finding with a library like Telerik’s MVC extensions is that in order to do something above and beyond what the default options are, you have to do too much CSI work to investigate undocumented features, and get under the covers. Once or twice for edge cases is ok. But I feel I’ve had to do this too often.
About my concerns about a different paradigm; there isn’t anything I don’t like about the Telerik methods or controller syntax. It’s more of the View syntax that bothers me. It just reminds me too much of ASP.NET Web Pages control syntax, perhaps. It’s just my personal preference.
Perhaps I have been spoilt, I have a 24hr direct line to the Telerik development team and yea thinking about it now, I would have been pretty stuck at times without that. Simply put there support is fantastic and the feature churn vs. bug rate is very good. There is simply no pure js library that comes close in the MVC world as far as I know.
However, because the library only came out about 12 months ago (3/4 official releases) and features are being added all the time there are times where there isnt that rich depth of stackoverflow Q/A that you can just delve into. However the forums and docs are pretty good and actually just inspecting with Chrome tools (use full js not min.js) you can see the datamodel and you have ALL the source code so you can actually step through the code to see what is happening.
I have asked for many features that Telerik have added (sometimes within hours!) or shown me how to do. I have now created my own n-deep hierarchical grid just by extending the hierarchy grid using jquery and some extension methods. That shows that the model is pretty extensible in jquery.
The view syntax is reminiscent of asp.net but only at a glance, you can also create telerik controls in pure javascript.. but actually the .net Fluent UI I find really powerful, especially the way you can mix Razor html sections as templates and pull stuff in from your ViewModel etc.
Ultimately I agree with you that it takes effort to go ‘above and beyond’ but that is not unique to this library, if you look at the issues you will encounter with extending other libraries I think you will find the same problems and probably even less documentation or support at that!
You may be correct that this is not unique to this library. Although I’ve had a lot of success with DynaTree, which is a jQuery plug-in, I have had struggles with that as well, and Chrome’s inspector has been invaluable there.
Telerik makes some great tools, and I know several fantastic people who work with them. They’re a huge sponsor for my user groups and our code camp events. I didn’t intend my posts to come off negative. But beyond my lack of comfort with an extra layer of abstraction, I do feel there’s room for improvement. Though that’s expected, as you mentioned, since these extensions are fairly new.
Cool Mark, totally get where you are coming from and I agree there’s room for improvement. Kind of interested in where this Kendo stuff is going, I am really looking for something to get me into Html 5, this could be it.
Hmm. This isn’t working for me. It looks like all the events are firing as expected, but isInArray returns false. It may be what I am doing. I have a custom command that allows a user to edit details not presented in the grid. When a detail row is updated, the master needs to be refreshed, so I am refreshing the entire grid. After that refresh, no expansion of the master row occurs.
I figured it out. The master row contains sum totals for the detail rows. The value of the row when expanded is not the value of the row when refreshed, ergo that isn’t the “row” in the array. I’ll have to figure out how to use one of the static cell values in the master that would not change between refreshes as the array value to compare against.
rowIndex is the solution to that – instead of using the value of the entire row, I tossed on .rowIndex and that solved it. Thanks for this article!
Not quite sure if this solution is for the issue I am struggling with, thanks for the blog. For some reason OnRowDataBound event is not fired in my code. Hope once I fix that this solution would.
[…] http://markfreedman.com/index.php/2012/01/28/restoring-expanded-row-state-with-teleriks-mvc-grid-con… […]