Skip to content

Commit 8d7c6c2

Browse files
feat(grid): sample for scrolling to selected item (#116)
1 parent abb3d2c commit 8d7c6c2

32 files changed

+1492
-0
lines changed

grid/scroll-to-selected-row/readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Select a Row and Scroll the Grid to it
2+
3+
In some cases, you want to select a row programmatically based on some program logic and conditions, and you want the user to see it, so you want to scroll the grid to it.
4+
5+
To do that with **regular paging**:
6+
1. Ensure the grid is on the same page as the record (not part of this example).
7+
* Implement if/as necessary for your project.
8+
1. Make sure the grid has rendered after the selection operation (usually a rendering frame delay is enough).
9+
1. Use a bit of JavaScript to make the browser scroll the first selected row into view.
10+
* Tweak this logic as necessary for your case.
11+
12+
13+
To do that with **virtual scrolling**, you must set the `Skip` of the grid state to the desired item index. The example offers comments in the code on the tricky points of this, because there is no guarantee on which "page" of data this item is.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31515.178
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "scroll_to_selected_row", "scroll-to-selected-row\scroll_to_selected_row.csproj", "{C1081687-DE9D-4B61-8665-06A7BE826075}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{C1081687-DE9D-4B61-8665-06A7BE826075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{C1081687-DE9D-4B61-8665-06A7BE826075}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{C1081687-DE9D-4B61-8665-06A7BE826075}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{C1081687-DE9D-4B61-8665-06A7BE826075}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {7628A00F-102A-488D-991B-E3DADE0268FC}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Router AppAssembly="typeof(Program).Assembly">
2+
<Found Context="routeData">
3+
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />
4+
</Found>
5+
<NotFound>
6+
<h1>Page not found</h1>
7+
<p>Sorry, but there's nothing here!</p>
8+
</NotFound>
9+
</Router>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
6+
namespace scroll_to_selected_row.Models
7+
{
8+
public class Employee
9+
{
10+
public int EmployeeId { get; set; }
11+
public string Name { get; set; }
12+
public string Team { get; set; }
13+
14+
// needed for the virtualization example only, remove it otherwise
15+
public override bool Equals(object obj)
16+
{
17+
if (obj is Employee)
18+
{
19+
return this.EmployeeId == (obj as Employee).EmployeeId;
20+
}
21+
return false;
22+
}
23+
}
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@page "/"
2+
3+
@using scroll_to_selected_row.Models
4+
@using scroll_to_selected_row.Services
5+
@inject GridDataService DataService
6+
@inject IJSRuntime JsInterop
7+
8+
<TelerikButton OnClick="@SelectItem">Select item 11</TelerikButton>
9+
10+
<TelerikGrid Data=@GridData
11+
SelectionMode="GridSelectionMode.Single"
12+
@bind-SelectedItems="@SelectedItems"
13+
Pageable="true" PageSize="30"
14+
Height="300px"
15+
Class="@theGridClass">
16+
<GridColumns>
17+
<GridColumn Field=@nameof(Employee.Name) />
18+
<GridColumn Field=@nameof(Employee.Team) Title="Team" />
19+
</GridColumns>
20+
</TelerikGrid>
21+
22+
@code {
23+
string theGridClass { get; set; } = "theSpecialGrid";
24+
25+
async Task SelectItem()
26+
{
27+
// select item 11 which would be hidden initially
28+
SelectedItems = GridData.Where(item => item.EmployeeId == 11).ToList();
29+
30+
// NOTE: The selected item must be on the current page for this to work
31+
// if it is not, you need to first move the grid to the page the item is on
32+
// you can do that by using its State: https://docs.telerik.com/blazor-ui/components/grid/state
33+
// you can find a sample data source operation to traverse the entire data collection to
34+
// look for that page index so you can alter the grid state in the Virtualization example
35+
// that also shows a glimpse of the complexity and database load doing so can cause
36+
37+
await Task.Delay(20);//rougly one rendering frame so this has the chance to render in the browser
38+
await JsInterop.InvokeVoidAsync("scrollToSelectedRow", "." + theGridClass);
39+
}
40+
41+
// data generation follows
42+
public List<Employee> GridData { get; set; }
43+
public IEnumerable<Employee> SelectedItems { get; set; } = Enumerable.Empty<Employee>();
44+
45+
protected override async Task OnInitializedAsync()
46+
{
47+
GridData = await DataService.GenerateData();
48+
}
49+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
@page "/virtualization"
2+
3+
@using scroll_to_selected_row.Models
4+
@using scroll_to_selected_row.Services
5+
@using Telerik.DataSource
6+
@using Telerik.DataSource.Extensions
7+
@inject GridDataService DataService
8+
@inject IJSRuntime JsInterop
9+
10+
<TelerikButton OnClick="@SelectItem">Select item 48</TelerikButton>
11+
12+
<TelerikGrid Data=@GridData
13+
SelectionMode="GridSelectionMode.Single"
14+
@bind-SelectedItems="@SelectedItems"
15+
ScrollMode="@GridScrollMode.Virtual"
16+
RowHeight="40"
17+
PageSize="@PageSize"
18+
@ref="@GridRef"
19+
TotalCount=@Total OnRead="@ReadItems"
20+
Height="300px"
21+
Sortable="true">
22+
<GridColumns>
23+
<GridColumn Field=@nameof(Employee.Name) />
24+
<GridColumn Field=@nameof(Employee.Team) Title="Team" />
25+
</GridColumns>
26+
</TelerikGrid>
27+
28+
@code {
29+
string theGridClass { get; set; } = "theSpecialGrid";
30+
TelerikGrid<Employee> GridRef { get; set; }
31+
int Total { get; set; }
32+
List<Employee> GridData { get; set; }
33+
int PageSize { get; set; } = 20;
34+
DataSourceRequest lastRequest { get; set; }
35+
36+
async Task SelectItem()
37+
{
38+
// select item 48 which would be hidden initially
39+
// since we will set the grid state, we set the selected items there too
40+
// otherwise the change may be lost
41+
// see also the Employee model for the Equals() override that allows selection comparisons
42+
var itemsToSelect = AllData.Where(item => item.EmployeeId == 48).ToList();
43+
44+
// We can't be sure whether the selected item will be in the collection of loaded items
45+
// so we will use the grid's state to scroll to that item by setting the Skip parameter
46+
// to the index of the item in the current data collection
47+
// NOTE: you must optimize this task according to your project
48+
// in this example we will sort and filter the entire data collection without paging
49+
// to get the index, but that may be slow/expensive/impossible in a real app
50+
// also, this example does not employ any defensive checks and error handling, which you should add
51+
52+
// first, we will store the original values to ensure we don't break anything if this is used elsewhere
53+
// then move the "marker" to no paging and start from the very beginning so we get all the data sorted and filtered
54+
lastRequest.PageSize = 0;
55+
int currSkip = lastRequest.Skip;
56+
lastRequest.Skip = 0;
57+
int TargetItemIndex = AllData.ToDataSourceResult(lastRequest).Data.IndexOf(itemsToSelect.First());
58+
lastRequest.PageSize = PageSize;
59+
lastRequest.Skip = currSkip;
60+
61+
// set the calculated scroll offset to show the desired item, and set the selected items in one rendering
62+
await SetSkip(TargetItemIndex, itemsToSelect);
63+
}
64+
65+
protected async Task ReadItems(GridReadEventArgs args)
66+
{
67+
//we store the request the grid makes because we use it in the index calculatoin above
68+
lastRequest = args.Request;
69+
70+
//this should actually be happening on the server, but for brevity we do it here
71+
//see more at https://github.com/telerik/blazor-ui/tree/master/grid/datasourcerequest-on-server
72+
var datasourceResult = AllData.ToDataSourceResult(args.Request);
73+
GridData = (datasourceResult.Data as IEnumerable<Employee>).ToList();
74+
Total = datasourceResult.Total;
75+
76+
77+
// see more about why this is done here https://docs.telerik.com/blazor-ui/knowledge-base/grid-large-skip-breaks-virtualization
78+
int allowedSkip = ValidateSkip(args.Request.Skip);
79+
if (allowedSkip != args.Request.Skip)
80+
{
81+
await SetSkip(allowedSkip);
82+
}
83+
84+
85+
await InvokeAsync(StateHasChanged);
86+
}
87+
88+
async Task SetSkip(int skip)
89+
{
90+
await SetSkip(skip, null);
91+
}
92+
93+
async Task SetSkip(int skip, IEnumerable<Employee> itemsToSelect)
94+
{
95+
if (GridRef != null)
96+
{
97+
var state = GridRef.GetState();
98+
if (itemsToSelect != null)
99+
{
100+
state.SelectedItems = (ICollection<Employee>)itemsToSelect;
101+
}
102+
state.Skip = ValidateSkip(skip);
103+
await GridRef.SetState(state);
104+
}
105+
}
106+
107+
int ValidateSkip(int desiredSkip)
108+
{
109+
if (desiredSkip < 0) return 0;
110+
int itemsThatFitPerPage = 7; // see how the grid renders and determine this from there
111+
bool isInvalidSkip = GridData.Count < itemsThatFitPerPage;
112+
return isInvalidSkip ? Total - itemsThatFitPerPage : desiredSkip;
113+
}
114+
115+
// data generation follows
116+
public List<Employee> AllData { get; set; }
117+
public IEnumerable<Employee> SelectedItems { get; set; } = Enumerable.Empty<Employee>();
118+
119+
protected override async Task OnInitializedAsync()
120+
{
121+
AllData = await DataService.GenerateData();
122+
}
123+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@page "/"
2+
@namespace scroll_to_selected_row.Pages
3+
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4+
5+
<!DOCTYPE html>
6+
<html lang="en">
7+
<head>
8+
<meta charset="utf-8" />
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
10+
<title>scroll_to_selected_row</title>
11+
<base href="~/" />
12+
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
13+
<link href="css/site.css" rel="stylesheet" />
14+
<link rel="stylesheet" href="_content/Telerik.UI.for.Blazor/css/kendo-theme-default/all.css" />
15+
<script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
16+
17+
@* This script is called from the C# code and does the actual scrolling for the regular paging mode *@
18+
<script src="scroll-to-row.js"></script>
19+
</head>
20+
<body>
21+
<component type="typeof(App)" render-mode="ServerPrerendered" />
22+
23+
<div id="blazor-error-ui">
24+
<environment include="Staging,Production">
25+
An error has occurred. This application may no longer respond until reloaded.
26+
</environment>
27+
<environment include="Development">
28+
An unhandled exception has occurred. See browser dev tools for details.
29+
</environment>
30+
<a href class="reload">Reload</a>
31+
<a class="dismiss">🗙</a>
32+
</div>
33+
34+
<script src="_framework/blazor.server.js"></script>
35+
</body>
36+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.AspNetCore;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Logging;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
12+
namespace scroll_to_selected_row
13+
{
14+
public class Program
15+
{
16+
public static void Main(string[] args)
17+
{
18+
CreateHostBuilder(args).Build().Run();
19+
}
20+
21+
public static IHostBuilder CreateHostBuilder(string[] args) =>
22+
Host.CreateDefaultBuilder(args)
23+
.ConfigureWebHostDefaults(webBuilder =>
24+
{
25+
webBuilder.UseStaticWebAssets();
26+
webBuilder.UseStartup<Startup>();
27+
});
28+
}
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:55276/",
7+
"sslPort": 44398
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"scroll_to_selected_row": {
19+
"commandName": "Project",
20+
"launchBrowser": true,
21+
"environmentVariables": {
22+
"ASPNETCORE_ENVIRONMENT": "Development"
23+
},
24+
"applicationUrl": "https://localhost:5001;http://localhost:5000"
25+
}
26+
}
27+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using scroll_to_selected_row.Models;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace scroll_to_selected_row.Services
8+
{
9+
public class GridDataService
10+
{
11+
public async Task<List<Employee>> GenerateData()
12+
{
13+
List<Employee> data = new List<Employee>();
14+
for (int i = 0; i < 55; i++)
15+
{
16+
data.Add(new Employee()
17+
{
18+
EmployeeId = i,
19+
Name = "Employee " + i.ToString(),
20+
Team = "Team " + i % 3
21+
});
22+
}
23+
return await Task.FromResult(data);
24+
}
25+
}
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@layout TelerikLayout
2+
3+
@inherits LayoutComponentBase
4+
5+
<div class="page">
6+
<div class="sidebar">
7+
<NavMenu />
8+
</div>
9+
10+
<div class="main">
11+
<div class="top-row px-4">
12+
<a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
13+
</div>
14+
15+
<div class="content px-4">
16+
@Body
17+
</div>
18+
</div>
19+
</div>

0 commit comments

Comments
 (0)