Angular Grid キーボード ナビゲーション
IgxGrid のキーボード ナビゲーションは、さまざまなキーボード操作をユーザーに提供します。これにより IgxGrid のアクセシビリティが向上し、内部の要素 (セル、行、列ヘッダー、ツールバー、フッターなど) をナビゲートできるようになります。この機能はデフォルトで有効になっています。デフォルトの動作を簡単にオーバーライドするオプションがあります。
IgxGrid のタブが削減され、ナビゲーションが W3C のアクセシビリティ標準に準拠し、使いやすくなりました。
現在、IgxGrid には以下のタブ位置が導入されています。
GroupBy または ツールバーの領域 (有効な場合)
IgxGrid ヘッダー
IgxGrid 本体
列集計 (有効な場合)
IgxGrid ページネーター (有効な場合)
この動作変更のため、Tab と Shift + Tab キーでセル間を移動することは IgxGrid でサポートされなくなりました。
Tab キーを押すと、グループ化 / ツール バー -> ヘッダー -> 本体 -> 集計 -> フッター / ページネーター の順序にタブ位置を移動します。
テンプレートによってフォーカス可能な 要素を IgxGrid の本体に公開すると、ブラウザのデフォルトの動作が防止されていないため、予期されない結果 が発生する可能性があります。
したがって、それを適切に防止/変更する のは開発者の責任です。
ヘッダー ナビゲーション
IgxGrid ヘッダーのキーボード ナビゲーションが完全にサポートされるようになりました。列ヘッダーは矢印キーで簡単にトラバースできます。さらに、フィルタリング 、ソート 、グループ化 などの列操作をトリガーするキーの組み合わせがいくつかあります。
IgxGrid ヘッダー コンテナーがフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
上矢印 - ヘッダーで 1 つ上のセルへ移動 (ループなし); 複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
下矢印 - ヘッダーの 1 つ下のセルに移動 (ラッピングなし); 複数行レイアウトまたは複数列ヘッダーが定義されている場合のみ使用できます。
左矢印 - 1 つ左のセルへ移動 (ループなし)
右矢印 - 1 つ右のセルへ移動 (行間のラッピングなし)
Ctrl + 左矢印 - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
Home - 行の左端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの左端のセルへ移動
Ctrl + 右矢印 - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
End - 行の右端のセルへ移動; 複数行レイアウトまたは複数列ヘッダーが有効な場合、同じレベルの右端のセルへ移動
Alt + L - 詳細フィルタリングが有効な場合、詳細フィルタリング ダイアログを開きます。
Ctrl + Shift + L - 列がフィルター可能な場合、Excel スタイル フィルターまたはデフォルト (行) フィルターを開きます。
Ctrl + Arrow Up - アクティブな列ヘッダーを昇順にソートします。列が昇順で既にソートされている場合、ソート状態を削除します。
Ctrl + Arrow Down - アクティブな列ヘッダーを降順にソートします。列が降順で既にソートされている場合、ソート状態を削除します。
Space - 列を選択します。列がすでに選択されている場合、選択を解除します。
Shift + Alt + 左矢印 - 列がグループ化可能としてマークされている場合、列をグループ化します。
Shift + Alt + 右矢印 - 列がグループ化可能としてマークされている場合、列のグループ化を解除します。
Alt +左矢印 または Alt +上矢印 - 列が縮小されていない場合、列グループ ヘッダーを縮小します。
Alt +右矢印 または Alt +下矢印 - 列がまだ展開されていない場合、列グループヘッダーを展開します。
本体ナビゲーション
IgxGrid 本体がフォーカスされている場合、以下のキー組み合わせを使用できます。
キーの組み合わせ
上矢印 - 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 + 下矢印 - グループ行はグループを展開します。
Alt + 左矢印 または Alt + 上矢印 - マスター/詳細行で詳細ビューを縮小します。
Alt + 右矢印 または Alt + 下矢印 - マスター/詳細行で詳細ビューを展開します。
Space - グループ行上 - rowSelection プロパティが複数に設定されている場合、グループ内のすべての行を選択します。
以下のデモサンプルで上記のすべての操作を実行できます。ナビゲーション可能なグリッド要素をフォーカスすると、利用可能な操作のリストが表示されます。
デモ
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 { GridKeyboardnavGuide } from "./grid/grid-keyboardnav-guide-sample/grid-keyboardnav-sample.component" ;
import {
IgxGridModule,
IgxListModule,
IgxOverlayService
} from "igniteui-angular" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridKeyboardnavGuide
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxGridModule,
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 {
IgxColumnComponent,
IgxColumnGroupComponent,
CellType,
IgxGridComponent,
IgxListComponent,
SortingDirection,
IActiveNodeChangeEventArgs
} from 'igniteui-angular' ;
import { Subject } from 'rxjs' ;
import { takeUntil } from 'rxjs/operators' ;
import { DATA } from '../../data/customers' ;
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 , 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;
}
}
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 ) {
this ._collection[idx].completed = true ;
}
public deselectItem (idx: number ) {
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('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)
];
@Component ({
selector : 'grid-keyboardnav' ,
templateUrl : './grid-keyboardnav-sample.component.html' ,
styleUrls : ['grid-keyboardnav-sample.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 GridKeyboardnavGuide implements OnInit , OnDestroy {
@ViewChild (IgxGridComponent, { static : true })
public grid: IgxGridComponent;
@ViewChild (IgxListComponent, { static : true })
public listref: IgxListComponent;
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 ) { }
public onActiveNodeChange (evt: IActiveNodeChangeEventArgs ) {
if (this .grid.crudService.cell) {
return ;
}
const gridSection = evt.row < 0 ? GridSection.THEAD : evt.row === this .grid.dataView.length ?
GridSection.FOOTER : GridSection.TBODY;
this .changeCombinationsCollection(gridSection);
this .toggleHeaderCombinations(evt);
this .toggleBodyCombinations(evt);
}
public ngOnInit ( ) {
this .grid.data = DATA;
for (const item of this .grid.data) {
const names = item.CompanyName.split(' ' );
item.FirstName = names[0 ];
item.LastName = names[names.length - 1 ];
item.FullAddress = `${item.Address} , ${item.City} , ${item.Country} ` ;
item.PersonelDetails = `${item.ContactTitle} : ${item.ContactName} ` ;
item.CompanysAnnualProfit = (100000 + (Math .random() * Math .floor(1000000 ))).toFixed(0 );
}
this .grid.groupingExpansionStateChange.pipe(takeUntil(this ._destroyer))
.subscribe(() => {
if (this ._keyboardHandler.gridSection === GridSection.TBODY) {
this ._keyboardHandler.selectItem(3 );
}
});
this .grid.columnSelectionChanging.pipe(takeUntil(this ._destroyer))
.subscribe((args ) => {
const evt = args.event;
if (evt.type === 'keydown' ) {
this ._keyboardHandler.selectItem(0 );
}
});
this .grid.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 .grid.groupingExpressions = [
{ fieldName : 'ContactTitle' , dir : SortingDirection.Asc }
];
this .listref.itemClicked.pipe(takeUntil(this ._destroyer))
.subscribe((args ) => {
args.event.stopPropagation();
});
}
public ngOnDestroy ( ) {
this ._destroyer.next();
}
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 .grid.navigation.activeNode;
if (this ._keyboardHandler.gridSection === GridSection.THEAD) {
if (key === 'l' && evt.altKey) {
this ._keyboardHandler.selectItem(5 );
return ;
}
const col = this .grid.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(4 );
}
if ((key === 'arrowleft' || key === 'arrowright' ) && evt.altKey && evt.shiftKey &&
col && !col.columnGroup && col.groupable) {
this ._keyboardHandler.selectItem(2 );
}
if ((key === 'arrowup' || key === 'arrowdown' ) && evt.ctrlKey) {
if (col && !col.columnGroup && col.sortable) {
this ._keyboardHandler.selectItem(1 );
}
}
}
if (this ._keyboardHandler.gridSection === GridSection.TBODY) {
if (key === 'enter' ) {
const cell = this .grid.getCellByColumnVisibleIndex(activeNode.row, activeNode.column);
const isCellSelected = cell.selected;
if (cell && cell.column.editable && cell.editMode) {
this ._keyboardHandler.selectItem(0 );
}
}
if ((key === 'end' || key === 'home' ) && evt.ctrlKey) {
this ._keyboardHandler.selectItem(4 );
this .cdr.detectChanges();
}
}
}
public expandChange ( ) {
if (!this ._keyboardHandler.collection.length) {
return ;
}
this ._keyboardHandler.selectItem(3 );
}
public onCheckChange (evt, idx ) {
evt.checked ? this ._keyboardHandler.selectItem(idx) : this ._keyboardHandler.deselectItem(idx);
}
public toggleHeaderCombinations (activeNode ) {
if (this ._keyboardHandler.gridSection !== GridSection.THEAD) {
return ;
}
const currColumn = this .grid.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 .grid.getRowByIndex(activeNode.row);
if (this ._keyboardHandler.gridSection !== GridSection.TBODY || !rowRef) {
return ;
}
if (rowRef.isGroupByRow) {
this ._keyboardHandler.enableActionItems([ItemAction.Expandable]);
} else {
const cell = this .grid.getCellByColumn(activeNode.row,
this .grid.columnList.find((col ) => col.visibleIndex === activeNode.column).field);
this .toggleCellCombinations(cell);
}
}
public toggleCellCombinations (cell?: CellType ) {
const actions = this .extractCellActions(cell);
this ._keyboardHandler.enableActionItems(actions);
}
public changeCombinationsCollection (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 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);
}
res.push(ItemAction.Collapsible);
return res;
}
}
ts コピー <div class ="sample" >
<div class ="grid_wrapper" >
<igx-grid height ="450px" width ="100%" [allowFiltering ]="true" [filterMode ]="'excelStyleFilter'" displayDensity ="compact"
summaryCalculationMode ="rootLevelOnly" columnSelection ="single" [allowAdvancedFiltering ]="true" [moving ]="true"
(keydown )="gridKeydown($event)" (activeNodeChange )="onActiveNodeChange($event)" >
<igx-paginator > </igx-paginator >
<igx-grid-toolbar *ngIf ="false" > </igx-grid-toolbar >
<ng-template igxGridDetail let-dataItem >
<div *ngIf ="dataItem.CompanysAnnualProfit" >
<header > Annual Profit:</header >
<span > {{dataItem.CompanysAnnualProfit}}</span >
</div >
</ng-template >
<igx-column-group header ="General Information" >
<igx-column field ="CompanyName" [hasSummary ]="true" [groupable ]="true" [editable ]="true" [sortable ]="true" [selectable ]="false" > </igx-column >
<igx-column-group header ="Personel Details" [collapsible ]="true"
dataType ="string" [expanded ]="true" (expandedChange )="expandChange()" >
<igx-column field ="PersonelDetails" [width ]="'250px'" [groupable ]="true"
[selectable ]="false" [visibleWhenCollapsed ]="true" [sortable ]="true" > </igx-column >
<igx-column field ="ContactName" [groupable ]="true" [visibleWhenCollapsed ]="false" [selectable ]="false"
[hasSummary ]="true" [sortable ]="true" [groupable ]="true" [editable ]="true" > </igx-column >
<igx-column field ="ContactTitle" [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" > </igx-column >
<igx-column field ="Country" [groupable ]="true" [selectable ]="false" [visibleWhenCollapsed ]="false" [hasSummary ]="true" [sortable ]="true" [editable ]="true" > </igx-column >
<igx-column field ="Region" [groupable ]="true" [visibleWhenCollapsed ]="false" [sortable ]="true" [groupable ]="true" [editable ]="true" > </igx-column >
<igx-column field ="City" [groupable ]="true" [selectable ]="false" [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" [groupable ]="true" [editable ]="true" [selectable ]="false" > </igx-column >
<igx-column field ="Fax" [editable ]="true" > </igx-column >
<igx-column field ="PostalCode" > </igx-column >
</igx-column-group >
</igx-column-group >
</igx-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($color : '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 : elevation(2 );
}
.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-grid #grid1 [data ]="data" [primaryKey ]="'ProductID'" (gridKeydown )="customKeydown($event)" >
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 < 10 ) {
return ;
}
this .grid1.navigateTo(target.row.index + 1 , target.column.visibleIndex, (obj ) => {
obj.target.activate();
});
typescript
実装したカスタム シナリオを試すには以下のデモを使用してください。
Order
列のセルをダブルクリックするか F2 キーを押し、値を 7
に変更して 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 { GridCustomKBNavigationComponent } from "./grid/grid-custom-kb-navigation/grid-custom-kb-navigation-sample.component" ;
import { IgxGridModule } from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridCustomKBNavigationComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, OnInit, ViewChild } from '@angular/core' ;
import { IGridKeydownEventArgs, CellType, IgxGridComponent, GridSelectionMode } from 'igniteui-angular' ;
import { DATA } from '../../data/nwindData' ;
@Component ({
selector : 'app-grid-custom-kb-navigation-sample' ,
styleUrls : ['./grid-custom-kb-navigation-sample.component.scss' ],
templateUrl : 'grid-custom-kb-navigation-sample.component.html'
})
export class GridCustomKBNavigationComponent implements OnInit {
@ViewChild ('grid1' , { read : IgxGridComponent, static : true })
public grid1: IgxGridComponent;
public selectionMode: GridSelectionMode = 'multiple' ;
public data: any [];
constructor ( ) {
}
public ngOnInit(): void {
this .data = 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 < 10 ) {
alert('The value should be bigger than 10' );
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-grid [igxPreventDocumentScroll ]="true" #grid1 [data ]="data" [moving ]="true" [primaryKey ]="'ProductID'" [autoGenerate ]="false" [displayDensity ]="'cosy'"
width ="100%" height ="350px" [rowSelection ]="selectionMode" (gridKeydown )="customKeydown($event)" >
<igx-paginator > </igx-paginator >
<igx-column field ="ProductID" header ="Product ID" width ="16%" [headerClasses ]="'prodId'"
[editable ]="true" >
</igx-column >
<igx-column field ="ReorderLevel" header ="Orders" width ="16%" [sortable ]="true" [filterable ]="true" [editable ]="true"
dataType ="number" >
</igx-column >
<igx-column field ="ProductName" width ="16%" header ="ProductName" [sortable ]="true" [dataType ]="'string'"
[editable ]="true" [resizable ]="true" >
</igx-column >
<igx-column field ="UnitsInStock" header ="UnitsInStock" width ="16%" dataType ="number" [editable ]="true"
[sortable ]="true" >
</igx-column >
<igx-column field ="OrderDate" width ="16%" [dataType ]="'date'" [sortable ]="true"
[editable ]="true" [resizable ]="true" >
</igx-column >
<igx-column field ="Discontinued" header ="Discontinued" [dataType ]="'boolean'" width ="20%"
[editable ]="true" [resizable ]="true" >
</igx-column >
</igx-grid >
</div >
html コピー .grid__wrapper {
margin : 5px 16px ;
}
scss コピー
既知の制限
制限
説明
スクロール可能な親コンテナーを使用してグリッド内を移動します。
グリッドがスクロール可能な親コンテナー内に配置され、ユーザーが表示されていないグリッドのセルへ移動した場合、親コンテナーはスクロールされません。
API リファレンス
その他のリソース
コミュニティに参加して新しいアイデアをご提案ください。