Angular 그리드에서 행 끌기
In Ignite UI for Angular Grid, RowDrag is initialized on the root igx-grid component and is configurable via the rowDraggable input. Enabling row dragging provides users with a row drag-handle with which they can initiate dragging of a row.
Angular Grid Row Drag Example
구성
행 드래그를igx-grid 활성화하려면 그리드rowDraggable를 설정하기만 하면 됩니다.true이 기능이 활성화되면 각 행에 행 드래그 핸들이 표시됩니다. 이 핸들은 행 드래그를 시작하는 데 사용할 수 있습니다.
<igx-grid [rowDraggable]="true">
...
</igx-grid>
드래그 핸들을 클릭하고 버튼을 누르고 있는 상태로 커서를 움직이 면 그리드의rowDragStart 이벤트가 발동합니다. 클릭을 언제rowDragEnd 든 놓으면 이벤트가 발동됩니다.
아래에서 행 드래그를 지원하는 설정igx-grid과 드롭 이벤트를 올바르게 처리하는 방법에 대한 공략을 확인할 수 있습니다.
이 예에서는 한 그리드에서 다른 그리드로 행을 끌어 첫 번째 데이터 소스에서 제거하고 두 번째 데이터 소스에 추가하는 작업을 처리합니다.
Drop Areas
행 드래그 활성화는 꽤 쉬웠지만, 이제 행 드래깅을 어떻게 처리할지 설정해야 합니다. 우리는 igxDrop지시를 사용하여 행을 어디에 드롭할지 정의할 수 있습니다.
먼저 앱 모듈에서 을IgxDragDropModule 가져와야 합니다:
import { ..., IgxDragDropModule } from 'igniteui-angular/directives';
// import { ..., IgxDragDropModule } from '@infragistics/igniteui-angular'; for licensed package
...
@NgModule({
imports: [..., IgxDragDropModule]
})
그런 다음 템플릿에서 지시어 선택기를 사용하여 드롭 영역을 정의합니다.
이 경우 놓기 영역은 행을 놓을 전체 두 번째 그리드가 됩니다.
<igx-grid #targetGrid igxDrop [data]="data2" [autoGenerate]="false" [emptyGridTemplate]="dragHereTemplate"
(enter)="onEnterAllowed($event)" (leave)="onLeaveAllowed($event)" (dropped)="onDropAllowed($event)" [primaryKey]="'ID'">
...
</igx-grid>
그리드는 처음에는 비어 있으므로 사용자에게 더 의미 있는 템플릿도 정의합니다.
<ng-template #dragHereTemplate>
Drop a row to add it to the grid
</ng-template>
드롭할 수 없는 영역에 행이 떨어졌을 때 다음 기능을 사용해 애니메이션을 활성화할 수 있습니다.animation의 매개변수rowDragEnd 행사. true로 설정하면, 드래그한 행은 드롭할 수 없는 영역 위에 떨어뜨렸을 때 원래 위치로 애니메이션이 됩니다.
다음과 같이 애니메이션을 활성화할 수 있습니다:
export class IgxGridRowDragComponent {
public onRowDragEnd(args) {
args.animation = true;
}
}
Drop Area Event Handlers
템플릿에서 드롭 영역을 정의한 후에는 컴포넌트 파일에 있는 핸들러igxDropenterleavedropped와 이벤트를 선언해야 합니다..ts
먼저, 저희enter와leave 핸들러들을 살펴보겠습니다. 이 방법들에서는 드래그의 유령 아이콘을 변경하여 사용자가 해당 행을 드롭할 수 있는 영역 위에 있음을 표시하고 싶습니다:
export class IgxGridRowDragComponent {
public onEnterAllowed(args) {
this.changeGhostIcon(args.drag.ghostElement, DragIcon.ALLOW);
}
public onLeaveAllowed(args) {
this.changeGhostIcon(args.drag.ghostElement, DragIcon.DEFAULT);
}
private changeGhostIcon(ghost, icon: string) {
if (ghost) {
const currentIcon = ghost.querySelector('.igx-grid__drag-indicator > igx-icon');
if (currentIcon) {
currentIcon.innerText = icon;
}
}
}
}
changeGhostIcon 개인 방법은 드래그 고스트 안의 아이콘을 변경하는 것뿐입니다. 메서드 내 로직은 아이콘을 포함하는 요소를 찾아내며(드래그 인디케이터 컨테이너에 적용된 클래스를 사용igx-grid__drag-indicator), 해당 요소의 내부 텍스트를 전달된 텍스트로 변경합니다. 아이콘 자체는 material폰트 집합에서 가져온 것이며, 별도의enum 방식으로 정의됩니다:
enum DragIcon {
DEFAULT = 'drag_indicator',
ALLOW = 'add'
}
다음으로 사용자가 실제로 놓기 영역 내부에 행을 놓을 때 어떤 일이 발생해야 하는지 정의해야 합니다.
export class IgxGridRowDragComponent {
@ViewChild('sourceGrid', { read: IgxGridComponent }) public sourceGrid: IgxGridComponent;
@ViewChild('targetGrid', { read: IgxGridComponent }) public targetGrid: IgxGridComponent;
public onDropAllowed(args) {
this.targetGrid.addRow(args.dragData.data);
this.sourceGrid.deleteRow(args.dragData.key);
}
}
We define a reference to each of our grids via the ViewChild decorator and the handle the drop as follows:
- add a row to the
targetGridthat contains the data of the row being dropped - remove the dragged row from the
sourceGrid
Note
이벤트 인자(args.dragData.data)나 다른 행 속성에서 행 데이터를 사용할 때, 인수에 전체 행이 참조로 전달된다는 점을 유의하세요. 즉, 소스 그리드의 데이터와 구분하려면 필요한 데이터를 복제해야 합니다.
Templating the drag ghost
드래그 고스트는 다음 명령어를 사용하여IgxRowDragGhost 템플릿화할 수 있으며, 이 명령은 몸체 내부<ng-template>에igx-grid 적용됩니다:
<igx-grid>
...
<ng-template igxRowDragGhost>
<div>
<igx-icon fontSet="material">arrow_right_alt</igx-icon>
</div>
</ng-template>
...
</igx-grid>
설정 결과는 아래 행 드래그와 다중 선택 활성화 상태에서igx-grid 확인할 수 있습니다. 데모는 현재 드래그된 행의 수를 보여줍니다:
예제 데모
Templating the drag icon
드래그 핸들 아이콘은 그리드dragIndicatorIconTemplate를 사용해 템플릿화할 수 있습니다. 우리가 만들고 있는 예시에서, 아이콘을 기본값인 (drag_indicator)에서 다음drag_handle으로 바꿔봅시다. 이를 위해 를igxDragIndicatorIcon 사용하여 본체 안에igx-grid 템플릿을 전달할 수 있습니다:
<igx-grid>
...
<ng-template igxDragIndicatorIcon>
<igx-icon>drag_handle</igx-icon>
</ng-template>
...
</igx-grid>
새 아이콘 템플릿을 설정한 후에는 다음 메서드로DEFAULT 제대로 변경할 수 있도록 아이콘도 조정DragIcon enumchangeIcon 해야 합니다:
enum DragIcon {
DEFAULT = "drag_handle",
...
}
드롭 핸들러가 올바르게 구성되면 작업을 시작할 수 있습니다! 구성 결과는 다음과 같습니다.
예제 데모
Application Demo
Using Row Drag Events
다음 데모에서는 행 드래그 이벤트 정보를 사용하여 행이 삭제되는 사용자 정의 구성 요소의 상태와 소스 그리드 자체를 모두 변경하는 방법을 보여줍니다. 그리드에서 달을 끌어 해당 행성에 놓아보세요. 행 드래그 고스트 배경은 호버링된 행성에 따라 동적으로 변경됩니다. 성공하면 그리드의 행이 선택되고 끌기가 비활성화됩니다. 행성을 클릭하면 유용한 정보를 얻을 수 있습니다.
Note
위 데모에서 사용된 행 드래그 고스트에 적용된 클래스는 ::ng-deep 수정자를 사용하고 있습니다. 행 드래그는 내부 그리드 기능이고 CSS 캡슐화로 인해 애플리케이션 수준에서 액세스할 수 없기 때문입니다.
Row Reordering Demo
그리드의 행 드래그 이벤트와igxDrop 디렉티브의 도움으로, 드래그하여 행을 재정렬할 수 있는 그리드를 만들 수 있습니다.
모든 동작이 그리드 본체 내부에서 일어나기 때문에, 다음 지시를 붙igxDrop 여야 합니다:
<igx-grid #grid [data]="data" [rowDraggable]="true" [primaryKey]="'ID'" igxDrop (dropped)="onDropAllowed($event)">
...
</igx-grid>
Note
그리드에 명시된 것이primaryKey 있는지 꼭 확인하세요! 로직은 행을 올바르게 재정렬할 수 있도록 고유한 식별자가 필요합니다
rowDraggable활성화되고 드롭존이 정의되면, 드롭 이벤트에 대한 간단한 핸들러를 구현해야 합니다. 행을 드래그할 때는 다음 사항을 확인하세요:
- 행이 그리드 내부에 삭제되었습니까?
- 그렇다면 드래그된 행이 다른 어느 행에 삭제되었습니까?
- 목표 행을 찾으면 레코드들의 위치를 배열에서
data바꿔 놓습니다
아래에서 컴포넌트.ts 파일에서 구현된 내용을 볼 수 있습니다:
export class GridRowReorderComponent {
public onDropAllowed(args) {
const event = args.originalEvent;
const currRowIndex = this.getCurrentRowIndex(this.grid.rowList.toArray(),
{ x: event.clientX, y: event.clientY });
if (currRowIndex === -1) { return; }
this.grid.deleteRow(args.dragData.key);
this.data.splice(currRowIndex, 0, args.dragData.data);
}
private getCurrentRowIndex(rowList, cursorPosition) {
for (const row of rowList) {
const rowRect = row.nativeElement.getBoundingClientRect();
if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
return this.data.indexOf(this.data.find((r) => r.rowID === row.rowID));
}
}
return -1;
}
}
이러한 몇 가지 간단한 단계를 통해 드래그/드롭을 통해 행을 재정렬할 수 있는 그리드를 구성했습니다! 다음 데모에서 위 코드가 실제로 작동하는 모습을 볼 수 있습니다.
드래그 아이콘을 누르고 있으면 그리드의 어느 곳으로든 행을 이동할 수 있습니다.
Improving UX in row drag scenarios
현재 커서 아래에 있는 행 인덱스를 얻을 수 있으면 풍부한 사용자 정의 기능을 구축하고 애플리케이션의 UX를 개선할 수 있는 기회를 얻을 수 있습니다. 예를 들어, 그리드 위에서 드래그된 행의 위치를 기반으로 드래그 고스트를 변경하거나 드롭 표시기를 표시할 수 있습니다. 이 방법으로 달성할 수 있는 또 다른 유용한 동작은 그리드의 테두리에 도달할 때 행을 드래그하는 동안 그리드를 위나 아래로 스크롤하는 것입니다.
아래에서는 행의 위치를 알고 달성할 수 있는 몇 가지 사용자 정의 구현의 예제 스니펫을 찾을 수 있습니다.
커서 위치에 따라 드래그 고스트 변경
아래 스니펫에서는 드래그 고스트 내부의 텍스트를 변경하여 마우스를 올린 행의 이름을 표시하는 방법을 볼 수 있습니다.
First, you specify a template which you'd like to use for the drag ghost. The dropName property will dynamically change, getting the name of the row over which the cursor is hovering:
<ng-template igxRowDragGhost>
<div class="customGhost">
<div>{{ dropName }}</div>
</div>
</ng-template>
그런 다음, 끝난 행의 인스턴스를 반환하는 메서드를 정의합니다(행 재정렬 데모에서 사용된 것과 유사).
class MyRowGhostComponent {
private getRowDataAtPoint(rowList: IgxGridRowComponent[], cursorPosition: Point): any {
for (const row of rowList) {
const rowRect = row.nativeElement.getBoundingClientRect();
if (cursorPosition.y > rowRect.top + window.scrollY && cursorPosition.y < rowRect.bottom + window.scrollY &&
cursorPosition.x > rowRect.left + window.scrollX && cursorPosition.x < rowRect.right + window.scrollX) {
return this.data.find((r) => r.rowID === row.rowID);
}
}
return null;
}
}
Finally, we create a method that will be used to handle the IgxDragDirective.dragMove event (emitted for the dragged row). The method will change the value of the property used in the igxRowDragGhost template and force a rerender.
We want to subscribe to the dragMove event only of the specific row we're dragging and unsubscribe from it (to prevent memory leaks) each time a row is dropped.
class MyRowGhostComponent {
public ngAfterViewInit(): void {
this.grid.rowDragStart.pipe(takeUntil(this.destroy$)).subscribe(this.onRowDragStart.bind(this));
}
private onRowDragStart(e: IRowDragStartEventArgs) {
if (e !== null) {
this._draggedRow = e.dragData.rowData;
}
const directive = e.dragDirective;
directive.dragMove
.pipe(takeUntil(this.grid.rowDragEnd))
.subscribe(this.onDragMove.bind(this));
}
private onDragMove(args: IDragMoveEventArgs) {
const cursorPosition = this.getCursorPosition(args.originalEvent);
const hoveredRowData = this.getRowDataAtPoint(
this.grid.rowList.toArray(),
cursorPosition
);
if (!hoveredRowData) {
args.cancel = true;
return;
}
const rowID = hoveredRowData.ID;
if (rowID !== null) {
let newName = this.dropName;
if (rowID !== -1) {
const targetRow = this.grid.rowList.find((e) => {
return e.rowData.ID === rowID;
});
newName = targetRow?.rowData.Name;
}
if (newName !== this.dropName) {
this.dropName = newName;
args.owner.cdr.detectChanges();
}
}
}
}
커서 위치에 따라 드롭 표시기 표시
다음 섹션의 데모에서는 드래그된 행이 삭제될 위치에 대한 표시기를 표시하는 방법을 볼 수 있습니다. 이 표시기를 원하는 대로 사용자 정의할 수 있습니다. 드래그된 행이 놓일 위치에 배치되는 자리 표시자 행, 드래그된 행이 현재 마우스오버된 행 위 또는 아래에 놓일지 여부를 나타내는 테두리 스타일 등이 될 수 있습니다.
In order to track the position of the cursor, we bind to the dragMove event of the IgxDragDirective when we start dragging a row.
Note
그리드에 명시된 것이primaryKey 있는지 꼭 확인하세요! 로직은 행을 올바르게 재정렬할 수 있도록 고유한 식별자가 필요합니다
public ngAfterViewInit() {
this.grid.rowDragStart
.pipe(takeUntil(this.destroy$))
.subscribe(this.handleRowStart.bind(this));
}
private handleRowStart(e: IRowDragStartEventArgs): void {
if (e !== null) {
this._draggedRow = e.dragData.data;
}
const directive = e.dragDirective;
directive.dragMove
.pipe(takeUntil(this.grid.rowDragEnd))
.subscribe(this.handleDragMove.bind(this));
}
private handleDragMove(event: IDragMoveEventArgs): void {
this.handleOver(event);
}
private handleOver(event: IDragMoveEventArgs) {
const ghostRect = event.owner.ghostElement.getBoundingClientRect();
const rowIndex = this.getRowIndexAtPoint(this.grid.rowList.toArray(), {
x: ghostRect.x,
y: ghostRect.y
});
if (rowIndex === -1) {
return;
}
const rowElement = this.grid.rowList.find(
e => e.rowData.ID === this.grid.data[rowIndex].ID
);
if (rowElement) {
this.changeHighlightedElement(rowElement.element.nativeElement);
}
}
private clearHighlightElement(): void {
if (this.highlightedRow !== undefined) {
this.renderer.removeClass(this.highlightedRow, 'underlined-class');
}
}
private setHightlightElement(newElement: HTMLElement) {
this.renderer.addClass(newElement, 'underlined-class');
this.highlightedRow = newElement;
}
private changeHighlightedElement(newElement: HTMLElement) {
if (newElement !== undefined) {
if (newElement !== this.highlightedRow) {
this.clearHighlightElement();
this.setHightlightElement(newElement);
} else {
return;
}
}
}
행 드래그 시 그리드 스크롤
매우 유용한 시나리오는 드래그된 행이 위쪽 또는 아래쪽 테두리에 도달할 때 그리드를 스크롤할 수 있다는 것입니다. 이를 통해 그리드의 행 수에 스크롤 막대가 필요한 경우 현재 뷰포트 외부에서 행을 재정렬할 수 있습니다.
Below you see an example of the two methods we use to check if we have reached the edge of the viewport and to scroll it if needed. The isGridScrolledToEdge accepts one parameter - the direction we'd like to scroll the grid (1 for "Down", -1 for "Up") and returns true if we've reach the final row in that direction. The scrollGrid method will attempt to scroll the grid in a direction (1 or -1), doing nothing if the grid is already at that edge.
class MyGridScrollComponent {
private isGridScrolledToEdge(dir: 1 | -1): boolean {
if (this.grid.data[0] === this.grid.rowList.first.data && dir === -1) {
return true;
}
if (
this.grid.data[this.grid.data.length - 1] === this.grid.rowList.last.data &&
dir === 1
) {
return true;
}
return false;
}
private scrollGrid(dir: 1 | -1): void {
if (!this.isGridScrolledToEdge(dir)) {
if (dir === 1) {
this.grid.verticalScrollContainer.scrollNext();
} else {
this.grid.verticalScrollContainer.scrollPrev();
}
}
}
}
We'll still be subscribing to the dragMove event of the specific row in the way we did in the previous example. Since dragMove is fired only when the cursor actually moves, we want to have a nice and simple way to auto-scroll the grid when the row is at one of the edges, but the user does not move the mouse. We'll an additional method which will setup an interval, auto-scrolling the grid every 500ms.
We create and subscribe to the interval when the pointer reaches the grid's edge and we unsubscribe from that interval every time the mouse moves or the row is dropped (regardless of cursor position).
class MyGridScrollComponent {
public ngAfterViewInit() {
this.grid.rowDragStart
.pipe(takeUntil(this.destroy$))
.subscribe(this.onDragStart.bind(this));
this.grid.rowDragEnd
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.unsubInterval());
}
private onDragMove(event: IDragMoveEventArgs): void {
this.unsubInterval();
const dir = this.isPointOnGridEdge(event.pageY);
if (!dir) {
return;
}
this.scrollGrid(dir);
if (!this.intervalSub) {
this.interval$ = interval(500);
this.intervalSub = this.interval$.subscribe(() => this.scrollGrid(dir));
}
}
private unsubInterval(): void {
if (this.intervalSub) {
this.intervalSub.unsubscribe();
this.intervalSub = null;
}
}
}
다음은 위에 설명된 두 가지 시나리오의 예입니다. 테두리 가장자리에 도달하면 드롭 표시기를 표시하고 뷰포트를 스크롤합니다.
Limitations
현재 이 지침에 대한rowDraggable 알려진 제한 사항은 없습니다.