hird party controls can be a great option when building cost-effective client solutions, but there’s always a learning curve that comes with it. It may cost you some significant non-billable time up-front, but will pay dividends down the line.
When I first experimented with Telerik’s MVC Grid control, I thought, “This is so cool! I can just drop this in, and have a really slick UI in minutes!”
When will I learn?
Yes, the Telerik MVC Grid control is very powerful and flexible, but it comes with some significant idiosyncrasies, and requires some workarounds (hacks) for limitations and bugs.
I spent a lot of time over a couple of weeks, implementing an AJAXified master / detail, editable grid. I had to make significant use of search engines, StackOverflow, and the Telerik support forums, often jumping between several articles and form posts at a time to troubleshoot many issues. Unfortunately, the documentation is inconsistent at best, and information is scattered everywhere. I guess that’s all you can expect from a “free” library.
So, I’m writing a detailed article here, in hope that it can be a one-stop resource for others wrestling with similar issues, as well as being a resource I can refer back to for future implementations.
I feel that for the majority of use cases, the best user experience takes advantage of the supported AJAX features of the grid, so my discussion will focus on such an implementation.
There are several ways you can tell the grid control how to databind. The method we’re using here is to pass the view model as a generic type to the control, but to not pass any parameters to the main call. Using the view model (or part of it) as a parameter would cause the grid to be bound when the view is loaded (usually via the Index controller action).
By not passing a parameter, and by just specifying the view model as the generic, causes the view to not be pre-loaded, but will instead cause the AJAX binding to load the data after the page initially loads:
Our grid is editable, so we need to add two critical options. One is the DataKeys option, which is where we specify what uniquely identifies a row we’ll be performing a CRUD operation on, above and beyond the required parameters for the route. By specifying CustomerId, this parameter will be passed in any subsequent AJAX call as a querystring value. If we omit this, the controller action would not receive this piece of data.
You may be wondering how it knows, for instance, what value(s) to pass in for the editable fields we may change when updating or adding a row. You’ll see when we discuss the detail view (this is the master view), that we would pass in those values as an object in the third parameter. I’ll discuss this in more detail in part 3.
Since we’re allowing the user to edit and delete rows, we’ll start out by defining a column dedicated to buttons that will trigger those commands. Although Telerik’s examples show command buttons on the right of each column, most sites and applications seem to show them on the left. We’ll use the Command type option for this first column. Since we’re putting both buttons in the same physical column, we’re specifying multiple statements in the lambda expression; one for the Edit button, and the other for the Delete button.
There are a few display options to choose from. In order to keep the rows lean, we’ve selected to show just the image (since commonly recognized icons are supplied) by using the GridButtonType of Image:
As with other web controls you may be used to, we can also define a template (using the Template option) to totally customize the look of the column, injecting specific HTML for formatting the data beyond the default, but we have no need to do that here. What we are using, though, is a ClientTemplate for laying out the full name of the customer. Don’t confuse ClientTemplate with the Template option also provided by Telerik. It’s important to realize that formatting of these columns is done on the client side when using an AJAX-driven grid, so you must use the ClientTemplate option here.
The grid is intelligent enough to format the column titles by creating separate words based on the camel casing of the model property name, but if you want total control, you can specify the Title option, as we’re doing for the Full Name column. But the second column, AccountNumber, will automatically and properly receive the title “Account Number”.
Of course, we’ll also need a way to add new rows. The grid provides us with a Toolbar option, where we can place an Add button. The context of the Edit and Delete buttons are self-explanatory, so we just used icons for those buttons. But I like to make the Add button explicit, since it’s physically separated from the grid body. So we’re going to use the GridButtonType of ImageAndText. I didn’t like the way the button was formatted by default, so I removed the margin using the ImageHtmlAttributes option:
We do this by locating the element with the first t-grid-add class within the master grid (using the Name we gave the grid – remember, it uses that to create the id and name attributes on the div wrapper tag). Then we overwrite the text. My tweak is what follows – we have to pre-pend the span that defines the Add icon. Yep – this is just about the only way to get this formatted within the grid’s required markup.
We need to specify the ClientEvent option, where we inform the grid of which events to raise, by chaining one or more events to this option:
In the replaceDeleteConfirmation function, above, we’re first grabbing a pointer to the grid itself. Since we’re passing the sender to the function from the event handler via “this”, we can easily get a handle to the correct grid. Then we’re creating a click handler for the delete button (via the t-grid-delete class), and changing the deleteConfirmation setting that isn’t currently exposed very well.
When I first experimented with Telerik’s MVC Grid control, I thought, “This is so cool! I can just drop this in, and have a really slick UI in minutes!”
When will I learn?
Yes, the Telerik MVC Grid control is very powerful and flexible, but it comes with some significant idiosyncrasies, and requires some workarounds (hacks) for limitations and bugs.
I spent a lot of time over a couple of weeks, implementing an AJAXified master / detail, editable grid. I had to make significant use of search engines, StackOverflow, and the Telerik support forums, often jumping between several articles and form posts at a time to troubleshoot many issues. Unfortunately, the documentation is inconsistent at best, and information is scattered everywhere. I guess that’s all you can expect from a “free” library.
So, I’m writing a detailed article here, in hope that it can be a one-stop resource for others wrestling with similar issues, as well as being a resource I can refer back to for future implementations.
I feel that for the majority of use cases, the best user experience takes advantage of the supported AJAX features of the grid, so my discussion will focus on such an implementation.
Supporting Pieces
In order to implement any Telerik MVC Grid control, several pieces need to be in place; most of which are typical for an MVC-based solution:- Adding required Telerik references, etc.
- Implementing the view, with the grid component, itself.
- Implementing several JavaScript functions to handle grid events.
- Implementing a View Model to support the page implementing the grid control.
- Implementing several controller actions to support the control.
@(Html.Telerik().ScriptRegistrar().jQuery(false))
Data Binding
The grid component makes use of a fluent syntax for specifying options. Since without data, a grid is useless, we’ll start our focus there.There are several ways you can tell the grid control how to databind. The method we’re using here is to pass the view model as a generic type to the control, but to not pass any parameters to the main call. Using the view model (or part of it) as a parameter would cause the grid to be bound when the view is loaded (usually via the Index controller action).
By not passing a parameter, and by just specifying the view model as the generic, causes the view to not be pre-loaded, but will instead cause the AJAX binding to load the data after the page initially loads:
- @{Html.Telerik().Grid<CustomerViewModel>()
- .Name("Customers")
- .DataKeys(keys => keys
- .Add(c => c.CustomerId)
- .RouteKey("CustomerId"))
- .DataBinding(dataBinding => dataBinding.Ajax()
- .Select("AjaxCustomersHierarchy", "Home")
- .Insert("AjaxAddCustomer", "Home")
- .Update("AjaxSaveCustomer", "Home")
- .Delete("AjaxDeleteCustomer", "Home"))
Note: In the grid code examples that build on each other, I’m using line numbers to retain continuity. If I break into other code, those examples will have line numbers starting at 1.
The Name option is important, as you’ll see later. It assigns an id attribute to the div tag it creates to wrap the grid. Since some hacks are needed to modify the default behavior of the grid, we’ll be making use of this.Our grid is editable, so we need to add two critical options. One is the DataKeys option, which is where we specify what uniquely identifies a row we’ll be performing a CRUD operation on, above and beyond the required parameters for the route. By specifying CustomerId, this parameter will be passed in any subsequent AJAX call as a querystring value. If we omit this, the controller action would not receive this piece of data.
Important: By default, the grid assumes a DataKey called Id when databinding, even if the lambda expression specifies a different name. If your route expects a different value, you must specify it using the RouteKey sub-option. We’re forcing it to use CustomerId here.
The second critical piece of the puzzle is the DataBinding option. If we were using server-side binding, we wouldn’t require the Select option, since the data would be loaded in the (typically) Index action. But since we’re using AJAX binding, the grid needs to be told how to load the data. Since we have an editable grid, we must also specify the Insert, Update, and Delete options. Quite simply, these specify the action and controller that the Telerik-generated jQuery AJAX calls will use as their URL.You may be wondering how it knows, for instance, what value(s) to pass in for the editable fields we may change when updating or adding a row. You’ll see when we discuss the detail view (this is the master view), that we would pass in those values as an object in the third parameter. I’ll discuss this in more detail in part 3.
Displaying the Grid
Now that we’ve specified the source of our data, as well as the types of operations we plan on using against that data, it’s time to focus on what to display in our grid. Several types of columns are available to display in the grid, and each are defined via lambda expressions.Since we’re allowing the user to edit and delete rows, we’ll start out by defining a column dedicated to buttons that will trigger those commands. Although Telerik’s examples show command buttons on the right of each column, most sites and applications seem to show them on the left. We’ll use the Command type option for this first column. Since we’re putting both buttons in the same physical column, we’re specifying multiple statements in the lambda expression; one for the Edit button, and the other for the Delete button.
There are a few display options to choose from. In order to keep the rows lean, we’ve selected to show just the image (since commonly recognized icons are supplied) by using the GridButtonType of Image:
Next, we’ll display the actual data. In the master grid, we’re only displaying one column. We use the Bound type option to bind this column to what’s defined in the lambda expression.
- .Columns(columns =>
- {
- columns.Command(commands =>
- {
- commands.Edit()
- .ButtonType(GridButtonType.Image);
- commands.Delete()
- .ButtonType(GridButtonType.Image);
- }).Width(80);
- columns.Bound(c => c.LastName)
- .ClientTemplate("<#=LastName#>, <#=FirstName#>" +
- "<#=MiddleInitial#>")
- .Title("Full Name");
- columns.Bound(c => c.AccountNumber);
- })
As with other web controls you may be used to, we can also define a template (using the Template option) to totally customize the look of the column, injecting specific HTML for formatting the data beyond the default, but we have no need to do that here. What we are using, though, is a ClientTemplate for laying out the full name of the customer. Don’t confuse ClientTemplate with the Template option also provided by Telerik. It’s important to realize that formatting of these columns is done on the client side when using an AJAX-driven grid, so you must use the ClientTemplate option here.
The grid is intelligent enough to format the column titles by creating separate words based on the camel casing of the model property name, but if you want total control, you can specify the Title option, as we’re doing for the Full Name column. But the second column, AccountNumber, will automatically and properly receive the title “Account Number”.
Of course, we’ll also need a way to add new rows. The grid provides us with a Toolbar option, where we can place an Add button. The context of the Edit and Delete buttons are self-explanatory, so we just used icons for those buttons. But I like to make the Add button explicit, since it’s physically separated from the grid body. So we’re going to use the GridButtonType of ImageAndText. I didn’t like the way the button was formatted by default, so I removed the margin using the ImageHtmlAttributes option:
Also, since we’re making this a master / detail grid, and the detail will also be editable, it could get a bit confusing if we had two generic Add buttons floating around on toolbars. So we’ll do one further thing to add custom text to our master grid Add button (and we’ll do something similar later for the detail grid). Unfortunately, Telerik doesn’t make this an easy option, so we’ll need to provide a bit of a jQuery hack, which I’ll explain below the code:
- .ToolBar(commands => commands.Insert()
- .ButtonType(GridButtonType.ImageAndText)
- .ImageHtmlAttributes(new { style = "margin-left:0" }))
In this forum post on the Telerik site, someone named Aaron provided part of this hack. But if you’re not using the Text GridButtonType (we’re using ImageAndText), this hack wipes out the icon. So I had to tweak the hack a bit. In the jQuery “document ready” block, we need to dynamically force the button text to our custom text.
- $(document).ready(function () {
- $("#Customers .t-grid-add")
- .first()
- .text("Add new Customer")
- .prepend("
- });
We do this by locating the element with the first t-grid-add class within the master grid (using the Name we gave the grid – remember, it uses that to create the id and name attributes on the div wrapper tag). Then we overwrite the text. My tweak is what follows – we have to pre-pend the span that defines the Add icon. Yep – this is just about the only way to get this formatted within the grid’s required markup.
Event Hooks
The grid’s default “delete confirmation” dialog is too generic for my taste as well, so there’s yet another hack we need to do. This is a good time to introduce the event hooks the grid provides us with.We need to specify the ClientEvent option, where we inform the grid of which events to raise, by chaining one or more events to this option:
We need to create an event handler for OnLoad and OnDataBound.
- .ClientEvents(events => events
- .OnError("onError")
- .OnLoad("onLoadCustomers")
- .OnDataBound("onLoadCustomers")
- .OnEdit("onEditCustomers"))
Note: If you don’t create an event handler for each of the events specified in ClientEvents, the grid will fail. You need to at least create an empty handler for each.
Also, since we’re going to want to customize the dialog for both the master and detail grids, we need to create a JavaScript function to accept the deletion target for the message:I spent a lot of time in the Chrome inspector to figure this out, and then I found this post that confirmed and improved upon my original attempt. These hacks make for a great learning experience, but I just hope Telerik adds some more elegant hooks in future releases. I’d much rather spend my time on the business problem than the plumbing that libraries like this are supposed to help us avoid. These tools are supposed to help us avoid plumbing, but as we’ve experienced with ASP.NET Web Forms, it helps maybe 90% of the time. Paradoxically, with the other 10%, it often feels like we have to plumb deeper we would’ve, if we had started from the bare metal.
- function onLoadCustomers(e) {
- replaceDeleteConfirmation(this, "Customer");
- }
- function replaceDeleteConfirmation(item, itemType) {
- var grid = $(item).data('tGrid');
- $(item).find('.t-grid-delete').click(function (e) {
- grid.localization.deleteConfirmation =
- "Are you sure you want to delete this " +
- itemType + "?";
- });
- }
In the replaceDeleteConfirmation function, above, we’re first grabbing a pointer to the grid itself. Since we’re passing the sender to the function from the event handler via “this”, we can easily get a handle to the correct grid. Then we’re creating a click handler for the delete button (via the t-grid-delete class), and changing the deleteConfirmation setting that isn’t currently exposed very well.
Note: Notice that we’re directly using the localization string here, which is not a best practice. It should only really be used for localization purposes. But since we only plan on US clients in the foreseeable future, this is ok. If we didn’t do this though, our hack would have been even “hackier,” as in the prior example for changing the Add button text. We could have done that using the localization string as well, but I wanted to show the pros and cons of each.
Editing
Since this grid will be editable, the next setting we need is the Editable option. I really wanted to use the InLine GridEditMode setting, because I feel that the user experience for editing is a lot cleaner in this type of grid. But there appears to be a bug in the current version of the control (also affecting the InForm mode). In those modes, it appears the “dirty bit” for tracking modified rows is not being reset correctly. So, upon each row save, all the previously modified rows are unnecessarily re-validated and re-saved as well. Therefore, we’ll have to settle with using the PopUp mode instead, which isn’t too bad:But that leads us to another annoying issue if we’d like to control the title bar of the pop-up. Since there’s no direct way to do this, it requires yet another JavaScript hack. We have to add an OnEdit handler, where we need to locate the pop-up window and change the title based on the edit mode:
- .Editable(editing => editing.Mode(GridEditMode.PopUp))
Another option would be to completely customize the pop-up using Telerik’s MVC Window control instead, but that’s beyond what we need in this example.
- function onEditCustomers(e) {
- var popup = $("#" + e.currentTarget.id + "PopUp");
- var popupDataWin = popup.data("tWindow");
- if (e.mode == "insert")
- popupDataWin.title("Add new Customer");
- else
- popupDataWin.title("Edit Customer");
- }
Other Options
Finally, we want to specify the remaining options for the master grid. We’re displaying 15 rows per page, using the PageSize option within the Pageable option. The KeyboardNavigation option allows for keyboard support, which is something I always try to provide in my Web apps. We also want to make this grid sortable and filterable. We can customize this at the column level, but I’ll show that when we discuss the detail grid. Finally, since I don’t want the grid to stretch completely across the page, we pass in a width using the HtmlAttributes option, similar to the way we do this with the MVC Html helpers.If we didn’t have a master / detail pair of grids, we would just need to chain the Render option to the grid definition and be done with it. But we have a lot more to cover in part 2 of this series before we get into the detail grid. We’ll likely get to the detail grid in part 3.
- .Pageable(pagerAction => pagerAction.PageSize(15))
- .KeyboardNavigation()
- .Sortable()
- .Filterable()
- .HtmlAttributes(new { style = "width:50%;" })
0 nhận xét:
Đăng nhận xét