I've been looking at your Keyboard Navigation page:
https://demos.telerik.com/blazor-ui/grid/keyboard-navigation
If you are navigating in the Grid and arrow over to the "+" sign and press ENTER it expands the Details. Then you can press TAB to access the button within the details. Great. Your demo works fine.
However, on my grid, I have another grid in my Details section. I would like to be able to expand the Details section and then TAB into those details so I can access the link in the header of the grid, and also be able to use arrow keys to navigate around this sub grid. Well, honestly MOSTLY I just wanted to be able to tab to the "View Checkout History" link within the Details grid. See attached screenshot.
However, pressing TAB after expanding the details simply moves the focus to the first button in the next column of that row. It doesn't go into the Details section like your web demo does for a button.
Please expand your Keyboard Navigation capabilities to allow more navigation into the Details section other than just a button like your demo shows. I'll bet a lot of people probably have sub-grids within their details section.
Thanks!
Hi Andrew,
Thank you for the update and for sharing your configuration.
Based on this setup, the behavior you described is expected. By default, the Tab key moves focus to the next focusable element, which in this case is the button next to the expand button. However, you can adjust this by setting TabIndex="-1" on the "Check Out" button and the "Name Of Client" link if you want to exclude them from the tab order.
Here’s an example of how you can implement this:
<TelerikGrid Data="@GridData"
TItem="@Product"
Pageable="true"
PageSize="10"
Sortable="true"
EditMode="@GridEditMode.Inline"
Navigable="true"
OnUpdate="UpdateProduct"
OnDelete="DeleteProduct"
OnCreate="CreateProduct">
<GridColumns>
@* I added these two columns; they have to be tabbed-thru first to get to the inner grid *@
<GridColumn>
<Template>
<TelerikButton TabIndex="-1" Icon="@SvgIcon.CheckCircle" Title="Check Out"></TelerikButton>
</Template>
</GridColumn>
<GridColumn>
<Template>
<a tabindex="-1" href="https://telerik.com">Name Of Client</a>
</Template>
</GridColumn>
<GridCommandColumn Width="200px">
<GridCommandButton Command="Edit" Icon="@SvgIcon.Pencil"></GridCommandButton>
<GridCommandButton Command="Save" Icon="@SvgIcon.Save" ShowInEdit="true"></GridCommandButton>
<GridCommandButton Command="Cancel" Icon="@SvgIcon.Cancel" ShowInEdit="true"></GridCommandButton>
<GridCommandButton Command="Delete" Icon="@SvgIcon.Trash"></GridCommandButton>
</GridCommandColumn>
<GridColumn Field=@nameof(Product.ProductName) Title="Product Name" />
<GridColumn Field=@nameof(Product.UnitPrice) Title="Unit Price" />
<GridColumn Field=@nameof(Product.UnitsInStock) Title="Units in Stock" />
<GridColumn Field=@nameof(Product.CreatedAt) Title="Date Created" />
<GridColumn Field=@nameof(Product.Discontinued) Title="Discontinued" Width="150px" />
</GridColumns>
<DetailTemplate Context="productItem">
@{
Product product = (Product)productItem;
<TelerikGrid Data="@product.OrderDetails"
Pageable="true"
Sortable="true"
PageSize="5"
EditMode="@GridEditMode.Inline"
Navigable="true"
OnUpdate="@((GridCommandEventArgs args) => UpdateOrder(product, args))"
OnDelete="@((GridCommandEventArgs args) => DeleteOrder(args, product))"
OnCreate="@((GridCommandEventArgs args) => CreateOrder(args, product))">
<GridToolBarTemplate>
<a href="https://www.telerik.com/blazor-ui/documentation/components/grid/hierarchy" style="text-decoration: underline;color: blue;">View Checkout History</a>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field=@nameof(OrderDetails.OrderId) Title="Order ID" Editable="false" />
<GridColumn Field=@nameof(OrderDetails.UnitPrice) Title="Price" />
<GridColumn Field=@nameof(OrderDetails.Discount) Title="Discount">
<Template Context="order">
@(String.Format("{0:0.00}%", ((OrderDetails)order).Discount))
</Template>
</GridColumn>
<GridColumn Field=@nameof(OrderDetails.Quantity) Title="Quantity" />
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="@SvgIcon.Pencil"></GridCommandButton>
<GridCommandButton Command="Save" Icon="@SvgIcon.Save" ShowInEdit="true"></GridCommandButton>
<GridCommandButton Command="Cancel" Icon="@SvgIcon.Cancel" ShowInEdit="true"></GridCommandButton>
<GridCommandButton Command="Delete" Icon="@SvgIcon.Trash"></GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
}
</DetailTemplate>
</TelerikGrid>
@code {
#region CUD operations for the main Grid
private void UpdateProduct(GridCommandEventArgs args)
{
var item = (Product)args.Item;
var index = GridData.FindIndex(x => x.ProductId == item.ProductId);
if (index != -1)
{
GridData[index] = item;
}
}
private void CreateProduct(GridCommandEventArgs args)
{
var item = (Product)args.Item;
item.ProductId = ++LastId;
GridData.Insert(0, item);
item.OrderDetails = GenerateOrderDetails(item);
}
private void DeleteProduct(GridCommandEventArgs args)
{
var item = (Product)args.Item;
GridData.Remove(item);
}
#endregion CUD operations for the main Grid
#region CUD operations for the detail Grid
private void UpdateOrder(Product product, GridCommandEventArgs args)
{
var item = (OrderDetails)args.Item;
var data = product.OrderDetails;
int index = data.FindIndex(x => x.OrderId == item.OrderId);
if (index != -1)
{
data[index] = item;
}
}
private void CreateOrder(GridCommandEventArgs args, Product product)
{
var item = (OrderDetails)args.Item;
var data = product.OrderDetails;
item.OrderId = data.Count + 1;
data.Insert(0, item);
}
private void DeleteOrder(GridCommandEventArgs args, Product product)
{
var item = (OrderDetails)args.Item;
var data = product.OrderDetails;
data.Remove(item);
}
#endregion CUD operations for the detail Grid
private List<Product> GridData { get; set; } = new();
private DateTime StartDate = new DateTime(2018, 1, 1);
private static Random RandomGenerator = new Random();
#region Sample Data and Models
private int LastId { get; set; }
protected override void OnInitialized()
{
GridData = GenerateProducts();
}
private List<Product> GenerateProducts()
{
List<Product> products = new List<Product>();
for (int i = 1; i <= 3; i++)
{
var product = new Product()
{
ProductId = ++LastId,
ProductName = $"Product {LastId}",
SupplierId = i,
UnitPrice = (decimal)(i * 3.14),
UnitsInStock = (short)(i * 1),
Discontinued = RandomGenerator.NextDouble() >= 0.5,
CreatedAt = GetRandomDate(StartDate)
};
product.OrderDetails = GenerateOrderDetails(product);
products.Add(product);
}
return products;
}
private List<OrderDetails> GenerateOrderDetails(Product product)
{
double minDiscount = 0.1;
double maxDiscount = 0.2;
var orderDetails = new List<OrderDetails>();
for (int i = 1; i <= 2; i++)
{
orderDetails.Add(new OrderDetails()
{
OrderId = Int32.Parse($"{product.ProductId}{i}"),
UnitPrice = (decimal)product.UnitPrice,
Quantity = (short)(1 + (RandomGenerator.Next() % 10)),
Discount = (float)((RandomGenerator.NextDouble() * (maxDiscount - minDiscount) + minDiscount)) * 100,
ProductId = product.ProductId,
});
}
return orderDetails;
}
private DateTime GetRandomDate(DateTime startDate)
{
int range = (DateTime.Today - startDate).Days;
return startDate.AddDays(RandomGenerator.Next(range));
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
public int SupplierId { get; set; }
public decimal UnitPrice { get; set; }
public short UnitsInStock { get; set; }
public bool Discontinued { get; set; }
public DateTime CreatedAt { get; set; }
public List<OrderDetails> OrderDetails { get; set; } = new();
public override bool Equals(object? obj) => Equals(obj as Product);
public bool Equals(Product? obj)
{
return obj != null && obj.ProductId == ProductId;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
public class OrderDetails
{
public int OrderId { get; set; }
public decimal UnitPrice { get; set; }
public short Quantity { get; set; }
public float Discount { get; set; }
public int ProductId { get; set; }
}
#endregion Sample Data and Models
}
Regards,
Hristian Stefanov
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.
Thanks Hristian for the demo. I figured out what is happening. Instead of having my action buttons in a GridCommandColumn like you do, I simply have buttons in the first column like this:
<GridColumn Width="80px" Resizable="false" Groupable="false" Filterable="false">Hi Andrew,
I attempted to reproduce the described behavior using a configuration based on the provided information. In my testing, pressing Tab correctly moves the focus from the "+/-" button to the "View Checkout History" link.
Here’s the REPL link to the sample I used for testing: REPL link. Please run it and test it to see if you experience the same results on your end.
Regards,
Hristian Stefanov
Progress Telerik
Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.