Currently I have a grid that uses a helper to specify the attributes:
.Filterable(f => f
.Extra(false)
.Operators(o => o
.ForString(str => str
.Clear()
.Contains("Contains")
.StartsWith("Starts with")
.IsEqualTo("Is equal to")
.IsNotEqualTo("Is not equal to")
)
.ForNumber(num => num
.Clear()
.IsEqualTo("Is equal to")
.IsGreaterThan("Is greater than")
.IsLessThan("Is less than")
)
)
)
The filterable data in the grid is encoded, s. o 'a b' is stored as 'a%20b'. I would like to build a custom filter using the helpers that allows the user to filter on 'a b'?
10 Answers, 1 is accepted
Could you explain in a bit more detail what you mean by a custom filter? Do you want to apply a filter without using the Grid filtering menu? Also, in what scenario and where is the space from the filter value encoded? If Ajax binding is used in the Grid, the DataSource passes the data using JSON format and such encoding is not applied to the filter parameters. Or are you using server binding for the Grid?
It would help if you could share the full Grid declaration together with the explanation of the specific problem.
Regards,
Tsvetina
Progress Telerik
I have attached an example of the full definition. As shown in the attached screen-shot I would like for the user to be able to type in 'a b' when the data stored is 'a%20b'. There are no matches for 'a b' but there are matches for 'a%20b'. I want to be able to intercept the filtering process so that I can either transform the filter string that was typed in to 'a%20b' or transform what is being matched into something that includes spaces.
Thank you for you help.
@{
const string gridName = "GlobalAgendaDashboardGrid";
const string tabName = "globalAgendaDashboardTab"; }
@(Html.Kendo().Grid<GlobalAgendaDashboardGridViewModel>()
.Name(gridName)
.Columns(columns =>
{
GridHelpers.FilterableColumn(columns, m => m.Title)
.ClientTemplate(@"" +
"<div style='display: none;'>#: decodeURIComponent(Title) #</div>");
GridHelpers.FilterableColumn(columns, m => m.Description)
.ClientTemplate(@"" +
"<div style='display: none;'>#: decodeURIComponent(Description) #</div>");
GridHelpers.FilterableColumn(columns, m => m.SessionItemId);
GridHelpers.FilterableColumn(columns, m => m.Authors);
GridHelpers.FilterableColumn(columns, m => m.GartnerSpeakers);
GridHelpers.FilterableDropdownColumn(columns, m => m.SessionCategory, "sessionCategory", gridName);
GridHelpers.FilterableDropdownColumn(columns, m => m.RegionHtmlString, "region", gridName).Encoded(false)
;
GridHelpers.FilterableColumn(columns, m => m.Location);
GridHelpers.FilterableDropdownColumn(columns, m => m.StatusHtmlString, "status", gridName)
.ClientTemplate(@"" +
"# if (SessionItemId) { #" +
"<a href='" + Url.Action("Details", "Item") + "/#: SessionItemId #' target='_blank'>#: IsDeclined ? StatusTextWithDeclinedInformation : StatusText #</a>" +
"# } else { #" +
"<a href='" + Url.Action("Details", "EventSeriesItem") + "/#: Id #' target='_blank'>#: IsDeclined ? StatusTextWithDeclinedInformation : StatusText #</a>" +
"# } #").Encoded(false)
;
columns.Template( @<text></text>).Width(120)
.HeaderTemplate("<div style='padding: 3px;margin-left:37px;color: #41708F;'>Action</div>")
.HtmlAttributes( new { @class = "actionMenuTemplateCell",@style="overflow:visible;" })
.ClientTemplate(@"" +
"<div class='btn-group-xs'>" +
"# if (CanEdit) { #" +
"# if (RegionalItemId != null) { #" +
"# if (SessionItemId == null) { #" +
"# if (EventWorkflowStageId == 1) { #" +
"<button id='addToGlobalAgendaButton_#:Key#' class='btn btn-primary' onclick='Gartner.GlobalAgendaTabGrid.promote(#= RegionalItemId #)'>Add</button>" +
"# } #" +
"<button id='removeFromGlobalAgendaButton_#:Key#' class='btn btn-danger' onclick='Gartner.GlobalAgendaTabGrid.decline(#= RegionalItemId #)' # if (IsDeclined) { # disabled # } #>Remove</button>" +
"# } #" +
"# } #" +
"# } #" +
"</div>")
;
GridHelpers.FilterableColumn(columns, m => m.IsDeclined).Hidden() ;
GridHelpers.FilterableColumn(columns, m => m.Id).Hidden() .ClientGroupHeaderTemplate("Title: #= decodeURIComponent(Gartner.GlobalAgendaTabGrid.seriesTitleTemplate(value)) # <br/>Description: #= decodeURIComponent(Gartner.GlobalAgendaTabGrid.seriesDescriptionTemplate(value)) # <br/>Author: #= Gartner.GlobalAgendaTabGrid.seriesAuthorTemplate(value) # <span class='badge badge-light'>#= count#/#= Gartner.GlobalAgendaTabGrid.seriesSizeTemplate(value) #</span>");
})
.AutoBind(false)
.DataSource(dataSource =>
{
dataSource
.Ajax() .Events(events => events.RequestEnd("Gartner.GlobalAgendaTabGrid.onReadRequestEnd"))
.PageSize(25)
.Read(read => read.Action("GlobalAgendaDashboardGridDataSource", "Home")) .Aggregates(aggregates =>{
aggregates.Add(p => p.Id).Count();
})
.Group(g => g.Add(p => p.Id))
;
})
.Deferred()
.Events(events => events.DataBound("function(e) { Gartner.refreshFiltersForGrid('" + gridName + "'); Gartner.buildActionMenu(e);Gartner.GlobalAgendaTabGrid.onDataGridBound(e);}"
))
.Excel(excel => excel
.FileName("Global Agenda Dashboard Grid.xlsx")
.Filterable(true)
.ProxyURL(Url.Action("Export", "Excel"))
.AllPages(true)
)
.Filterable(f => f
.Extra(false)
.Operators(o => o
.ForString(str => str
.Clear()
.Contains("Contains")
.StartsWith("Starts with")
.IsEqualTo("Is equal to")
.IsNotEqualTo("Is not equal to")
)
.ForNumber(num => num
.Clear()
.IsEqualTo("Is equal to")
.IsGreaterThan("Is greater than")
.IsLessThan("Is less than")
)
)
)
.Pageable()
.Resizable(resize => resize.Columns(true))
.Scrollable()
.Sortable()
.Groupable()
.ToolBar(tools =>
{
tools.Template(
@<text>
<div class="show-declined-checkbox-group">
<input class="k-checkbox" id="ShowDeclinedItemsCheckBox" name="ShowDeclinedItemsCheckBox" type="checkbox">
<label class="k-checkbox-label" for="ShowDeclinedItemsCheckBox">Show Declined Items</label>
</div>
<div class="show-appoved-checkbox-group">
<input class="k-checkbox" id="ShowApprovedItemsCheckBox" name="ShowApprovedItemsCheckBox" type="checkbox">
<label class="k-checkbox-label" for="ShowApprovedItemsCheckBox">Items to be Approved</label>
<span id="needs-approval-span" class="badge badge-light"></span>
</div>
</text>
);
})
)
@Html.DeferTypeScriptObject("GlobalAgendaTabGrid", new
{
TabId = tabName,
GridId = gridName
})
@Html.DeferTypeScriptObject("ToggleShowDeclinedItems", new
{
gridSelector = "#" + gridName,
toggleSelector = "#" + "ShowDeclinedItemsCheckBox"
})
@Html.DeferTypeScriptObject("ToggleShowApprovedItems", new
{
gridSelector = "#" + gridName,
toggleSelector = "#" + "ShowApprovedItemsCheckBox"
})
With this configuration, the filtering is taking place on the server, in the GlobalAgendaDashboardGridDataSource method. If you are following our examples, this method accepts a parameter of type DataSourceRequest. You could loop through the Filters collection in this parameter and replace the filter values with ones that are properly formatted to match your data.
For example, you can declare a recursive function that traverses all filters and replaces the filter values:
private
void
RecurseFilterDescriptors(IList<IFilterDescriptor> requestFilters)
{
foreach
(var filterDescriptor
in
requestFilters)
{
if
(filterDescriptor
is
FilterDescriptor)
{
var currentDescriptor = (FilterDescriptor)filterDescriptor;
// you can have a check on the Member name if you need to replace values only for a given field
if
(currentDescriptor.Member ==
"ShipCity"
&& currentDescriptor.Value.GetType() ==
typeof
(
string
)) {
currentDescriptor.Value = currentDescriptor.Value.ToString().Replace(
" "
,
"%20"
);
}
}
else
if
(filterDescriptor
is
CompositeFilterDescriptor)
{
var descriptor = (CompositeFilterDescriptor)filterDescriptor;
RecurseFilterDescriptors(descriptor.FilterDescriptors);
}
}
}
Then, you can call this method on the request param before passing it to ToDataSourceResult():
public
ActionResult Orders_Read([DataSourceRequest]DataSourceRequest request)
{
var formattedRequest = ReplaceFilterValues(request);
var result = Enumerable.Range(0, 50).Select(i =>
new
OrderViewModel
{
OrderID = i,
Freight = i * 10,
OrderDate = DateTime.Now.AddDays(i),
ShipName =
"ShipName "
+ i,
ShipCity =
"ShipCity "
+ i
});
return
Json(result.ToDataSourceResult(request));
}
Regards,
Tsvetina
Progress Telerik
Thank you. This has been most helpful. My problem now is that this line
result.ToDataSourceResult(request)
seems to return an empty result. I look at result and in my case it has 14 rows, but the result of the above is zero rows?
Are you applying filtering logic of your own on the data? The ToDataSourceResult also applies filtering and there could be a conflict between the two queries. Could you show us what your Read controller method currently looks like?
If you want to apply filtering of your own, make sure you do not call ToDataSourceResult afterwards. You can see such an example in this demo:
Grid / Custom ajax binding
Regards,
Tsvetina
Progress Telerik
The Read property in defining the grid looks like:
.Read(read => read.Action("GlobalAgendaDashboardGridDataSource", "Home"))
In that data source I get the filtered data
var filteredData = (IList<GlobalAgendaDashboardGridViewModel>)result.ToDataSourceResult(filterOnly).Data;
Then do a lot of manipulation of the filtered data then finally
var dsResult = filteredData.ToDataSourceResult(request);
var rawResult = new
{
dsResult.AggregateResults,
dsResult.Data,
dsResult.Errors,
dsResult.Total,
needsApproval
};
return Json(rawResult);
I guess I am not sure how to turn this into the proper response if I am not suppose to call ToDataSourceResult again?
Thanks again for the support.
It depends on the manipulation that you do on the data. If you are able to take care of applying the sorting and paging, too (if your Grid uses them), you can just create a new DataSourceResult object and populate it with the result:
var rawResult =
new
DataSourceResult()
{
Data = filteredData.Data,
Total = filteredData.Total
};
return
Json(rawResult);
Regards,
Tsvetina
Progress Telerik
Thanks again.
What do I fill in for Aggregates? And added properties? Currently I do
var rawResult = new
{
dsResult.AggregateResults,
dsResult.Data,
dsResult.Errors,
dsResult.Total,
needsApproval
};
I understand inserting
Data = filteredData.Data,
Total
= filteredData.Total
How do I mimic the current behavior that includes an Errors property, an AggregateResults, and a custom property 'needsApproval'.
If you also need to apply aggregates and possibly other data operations, you either need to revert to using ToDataSourceResult on the data or you need to calculate and pass these values manually.
To keep using ToDataSourceResult, you can try deleting the filter before calling the method a second time:
var filters = request.Filters;
RecurseFilterDescriptors(filters);
var filterRequest =
new
DataSourceRequest { Filters = filters };
var filteredData = (IList<GlobalAgendaDashboardGridViewModel>)result.ToDataSourceResult(filterRequest).Data;
// do subsequent manipulation
request.Filters =
null;
var dsResult = filteredData.ToDataSourceResult(request);
return
Json(dsResult);
Regards,
Tsvetina
Progress Telerik