Adding a generic search box to Teleriks ASP.Net MVC Grid
In the new version of the MDT Web FrontEnd, I make use of some components from Teleriks extensions for ASP.Net MVC. Especially the grid, as it comes with all the necessary stuff like sorting, filtering, paging, etc. There is also a free/open source version available, that can easily be added to your own projects using NuGet.
So enough marketing , however, one of the things that I miss in the default implementation is the ability to have a single text box that can be used for searching. On default, the grid adds the ability to search per column with different operators (contains, Begins with, Ends with, etc.). What is nice and enables some more complex searches. But it takes a couple more clicks to use them and pretty often I just want to type in some text and have it search over all content, maybe including columns that aren’t visible or even over content that isn’t even available to the grid at all.
As I’ve seen a couple similar questions and requests about this, I thought it might be helpful to write down and publish a solution on how that could be implemented. I’ve published a working demo project to Teleriks Code library so that you can just Copy&Paste the stuff you need and implement it in your own projects. It’s based on the Default ASP.Net MVC 3 template. I just added the Telerik ASP.Net MVC extensions via NuGet. However it’s written in VB.Net, mainly as I prefer this language and also because there aren’t many examples in VB.Net. So be nice with me
OK, let’s start. The first thing we need is a textbox, where we can type in the string we would like to search for.
1 |
<input type="text" id="GridSearch" /> |
Now we need a controller action, that can take a search string and returns our objects based on that search string. Let’s call it “_Select”
1 2 3 4 5 6 |
<GridAction()> Function _Select(Optional searchstring As String = "") As ActionResult Return View(New GridModel(Of ComputerIdentity)(context.GetAll(searchstring))) End Function |
As you can see, we take an optional string (avoids dealing with nothing) and pass this string to a different function that returns our objects based on that string. How we implement this search, what properties etc. is completely up to ourselves and doesn’t really matter at the moment. In my case I just do a search about all properties/columns. Additionally we decorate this function with the “GridAction” attribute and return a GridModel. That’s Telerik specific and will just enable all the magic for sorting, paging, etc. I’m not digging deeper into this as its already well documented.
Now all we need to do is hook into each call from the grid and add our custom search string to the list of parameters before they are send to our controller. For refresh, sorting, paging and filtering (yes, we could combine this with column based filtering), we can make use of the “onDataBinding” event. This is the necessary code to have the Grid call a javascript function at runtime:
1 2 3 |
.ClientEvents(Sub(Events) Events.OnDataBinding("Grid_onDataBinding") End Sub) _ |
and the corresponding javascript function
1 2 3 4 5 6 7 |
function Grid_onDataBinding(e) { var searchString = $('#GridSearch').val(); e.data = { SearchString: searchString }; } |
Here we just get any value from our search box and add it to the data sent to our action method. If the grid shall also support insert, update and delete operations, we need to hook into two additional events, as “onDataBinding” isn’t called for them. For Insert and Update its “OnSave” and for Delete it’s “OnDelete”. For our purposes, we can target them to a single function. Let’s call it “Grid_onChange”:
1 2 3 4 5 |
.ClientEvents(Sub(events) events.OnDataBinding("Grid_onDataBinding") events.OnDelete("Grid_onChange") events.OnSave("Grid_onChange") End Sub) |
and the corresponding Javascript function
1 2 3 4 5 |
function Grid_onChange(e) { var searchString = $('#GridSearch').val(); e.values.SearchString = searchString; } |
As you can see, there is a small difference in the way how we add the search string information, as we add it to the list of values this time. This would actually be enough to get it working. If we type anything in the search box and click on the grid to refresh, sort, whatever, it would immediately filter the results. We could also add a search button to have it explicitly filter the results. But how about a nicer solution? I want it to automatically filter the values, whenever someone types something in there. We could hook into the onChange event of the textbox, but that doesn’t cover all possible scenarios. Let’s try a different approach.
We define a local variable in our Javascript, that stores the search text that has been used the last time the grid has been updated. Now we just need to regularly check if that value differs from the value in the textbox and if so, force the Grid to refresh.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var OldSearchValue = ''; var timeout = 200; function checkSearchChanged() { var CurrentSearchValue = $('#GridSearch').val(); if (CurrentSearchValue != OldSearchValue) { OldSearchValue = CurrentSearchValue; $('#GridComputers').data('tGrid').ajaxRequest(); } else { setTimeout(checkSearchChanged, timeout); } } |
This function will ensure, that if the text in the search box has changed, it will force the grid to update the data. If not, it will call itself again after the specified timeout to check for changes again. I use 200 ms which “feels” like almost immediately but just tweak it to whatever feels appropriate to you.
Now we need to initiate this function at least one time, so it can call itself after that. The most appropriate time for this is, when the grid has finished loading its data for the first time. For this, we have another Client event, we could hook into called “onComplete”. This will also ensure, that it re-starts this process every time it has been updated. So our Client events definition now looks like
1 2 3 4 5 6 |
.ClientEvents(Sub(events) events.OnDataBinding("Grid_onDataBinding") events.OnDelete("Grid_onChange") events.OnSave("Grid_onChange") events.OnComplete("Grid_onComplete") End Sub) |
and the Javascript function
1 2 3 |
function Grid_onComplete(e) { setTimeout(checkSearchChanged, timeout); } |
Now to make it pretty, I want to have the search box within the Toolbar on top of the grid. Currently the grid doesn’t allow you to mix standard buttons like “Insert” with a custom template to add a textbox. So let’s use some jQuery magic here and simply move the textbox to the toolbar. Normally one would use the Page “load” or “ready“ event, but if you get the grid dynamically via AJAX call, that might not work. Instead we will use the “OnLoad” event of the Grid, that gets called when the grid itself is ready.
1 2 3 4 5 6 7 |
.ClientEvents(Sub(events) events.OnDataBinding("Grid_onDataBinding") events.OnDelete("Grid_onChange") events.OnSave("Grid_onChange") events.OnComplete("Grid_onComplete") events.OnLoad("Grid_onLoad") End Sub) |
and the Javascript function
1 2 3 |
function Grid_onLoad(e) { $('#GridSearch').addClass('search').appendTo($('#GridComputers > .t-toolbar.t-grid-toolbar.t-grid-top')); } |
I also add a custom class “search” to the textbox, so that we can configure a “float: right;” via CSS. This will move it nicely to the right of the toolbar, without to interfere with any of the other buttons in the toolbar. If there is no button in the toolbar, the toolbar won’t be rendered at all and we would be able to move the search box then. In this case just add a custom button to it as a placeholder. And instead of moving the textbox, we replace the placeholder with it. The final view could now look like this:
This is just an initial implementation that can be extended in a lot of ways. As mentioned, a sample project, where you can see all this in action, has been published on Teleriks Code library. It also contains the methods that implement the same for Insert, Update and Delete. And in case you don’t want to download it and just want to see all samples above at once, here the full source of the view. It’s based on a list of computers that will be generated at runtime:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<script type="text/javascript"> var OldSearchValue = ''; var timeout = 100; // Called when the Grid has finished its initial load function Grid_onLoad(e) { // Move Search Box into toolbar and add "search" class $('#GridSearch').addClass('search').appendTo($('#GridComputers > .t-toolbar.t-grid-toolbar.t-grid-top')); } // Called each time the Grid has been updated (paging/sorting/filtering) // re-establish Timeout method function Grid_onComplete(e) { setTimeout(checkSearchChanged, timeout); } // Called each time the grid is bound to a new query // Pass search box text to controller function Grid_onDataBinding(e) { var searchString = $('#GridSearch').val(); e.data = { SearchString: searchString }; } // Called each time the grid has changed (Add/Update/Delete) // Pass search box text to controller function Grid_onChange(e) { var searchString = $('#GridSearch').val(); e.values.SearchString = searchString; } // Checks regularly if the search box has changed // Requests a Grid update on any change function checkSearchChanged() { var CurrentSearchValue = $('#GridSearch').val(); if (CurrentSearchValue != OldSearchValue) { OldSearchValue = CurrentSearchValue; $("#GridComputers").data("tGrid").ajaxRequest(); } else { setTimeout(checkSearchChanged, timeout); } } </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<input type="text" id="GridSearch" /> <%: Html.Telerik _ .Grid(Of ComputerIdentity)() _ .Name("GridComputers") _ .DataKeys(Function(key) key.Add(Function(computer) computer.ID)) _ .DataBinding(Sub(binding) binding.Ajax.Select("_Select", "Home") binding.Ajax.Insert("_Insert", "Home") binding.Ajax.Update("_Update", "Home") binding.Ajax.Delete("_Delete", "Home") End Sub) _ .ClientEvents(Sub(events) events.OnLoad("Grid_onLoad") events.OnComplete("Grid_onComplete") events.OnDataBinding("Grid_onDataBinding") events.OnDelete("Grid_onChange") events.OnSave("Grid_onChange") End Sub) _ .Editable(Sub(editing) editing.DisplayDeleteConfirmation(True) editing.Mode(GridEditMode.PopUp) End Sub) _ .Columns(Sub(columns) columns.Bound(Function(computer) computer.Name) columns.Bound(Function(computer) computer.Description).Hidden(True) columns.Bound(Function(computer) computer.SerialNumber) columns.Bound(Function(computer) computer.MACAddress) columns.Bound(Function(computer) computer.AssetTag).Hidden(True) columns.Bound(Function(computer) computer.UUID).Hidden(True) columns.Command(Sub(command) command.Edit.ButtonType(GridButtonType.ImageAndText) command.Delete.ButtonType(GridButtonType.ImageAndText) End Sub).IncludeInContextMenu(False) End Sub) _ .ColumnContextMenu _ .Pageable(Sub(paging) paging.PageSize(20) End Sub) _ .Sortable(Sub(sorting) sorting.OrderBy(Sub(OrderBy) OrderBy.Add(Function(computer) computer.Name) End Sub) End Sub) _ .ToolBar(Sub(toolbar) toolbar.Insert() End Sub) %> |
Recent Comments