Web Components Tree Grid キーボード ナビゲーション
Web Components Tree Grid の Ignite UI for Web Components キーボード ナビゲーションは、さまざまなキーボード操作をユーザーに提供します。これにより IgcTreeGridComponent
のアクセシビリティが向上し、内部の要素 (セル、行、列ヘッダー、ツールバー、フッターなど) をナビゲートできるようになります。この機能はデフォルトで有効になっています。デフォルトの動作を簡単にオーバーライドするオプションがあります。
IgcTreeGridComponent
のタブが削減され、ナビゲーションが W3C のアクセシビリティ標準に準拠し、使いやすくなりました。
現在、IgcTreeGridComponent
には以下のタブ位置が導入されています。
- グループ化またはツールバーの領域 (有効な場合)
- Tree Grid ヘッダー
- Tree Grid 本体
- 列の集計 (有効な場合)
- Tree Grid ページネーター (有効な場合)
この変更のため、Tab と Shift + Tab キーでセル間を移動することは IgcTreeGridComponent でサポートされなくなりました。 Tab キーを押すと、グループ化 / ツール バー -> ヘッダー -> 本体 -> 集計 -> フッター/ページネーターの順序にタブ位置を移動します。
テンプレートによっフォーカス可能な要素を IgcTreeGridComponent の本体に公開すると、ブラウザのデフォルトの動作が防止されていないため、予期されない結果が発生する可能性があります。 したがって、それを適切に防止/変更するのは開発者の責任です。
ヘッダー ナビゲーション
IgcTreeGridComponent
ヘッダーのキーボード ナビゲーションが完全にサポートされるようになりました。列ヘッダーは矢印キーで簡単にトラバースできます。さらに、フィルタリング、ソート、グループ化などの列操作をトリガーするキーの組み合わせがいくつかあります。
IgcTreeGridComponent
ヘッダー コンテナーがフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
↑ - ヘッダーで 1 つ上のセルへ移動 (ループなし)複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
↓ - ヘッダーの 1 つ下のセルに移動 (ラッピングなし)複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
← - 1 つ左のセルへ移動 (ループなし)
→ - 1 つ右のセルへ移動 (行間のラッピングなし)
Ctrl + ← - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
Home - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
Ctrl + → - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
End - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
Alt + L - 詳細フィルタリングが有効な場合、詳細フィルタリング ダイアログを開きます。
Ctrl + Shift + L - 列がフィルター可能な場合、Excel スタイル フィルターまたはデフォルト (行) フィルターを開きます。
Ctrl + ↑ - アクティブな列ヘッダーを昇順にソートします。列が昇順で既にソートされている場合、ソート状態を削除します。
Ctrl + ↓ - アクティブな列ヘッダーを降順にソートします。列が降順で既にソートされている場合、ソート状態を削除します。
Space - 列を選択します。列がすでに選択されている場合、選択を解除します。
Shift + Alt + → - 列がグループ化可能としてマークされている場合、列のグループ化を解除します。
Alt + ← または Alt + ↑ - 列が縮小されていない場合、列グループ ヘッダーを縮小します。
Alt + → または Alt + ↓ - 列がまだ展開されていない場合、列グループヘッダーを展開します。
本体ナビゲーション
IgcTreeGridComponent
本体がフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
- ↑ - 1 つ上のセルへ移動。
- ↓ - 1 つ下のセルへ移動。
- ← - 1 つ左のセルへ移動 (行間のラッピングなし)
- → - 1 つ右のセルへ移動 (行間のラッピングなし)
- Ctrl + ← - 行の左端のセルへ移動
- Ctrl + → - 行の右端のセルへ移動
- Ctrl + ↑ - 列の最初のセルへ移動
- Ctrl + ↓ - 列の最後のセルへ移動
- Home - 行の左端のセルへ移動
- End - 行の右端のセルへ移動
- Ctrl + Home - グリッドの最も左上のデータ セルへ移動
- Ctrl + End - グリッドの最も右下のデータ セルへ移動
- Page Up - 1 ページ (ビューポート) 上へスクロール
- Page Down - 1 ページ (ビューポート) 下へスクロール
- Enter - 編集モードに入る
- F2 - 編集モードに入る
- Esc - 編集モードを終了する
- Tab - 編集モードのセルがある場合のみ使用できます。行の次の編集可能なセルにフォーカスを移動します。行の最後のセルに達した場合、フォーカスを次の行の最初の編集可能なセルに移動します。行編集が有効な場合、フォーカスを編集可能な一番右のセルから [キャンセル] および [完了] ボタンへ移動し、[完了] ボタンから行の一番左の編集可能なセルへ移動します。
- Shift + Tab - 編集モードのセルがある場合のみ使用できます。行の一つ前の編集可能なセルにフォーカスを移動します。行の最初のセルに達した場合、フォーカスを前の行の最後の編集可能なセルに移動します。行編集が有効な場合、フォーカスを編集可能な一番右のセルから [キャンセル] および [完了] ボタンへ移動し、[完了] ボタンから行の一番右の編集可能なセルへ移動します。
- Space - 行の選択が有効な場合、行を選択します。
- Alt + ← または Alt + ↑ -
現在のノードを縮小します。
- Alt + → または Alt + ↓ - グループ行はグループを展開します。
現在のノードを展開します。
以下のデモサンプルで上記のすべての操作を実行できます。ナビゲーション可能なグリッド要素をフォーカスすると、利用可能な操作のリストが表示されます。
デモ
// NOTE this file contains multiple data sources:
// Data Source #1
export class EmployeesFlatDetailsItem {
public constructor(init: Partial<EmployeesFlatDetailsItem>) {
Object.assign(this, init);
}
public Address: string;
public Age: number;
public City: string;
public Country: string;
public Fax: string;
public HireDate: string;
public ID: number;
public Name: string;
public ParentID: number;
public Phone: string;
public PostalCode: string;
public Title: string;
public LastName: string;
public FullAddress: string;
}
export class EmployeesFlatDetails extends Array<EmployeesFlatDetailsItem> {
public constructor() {
super();
this.push(new EmployeesFlatDetailsItem(
{
Address: `Obere Str. 57`,
Age: 55,
City: `Berlin`,
Country: `Germany`,
Fax: `030-0076545`,
HireDate: `2008, 3, 20`,
ID: 1,
Name: `Johnathan Winchester`,
ParentID: -1,
Phone: `030-0074321`,
PostalCode: `12209`,
Title: `Development Manager`,
LastName: `Winchester`,
FullAddress: `Obere Str. 57, Berlin, Germany`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Avda. de la Constitución 2222`,
Age: 42,
City: `México D.F.`,
Country: `Mexico`,
Fax: `(5) 555-3745`,
HireDate: `2014, 1, 22`,
ID: 4,
Name: `Ana Sanders`,
ParentID: -1,
Phone: `(5) 555-4729`,
PostalCode: `05021`,
Title: `CEO`,
LastName: `Sanders`,
FullAddress: `Avda. de la Constitución 2222, México D.F., Mexico`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Mataderos 2312`,
Age: 49,
City: `México D.F.`,
Country: `Mexico`,
Fax: `(5) 555-3995`,
HireDate: `2014, 1, 22`,
ID: 18,
Name: `Victoria Lincoln`,
ParentID: -1,
Phone: `(5) 555-3932`,
PostalCode: `05023`,
Title: `Accounting Manager`,
LastName: `Lincoln`,
FullAddress: `Mataderos 2312, México D.F., Mexico`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `120 Hanover Sq.`,
Age: 61,
City: `London`,
Country: `UK`,
Fax: `(171) 555-6750`,
HireDate: `2010, 1, 1`,
ID: 10,
Name: `Yang Wang`,
ParentID: -1,
Phone: `(171) 555-7788`,
PostalCode: `WA1 1DP`,
Title: `Localization Manager`,
LastName: `Wang`,
FullAddress: `120 Hanover Sq., London, UK`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Berguvsvägen 8`,
Age: 43,
City: `Luleå`,
Country: `Sweden`,
Fax: `0921-12 34 67`,
HireDate: `2011, 6, 3`,
ID: 3,
Name: `Michael Burke`,
ParentID: 1,
Phone: `0921-12 34 65`,
PostalCode: `S-958 22`,
Title: `Senior Software Developer`,
LastName: `Burke`,
FullAddress: `Berguvsvägen 8, Luleå, Sweden`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Forsterstr. 57`,
Age: 29,
City: `Mannheim`,
Country: `Germany`,
Fax: `0621-08924`,
HireDate: `2009, 6, 19`,
ID: 2,
Name: `Thomas Anderson`,
ParentID: 1,
Phone: `0621-08460`,
PostalCode: `68306`,
Title: `Senior Software Developer`,
LastName: `Anderson`,
FullAddress: `Forsterstr. 57, Mannheim, Germany`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `24, place Kléber`,
Age: 31,
City: `Strasbourg`,
Country: `France`,
Fax: `88.60.15.32`,
HireDate: `2014, 8, 18`,
ID: 11,
Name: `Monica Reyes`,
ParentID: 1,
Phone: `88.60.15.31`,
PostalCode: `67000`,
Title: `Software Development Team Lead`,
LastName: `Reyes`,
FullAddress: `24, place Kléber, Strasbourg, France`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `C/ Araquil, 67`,
Age: 35,
City: `Madrid`,
Country: `Spain`,
Fax: `(91) 555 91 99`,
HireDate: `2015, 9, 17`,
ID: 6,
Name: `Roland Mendel`,
ParentID: 11,
Phone: `(91) 555 22 82`,
PostalCode: `28023`,
Title: `Senior Software Developer`,
LastName: `Mendel`,
FullAddress: `C/ Araquil, 67, Madrid, Spain`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `12, rue des Bouchers`,
Age: 44,
City: `Marseille`,
Country: `France`,
Fax: `91.24.45.41`,
HireDate: `2009, 10, 11`,
ID: 12,
Name: `Sven Cooper`,
ParentID: 11,
Phone: `91.24.45.40`,
PostalCode: `13008`,
Title: `Senior Software Developer`,
LastName: `Cooper`,
FullAddress: `12, rue des Bouchers, Marseille, France`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `23 Tsawassen Blvd.`,
Age: 44,
City: `Tsawassen`,
Country: `Canada`,
Fax: `(604) 555-3745`,
HireDate: `2014, 4, 4`,
ID: 14,
Name: `Laurence Johnson`,
ParentID: 4,
Phone: `(604) 555-4729`,
PostalCode: `T2F 8M4`,
Title: `Director`,
LastName: `Johnson`,
FullAddress: `23 Tsawassen Blvd., Tsawassen, Canada`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Fauntleroy Circus`,
Age: 25,
City: `London`,
Country: `UK`,
Fax: `(5) 555-3798`,
HireDate: `2017, 11, 9`,
ID: 5,
Name: `Elizabeth Richards`,
ParentID: 4,
Phone: `(171) 555-1212`,
PostalCode: `EC2 5NT`,
Title: `Vice President`,
LastName: `Richards`,
FullAddress: `Fauntleroy Circus, London, UK`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Cerrito 333`,
Age: 39,
City: `Buenos Aires`,
Country: `Argentina`,
Fax: `(1) 135-4892`,
HireDate: `2010, 3, 22`,
ID: 13,
Name: `Trevor Ashworth`,
ParentID: 5,
Phone: `(1) 135-5555`,
PostalCode: `1010`,
Title: `Director`,
LastName: `Ashworth`,
FullAddress: `Cerrito 333, Buenos Aires, Argentina`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Sierras de Granada 9993`,
Age: 44,
City: `México D.F.`,
Country: `Mexico`,
Fax: `(5) 555-7293`,
HireDate: `2014, 4, 4`,
ID: 17,
Name: `Antonio Moreno`,
ParentID: 18,
Phone: `(5) 555-3392`,
PostalCode: `05022`,
Title: `Senior Accountant`,
LastName: `Moreno`,
FullAddress: `Sierras de Granada 9993, México D.F., Mexico`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Hauptstr. 29`,
Age: 50,
City: `Sao Paulo`,
Country: `Brazil`,
Fax: `(5) 555-6691`,
HireDate: `2007, 11, 18`,
ID: 7,
Name: `Pedro Rodriguez`,
ParentID: 10,
Phone: `0452-076545`,
PostalCode: `3012`,
Title: `Senior Localization Developer`,
LastName: `Rodriguez`,
FullAddress: `Hauptstr. 29, Sao Paulo, Brazil`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Av. dos Lusíadas, 23`,
Age: 27,
City: `Bern`,
Country: `Switzerland`,
Fax: ``,
HireDate: `2016, 2, 19`,
ID: 8,
Name: `Casey Harper`,
ParentID: 10,
Phone: `(11) 555-7647`,
PostalCode: `05432-043`,
Title: `Senior Localization`,
LastName: `Harper`,
FullAddress: `Av. dos Lusíadas, 23, Bern, Switzerland`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Berkeley Gardens 12`,
Age: 25,
City: `London`,
Country: `UK`,
Fax: `(171) 555-9199`,
HireDate: `2017, 11, 9`,
ID: 15,
Name: `Patricia Simpson`,
ParentID: 7,
Phone: `(171) 555-2282`,
PostalCode: `WX1 6LT`,
Title: `Localization Intern`,
LastName: `Simpson`,
FullAddress: `Berkeley Gardens 12, London, UK`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `Walserweg 21`,
Age: 39,
City: `Aachen`,
Country: `Germany`,
Fax: `0241-059428`,
HireDate: `2010, 3, 22`,
ID: 9,
Name: `Francisco Chang`,
ParentID: 7,
Phone: `0241-039123`,
PostalCode: `52066`,
Title: `Localization Intern`,
LastName: `Chang`,
FullAddress: `Walserweg 21, Aachen, Germany`
}));
this.push(new EmployeesFlatDetailsItem(
{
Address: `35 King George`,
Age: 25,
City: `London`,
Country: `UK`,
Fax: `(171) 555-3373`,
HireDate: `2018, 3, 18`,
ID: 16,
Name: `Peter Lewis`,
ParentID: 7,
Phone: `(171) 555-0297`,
PostalCode: `WX3 6FW`,
Title: `Localization Intern`,
LastName: `Lewis`,
FullAddress: `35 King George, London, UK`
}));
}
}
// Data Source #2
export enum ItemAction {
Filterable,
Sortable,
Selectable,
Groupable,
Collapsible,
Expandable,
Editable,
Always
}
export enum GridSection {
THEAD = 'igx-grid__thead-wrapper',
TBODY = 'igx-grid__tbody-content',
FOOTER = 'igx-grid__tfoot'
}
export class Item {
public title: string;
public subTitle: string;
public action: ItemAction;
public active = false;
private _completed: boolean;
public constructor(title: string, subTitle: string, completed: boolean, itemAction?: ItemAction) {
this.title = title;
this.subTitle = subTitle;
this.completed = completed;
this.action = itemAction;
if (itemAction === ItemAction.Always) {
this.active = true;
}
}
public set completed(value: boolean) {
if (this.active || (!value && !this.completed)) {
this._completed = value;
}
}
public get completed() {
return this._completed;
}
}
tsimport 'igniteui-webcomponents-grids/grids/combined';
import { IgcActiveNodeChangeEventArgs, IgcCellType, IgcColumnComponent, IgcColumnGroupComponent, IgcGridComponent, IgcGridMasterDetailContext, IgcTreeGridComponent, SortingDirection } from 'igniteui-webcomponents-grids/grids';
import { EmployeesFlatDetails } from './EmployeesFlatDetails';
import { html } from 'igniteui-webcomponents-core';
import "igniteui-webcomponents-grids/grids/themes/light/bootstrap.css";
import "./index.css";
import { Item, ItemAction, GridSection } from './Item';
import { defineComponents, IgcListComponent, IgcCheckboxComponent } from 'igniteui-webcomponents';
defineComponents(IgcListComponent, IgcCheckboxComponent);
const theadKeyCombinations = [
new Item('space key', 'select column', false, ItemAction.Selectable),
new Item('ctrl + arrow up/down', 'sorts the column asc/desc', false, ItemAction.Sortable),
new Item('shift + alt + arrow left/right', 'group/ungroup the active column', false, ItemAction.Groupable),
new Item('alt + arrow left/right/up/down', 'expand/collapse active multi column header',
false, ItemAction.Collapsible),
new Item('ctrl + shift + l', 'opens the excel style filtering', false, ItemAction.Filterable),
new Item('alt + l', 'opens the advanced filtering', false, ItemAction.Filterable)
];
const tbodyKeyCombinations: Item[] = [
new Item('enter', 'enter in edit mode', false, ItemAction.Editable),
new Item('alt + arrow left/up', 'collapse master details row', false, ItemAction.Collapsible),
new Item('alt + arrow right/down', 'expand master details row', false, ItemAction.Collapsible),
new Item('alt + arrow right/left', 'expand/collapse the group row', false, ItemAction.Expandable),
new Item('ctrl + Home/End', 'navigates to the upper-left/bottom-right cell', false, ItemAction.Always)
];
const summaryCombinations: Item[] = [
new Item('ArrowLeft', 'navigates one summary cell left', false, ItemAction.Always),
new Item('ArrowRight', 'navigates one summary cell right', false, ItemAction.Always),
new Item('Home', 'navigates to the first summary cell', false, ItemAction.Always),
new Item('End', 'navigates to the last summary cell', false, ItemAction.Always)
];
export class Sample {
private grid: IgcTreeGridComponent;
private list: IgcListComponent;
private activeCollection: Item[];
private gridSection: GridSection;
private activeNode: any;
constructor() {
var grid = this.grid = document.getElementById('grid') as IgcTreeGridComponent;
var list = this.list = document.getElementById('list') as IgcListComponent;
this.onActiveNodeChange = this.onActiveNodeChange.bind(this);
this.gridKeydown = this.gridKeydown.bind(this);
this.keydown = this.keydown.bind(this);
grid.data = this.data;
grid.addEventListener("activeNodeChange", this.onActiveNodeChange);
grid.addEventListener("gridKeydown", this.gridKeydown);
grid.addEventListener("keydown", this.keydown);
}
public changeCombinationsCollection(gridSection: GridSection, evt: any) {
switch (gridSection) {
case GridSection.THEAD:
this.activeCollection = theadKeyCombinations;
this.toggleHeaderCombinations(evt);
break;
case GridSection.TBODY:
this.activeCollection = tbodyKeyCombinations;
this.toggleBodyCombinations(evt);
break;
case GridSection.FOOTER:
this.activeCollection = summaryCombinations;
break;
default:
this.activeCollection = [];
return;
}
this.updateList();
}
public updateList() {
this.list.innerHTML = this.listTemplate();
}
public toggleHeaderCombinations(activeNode: any) {
const currColumn = this.grid.columns
.find(c => c.visibleIndex === activeNode.column && c.level === activeNode.level) as IgcColumnGroupComponent;
this.activeCollection.forEach(x => x.active = true);
const actions = this.extractColumnActions(currColumn);
this.activeCollection.filter(x => actions.indexOf(x.action) === -1 && x.action !== ItemAction.Always)?.forEach(x => x.active = false);
}
public toggleBodyCombinations(activeNode: any) {
const rowRef = this.grid.getRowByIndex(activeNode.row);
if (rowRef.isGroupByRow) {
this.activeCollection.forEach(x => x.active = false);
this.activeCollection.filter(x => x.action === ItemAction.Expandable || x.action === ItemAction.Always)?.forEach(x => x.active = true);
} else {
const currColumn = this.grid.columns.filter(x => !x.columnGroup)
.find(c => c.visibleIndex === activeNode.column);
const cell = this.grid.getCellByColumn(activeNode.row, currColumn.field);
this.toggleCellCombinations(cell);
}
}
public toggleCellCombinations(cell?: IgcCellType) {
this.activeCollection.forEach(x => x.active = true);
const actions = this.extractCellActions(cell);
this.activeCollection.filter(x => actions.indexOf(x.action) === -1 && x.action !== ItemAction.Always)?.forEach(x => x.active = false);
}
public extractCellActions(cell: IgcCellType) {
const res: any[] = [];
if(!cell) return res;
if (cell?.editable) {
res.push(ItemAction.Editable);
}
res.push(ItemAction.Collapsible);
return res;
}
public extractColumnActions(col: IgcColumnGroupComponent) {
const res = [];
if (col.sortable) {
res.push(ItemAction.Sortable);
}
if (col.filterable && !col.columnGroup) {
res.push(ItemAction.Filterable);
}
if (col.collapsible) {
res.push(ItemAction.Collapsible);
}
if (col.groupable) {
res.push(ItemAction.Groupable);
}
if (col.selectable) {
res.push(ItemAction.Selectable);
}
return res;
}
public onActiveNodeChange(event: any) {
const evt = (event as any).detail;
this.activeNode = evt;
const row = this.grid.getRowByIndex(evt.row);
this.gridSection = evt.row < 0 ? GridSection.THEAD : row === undefined || row.isSummaryRow ?
GridSection.FOOTER : GridSection.TBODY;
this.changeCombinationsCollection(this.gridSection, evt);
}
public keydown(event: any) {
const key = event.key.toLowerCase();
if (key === 'tab') { return; }
if (this.gridSection === GridSection.FOOTER) {
switch (key) {
case 'end':
this.activeCollection.at(3).completed = true;
break;
case 'home':
this.activeCollection.at(2).completed = true;
break;
case 'arrowleft':
this.activeCollection.at(0).completed = true;
break;
case 'arrowright':
this.activeCollection.at(1).completed = true;
break;
default:
break;
}
return;
}
const activeNode = this.activeNode;
if (this.gridSection === GridSection.THEAD) {
if (key === 'l' && event.altKey) {
this.activeCollection.at(5).completed = true;
}
const col = this.grid.columns
.find(c => c.visibleIndex === activeNode.column && c.level === activeNode.level);
if (key === 'l' && event.ctrlKey && event.shiftKey && col && !col.columnGroup && col.filterable) {
this.activeCollection.at(4).completed = true;
}
if ((key === 'arrowleft' || key === 'arrowright') && event.altKey && event.shiftKey &&
col && !col.columnGroup && col.groupable) {
this.activeCollection.at(2).completed = true;
}
if ((key === 'arrowup' || key === 'arrowdown') && event.ctrlKey) {
if (col && !col.columnGroup && col.sortable) {
this.activeCollection.at(1).completed = true;
}
}
if (key === " ") {
this.activeCollection.at(0).completed = true;
}
if (col && col.columnGroup && (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright') && event.altKey) {
this.activeCollection.at(3).completed = true;
}
}
if (this.gridSection === GridSection.TBODY) {
if (key === 'enter') {
const currColumn = this.grid.columns.filter(x => !x.columnGroup)
.find(c => c.visibleIndex === activeNode.column);
const cell = this.grid.getCellByColumn(activeNode.row, currColumn.field);
if (cell && cell.column.editable) {
this.activeCollection.at(0).completed = true;
}
}
if ((key === 'end' || key === 'home') && event.ctrlKey) {
this.activeCollection.at(4).completed = true;
}
const rowRef = this.grid.getRowByIndex(activeNode.row);
const isGroupByRow = rowRef.isGroupByRow;
if (!isGroupByRow && (key === 'arrowdown' || key === 'arrowright') && event.altKey) {
this.activeCollection.at(2).completed = true;
}
if (!isGroupByRow && (key === 'arrowup' || key === 'arrowleft') && event.altKey) {
this.activeCollection.at(1).completed = true;
}
if (isGroupByRow && (key === 'arrowright' || key === 'arrowleft') && event.altKey ) {
this.activeCollection.at(3).completed = true;
}
}
this.updateList();
}
public gridKeydown(event: any) {
const evt = event.detail.event;
this.keydown(evt);
}
public listTemplate = () => {
let htmlContent = "";
(window as any).onChangeHandler = (i: number) => {
this.activeCollection.at(i).completed = (event.currentTarget as any).checked;
}
let i = 0;
const headerText = this.gridSection === GridSection.THEAD ? "HEADER COMBINATIONS" : this.gridSection === GridSection.TBODY ? "BODY COMBINATIONS" : "SUMMARY COMBINATIONS";
const header = "<igc-list-header><h1>"+ headerText +"</h1></igc-list-header>";
htmlContent += header;
for (const elem of this.activeCollection) {
const checkbox = elem.completed ? "<input type='checkbox' slot='end' checked onchange='onChangeHandler("+i+")'></input>" : "<input type='checkbox' onchange='onChangeHandler("+i+")' slot='end'></input>";
const disabledClass = !elem.active ? "disabled" : "";
htmlContent += "<igc-list-item class='" + disabledClass +
"'> <h2 slot='title'>" + elem.title + "</h2>" +
"<span slot='subtitle'>" + elem.subTitle +"</span>" +
checkbox +
"</igc-list-item>";
i++;
}
return htmlContent;
}
private _data: EmployeesFlatDetails = null;
public get data(): EmployeesFlatDetails {
if (this._data == null)
{
this._data = new EmployeesFlatDetails();
}
return this._data;
}
}
new Sample();
ts<!DOCTYPE html>
<html>
<head>
<title>Sample | Ignite UI | Web Components | infragistics</title>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="https://static.infragistics.com/xplatform/images/browsers/wc.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Kanit&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium Web" />
<link rel="stylesheet" href="https://static.infragistics.com/xplatform/css/samples/shared.v8.css" />
<link rel="stylesheet" href="/src/index.css" type="text/css" />
</head>
<body>
<div id="root">
<div class="container sample">
<div class="container fill">
<div class="grid_wrapper">
<igc-tree-grid column-selection="single" auto-generate="false" name="grid" id="grid" primary-key="ID" foreign-key="ParentID"
allow-advanced-filtering="true" allow-filtering="true">
<igc-paginator></igc-paginator>
<igc-grid-toolbar></igc-grid-toolbar>
<igc-column-group header="General Information" >
<igc-column field="Name" data-type="string" sortable="true" editable="true">
</igc-column>
<igc-column-group header="Personel Details" collapsible="true">
<igc-column field="ID" data-type="number" sortable="true" visible-when-collapsed="true">
</igc-column>
<igc-column field="Title" data-type="string" editable="true" sortable="true" visible-when-collapsed="false">
</igc-column>
<igc-column field="HireDate" data-type="date" editable="true" sortable="true" visible-when-collapsed="true">
</igc-column>
<igc-column field="Age" data-type="number" editable="true" sortable="true" visible-when-collapsed="false">
</igc-column>
</igc-column-group>
</igc-column-group>
<igc-column-group header="Address Information">
<igc-column-group header="Location" collapsible="true" expanded="false">
<igc-column field="Address" editable="true" data-type="string" sortable="true" visible-when-collapsed="true">
</igc-column>
<igc-column field="City" editable="true" data-type="string" sortable="true" visible-when-collapsed="false">
</igc-column>
<igc-column field="Country" editable="true" data-type="string" sortable="true" visible-when-collapsed="false">
</igc-column>
</igc-column-group>
<igc-column-group header="Contact Information">
<igc-column field="Fax" editable="true" data-type="string" sortable="true">
</igc-column>
<igc-column field="PostalCode" editable="true" header="Postal Code" data-type="string" sortable="true">
</igc-column>
<igc-column field="Phone" editable="true" data-type="string" sortable="true">
</igc-column>
</igc-column-group>
</igc-column-group>
</igc-tree-grid>
</div>
<div class="list_wrapper">
<igc-list id="list">
<span class="empty-list">
<h6>Use the native navigation of the browser until you reach some of the following grid
sections below:</h6>
<ul>
<li>Header</li>
<li>Body</li>
<li>Summary</li>
</ul>
<h6>When reached, an <b>action list</b> will be shown.</h6>
</span>
</div>
</div>
</div>
</div>
<!-- This script is needed only for parcel and it will be excluded for webpack -->
<% if (false) { %>
<script src="src/index.ts"></script>
<% } %>
</body>
</html>
html/* shared styles are loaded from: */
/* https://static.infragistics.com/xplatform/css/samples */
css
このサンプルが気に入りましたか? 完全な Ignite UI for Web Componentsツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
カスタム キーボード ナビゲーション
特定のキーまたはキーの組み合わせのデフォルトの動作をオーバーライドすることができるは、キーボード ナビゲーション機能の利点の 1 つです。たとえば、Enter キーまたは Tab キーを押して次のセルまたは下のセルへ移動します。 この以外のナビゲーションシナリオでも、Keyboard Navigation API で簡単に実現できます。
API | 説明 | 引数 |
---|---|---|
GridKeydown |
上記のキー押下やキー押下の組み合わせのいずれかが実行されたときに発生されるイベント。キャンセルできます。その他のキーの押下/組み合わせには、デフォルトの onkeydown イベントを使用します。 |
IgcGridKeydownEventArgs |
ActiveNodeChange |
アクティブ ノードが変更されたときに発行されるイベント。これを使用して、アクティブ フォーカス位置 (ヘッダー、tbody など)、列インデックス、行インデックス、またはネストされたレベルを決定できます。 | IgcActiveNodeChangeEventArgs |
navigateTo |
提供された Rowindex と VisibleColumnIndex に基づいてグリッド内の位置に移動します。{ targetType: GridKeydownTargetType, target: Object } タイプのパラメーターを受け入れるコールバック関数を通してターゲット要素上でカスタム ロジックを実行することもできます。使用方法: grid.navigateTo(10, 3, (args) => { args.target.nativeElement.focus(); }); |
RowIndex: number, VisibleColumnIndex: number, callback: ({ targetType: GridKeydownTargetType, target: Object } ) => {} |
getNextCell |
RowIndex と VisibleColumnIndex で次のセルを定義する ICellPosition オブジェクトを返します。コールバック関数は、getNextCell メソッドの 3 番目のパラメーターとして渡すことができます。コールバック関数は、パラメーターとして IgcColumnComponent を受け取り、指定された条件が満たされた場合に boolean 値を返します: const nextEditableCell = grid.getNextCell(0, 4, (col) => col.editable); |
currentRowIndex: number, currentVisibleColumnIndex: number, callback: (Column) => boolean |
getPreviousCell |
RowIndex と VisibleColumnIndex で前のセルを定義する ICellPosition オブジェクトを返します。コールバック関数は、getPreviousCell メソッドの 3 番目のパラメーターとして渡すことができます。コールバック関数は、パラメーターとして IgcColumnComponent を受け取り、指定された条件が満たされた場合に boolean 値を返します: const prevEditableCell = grid.getPreviousCell(0, 4, (col) => col.editable); |
CurrentRowIndex: number, CurrentVisibleColumnIndex: number, callback: (Column) => boolean |
API を使用して、ユーザー入力の検証やカスタム ナビゲーションなどの一般的なシナリオを実現する方法を示します。最初に、GridKeydown
イベントのイベント ハンドラーを登録する必要があります。
<igc-tree-grid id="grid1" primary-key="ProductID">
</igc-tree-grid>
html
constructor() {
const grid = this.grid = document.getElementById('grid1') as IgcTreeGridComponent;
grid.data = this.data
grid.addEventListener("gridKeydown", this.customKeydown);
}
ts
public customKeydown(args: : CustomEvent<IgcGridKeydownEventArgs>) {
const evt = args.detail;
const target = evt.target as IgcCellType;
const evt: KeyboardEvent = evt.event as KeyboardEvent;
const type = evt.targetType;
if (type === 'dataCell' && target.inEditMode && evt.key.toLowerCase() === 'tab') {
// 1. USER INPUT VALIDATION ON TAB
}
if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') {
// 2. CUSTOM NAVIGATION ON ENTER KEY PRESS
}
}
typescript
イベント引数の値に基づいて、独自のロジックを提供する 2つ のケースを識別しました (上記を参照)。API のメソッドを使用して、目的の処理を実行しましょう。ユーザーが編集モードでセル上で Tab キーを押している場合、入力の検証を実行します。ユーザーがセル上で Enter キーを押すと、次の行のセルへフォーカスを移動します。
// 1. USER INPUT VALIDATION ON TAB
if (target.column.dataType === 'number' && target.editValue < 10) {
// alert the user that the input is invalid
return;
}
// 2. CUSTOM NAVIGATION ON ENTER KEY PRESS
this.grid1.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj) => {
obj.target.activate();
});
typescript
実装の詳細は、サンプルコードを参照してください。
実装したカスタム シナリオを試すには以下のデモを使用してください。
- 数値列のセルをダブルクリックするか F2 キーを押し、値を 7 に変更して Tab キーを押します。プロンプト メッセージが表示されます。
- セルを選択して Enter キー を数回押します。キーを押すたびに、同じ列の下にある次の行のセルへフォーカスを移動します。
デモ
export class EmployeesNestedDataItem {
public constructor(init: Partial<EmployeesNestedDataItem>) {
Object.assign(this, init);
}
public ID: number;
public Age: number;
public Salary: number;
public Productivity: number;
public City: string;
public Country: string;
public Phone: string;
public HireDate: string;
public Name: string;
public Title: string;
public Employees: EmployeesNestedDataItem_EmployeesItem[];
}
export class EmployeesNestedDataItem_EmployeesItem {
public constructor(init: Partial<EmployeesNestedDataItem_EmployeesItem>) {
Object.assign(this, init);
}
public Age: number;
public Salary: number;
public Productivity: number;
public City: string;
public Country: string;
public Phone: string;
public HireDate: string;
public ID: number;
public Name: string;
public Title: string;
}
export class EmployeesNestedData extends Array<EmployeesNestedDataItem> {
public constructor(items: Array<EmployeesNestedDataItem> | number = -1) {
if (Array.isArray(items)) {
super(...items);
} else {
const newItems = [
new EmployeesNestedDataItem(
{
ID: 1,
Age: 55,
Salary: 80000,
Productivity: 90,
City: `Berlin`,
Country: `Germany`,
Phone: `609-202-505`,
HireDate: `2008-03-20`,
Name: `John Winchester`,
Title: `Development Manager`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 43,
Salary: 70000,
Productivity: 80,
City: `Hamburg`,
Country: `Germany`,
Phone: `609-444-555`,
HireDate: `2011-06-03`,
ID: 3,
Name: `Michael Burke`,
Title: `Senior Software Developer`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 29,
Salary: 60000,
Productivity: 80,
City: `Munich`,
Country: `Germany`,
Phone: `609-333-444`,
HireDate: `2009-06-19`,
ID: 2,
Name: `Thomas Anderson`,
Title: `Senior Software Developer`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 31,
Salary: 90000,
Productivity: 80,
City: `Warasw`,
Country: `Poland`,
Phone: `609-222-205`,
HireDate: `2014-08-18`,
ID: 11,
Name: `Monica Reyes`,
Title: `Software Development Team Lead`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 35,
Salary: 70000,
Productivity: 70,
City: `Koln`,
Country: `Germany`,
Phone: `609-502-525`,
HireDate: `2015-09-17`,
ID: 6,
Name: `Roland Mendel`,
Title: `Senior Software Developer`
})]
}),
new EmployeesNestedDataItem(
{
ID: 4,
Age: 42,
Salary: 90000,
Productivity: 80,
City: `Kielce`,
Country: `Poland`,
Phone: `609-202-505`,
HireDate: `2014-01-22`,
Name: `Ana Sanders`,
Title: `CEO`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 44,
Salary: 80000,
Productivity: 80,
City: `Warasw`,
Country: `Poland`,
Phone: `609-202-505`,
HireDate: `2014-04-04`,
ID: 14,
Name: `Laurence Johnson`,
Title: `Director`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 25,
Salary: 85000,
Productivity: 55,
City: `Paris`,
Country: `France`,
Phone: `609-202-505`,
HireDate: `2017-11-09`,
ID: 5,
Name: `Elizabeth Richards`,
Title: `Vice President`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 39,
Salary: 88000,
Productivity: 88,
City: `London`,
Country: `UK`,
Phone: `609-202-505`,
HireDate: `2010-03-22`,
ID: 13,
Name: `Trevor Ashworth`,
Title: `Director`
})]
}),
new EmployeesNestedDataItem(
{
ID: 18,
Age: 49,
Salary: 77000,
Productivity: 70,
City: `Manchester`,
Country: `UK`,
Phone: `222-555-577`,
HireDate: `2014-01-22`,
Name: `Victoria Lincoln`,
Title: `Senior Accountant`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 43,
Salary: 70000,
Productivity: 80,
City: `Hamburg`,
Country: `Germany`,
Phone: `609-444-555`,
HireDate: `2011-06-03`,
ID: 23,
Name: `Thomas Burke`,
Title: `Senior Accountant`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 29,
Salary: 60000,
Productivity: 80,
City: `Munich`,
Country: `Germany`,
Phone: `609-333-444`,
HireDate: `2009-06-19`,
ID: 22,
Name: `Michael Anderson`,
Title: `Junior Accountant`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 31,
Salary: 90000,
Productivity: 80,
City: `Warasw`,
Country: `Poland`,
Phone: `609-222-205`,
HireDate: `2014-08-18`,
ID: 21,
Name: `Roland Reyes`,
Title: `Accountant Team Lead`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 35,
Salary: 70000,
Productivity: 70,
City: `Koln`,
Country: `Germany`,
Phone: `609-502-525`,
HireDate: `2015-09-17`,
ID: 24,
Name: `Monica Mendel`,
Title: `Senior Software Developer`
})]
}),
new EmployeesNestedDataItem(
{
ID: 10,
Age: 61,
Salary: 85000,
Productivity: 890,
City: `Lyon`,
Country: `France`,
Phone: `259-266-887`,
HireDate: `2010-01-01`,
Name: `Yang Wang`,
Title: `Localization Developer`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 31,
Salary: 90000,
Productivity: 80,
City: `Warasw`,
Country: `Poland`,
Phone: `609-222-205`,
HireDate: `2014-08-18`,
ID: 11,
Name: `Monica Reyes`,
Title: `Software Development Team Lead`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 35,
Salary: 70000,
Productivity: 70,
City: `Koln`,
Country: `Germany`,
Phone: `609-502-525`,
HireDate: `2015-09-17`,
ID: 6,
Name: `Roland Mendel`,
Title: `Senior Software Developer`
})]
}),
new EmployeesNestedDataItem(
{
ID: 35,
Age: 35,
Salary: 75000,
Productivity: 75,
City: `Warasw`,
Country: `Poland`,
Phone: `688-244-844`,
HireDate: `2014-01-22`,
Name: `Janine Munoz`,
Title: `HR`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 43,
Salary: 70000,
Productivity: 80,
City: `Hamburg`,
Country: `Germany`,
Phone: `609-444-555`,
HireDate: `2011-06-03`,
ID: 3,
Name: `Michael Burke`,
Title: `Senior Software Developer`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 31,
Salary: 90000,
Productivity: 80,
City: `Warasw`,
Country: `Poland`,
Phone: `609-222-205`,
HireDate: `2014-08-18`,
ID: 11,
Name: `Monica Reyes`,
Title: `Software Development Team Lead`
})]
}),
new EmployeesNestedDataItem(
{
ID: 10,
Age: 49,
Salary: 95000,
Productivity: 80,
City: `Krakow`,
Country: `Poland`,
Phone: `677-266-555`,
HireDate: `2010-01-01`,
Name: `Yang Wang`,
Title: `Sales Manager`,
Employees: [
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 29,
Salary: 60000,
Productivity: 80,
City: `Munich`,
Country: `Germany`,
Phone: `609-333-444`,
HireDate: `2009-06-19`,
ID: 2,
Name: `Thomas Anderson`,
Title: `Senior Software Developer`
}),
new EmployeesNestedDataItem_EmployeesItem(
{
Age: 35,
Salary: 70000,
Productivity: 70,
City: `Koln`,
Country: `Germany`,
Phone: `609-502-525`,
HireDate: `2015-09-17`,
ID: 6,
Name: `Roland Mendel`,
Title: `Senior Software Developer`
})]
}),
];
super(...newItems.slice(0));
}
}
}
tsimport { IgcPropertyEditorPanelModule } from 'igniteui-webcomponents-layouts';
import 'igniteui-webcomponents-grids/grids/combined';
import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebTreeGridDescriptionModule } from 'igniteui-webcomponents-core';
import { IgcTreeGridComponent } from 'igniteui-webcomponents-grids/grids';
import { EmployeesNestedDataItem, EmployeesNestedDataItem_EmployeesItem, EmployeesNestedData } from './EmployeesNestedData';
import { IgcGridComponent, IgcGridKeydownEventArgs, GridKeydownTargetType } from 'igniteui-webcomponents-grids/grids';
import "igniteui-webcomponents-grids/grids/themes/light/bootstrap.css";
import { ModuleManager } from 'igniteui-webcomponents-core';
import "./index.css";
ModuleManager.register(
IgcPropertyEditorPanelModule
);
export class Sample {
private treeGrid: IgcTreeGridComponent
private _bind: () => void;
constructor() {
var treeGrid = this.treeGrid = document.getElementById('treeGrid') as IgcTreeGridComponent;
this.webGridCustomKBNav = this.webGridCustomKBNav.bind(this);
this._bind = () => {
treeGrid.data = this.employeesNestedData;
treeGrid.addEventListener("gridKeydown", this.webGridCustomKBNav);
}
this._bind();
}
private _employeesNestedData: EmployeesNestedData = null;
public get employeesNestedData(): EmployeesNestedData {
if (this._employeesNestedData == null)
{
this._employeesNestedData = new EmployeesNestedData();
}
return this._employeesNestedData;
}
private _componentRenderer: ComponentRenderer = null;
public get renderer(): ComponentRenderer {
if (this._componentRenderer == null) {
this._componentRenderer = new ComponentRenderer();
var context = this._componentRenderer.context;
PropertyEditorPanelDescriptionModule.register(context);
WebTreeGridDescriptionModule.register(context);
}
return this._componentRenderer;
}
public webGridCustomKBNav(evtArgs: CustomEvent<IgcGridKeydownEventArgs>): void {
const args = evtArgs.detail;
const target = args.target;
const evt = args.event;
const type = args.targetType;
var grid = this.treeGrid as any;
if (type === "dataCell" && target.editMode && (evt as any).key.toLowerCase() === 'tab') {
// Value validation for number column.
// This covers both 'tab' and 'shift+tab' key interactions.
args.event.preventDefault();
args.cancel = true;
if (target.column.dataType === 'number' && target.editValue < 10) {
alert('The value should be bigger than 10');
return;
}
const cell = (evt as any).shiftKey ?
grid.getPreviousCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable) :
grid.getNextCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable);
grid.navigateTo(cell.rowIndex, cell.visibleColumnIndex,
(obj: any) => { obj.target.activate(); });
} else if (type === "dataCell" && (evt as any).key.toLowerCase() === 'enter') {
// Perform column based kb navigation with 'enter' key press
args.cancel = true;
grid.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj: any) => {
obj.target.activate();
});
}
}
}
new Sample();
ts<!DOCTYPE html>
<html>
<head>
<title>Sample | Ignite UI | Web Components | infragistics</title>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="https://static.infragistics.com/xplatform/images/browsers/wc.png" >
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Kanit&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Titillium Web" />
<link rel="stylesheet" href="https://static.infragistics.com/xplatform/css/samples/shared.v8.css" />
<link rel="stylesheet" href="/src/index.css" type="text/css" />
</head>
<body>
<div id="root">
<div class="container sample ig-typography">
<div class="container fill">
<igc-tree-grid
auto-generate="false"
name="treeGrid"
id="treeGrid"
id="treeGrid"
child-data-key="Employees"
row-selection="multiple"
moving="true"
allow-filtering="true">
<igc-paginator
per-page="15">
</igc-paginator>
<igc-column
field="Name"
header="Name"
data-type="string"
sortable="true"
editable="true"
resizable="true">
</igc-column>
<igc-column
field="HireDate"
header="Hire Date"
data-type="date"
sortable="true"
editable="true"
resizable="true">
</igc-column>
<igc-column
field="Age"
header="Age"
data-type="number"
sortable="true"
editable="true"
resizable="true">
</igc-column>
</igc-tree-grid>
</div>
</div>
</div>
<!-- This script is needed only for parcel and it will be excluded for webpack -->
<% if (false) { %><script src="src/index.ts"></script><% } %>
</body>
</html>
html/* shared styles are loaded from: */
/* https://static.infragistics.com/xplatform/css/samples */
css
既知の問題と制限
制限 | 説明 |
---|---|
スクロール可能な親コンテナーを使用してグリッド内を移動します。 | グリッドがスクロール可能な親コンテナー内に配置され、ユーザーが表示されていないグリッドのセルへ移動した場合、親コンテナーはスクロールされません。 |
その他のリソース
コミュニティに参加して新しいアイデアをご提案ください。