Angular Tree Grid Keyboard Navigation
IgxTreeGrid 의 키보드 탐색은 사용자에게 다양한 키보드 상호 작용을 제공합니다. IgxTreeGrid 의 접근성을 향상하고 내부의 모든 유형의 요소(셀, 행, 열 머리글, 도구 모음, 바닥글 등)를 탐색할 수 있습니다. 이 기능은 기본적으로 활성화되어 있으며 개발자는 기본 동작을 쉽게 재정의할 수 있는 옵션이 있습니다.
탐색이 W3C 접근성 표준을 준수하고 사용하기 편리하도록 IgxTreeGrid의 표가 줄어들었습니다.
현재 IgxTreeGrid에는 다음 탭 정지가 도입되었습니다.
GroupBy 또는 도구 모음 영역 (활성화된 경우)
IgxTreeGrid header ;
IgxTreeGrid body ;
열 요약 (활성화된 경우)
IgxTreeGrid paginator (if enabled);
이러한 변경으로 인해 IgxTreeGrid에서는 Tab 및 Shift + Tab을 사용한 셀 간 탐색이 더 이상 지원되지 않습니다. 이제 Tab 키를 누르면 GroupBy / Toolbar -> Headers -> Body -> Summaries -> Footer / Paginator 순서로 탭 정지가 진행됩니다.
템플릿을 통해 포커스 가능한 요소를 IgxTreeGrid 본문에 노출하면 기본 브라우저 동작이 방지되지 않으므로 키보드 탐색에 부작용이 발생할 수 있습니다. 이를 적절하게 방지하거나 수정하는 것은 개발자의 책임입니다.
전체 키보드 탐색 에서 지원 IgxTreeGrid 이제 헤더가 도입되었습니다. 열 머리글은 화살표 키를 사용하여 쉽게 이동할 수 있습니다. 또한 다음과 같이 열에 대한 작업을 트리거하는 여러 가지 키 조합이 있습니다. 필터링 , 정렬 , 그룹화 등등. IgxTreeGrid 헤더 컨테이너에 초점이 맞춰지면 다음 키 조합을 사용할 수 있습니다.
주요 조합
위쪽 화살표는 머리글에서 한 셀 위로 이동합니다(반복 없음). 다중 행 레이아웃(MRL) 또는 다중 열 헤더(MCH)가 정의된 경우에만 사용할 수 있습니다.
아래쪽 화살표는 헤더에서 한 셀 아래로 이동합니다(줄 바꿈 없음). 다중 행 레이아웃(MRL) 또는 다중 열 헤더(MCH)가 정의된 경우에만 사용할 수 있습니다.
왼쪽 화살표는 한 셀 왼쪽으로 이동합니다(반복 없음).
오른쪽 화살표 는 한 셀 오른쪽으로 이동합니다(줄 바꿈 없음).
Ctrl + 왼쪽 화살표는 행의 가장 왼쪽 셀로 이동합니다. MRL 또는 MCH가 활성화된 경우 동일한 수준의 가장 왼쪽 셀로 이동합니다.
홈은 행의 가장 왼쪽 셀로 이동합니다. MRL 또는 MCH가 활성화된 경우 동일한 수준의 가장 왼쪽 셀로 이동합니다.
Ctrl + 오른쪽 화살표는 행의 가장 오른쪽 셀로 이동합니다. MRL 또는 MCH가 활성화된 경우 동일한 수준의 가장 오른쪽 셀로 이동합니다.
End는 행의 가장 오른쪽 셀로 이동합니다. MRL 또는 MCH가 활성화된 경우 동일한 수준의 가장 오른쪽 셀로 이동합니다.
고급 필터링이 활성화된 경우 Alt + L은 고급 필터링 대화 상자를 엽니다.
Ctrl + Shift + L은 Excel 스타일 필터 대화 상자를 열거나 열을 필터링할 수 있는 경우 기본(행) 필터를 엽니다.
Ctrl + 위쪽 화살표는 활성 열 머리글을 ASC 순서로 정렬합니다. 열이 이미 ASC로 정렬되어 있는 경우 정렬 상태가 지워집니다.
Ctrl + 아래쪽 화살표는 활성 열 머리글을 DSC 순서로 정렬합니다. DSC에서 열이 이미 정렬된 경우 정렬 상태가 지워집니다.
Space는 열을 선택합니다. 열이 이미 선택되어 있으면 선택이 지워집니다.
본체 탐색
IgxTreeGrid 본체에 초점이 맞춰지면 다음 키 조합을 사용할 수 있습니다.
키 조합
위쪽 화살표 - 한 셀 위로 이동합니다(줄바꿈 없음).
아래쪽 화살표는 한 셀 아래로 이동합니다(줄바꿈 없음).
왼쪽 화살표는 한 셀 왼쪽으로 이동합니다(줄 바꿈 없음).
오른쪽 화살표 - 한 셀 오른쪽으로 이동합니다(줄 바꿈 없음).
Ctrl + 왼쪽 화살표는 행의 가장 왼쪽 셀로 이동합니다.
Ctrl + 오른쪽 화살표는 행의 가장 오른쪽 셀로 이동합니다.
Ctrl + 위쪽 화살표는 열의 첫 번째 셀로 이동합니다.
Ctrl + 아래쪽 화살표는 열의 마지막 셀로 이동합니다.
홈은 행의 가장 왼쪽 셀로 이동합니다.
End는 행의 가장 오른쪽 셀로 이동합니다.
Ctrl + Home은 그리드의 가장 왼쪽 상단 데이터 셀로 이동합니다.
Ctrl + End는 그리드의 가장 오른쪽 하단 데이터 셀로 이동합니다.
Page Up은 한 페이지(뷰 포트) 위로 스크롤합니다.
Page Down은 한 페이지(뷰 포트) 아래로 스크롤합니다.
Enter 편집 모드로 들어갑니다.
F2는 편집 모드로 들어갑니다
Esc를 눌러 편집 모드를 종료합니다.
편집 모드에 셀이 있는 경우에만 탭을 사용할 수 있습니다. 행의 편집 가능한 다음 셀로 포커스를 이동합니다. 행의 마지막 셀에 도달한 후 다음 행의 편집 가능한 첫 번째 셀로 포커스를 이동합니다. 행 편집이 활성화되면 행의 가장 오른쪽 편집 가능한 셀에서 CANCEL 및 DONE 버튼으로 포커스가 이동하고 DONE 버튼에서 행의 가장 왼쪽 편집 가능한 셀로 포커스가 이동됩니다.
Shift + Tab - 편집 모드에 셀이 있는 경우에만 사용할 수 있습니다. 행의 편집 가능한 이전 셀로 포커스를 이동합니다. 행의 첫 번째 셀에 도달한 후 이전 행의 편집 가능한 마지막 셀로 포커스를 이동합니다. 행 편집이 활성화되면 행의 가장 오른쪽 편집 가능한 셀에서 CANCEL 및 DONE 버튼으로 초점이 이동하고 DONE 버튼에서 행의 가장 오른쪽 편집 가능한 셀로 이동됩니다.
공백 -행 선택이 활성화된 경우 행을 선택합니다.
Alt + Arrow Left or Alt + Arrow Up - collapses the current node
Alt + Arrow Right or Alt + Arrow Down - expands the current node
아래 데모 샘플에서 위에 언급된 모든 작업을 연습해 보세요. 탐색 가능한 그리드 요소에 초점을 맞추면 해당 요소에 사용 가능한 일부 작업이 포함된 목록이 표시되어 안내됩니다.
데모
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, IgxPaginatorComponent, IgxGridToolbarComponent, IgxCellTemplateDirective, IgxListItemComponent, IgxListLineTitleDirective, IgxListLineSubTitleDirective, IgxCheckboxComponent, IgxEmptyListTemplateDirective } from 'igniteui-angular' ;
import { Subject } from 'rxjs' ;
import { takeUntil } from 'rxjs/operators' ;
import { generateEmployeeDetailedFlatData } from '../data/employees-flat-detailed' ;
import { NgIf, NgFor, NgClass } from '@angular/common' ;
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 }))
])
])
],
imports : [IgxTreeGridComponent, IgxPaginatorComponent, NgIf, IgxGridToolbarComponent, IgxColumnComponent, IgxColumnGroupComponent, IgxCellTemplateDirective, IgxListComponent, IgxListItemComponent, NgFor, NgClass, IgxListLineTitleDirective, IgxListLineSubTitleDirective, IgxCheckboxComponent, IgxEmptyListTemplateDirective]
})
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<void >();
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 columnName = this .tgrid.getColumnByVisibleIndex(activeNode.column).field;
const cell = this .tgrid.getCellByColumn(activeNode.row, columnName);
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'"
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 {
--ig-size: var(--ig-size-small);
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 툴킷에 액세스하고 몇 분 안에 나만의 앱을 구축해 보세요. 무료로 다운로드하세요.
맞춤 키보드 탐색
특정 키 또는 키 조합에 대한 기본 동작을 재정의하는 것은 키보드 탐색 기능이 제공하는 이점 중 하나입니다. 예를 들어 다음 셀이나 아래 셀로 이동하려면 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
보고ICellPosition
다음 셀을 정의하는 객체rowIndex
그리고visibileColumnIndex
. 콜백 함수는 세 번째 매개변수로 전달될 수 있습니다.getNextCell
방법. 콜백 함수는 다음을 허용합니다.IgxColumnComponent
매개변수로 반환하고boolean
주어진 기준이 충족되면 값 표시:const nextEditableCell = Grid.getNextCell(0, 4, (col) => col.editable);
currentRowIndex
: 숫자,currentVisibleColumnIndex
: 숫자,callback
: (IgxColumnComponent
) => 부울
getPreviousCell
보고ICellPosition
이전 셀을 정의하는 객체rowIndex
그리고visibileColumnIndex
. 콜백 함수는 세 번째 매개변수로 전달될 수 있습니다.getPreviousCell
방법. 콜백 함수는 다음을 허용합니다.IgxColumnComponent
매개변수로 반환하고boolean
주어진 기준이 충족되면 값 표시:const prevEditableCell = Grid.getPreviousCell(0, 4, (col) => col.editable);
currentRowIndex
: 숫자,currentVisibleColumnIndex
: 숫자,callback
: (IgxColumnComponent
) => 부울
사용자 입력 유효성 검사 및 사용자 지정 탐색과 같은 일반적인 시나리오를 달성하는 방법을 보여주기 위해 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.inEditMode && evt.key.toLowerCase() === 'tab' ) {
}
if (type === 'dataCell' && evt.key.toLowerCase() === 'enter' ) {
}
}
typescript
IGridKeydownEventArgs 값을 기반으로 우리는 자체 논리를 제공하는 두 가지 사례를 식별했습니다(위 참조). 이제 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
참고: 전체 구현 세부정보는 샘플 코드를 참조하세요.
아래 데모를 사용하여 방금 구현한 사용자 지정 시나리오를 시험해 보세요.
더블클릭 또는 누르기 F2 셀에 있는 키 Age
열에서 값을 다음으로 변경합니다. 16
그리고 누르세요 탭 열쇠. 프롬프트 메시지가 표시됩니다.
셀을 선택하고 Enter 키를 두 번 누릅니다. 키를 누를 때마다 동일한 열 아래의 다음 행에 있는 셀로 초점이 이동합니다.
import { Component, OnInit, ViewChild } from '@angular/core' ;
import { IGridKeydownEventArgs, CellType, IgxTreeGridComponent, GridSelectionMode, IgxPaginatorComponent, IgxColumnComponent } from 'igniteui-angular' ;
import { EMPLOYEE_DATA } from './data' ;
import { IgxPreventDocumentScrollDirective } from '../directives/prevent-scroll.directive' ;
@Component ({
selector : 'app-tree-grid-keyboard-navigation-sample' ,
styleUrls : ['./tree-grid-keyboard-navigation-sample.component.scss' ],
templateUrl : './tree-grid-keyboard-navigation-sample.component.html' ,
imports : [IgxTreeGridComponent, IgxPreventDocumentScrollDirective, IgxPaginatorComponent, IgxColumnComponent]
})
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"
(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 {
--ig-size: var(--ig-size-small);
margin : 15px ;
}
scss コピー
알려진 제한 사항
한정
설명
스크롤 가능한 상위 컨테이너를 사용하여 그리드 내부를 탐색합니다.
그리드가 스크롤 가능한 상위 컨테이너 내부에 배치되고 사용자가 보이지 않는 그리드 셀로 이동하는 경우 상위 컨테이너는 스크롤되지 않습니다.
API 참조
추가 리소스
우리 커뮤니티는 활동적이며 항상 새로운 아이디어를 환영합니다.