Angular Tree Grid キーボード ナビゲーション
IgxTreeGrid のキーボード ナビゲーションは、さまざまなキーボード操作をユーザーに提供します。これにより IgxTreeGrid のアクセシビリティが向上し、内部の要素 (セル、行、列ヘッダー、ツールバー、フッターなど) をナビゲートできるようになります。この機能はデフォルトで有効になっています。デフォルトの動作を簡単にオーバーライドするオプションがあります。
IgxTreeGrid のタブが削減され、ナビゲーションが W3C のアクセシビリティ標準に準拠し、使いやすくなりました。
現在、IgxTreeGrid には以下のタブ位置が導入されています。
- GroupBy または ツールバーの領域 (有効な場合)
- IgxTreeGrid ヘッダー
- IgxTreeGrid 本体
- 列集計 (有効な場合)
- IgxTreeGrid ページネーター (有効な場合)
この動作変更のため、Tab と Shift + Tab キーでセル間を移動することは IgxTreeGrid でサポートされなくなりました。
Tabキーを押すと、グループ化 / ツール バー -> ヘッダー -> 本体 -> 集計 -> フッター / ページネーター の順序にタブ位置を移動します。
テンプレートによってフォーカス可能な要素を IgxTreeGrid の本体に公開すると、ブラウザのデフォルトの動作が防止されていないため、予期されない結果が発生する可能性があります。
したがって、それを適切に防止/変更するのは開発者の責任です。
ヘッダー ナビゲーション
IgxTreeGrid ヘッダーのキーボード ナビゲーションが完全にサポートされるようになりました。列ヘッダーは矢印キーで簡単にトラバースできます。さらに、フィルタリング、ソート、グループ化などの列操作をトリガーするキーの組み合わせがいくつかあります。
IgxTreeGrid ヘッダー コンテナーがフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
- 上矢印 - ヘッダーで 1 つ上のセルへ移動 (ループなし); 複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
- 下矢印 - ヘッダーの 1 つ下のセルに移動 (ラッピングなし); 複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
- 左矢印 - 1 つ左のセルへ移動 (ループなし)
- 右矢印 - 1 つ右のセルへ移動 (行間のラッピングなし)
- Ctrl + 左矢印 - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
- Home - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
- Ctrl + 右矢印 - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
- End - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
- Alt + L - 詳細フィルタリングが有効な場合、詳細フィルタリング ダイアログを開きます。
- Ctrl + Shift + L - 列がフィルター可能な場合、Excel スタイル フィルターまたはデフォルト (行) フィルターを開きます。
- Ctrl + Arrow Up - アクティブな列ヘッダーを昇順にソートします。列が昇順で既にソートされている場合、ソート状態を削除します。
- Ctrl + Arrow Down - アクティブな列ヘッダーを降順にソートします。列が降順で既にソートされている場合、ソート状態を削除します。
- Space - 列を選択します。列がすでに選択されている場合、選択を解除します。
本体ナビゲーション
IgxTreeGrid 本体がフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
- 上矢印 - 1 つ上のセルへ移動 (ラッピングなし)
- 下矢印 - 1 つ下のセルへ移動 (ラッピングなし)
- 左矢印 - 1 つ左のセルへ移動 (行間のラッピングなし)
- 右矢印 - 1 つ右のセルへ移動 (行間のラッピングなし)
- Ctrl + 左矢印 - 行の左端のセルへ移動
- Ctrl + 右矢印 - 行の右端のセルへ移動
- Ctrl + 上矢印 - 列の最初のセルへ移動
- Ctrl + 下矢印 - 列の最後のセルへ移動
- Home - 行の左端のセルへ移動
- End - 行の右端のセルへ移動
- Ctrl + Home - グリッドの最も左上のデータ セルへ移動
- Ctrl + End - グリッドの最も右下のデータ セルへ移動
- Page Up - 1 ページ (ビューポート) 上へスクロール
- Page Down - 1 ページ (ビューポート) 下へスクロール
- Enter 編集モードに入る
- F2 編集モードに入る
- Esc 編集モードを終了する
- Tab - 編集モードのセルがある場合のみ使用できます。行の次の編集可能なセルにフォーカスを移動します。行の最後のセルに達した場合、フォーカスを次の行の最初の編集可能なセルに移動します。行編集が有効な場合、フォーカスを編集可能な一番右のセルから CANCEL および DONE ボタンへ移動し、DONE ボタンから行の一番左の編集可能なセルへ移動します。
- Shift + Tab - 編集モードのセルがある場合のみ使用できます。行の一つ前の編集可能なセルにフォーカスを移動します。行の最初のセルに達した場合、フォーカスを前の行の最後の編集可能なセルに移動します。行編集が有効な場合、フォーカスを編集可能な一番右のセルから CANCEL および DONE ボタンへ移動し、DONE ボタンから行の一番右の編集可能なセルへ移動します。
- Space - 行の選択が有効な場合、行を選択します。
- Alt + 左矢印 または Alt + 上矢印 - 現在のノードを縮小します。
- Alt + 右矢印 または Alt + 下矢印 - 現在のノードを展開します。
以下のデモサンプルで上記のすべての操作を実行できます。ナビゲーション可能なグリッド要素をフォーカスすると、利用可能な操作のリストが表示されます。
デモ
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppComponent } from "./app.component";
import { TGridKeyboardnavGuide } from "./tree-grid/tgrid-keyboard-guide/tgrid-keyboardnav-guide.component";
import {
IgxTreeGridModule,
IgxListModule,
IgxOverlayService
} from "igniteui-angular";
@NgModule({
bootstrap: [AppComponent],
declarations: [
AppComponent,
TGridKeyboardnavGuide
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxTreeGridModule,
IgxListModule
],
providers: [IgxOverlayService],
entryComponents: [],
schemas: []
})
export class AppModule {}
ts
import { animate, state, style, transition, trigger } from '@angular/animations';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
IgxColumnComponent,
IgxColumnGroupComponent,
CellType,
IgxListComponent,
IgxOverlayService,
IgxTreeGridComponent,
IActiveNodeChangeEventArgs
} from 'igniteui-angular';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { generateEmployeeDetailedFlatData } from '../data/employees-flat-detailed';
enum GridSection {
THEAD = 'igx-grid__thead-wrapper',
TBODY = 'igx-grid__tbody-content',
FOOTER = 'igx-grid__tfoot'
}
enum ItemAction {
Filterable,
Sortable,
Selectable,
Groupable,
Collapsible,
Expandable,
Editable,
Always
}
enum ElementTags {
GROUPBY_ROW = 'IGX-GRID-GROUPBY-ROW',
COLUMN_GROUP = 'IGX-COLUMN-GROUP'
}
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, ítemAction?: ItemAction) {
this.title = title;
this.subTitle = subTitle;
this.completed = completed;
this.action = ítemAction;
if (ítemAction === 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;
}
}
class KeyboardHandler {
private _collection: Item[];
private _section: GridSection;
public constructor(colleciton: Item[], section: GridSection) {
this._collection = colleciton;
this._section = section;
}
public set collection(collection: Item[]) {
this._collection = collection;
}
public get collection() {
return this._collection;
}
public set gridSection(section: GridSection) {
this._section = section;
}
public get gridSection() {
return this._section;
}
public enableActionItems(action: ItemAction[]) {
this.resetCollection();
action.forEach(element => {
this._collection
.filter(e => e.action === element)
.map(e => e.active = true);
});
}
public resetCollection() {
this._collection.forEach(e => {
if (e.action !== ItemAction.Always) {
e.active = false;
}
});
}
public selectItem(idx: number) {
if (!this._collection.length) {
return;
}
this._collection[idx].completed = true;
}
public deselectItem(idx: number) {
if (!this._collection.length) {
return;
}
this._collection[idx].completed = false;
}
}
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('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 row', false, ItemAction.Collapsible),
new Item('alt + arrow right/down', 'expand row', false, ItemAction.Collapsible),
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 right', false, ItemAction.Always),
new Item('ArrowRight', 'navigates one summary cell left', 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)
];
@Component({
selector: 'app-grid-keyboardnav',
templateUrl: './tgrid-keyboardnav-guide.component.html',
styleUrls: ['tgrid-keyboardnav-guide.component.scss'],
animations: [
trigger('toggle', [
state('selected', style({
color: '#4eb862'
})),
state('deselected', style({
color: 'black'
})),
transition('deselected => selected', [
animate('.3s')
]),
transition('selected => deselected', [
animate('.3s')
])
]),
trigger('load', [
transition(':enter', [
style({ opacity: 0 }),
animate('.3s', style({ opacity: .4 }))
])
])
]
})
export class TGridKeyboardnavGuide implements OnInit, OnDestroy {
@ViewChild(IgxTreeGridComponent, { static: true })
public tgrid: IgxTreeGridComponent;
@ViewChild(IgxListComponent, { static: true })
public listref: IgxListComponent;
public data;
public get keyboardCollection() {
return this._keyboardHandler.collection;
}
public get headerList() {
return this._keyboardHandler.gridSection === GridSection.THEAD ?
'HEADER COMBINATIONS' : this._keyboardHandler.gridSection === GridSection.TBODY ?
'BODY COMBITNATIONS' : this._keyboardHandler.gridSection === GridSection.FOOTER ?
'SUMMARY COMBINATIONS' : '';
}
private _destroyer = new Subject();
private _keyboardHandler = new KeyboardHandler([], GridSection.THEAD);
public constructor(private cdr: ChangeDetectorRef, private _overlay: IgxOverlayService) { }
public onActiveNodeChange(evt: IActiveNodeChangeEventArgs) {
if (this.tgrid.crudService.cell) {
return;
}
const gridSection = evt.row < 0 ? GridSection.THEAD : evt.row === this.tgrid.dataView.length ?
GridSection.FOOTER : GridSection.TBODY;
this.changeKeyboardCollection(gridSection);
this.toggleHeaderCombinations(evt);
this.toggleBodyCombinations(evt);
}
public ngOnInit() {
this.data = generateEmployeeDetailedFlatData();
this.tgrid.columnSelectionChanging.pipe(takeUntil(this._destroyer))
.subscribe((args) => {
const evt = args.event;
if (evt.type === 'keydown') {
this._keyboardHandler.selectItem(0);
}
});
this.tgrid.rowToggle.pipe(takeUntil(this._destroyer))
.subscribe((args) => {
const evt = args.event as KeyboardEvent;
if (evt.type !== 'keydown') {
return;
}
return evt.code === 'ArrowLeft' || evt.code === 'ArrowUp' ? this._keyboardHandler.selectItem(1) :
this._keyboardHandler.selectItem(2);
});
this.listref.itemClicked.pipe(takeUntil(this._destroyer))
.subscribe((args) => {
args.event.stopPropagation();
});
}
public ngOnDestroy() {
this._destroyer.next();
}
public expandChange() {
if (!this._keyboardHandler.collection.length) {
return;
}
this._keyboardHandler.selectItem(2);
}
public onCheckChange(evt, idx) {
evt.checked ? this._keyboardHandler.selectItem(idx) : this._keyboardHandler.deselectItem(idx);
}
public changeKeyboardCollection(gridSection: GridSection) {
switch (gridSection) {
case GridSection.THEAD:
this._keyboardHandler.collection = theadKeyCombinations;
break;
case GridSection.TBODY:
this._keyboardHandler.collection = tbodyKeyCombinations;
break;
case GridSection.FOOTER:
this._keyboardHandler.collection = summaryCombinations;
break;
default:
this._keyboardHandler.collection = [];
return;
}
this._keyboardHandler.gridSection = gridSection;
}
public gridKeydown(evt) {
const key = evt.key.toLowerCase();
if (key === 'tab') { return; }
if (this._keyboardHandler.gridSection === GridSection.FOOTER) {
switch (key) {
case 'end':
this._keyboardHandler.selectItem(3);
break;
case 'home':
this._keyboardHandler.selectItem(2);
break;
case 'arrowleft':
this._keyboardHandler.selectItem(0);
break;
case 'arrowright':
this._keyboardHandler.selectItem(1);
break;
default:
break;
}
return;
}
const activeNode = this.tgrid.navigation.activeNode;
if (this._keyboardHandler.gridSection === GridSection.THEAD) {
if (key === 'l' && evt.altKey) {
this._keyboardHandler.selectItem(4);
return;
}
const col = this.tgrid.visibleColumns.find
(c => c.visibleIndex === activeNode.column && c.level === activeNode.level);
if (key === 'l' && evt.ctrlKey && evt.shiftKey && col && !col.columnGroup && col.filterable) {
this._keyboardHandler.selectItem(3);
}
if ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey && col && !col.columnGroup && col.sortable) {
this._keyboardHandler.selectItem(1);
}
}
if (this._keyboardHandler.gridSection === GridSection.TBODY) {
if (key === 'enter') {
const cell = this.tgrid.getCellByColumnVisibleIndex(activeNode.row, activeNode.column);
if (cell && cell.column.editable && cell.editMode) {
this._keyboardHandler.selectItem(0);
}
}
if ((evt.code === 'End' || evt.code === 'Home') && evt.ctrlKey) {
this._keyboardHandler.selectItem(3);
this.cdr.detectChanges();
}
}
}
public toggleHeaderCombinations(activeNode) {
if (this._keyboardHandler.gridSection !== GridSection.THEAD) {
return;
}
const currColumn = this.tgrid.columnList
.find(c => c.visibleIndex === activeNode.column && c.level === activeNode.level);
const actions = this.extractColumnActions(currColumn);
this._keyboardHandler.enableActionItems(actions);
}
public toggleBodyCombinations(activeNode) {
const rowRef = this.tgrid.getRowByIndex(activeNode.row);
if (this._keyboardHandler.gridSection !== GridSection.TBODY || !rowRef) {
return;
}
const cell = this.tgrid.getCellByColumn(activeNode.row,
this.tgrid.columnList.find((col) => col.visibleIndex === activeNode.column).field);
this.toggleCellCombinations(cell);
}
public toggleCellCombinations(cell?: CellType) {
if (this._keyboardHandler.gridSection !== GridSection.TBODY) {
return;
}
const actions = this.extractCellActions(cell);
this._keyboardHandler.enableActionItems(actions);
}
public extractColumnActions(col: IgxColumnComponent | IgxColumnGroupComponent) {
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 extractCellActions(cell: CellType) {
const res = [];
if (cell.editable) {
res.push(ItemAction.Editable);
}
if (cell.row.children && cell.row.children.length) {
res.push(ItemAction.Collapsible);
}
return res;
}
}
ts
<div class="sample">
<div class="grid_wrapper">
<igx-tree-grid [data]="data" height="450px" width="100%" [moving]="true" [allowFiltering]="true" [filterMode]="'excelStyleFilter'" displayDensity="compact"
summaryCalculationMode="rootLevelOnly" primaryKey="ID" foreignKey="ParentID" columnSelection="single" [allowAdvancedFiltering]="true"
(keydown)="gridKeydown($event)" (activeNodeChange)="onActiveNodeChange($event)">
<igx-paginator></igx-paginator>
<igx-grid-toolbar *ngIf="false"></igx-grid-toolbar>
<igx-column field="Name" [hasSummary]="true" [editable]="true" [sortable]="true"></igx-column>
<igx-column-group header="General Information" >
<igx-column field="HireDate" [hasSummary]="true" [editable]="true" [sortable]="true"></igx-column>
<igx-column-group header="Personel Details" [collapsible]="true"
dataType="string" [expanded]="false" (expandedChange)="expandChange()">
<igx-column field="ID" [width]="'250px'" [visibleWhenCollapsed]="true" [sortable]="true"></igx-column>
<igx-column field="Title" [visibleWhenCollapsed]="false"
[hasSummary]="true" [sortable]="true" [editable]="true"></igx-column>
<igx-column field="Age" [visibleWhenCollapsed]="false" [sortable]="true" [groupable]="true" [editable]="true"></igx-column>
</igx-column-group>
</igx-column-group>
<igx-column-group header="Address Information">
<igx-column-group header="Location" [collapsible]="true" [expanded]="false" (expandedChange)="expandChange()">
<igx-column field="FullAddress" header="Full Address" [width]="'250px'" [visibleWhenCollapsed]="true"
[dataType]="'string'" [visibleWhenCollapsed]="true" [sortable]="true">
<ng-template igxCell let-cell="cell">
<div class="address-container">
<span><strong>Country:</strong> {{cell.row.data.Country}}</span>
<br/>
<span><strong>City:</strong> {{cell.row.data.City}}</span>
<br/>
<span><strong>Postal Code:</strong> {{cell.row.data.Address}}</span>
</div>
</ng-template>
</igx-column>
<igx-column field="Country" [visibleWhenCollapsed]="false" [hasSummary]="true" [sortable]="true" [editable]="true"></igx-column>
<igx-column field="City" [visibleWhenCollapsed]="false" [hasSummary]="true" [groupable]="true" [editable]="true"></igx-column>
<igx-column field="Address" [visibleWhenCollapsed]="false"></igx-column>
</igx-column-group>
<igx-column-group header="Contact Information">
<igx-column field="Phone" [editable]="true"></igx-column>
<igx-column field="Fax" [editable]="true"></igx-column>
<igx-column field="PostalCode"></igx-column>
</igx-column-group>
</igx-column-group>
</igx-tree-grid>
</div>
<div class="list-sample">
<igx-list>
<igx-list-item *ngIf="keyboardCollection.length > 0" [isHeader]="true">{{ headerList }}</igx-list-item>
<igx-list-item @load *ngFor="let c of keyboardCollection; let idx=index" [ngClass]="{ 'active': c.active, 'disabled': !c.active}" [@toggle]="c.completed ? 'selected' : 'deselected'">
<h4 igxListLineTitle>{{ c.title }}</h4>
<p igxListLineSubTitle>{{ c.subTitle }}</p>
<igx-checkbox [disabled]="!c.active" [checked]="c.completed" (change)="onCheckChange($event, idx)"></igx-checkbox>
</igx-list-item>
<ng-template igxEmptyList>
<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>
</ng-template>
</igx-list>
</div>
</div>
html
@use '../../../variables' as *;
$my-color: color($default-palette, 'success');
$custom-checkbox-theme: checkbox-theme(
$fill-color: $my-color,
$border-radius: 10px
);
.list-sample ::ng-deep {
@include checkbox($custom-checkbox-theme);
}
.sample {
display: flex;
.grid_wrapper {
padding-left: 15px;
padding-top: 15px;
width: 75%;
}
.list-sample {
padding-top: 15px;
width: 20%;
.disabled {
opacity: .4;
}
.active {
opacity: 1;
}
igx-list {
.igx-list__item-line-title, .igx-list__item-line-subtitle {
font-size: 13px;
}
height: 450px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2),
0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}
.empty-list {
opacity: 1;
h6 {
padding: 15px;
font-size: 15px;
}
ul {
li {
font-size: 13px;
}
margin-left: 15px;
font-weight: 400;
}
}
}
}
scss
このサンプルが気に入りましたか? 完全な Ignite UI for Angularツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
カスタム キーボード ナビゲーション
特定のキーまたはキーの組み合わせのデフォルトの動作をオーバーライドすることができるは、キーボード ナビゲーション機能の利点の 1 つです。たとえば、Enter キーまたは Tab キーを押して次のセルまたは下のセルへ移動します。この以外のナビゲーションシナリオでも、キーボード ナビゲーションの API で簡単に実現できます。
API |
説明 |
引数 |
gridKeydown |
上記のキー押下やキー押下の組み合わせのいずれかが実行されたときに発生されるイベント。キャンセルできます。その他のキーの押下/組み合わせには、デフォルトの onkeydown イベントを使用します。 |
IGridKeydownEventArgs |
activeNodeChange |
アクティブ ノードが変更されたときに発生するイベント。これを使用して、アクティブ フォーカス位置 (ヘッダー、tbody など)、列インデックス、行インデックス、またはネストされたレベルを決定できます。 |
IActiveNodeChangeEventArgs |
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 と visibileColumnIndex で次のセルを定義する ICellPosition オブジェクトを返します。コールバック関数は、getNextCell メソッドの 3 番目のパラメーターとして渡すことができます。コールバック関数は、パラメーターとして IgxColumnComponent を受け取り、指定された条件が満たされた場合に boolean 値を返します: const nextEditableCell = grid.getNextCell(0, 4, (col) => col.editable); |
currentRowIndex : number, currentVisibleColumnIndex : number, callback : (IgxColumnComponent ) => boolean |
getPreviousCell |
rowIndex と visibileColumnIndex で前のセルを定義する ICellPosition オブジェクトを返します。コールバック関数は、getPreviousCell メソッドの 3 番目のパラメーターとして渡すことができます。コールバック関数は、パラメーターとして IgxColumnComponent を受け取り、指定された条件が満たされた場合に boolean 値を返します: const prevEditableCell = grid.getPreviousCell(0, 4, (col) => col.editable); |
currentRowIndex : number, currentVisibleColumnIndex : number, callback : (IgxColumnComponent ) => boolean |
API を使用して、ユーザー入力の検証やカスタム ナビゲーションなどの一般的なシナリオを実現する方法を示します。最初に、gridKeydown
イベントのイベント ハンドラーを登録する必要があります。
<igx-tree-grid #grid1 [data]="data" (gridKeydown)="customKeydown($event)">
</igx-tree-grid>
html
public customKeydown(args: IGridKeydownEventArgs) {
const target: IgxGridCell = args.target as IgxGridCell;
const evt: KeyboardEvent = args.event as KeyboardEvent;
const type = args.targetType;
if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') {
}
if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') {
}
}
typescript
IGridKeydownEventArgs 値に基づいて、独自のロジックを提供する 2つ のケースを識別しました (上記を参照)。API のメソッドを使用して、目的の処理を実行しましょう。ユーザーが編集モードでセル上で Tab キーを押している場合、入力の検証を実行します。ユーザーがセル上で Enter キーを押すと、次の行のセルへフォーカスを移動します。
if (target.column.dataType === 'number' && target.editValue < 18) {
return;
}
const nexRowIndex = target.row.expanded ? target.rowIndex + 2 : target.rowIndex + 1;
grid.navigateTo(nexRowIndex, target.visibleColumnIndex,
(obj) => { obj.target.nativeElement.focus(); });
typescript
注: 実装の詳細は、サンプルコードを参照してください。
実装したカスタム シナリオを試すには以下のデモを使用してください。
Age
列のセルをダブルクリックするか F2 キーを押し、値を 16
に変更して Tab キーを押します。プロンプト メッセージが表示されます。
- セルを選択して Enter キー を数回押します。キーを押すたびに、同じ列の下にある次の行のセルへフォーカスを移動します。
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { AppComponent } from "./app.component";
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive";
import { IgxTreeGridModule } from "igniteui-angular";
import { TreeGridKBNavigationComponent } from "./tree-grid-keyboard-navigation/tree-grid-keyboard-navigation-sample.component";
@NgModule({
bootstrap: [AppComponent],
declarations: [
AppComponent,
TreeGridKBNavigationComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxTreeGridModule
],
providers: [],
entryComponents: [],
schemas: []
})
export class AppModule {}
ts
import { Component, OnInit, ViewChild } from '@angular/core';
import { IGridKeydownEventArgs, CellType, IgxTreeGridComponent, GridSelectionMode } from 'igniteui-angular';
import { EMPLOYEE_DATA } from './data';
@Component({
selector: 'app-tree-grid-keyboard-navigation-sample',
styleUrls: ['./tree-grid-keyboard-navigation-sample.component.scss'],
templateUrl: './tree-grid-keyboard-navigation-sample.component.html'
})
export class TreeGridKBNavigationComponent implements OnInit {
@ViewChild('grid1', { read: IgxTreeGridComponent, static: true })
public grid1: IgxTreeGridComponent;
public localData: any[];
public selectionMode: GridSelectionMode = 'multiple';
constructor() { }
public ngOnInit() {
this.localData = EMPLOYEE_DATA;
}
public customKeydown(args: IGridKeydownEventArgs) {
const target: CellType = args.target as CellType;
const evt: KeyboardEvent = args.event as KeyboardEvent;
const type = args.targetType;
if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') {
args.event.preventDefault();
args.cancel = true;
if (target.column.dataType === 'number' && target.editValue < 18) {
alert('The value should be bigger than 18');
return;
}
const cell = evt.shiftKey ?
this.grid1.getPreviousCell(target.row.index, target.column.visibleIndex, (col) => col.editable) :
this.grid1.getNextCell(target.row.index, target.column.visibleIndex, (col) => col.editable);
this.grid1.navigateTo(cell.rowIndex, cell.visibleColumnIndex,
(obj) => { obj.target.activate(); });
} else if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') {
args.cancel = true;
this.grid1.navigateTo(target.row.index + 1, target.column.visibleIndex,
(obj) => { obj.target.activate(); });
}
}
}
ts
<div class="grid__wrapper">
<igx-tree-grid [igxPreventDocumentScroll]="true" #grid1 [data]="localData" childDataKey="Employees" width="100%" height="500px"
displayDensity="compact" (gridKeydown)="customKeydown($event)" [moving]="true" [autoGenerate]="false" [rowSelection]="selectionMode" [allowFiltering]="true">
<igx-paginator></igx-paginator>
<igx-column field="HireDate" dataType="date" [sortable]="true" [editable]="true"
[resizable]="true"></igx-column>
<igx-column field="Age" dataType="number" [sortable]="true" [editable]="true"
[resizable]="true"></igx-column>
<igx-column field="Name" dataType="string" [sortable]="true" [editable]="true"
[resizable]="true"></igx-column>
</igx-tree-grid>
</div>
html
.grid__wrapper {
margin: 15px;
}
scss

既知の制限
制限 |
説明 |
スクロール可能な親コンテナーを使用してグリッド内を移動します。 |
グリッドがスクロール可能な親コンテナー内に配置され、ユーザーが表示されていないグリッドのセルへ移動した場合、親コンテナーはスクロールされません。 |
API リファレンス
その他のリソース
コミュニティに参加して新しいアイデアをご提案ください。