Hi there,
This is a fairly common scenario which others have posted about but can't seem to find a solution. I have a paged Kendo grid loaded from a remote datasource. When the page loads, optionally there is an ID in the querystring representing the ID of a row. I need the grid to go to the page of the grid for that ID.
The approach I'm taking is that in the dataBound event, query the datasource to get the index of the row with that ID, and then use that along with the page size to work out which page to goto. However, I can't seem to find my ID in the datasource if it isn't on the currently visible page. Here is my jQuery code to try and get the index :-
//// jump to page of querystring ID
var ds = $('#grid').data('kendoGrid').dataSource;
var view = kendo.data.Query.process(ds.data(), {
filter: ds.filter(),
sort: ds.sort()
}).data;
var index = -1;
// find the index of the matching dataItem
for (var x = 0; x < view.length; x++) {
if (view[x].QuoteID == "11847") { // hard-coded here for testing but will become a variable from querystring
index = x;
break;
}
}
Is my approach correct, and if so how can I make this work? I need to be able to access the other items in the datasource not on the current visible page. A code example would be appreciated.
Thanks, Mark
10 Answers, 1 is accepted
Could you provide the grid dataSource configuration? The code that you provided should work unless you are using server-side paging. If you are using server paging then you should find the page on which the specific record is on the server.
Regards,
Daniel
Telerik

Hi Daniel,
Here is my view code...
@( Html.Kendo().Grid<Models.QuoteModel>()
.Name("grid")
.DataSource(datasource => datasource
.Ajax()
.Read(read => read.Action("GetQuotes", "Quote"))
.Sort(sort => sort.Add("Created").Descending())
)
.Columns(columns =>
{
columns.Bound(Quote => Quote.QuoteNoFormatted).Title("Quote Number");
columns.Bound(Quote => Quote.QuoteTypeDescription).Title("Quote Type");
columns.Bound(Quote => Quote.Value).Title("Value");
columns.Bound(Quote => Quote.Created).Title("Created");
columns.Bound(Quote => Quote.Status).Title("Notes");
columns.Template(Quote => { }).ClientTemplate(" ").Title("Quote");
})
.ClientRowTemplate(Html.Partial("~/Views/Quote/QuoteTemplate.cshtml").ToHtmlString())
.Sortable()
.ToolBar(tools =>
{
tools.Template(@<text>
<div class="toolbar">
<a class="k-button k-button-icontext k-grid-excel" href="#"><span class="k-icon k-i-excel"></span>Export to Excel</a>
</div>
</text>);
})
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(5))
.Events(e => e
.ExcelExport("excelExport")
.DataBound("quotesDatabound"))
.Excel(excel => excel
.FileName("Quotes.xlsx")
.AllPages(true))
)
Thanks, Mark
The dataSource is configured for server operations(the default for the MVC Grid wrapper) and so all records will not be available on the client. You should either enable client-side operations or get the page number from the server. I attached a sample project that demonstrates one possible implementation to get the page when using server operations.
Regards,
Daniel
Telerik

Hi Daniel,
Thanks for the reply - I have got that working now (also generically across a number of grids!). One minor thing, and I'm not sure if there's a way around this, but is it possible to load the matched tab straight away - this way loads the grid on page 1 and then does a separate load of the correct page. I've saved the collection into session to save waiting for another DB hit which speeds it up, but visually it is noticeable that it's a 2-stage loading.
Any ideas would be appreciated.
Thanks, Mark
You can use the autoBind option as in the example from my previous reply. It will prevent the grid request on initialization for the first page and the grid data will be loaded only once when the request is triggered with the query method.
Regards,
Daniel
Telerik

Thanks Daniel,
Mark

Hi Daniel,
We've put this code live and it's working fine. However, because the datasets being played with are quite large, if an ID is passed in via the URL then I'd like the first load of the grid to go to that page, rather than re-load the grid with the required page after the first load as currently. I've tried getting this working by adding Data parameters to the read.Action value of the datasource on the grid. This works fine to change the requested page number data returned, however the page still thinks it's page 1 loaded (as page 1 is highlighted underneath the grid data in the grid controls). I've put my view and controller code below, plus attached the output in an image attached. Please can you take a look and advise how I can achieve this functionality?
===VIEW===
@( Html.Kendo().Grid<TEAMSPortalV2.Models.SurveyViewModel>()
.Name("grid")
.DataSource(datasource => datasource
.Ajax()
.PageSize(15)
.Read(read => read.Action("GetSurveys", "Survey").Data("{ PageOverride: 2}"))
) ........
==== CONTROLLER ====
// Get Surveys
public ActionResult GetSurveys([DataSourceRequest]DataSourceRequest request, int? PageOverride)
{
// get data
if (PageOverride.HasValue && PageOverride.Value > 0)
{
request.Page = PageOverride.Value;
} ......​
Thanks, Mark
I am not sure if I understand this statement:
rather than re-load the grid with the required page after the first load as currently
Using the autoBind option should prevent the first load?
As for setting the page in the pager - this is not currently supported. If the dataSource is configured for server operations than any method that updates the pager will also trigger a new request to the server. it is possible to achieve this by changing the internal _skip field in the requestEvent:
function
onRequestEnd(e) {
this
._skip = (PageOverride - 1) *
this
.pageSize();
}
Regards,
Daniel
Telerik

Hi Daniel,
I tried adding the autoBind(false) option, but nothing rendered at all then.
I'll try and explain the situation - in my URL I have an ID which represents an item on the grid, but not necessarily on page 1. The system needs to jump to the correct page for that ID. Currently I handle this in the databound event - so the page loads the grid via the controller on page 1 by default initially, and then the databound event tests to see if the requested ID is found on the page, if not then it calculates the index of the page that it is on and then makes a second call to the controller to load the data for the correct page of the grid. Because my loads can be quite slow because of how the data is retrieved, it would be better to only hit the controller once, somehow passing the ID so that the returned object to render the grid renders it on the page of the requested ID first time around.
It's not essential behaviour, but desirable to improve the user experience. Any help would be appreciated.
Here is my code currently :-
==== VIEW ====
@( Html.Kendo().Grid<Models.SurveyViewModel>()
.Name("grid")
.DataSource(datasource => datasource
.Ajax()
.PageSize(15)
.Read(read => read.Action("GetSurveys", "Survey")) //.Data("{ PageOverride: 2}"))
)
.Columns(columns =>
{
columns.Template(Survey => { }).ClientTemplate(" ").Title("Documents");
columns.Bound(Survey => Survey.JobNoFormatted).Title("Job Number");
columns.Bound(Survey => Survey.Address).Title("Site");
columns.Bound(Survey => Survey.surveyType).Title("Report Type");
columns.Bound(Survey => Survey.ApprovedFormatted).Title("Approved");
})
.ClientRowTemplate(Html.Partial("~/Views/Survey/SurveyTemplate.cshtml").ToHtmlString())
.Sortable()
.ToolBar(tools =>
{
tools.Template(@<text>
<div class="toolbar">
<a class="k-button k-button-icontext k-grid-excel" href="#"><span class="k-icon k-i-excel"></span>Export to Excel</a>
<a href="#" onclick="toggleFilter();"><img src="~/Content/images/icons/filter.png" class="floatright" style="padding: 3px;" title="Show/Hide Filters" /></a>
<div class="toolbar_filter" style="display:none;">
@*initially hidden*@
@Html.Partial("~/Views/Survey/SurveyFilterPanel.cshtml")
</div>
</div>
</text>);
})
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(new[] { 15, 30, 60 })
.ButtonCount(5))
.Events(e => e
.ExcelExport("excelExport")
.DataBound("surveysDatabound"))
.Excel(excel => excel
.FileName("Surveys.xlsx")
.AllPages(true))
)
=== DATABOUND JS ====
// change the page to the one with the matched row highlighted (written generically for all Kendo grids, assuming that :-
// a) Grid's name is 'grid'
// b) The controller is named the same as the current URL section (e.g. Quote) - case insenstive btw
// c) The controller has an action called GetRecordPage returning a JSON-ed integer of the required page
var id = getQueryVariable('id');
if (id > 0) {
$(function () {
var grid = $("#grid").data("kendoGrid");
var dataSource = grid.dataSource;
var requestData = dataSource.transport.parameterMap({
sort: dataSource.sort(),
filter: dataSource.filter(),
pageSize: dataSource.pageSize(),
id: id
});
$.ajax({
url: '@Url.Action("GetRecordPage", selectedTab)',
type: "POST",
dataType: "json",
data: requestData,
success: function (page) {
var grid2 = $("#grid").data("kendoGrid");
// we have to test the returned page against the current page, otherwise it would keep reloading forever in a loop
if (grid2.dataSource.page() != page) {
dataSource.query({
page: page,
pageSize: dataSource.pageSize(),
sort: dataSource.sort(),
filter: dataSource.filter(),
});
}
}
});
});
}
=== CONTROLLER ====
// Get Surveys
public ActionResult GetSurveys([DataSourceRequest]DataSourceRequest request)
{
.... INTERNAL LOGIC.....
// return JSON to UI
return Json(ret.ToDataSourceResult(request));
}​
// get record page of a survey if an ID has been passed in
public ActionResult GetRecordPage([DataSourceRequest] DataSourceRequest request, int id)
{
// get the current page size beign used
var pageSize = request.PageSize;
request.PageSize = 0;
// get the collection out of session from initial data call (GetSurveys)
SurveyCollection col = (SurveyCollection)Session["SurveyDataCollection"];
// work out which page it's on
int page = 1; // default in case no ID match
Survey match = col.FirstOrDefault(s => s.Job.JobID == id);
if (match != null)
{
page = (col.IndexOf(match) / pageSize) + 1;
}
// clear out the session variable
Session.Remove("SurveyDataCollection");
// returned JSONed page number
return Json(page);
}​
Hello Mark,
Here is a prototype of how you should implement the scenario: - based in the ID value (parsed from query string) find the record in all items, calculate the page index on which the item should appear and position the Grid to that page.
In order to implement this scenario you have two options:
1. Page the grid on client - the one which you are currently implementing. In order to implement this you have to load all data items in the data source, by setting .ServerOperation(false). Thus all data items will be served on client and you will be able to calculate the page index and page the Grid.
The cons of this is impementation:
- all data items will be served on client
- the widget will be initially bound to first page
2. Page the grid on server - parse the query string and calculate the page index on server. Here is how you could implement this:
- inside the controller - parse the query string and find the page index in all items. Then set the calculated value to a property in the ViewBag. Example:
public ActionResult Remote_Data_Binding()
{
ViewBag.Page = 5; //5 is result of the calculations
return View();
}
- configure the Grid for server operations - thus only page size items will be loaded on client
- set .AutoBind(false) to the Grid. Thus initial binding will be prevented
- add the following script which will bind the widget to the page calculated on server:
<script>
$(function() {
var dataSource = $("#grid").data("kendoGrid").dataSource;
var request = {
page: @ViewBag.Page,
pageSize: dataSource.pageSize(),
sort: dataSource.sort(),
filter: dataSource.filter(),
group: dataSource.group()
};
dataSource.query(request);
});
</script>
Regards,
Nikolay RusevTelerik